diff --git a/.ci/jenkins/selenium-py3/run_tests.sh b/.ci/jenkins/selenium-py3/run_tests.sh index 0fb4805fdaf..827151835f6 100755 --- a/.ci/jenkins/selenium-py3/run_tests.sh +++ b/.ci/jenkins/selenium-py3/run_tests.sh @@ -2,7 +2,6 @@ # Enable retries on tests to reduce chances of transient failures. : ${GALAXY_TEST_SELENIUM_RETRIES:=1} -: ${GALAXY_TEST_CLIENT_BUILD_IMAGE:='node:9.4.0'} # If in Jenkins environment, use it for artifacts. if [ -n "$BUILD_NUMBER" ]; @@ -20,15 +19,8 @@ mkdir -p "$GALAXY_TEST_SCREENSHOTS_DIRECTORY" mkdir -p ~/.jenkins-yarn-cache YARN_CACHE_FOLDER=~/.jenkins-yarn-cache -# Set git environment variables to enable Git. https://github.com/galaxyproject/galaxy/issues/5912 -# Setup volume and environment variable to cache this users yarn build. -docker run -e GIT_COMMITTER_NAME=Jenkins -e GIT_COMMITTER_EMAIL=jenkins@galaxyproject.org \ - -e YARN_CACHE_FOLDER=$YARN_CACHE_FOLDER -v $YARN_CACHE_FOLDER:$YARN_CACHE_FOLDER:rw \ - -v `pwd`:`pwd`:rw -w `pwd` -u $UID $GALAXY_TEST_CLIENT_BUILD_IMAGE \ - /bin/bash -c 'make client-production-maps' - # Start Selenium server in the test Docker container. -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} ${DOCKER_RUN_EXTRA_ARGS}" +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/.ci/jenkins/selenium/run_tests.sh b/.ci/jenkins/selenium/run_tests.sh index a5991b9685d..30840006815 100755 --- a/.ci/jenkins/selenium/run_tests.sh +++ b/.ci/jenkins/selenium/run_tests.sh @@ -2,7 +2,6 @@ # Enable retries on tests to reduce chances of transient failures. : ${GALAXY_TEST_SELENIUM_RETRIES:=1} -: ${GALAXY_TEST_CLIENT_BUILD_IMAGE:='node:9.4.0'} # If in Jenkins environment, use it for artifacts. if [ -n "$BUILD_NUMBER" ]; @@ -20,15 +19,8 @@ mkdir -p "$GALAXY_TEST_SCREENSHOTS_DIRECTORY" mkdir -p ~/.jenkins-yarn-cache YARN_CACHE_FOLDER=~/.jenkins-yarn-cache -# Set git environment variables to enable Git. https://github.com/galaxyproject/galaxy/issues/5912 -# Setup volume and environment variable to cache this users yarn build. -docker run -e GIT_COMMITTER_NAME=Jenkins -e GIT_COMMITTER_EMAIL=jenkins@galaxyproject.org \ - -e YARN_CACHE_FOLDER=$YARN_CACHE_FOLDER -v $YARN_CACHE_FOLDER:$YARN_CACHE_FOLDER:rw \ - -v `pwd`:`pwd`:rw -w `pwd` -u $UID $GALAXY_TEST_CLIENT_BUILD_IMAGE \ - /bin/bash -c 'make client-production-maps' - # Start Selenium server in the test Docker container. -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} ${DOCKER_RUN_EXTRA_ARGS}" +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 --db postgres --clean_pyc --skip_flakey_fails --selenium "$@" diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000000..01b3105991a --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,212 @@ +# Python CircleCI 2.0 configuration file +version: 2 +variables: + restore_repo_cache: &restore_repo_cache + restore_cache: + keys: + - v1-repo-{{ .Environment.CIRCLE_SHA1 }} + restore_yarn_cache: &restore_yarn_cache + restore_cache: + keys: + - v1-repo-{{ .Environment.CIRCLE_SHA1 }} + - yarn-packages-{{ checksum "client/yarn.lock" }} + save_yarn_cache: &save_yarn_cache + save_cache: + key: yarn-packages-{{ checksum "client/yarn.lock" }} + paths: + - ~/.cache/yarn + install_tox: &install_tox + run: sudo pip install tox + set_workdir: &set_workdir + working_directory: ~/repo + requires_get_code: &requires_get_code + requires: + - get_code +jobs: + get_code: + docker: + - image: circleci/python:2.7.15 + <<: *set_workdir + steps: + # Replace standard code checkout with shallow clone to speed things up. + - run: + name: Checkout code + command: |- + # Add github.com to known hosts + mkdir -p ~/.ssh + echo 'github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== + ' >> ~/.ssh/known_hosts + + # Add the user ssh key and set correct perms + (umask 077; touch ~/.ssh/id_rsa) + chmod 0600 ~/.ssh/id_rsa + echo "$CHECKOUT_KEY" > ~/.ssh/id_rsa + + # Use git+ssh instead of https + git config --global url."ssh://git@github.com".insteadOf "https://github.com" || true + git config --global gc.auto 0 || true + + # Shallow clone + git clone --depth=1 "${CIRCLE_REPOSITORY_URL}" . + + if [[ -n "${CIRCLE_PR_NUMBER}" ]]; then + # Update PR refs for testing. + FETCH_REFS="${FETCH_REFS} +refs/pull/${CIRCLE_PR_NUMBER}/head:pr/${CIRCLE_PR_NUMBER}/head" + FETCH_REFS="${FETCH_REFS} +refs/pull/${CIRCLE_PR_NUMBER}/merge:pr/${CIRCLE_PR_NUMBER}/merge" + + # Retrieve the refs + git fetch --force origin ${FETCH_REFS} + + # Checkout PR merge ref. + git checkout -f "pr/${CIRCLE_PR_NUMBER}/merge" + + # Test for *some* merge conflicts. + git branch --merged | grep "pr/${CIRCLE_PR_NUMBER}/head" > /dev/null + else + if [ -n "$CIRCLE_TAG" ]; then + git fetch --depth=1 --force origin "refs/tags/${CIRCLE_TAG}" + else + git fetch --depth=1 --force origin "$CIRCLE_BRANCH:remotes/origin/$CIRCLE_BRANCH" + fi + if [ -n "$CIRCLE_TAG" ]; then + git reset --hard "$CIRCLE_SHA1" + git checkout "$CIRCLE_TAG" + elif [ -n "$CIRCLE_BRANCH" ]; then + git reset --hard "$CIRCLE_SHA1" + git checkout -B "$CIRCLE_BRANCH" + fi + git reset --hard "${CIRCLE_SHA1}" + fi + - save_cache: + key: v1-repo-{{ .Environment.CIRCLE_SHA1 }} + paths: + - ~/repo + py27_lint: + docker: + - image: circleci/python:2.7.15 + <<: *set_workdir + steps: + - *restore_repo_cache + - *install_tox + - run: tox -e py27-lint + py27_unit: + docker: + - image: circleci/python:2.7.15 + <<: *set_workdir + steps: + - *restore_repo_cache + - *install_tox + - run: tox -e py27-unit + py27_docstring: + docker: + - image: circleci/python:2.7.15 + <<: *set_workdir + steps: + - *restore_repo_cache + - *install_tox + - run: tox -e py27-lint_docstring_include_list + py27_first_startup: + docker: + - image: circleci/python:2.7.15 + <<: *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 + - *install_tox + - run: tox -e py27-first_startup + py35_lint: + docker: + - image: circleci/python:3.5 + <<: *set_workdir + steps: + - *restore_repo_cache + - *install_tox + - run: tox -e py35-lint + py35_unit: + docker: + - image: circleci/python:3.5 + <<: *set_workdir + steps: + - *restore_repo_cache + - *install_tox + - run: tox -e py35-unit + py35_first_startup: + docker: + - image: circleci/python:3.5 + <<: *set_workdir + steps: + - *restore_repo_cache + - *install_tox + - run: sudo apt-get update + # For uwsgi + - run: sudo apt-get install -y libpython3.5-dev + - run: tox -e py35-first_startup + validate_test_tools: + docker: + - image: circleci/python:2.7.15 + <<: *set_workdir + steps: + - *restore_repo_cache + - run: sudo apt-get update + - run: sudo apt-get install -y libxml2-utils + - *install_tox + - run: tox -e validate_test_tools + check_py3_compatibility: + docker: + - image: circleci/python:2.7.15 + <<: *set_workdir + steps: + - *restore_repo_cache + - run: sudo apt-get update + - run: sudo apt-get install -y ack-grep + - *install_tox + - run: tox -e check_py3_compatibility + js_unit: + docker: + - image: circleci/node:10-browsers + <<: *set_workdir + steps: + - *restore_yarn_cache + - run: cd client && yarn install --frozen-lockfile + - *save_yarn_cache + - run: cd client && yarn run build + - run: cd client && yarn run test + js_lint: + docker: + - image: circleci/node:10-browsers + <<: *set_workdir + steps: + - *restore_yarn_cache + - run: cd client && yarn install --frozen-lockfile + - *save_yarn_cache + - run: cd client && yarn run eslint +workflows: + version: 2 + get_code_and_test: + jobs: + - get_code + - py27_lint: + <<: *requires_get_code + - py27_unit: + <<: *requires_get_code + - py27_first_startup: + <<: *requires_get_code + - py27_docstring: + <<: *requires_get_code + - py35_lint: + <<: *requires_get_code + - py35_unit: + <<: *requires_get_code + - py35_first_startup: + <<: *requires_get_code + - validate_test_tools: + <<: *requires_get_code + - check_py3_compatibility: + <<: *requires_get_code + - js_unit: + <<: *requires_get_code + - js_lint: + <<: *requires_get_code diff --git a/.gitignore b/.gitignore index 9bd8443d03b..9fbaa7c97f3 100644 --- a/.gitignore +++ b/.gitignore @@ -117,6 +117,7 @@ test/unit/**.log # Project files *.kpf +.idea .vscode client/**/jsconfig.json @@ -142,15 +143,20 @@ doc/schema.md doc/source/admin/config_logging_default_yaml.rst doc/source/api/api.rst doc/source/api/ts_api.rst +doc/source/dev/schema.md doc/source/dev/schema.rst +client/styleguide # webpack stats client/webpack-stats.json +# packages +packages/*/build +packages/*/dist +packages/*/*.egg-info + # Misc *.orig .DS_Store *.rej *~ -.idea -.vscode diff --git a/.travis.yml b/.travis.yml index e69c5abc105..32b0bba580d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,64 +1,10 @@ -language: python -python: 2.7 -os: - - linux +os: osx +# No version of Python is available via virtualenv on OS X workers, see https://github.com/travis-ci/travis-ci/issues/2312 +language: generic + env: - - TOX_ENV=py34-lint - - TOX_ENV=py27-lint - - TOX_ENV=py27-unit - TOX_ENV=py27-first_startup - - TOX_ENV=py27-lint_docstring_include_list - -matrix: - include: - - env: TOX_ENV=py34-first_startup - addons: - &py3_addons - apt: - packages: - # For psutil, pyyaml, uwsgi... - - libpython3.4-dev - - env: TOX_ENV=validate_test_tools - addons: - apt: - packages: - - libxml2-utils - - env: TOX_ENV=check_py3_compatibility - addons: - apt: - packages: - - ack-grep - - env: TOX_ENV=py34-unit - addons: *py3_addons - - os: osx - # No version of Python is available via virtualenv on OS X workers, see https://github.com/travis-ci/travis-ci/issues/2312 - language: generic - env: TOX_ENV=py27-first_startup - - name: js-unit - language: node_js - node_js: - - 10 - before_install: - - cd client - install: - - yarn - - yarn run build - script: - - yarn run test - - name: js-lint - language: node_js - node_js: - - 10 - before_install: - - cd client - install: - - yarn - script: - - yarn run eslint - -before_install: - # Workaround for https://github.com/travis-ci/travis-ci/issues/7940 - - sudo rm -f /etc/boto.cfg + - TOX_ENV=py35-first_startup install: - set -e @@ -69,6 +15,13 @@ install: wget -q https://github.com/jmchilton/galaxy-downloads/raw/master/db_gx_rev_0127.sqlite mv db_gx_rev_0127.sqlite database/universe.sqlite sh manage_db.sh -c ./config/galaxy.yml.sample upgrade + elif [ "$TOX_ENV" == "py35-first_startup" ]; then + MINICONDA_URL="https://repo.anaconda.com/miniconda" + MINICONDA_FILE="Miniconda3-latest-MacOSX-x86_64.sh" + curl -L -O "${MINICONDA_URL}/${MINICONDA_FILE}" + bash $MINICONDA_FILE -b + ~/miniconda3/bin/conda create --yes --override-channels --channel conda-forge --channel defaults --name _galaxy_ 'python=3.5' 'pip>=9' 'virtualenv>=16' + . ~/miniconda3/bin/activate _galaxy_ fi script: tox -e $TOX_ENV diff --git a/CITATION b/CITATION index 2142df9d514..c917c23e30f 100644 --- a/CITATION +++ b/CITATION @@ -1,24 +1,26 @@ If you use or extend Galaxy in your published work, please cite this publication: -- Afgan E, Baker D, van den Beek M, Blankenberg D, Bouvier D, Čech M, Chilton - J, Clements D, Coraor N, Eberhard C, Grüning B, Guerler A, Hillman-Jackson - J, Von Kuster G, Rasche E, Soranzo N, Turaga N, Taylor J, Nekrutenko A, Goecks - J. "The Galaxy platform for accessible, reproducible and collaborative - biomedical analyses: 2016 update" - Nucleic Acids Research (2016) 44(W1):W3-W10 doi:10.1093/nar/gkw343 +- Afgan E, Baker D, Batut B, van den Beek M, Bouvier D, Čech M, Chilton J, + Clements D, Coraor N, Grüning BA, Guerler A, Hillman-Jackson J, Hiltemann S, + Jalili V, Rasche H, Soranzo N, Goecks J, Taylor J, Nekrutenko A, Blankenberg + D. "The Galaxy platform for accessible, reproducible and collaborative + biomedical analyses: 2018 update" + Nucleic Acids Res. (2018) 46(W1):W537-W544 doi:10.1093/nar/gky379 BibTeX format: -@article{afgan2016galaxy, - title={The {Galaxy} platform for accessible, reproducible and collaborative biomedical analyses: 2016 update}, - author={Afgan, Enis and Baker, Dannon and van den Beek, Marius and Blankenberg, Daniel and Bouvier, Dave and \v{C}ech, Martin and Chilton, John and Clements, Dave and Coraor, Nate and Eberhard, Carl and Gr\"uning, Bj\"orn and Guerler, Aysam and Hillman-Jackson, Jennifer and Von Kuster, Greg and Rasche, Eric and Soranzo, Nicola and Turaga, Nitesh and Taylor, James and Nekrutenko, Anton and Goecks, Jeremy}, - journal={Nucleic Acids Res.}, - volume={44}, - number={W1}, - pages={W3-W10}, - year={2016}, - publisher={Oxford University Press} +@article{10.1093/nar/gky379, + title = {The {Galaxy} platform for accessible, reproducible and collaborative biomedical analyses: 2018 update}, + author = {Afgan, Enis and Baker, Dannon and Batut, B\'{e}r\'{e}nice and van den Beek, Marius and Bouvier, Dave and \v{C}ech, Martin and Chilton, John and Clements, Dave and Coraor, Nate and Gr\"uning, Bj\"orn A. and Guerler, Aysam and Hillman-Jackson, Jennifer and Hiltemann, Saskia and Jalili, Vahid and Rasche, Helena and Soranzo, Nicola and Goecks, Jeremy and Taylor, James and Nekrutenko, Anton and Blankenberg, Daniel}, + journal = {Nucleic Acids Res.}, + doi = {10.1093/nar/gky379}, + volume = {46}, + number = {W1}, + pages = {W537-W544}, + year = {2018}, + url = {https://doi.org/10.1093/nar/gky379}, + publisher = {Oxford University Press} } diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 153d86cbcd2..830f2384304 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -49,6 +49,7 @@ The following individuals have contributed code to Galaxy: * Gianmauro Cuccuru * Frederik Delaere * Matthias Desmet +* Matthew Ryan Dillon * Olivia Doppelt * Shane Dowling * John Duddy diff --git a/Makefile b/Makefile index 7d19740f0e5..cd5aaca01c0 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,15 @@ +# Location of virtualenv used for development. +VENV?=.venv +# Source virtualenv to execute command (flake8, sphinx, twine, etc...) +IN_VENV=if [ -f $(VENV)/bin/activate ]; then . $(VENV)/bin/activate; fi; RELEASE_CURR:=16.01 -RELEASE_CURR_MINOR_NEXT:=$(shell python scripts/bootstrap_history.py --print-next-minor-version) +RELEASE_CURR_MINOR_NEXT:=$(shell $(IN_VENV) python scripts/bootstrap_history.py --print-next-minor-version) RELEASE_NEXT:=16.04 # TODO: This needs to be updated with create_release_rc #RELEASE_NEXT_BRANCH:=release_$(RELEASE_NEXT) RELEASE_NEXT_BRANCH:=dev RELEASE_UPSTREAM:=upstream MY_UPSTREAM:=origin -# Location of virtualenv used for development. -VENV?=.venv -# Source virtualenv to execute command (flake8, sphinx, twine, etc...) -IN_VENV=if [ -f $(VENV)/bin/activate ]; then . $(VENV)/bin/activate; fi; CONFIG_MANAGE=$(IN_VENV) python lib/galaxy/webapps/config_manage.py PROJECT_URL?=https://github.com/galaxyproject/galaxy DOCS_DIR=doc @@ -27,7 +27,6 @@ docs: ## Generate HTML documentation. # $ ./scripts/common_startup.sh # $ . .venv/bin/activate # $ pip install -r lib/galaxy/dependencies/dev-requirements.txt -# You also need to install pandoc separately. $(IN_VENV) $(MAKE) -C doc clean $(IN_VENV) $(MAKE) -C doc html @@ -141,7 +140,7 @@ 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; else - cd client && yarn install --network-timeout 120000 --check-files + cd client && yarn install --network-timeout 300000 --check-files endif diff --git a/README.rst b/README.rst index 80c7116f34a..ac5af154f94 100644 --- a/README.rst +++ b/README.rst @@ -1,11 +1,9 @@ .. figure:: https://galaxyproject.org/images/galaxy-logos/galaxy_project_logo.jpg :alt: Galaxy Logo -The latest information about Galaxy is available via `https://galaxyproject.org/ `__ +The latest information about Galaxy can be found on the `Galaxy Community Hub `__. -.. image:: https://img.shields.io/badge/questions-galaxy%20biostar-blue.svg - :target: https://biostar.usegalaxy.org - :alt: Ask a question +Community support is available at `Galaxy Help `__. .. image:: https://img.shields.io/badge/chat-irc.freenode.net%23galaxyproject-blue.svg :target: https://webchat.freenode.net/?channels=galaxyproject diff --git a/client/.babelrc b/client/.babelrc deleted file mode 100644 index f9d881a541d..00000000000 --- a/client/.babelrc +++ /dev/null @@ -1,12 +0,0 @@ -{ - "presets": [ - [ - "@babel/preset-env", - { - "modules": false - } - ] - ], - "plugins": ["transform-vue-template", "@babel/plugin-syntax-dynamic-import"], - "ignore": ["i18n.js", "utils/localization.js", "nls/*"] -} diff --git a/client/.node_version b/client/.node_version new file mode 100644 index 00000000000..db24ab967fb --- /dev/null +++ b/client/.node_version @@ -0,0 +1 @@ +10.13.0 diff --git a/client/README.md b/client/README.md index 1eee5fbe0c2..3094d6b69d7 100644 --- a/client/README.md +++ b/client/README.md @@ -2,32 +2,30 @@ Client Build System =================== Installs, stages, and builds the client-side scripts necessary for running the -Galaxy webapp. There's no need to use this system unless you are modifying or -developing client-side scripts, or are running the development branch of -Galaxy. When started through `run.sh` or any other method that utilizes -`common_startup.sh`, Galaxy will also (since 18.09) *automatically* build +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 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 -`source .venv/bin/activate` from the Galaxy root directory. +`. .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 -nodejs.org and yarnpkg.com. +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/ and https://yarnpkg.com/ . The Galaxy client build has necessarily grown more complex in the past several -years, but we're still trying to keep things as simple as possible for -developers (everyone, really). If you're having any trouble with building the -client after following the instructions below please create an issue on GitHub +years, but we are still trying to keep things as simple as possible for +everyone. If you're having any trouble with building the +client after following the instructions below, please create an issue on GitHub or reach out for help directly on Gitter at -https://gitter.im/galaxyproject/Lobby. +https://gitter.im/galaxyproject/Lobby . Complete Client Build -================================================ +===================== 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 diff --git a/client/bower.json b/client/bower.json index 5ef262cb734..73a8b74d77e 100644 --- a/client/bower.json +++ b/client/bower.json @@ -9,7 +9,6 @@ "homepage": "usegalaxy.org", "dependencies": { "farbtastic": "~2.0.0-alpha.1", - "toastr": "~2.1.0", "jquery-form": "~3.46.0", "jquery-autocomplete": "git://github.com/dyve/jquery-autocomplete", "select2": "~3.5.2", diff --git a/client/galaxy/scripts/admin.toolshed.js b/client/galaxy/scripts/admin.toolshed.js index 84ba284b098..8a94d153909 100644 --- a/client/galaxy/scripts/admin.toolshed.js +++ b/client/galaxy/scripts/admin.toolshed.js @@ -30,7 +30,7 @@ var AdminToolshedRouter = Backbone.Router.extend({ "status/r/:repositories": "status", status: "status", "categories/s/:tool_shed": "categories", - "category/s/:tool_shed/c/:cateory_id": "repositories", + "category/s/:tool_shed/c/:category_id/k/:sort_key/p/:page/t/:sort_order": "repositories", "repository/s/:tool_shed/r/:repository_id": "repository" }, @@ -71,12 +71,18 @@ var GalaxyAdminToolshedApp = Backbone.View.extend({ tool_shed: tool_shed.replace(/\//g, "%2f") }); }); - this.admin_toolshed_router.on("route:repositories", (tool_shed, category_id) => { - Galaxy.admintoolshedapp.adminShedCategoryView = new mod_repositories_view.Category({ - tool_shed: tool_shed.replace(/\//g, "%2f"), - category_id: category_id - }); - }); + this.admin_toolshed_router.on( + "route:repositories", + (tool_shed, category_id, sort_key = "name", page = 1, sort_order = "asc") => { + Galaxy.admintoolshedapp.adminShedCategoryView = new mod_repositories_view.Category({ + tool_shed: tool_shed.replace(/\//g, "%2f"), + category_id: category_id, + page: page, + sort_order: sort_order, + sort_key: sort_key + }); + } + ); this.admin_toolshed_router.on("route:repository", (tool_shed, repository_id) => { Galaxy.admintoolshedapp.adminRepositoryView = new mod_repository_view.RepoDetails({ tool_shed: tool_shed.replace(/\//g, "%2f"), diff --git a/client/galaxy/scripts/bundleEntries.js b/client/galaxy/scripts/bundleEntries.js index c630dbb4dfc..be5c973607f 100644 --- a/client/galaxy/scripts/bundleEntries.js +++ b/client/galaxy/scripts/bundleEntries.js @@ -96,7 +96,7 @@ export let chartUtilities = { export { initMasthead } from "components/Masthead/initMasthead"; export { panelManagement } from "onload/globalInits/panelManagement"; -export { init_tag_click_function } from "ui/autocom_tagging"; +export { mountMakoTags } from "components/Tags"; // Used in common.mako -export { default as store } from "store"; +export { default as store } from "storemodern"; diff --git a/client/galaxy/scripts/components/DataDialog.vue b/client/galaxy/scripts/components/DataDialog.vue deleted file mode 100644 index a432069420d..00000000000 --- a/client/galaxy/scripts/components/DataDialog.vue +++ /dev/null @@ -1,124 +0,0 @@ - - - - diff --git a/client/galaxy/scripts/components/DataDialog/DataDialog.test.js b/client/galaxy/scripts/components/DataDialog/DataDialog.test.js new file mode 100644 index 00000000000..682ff54a4a4 --- /dev/null +++ b/client/galaxy/scripts/components/DataDialog/DataDialog.test.js @@ -0,0 +1,180 @@ +import sinon from "sinon"; +import { mount } from "@vue/test-utils"; +import DataDialog from "./DataDialog.vue"; +import { __RewireAPI__ as rewire } from "./DataDialog"; +import { Model } from "./model.js"; +import { UrlTracker } from "./utilities.js"; +import { Services } from "./services"; +import Vue from "vue"; + +const mockOptions = { + callback: () => {}, + host: "host", + root: "root", + history: "history" +}; + +describe("model.js", () => { + let result = null; + it("Model operations for single, no format", () => { + let model = new Model(); + try { + model.add({ idx: 1 }); + throw "Accepted invalid record."; + } catch (error) { + expect(error).to.equals("Invalid record with no ."); + } + model.add({ id: 1 }); + expect(model.count()).to.equals(1); + expect(model.exists(1)).to.equals(true); + model.add({ id: 2, tag: "tag" }); + expect(model.count()).to.equals(1); + expect(model.exists(1)).to.equals(false); + expect(model.exists(2)).to.equals(true); + result = model.finalize(); + expect(result.id).to.equals(2); + expect(result.tag).to.equals("tag"); + }); + it("Model operations for multiple, with format", () => { + let model = new Model({ multiple: true, format: "tag" }); + model.add({ id: 1, tag: "tag_1" }); + expect(model.count()).to.equals(1); + model.add({ id: 2, tag: "tag_2" }); + expect(model.count()).to.equals(2); + result = model.finalize(); + expect(result.length).to.equals(2); + expect(result[0]).to.equals("tag_1"); + expect(result[1]).to.equals("tag_2"); + model.add({ id: 1 }); + expect(model.count()).to.equals(1); + result = model.finalize(); + expect(result[0]).to.equals("tag_2"); + }); +}); + +describe("utilities.js/UrlTracker", () => { + it("Test url tracker", () => { + let urlTracker = new UrlTracker("url_initial"); + let url = urlTracker.getUrl(); + expect(url).to.equals("url_initial"); + expect(urlTracker.atRoot()).to.equals(true); + url = urlTracker.getUrl("url_1"); + expect(url).to.equals("url_1"); + expect(urlTracker.atRoot()).to.equals(false); + url = urlTracker.getUrl("url_2"); + expect(url).to.equals("url_2"); + expect(urlTracker.atRoot()).to.equals(false); + url = urlTracker.getUrl(); + expect(url).to.equals("url_1"); + expect(urlTracker.atRoot()).to.equals(false); + url = urlTracker.getUrl(); + expect(url).to.equals("url_initial"); + expect(urlTracker.atRoot()).to.equals(true); + }); +}); + +describe("services/Services:isDataset", () => { + it("Test dataset identifier", () => { + let services = new Services(mockOptions); + expect(services.isDataset({})).to.equals(false); + expect(services.isDataset({ history_content_type: "dataset" })).to.equals(true); + expect(services.isDataset({ history_content_type: "xyz" })).to.equals(false); + expect(services.isDataset({ type: "file" })).to.equals(true); + expect(services.getRecord({ hid: 1, history_content_type: "dataset" }).isDataset).to.equals(true); + expect(services.getRecord({ hid: 2, history_content_type: "xyz" }).isDataset).to.equals(false); + expect(services.getRecord({ type: "file" }).isDataset).to.equals(true); + }); +}); + +describe("services.js/Services", () => { + it("Test data population from raw data", () => { + let rawData = { + hid: 1, + id: 1, + history_id: 0, + name: "name_1" + }; + let services = new Services(mockOptions); + let items = services.getItems(rawData); + expect(items.length).to.equals(1); + let first = items[0]; + expect(first.name).to.equals("1: name_1"); + expect(first.download).to.equals("host/api/histories/0/contents/1/display"); + }); +}); + +describe("DataDialog.vue", () => { + let stub; + let wrapper; + let emitted; + + let rawData = [ + { + id: 1, + hid: 1, + name: "dataset_1", + history_content_type: "dataset" + }, + { + id: 2, + name: "dataset_2", + type: "file" + }, + { + id: 3, + hid: 3, + name: "collection_1", + history_content_type: "dataset_collection" + } + ]; + + let mockServices = class { + get(url) { + let services = new Services(mockOptions); + let items = services.getItems(rawData); + return new Promise((resolve, reject) => { + resolve(items); + }); + } + }; + + beforeEach(() => { + rewire.__Rewire__("Services", mockServices); + }); + + afterEach(() => { + if (stub) stub.restore(); + }); + + it("loads correctly, shows alert", () => { + wrapper = mount(DataDialog, { + propsData: mockOptions + }); + emitted = wrapper.emitted(); + expect(wrapper.classes()).contain("data-dialog-modal"); + expect(wrapper.find(".fa-spinner").text()).to.equals(""); + expect(wrapper.find(".btn-secondary").text()).to.equals("Clear"); + expect(wrapper.find(".btn-primary").text()).to.equals("Ok"); + expect(wrapper.contains(".fa-spinner")).to.equals(true); + return Vue.nextTick().then(() => { + expect(wrapper.findAll(".fa-copy").length).to.equals(2); + expect(wrapper.findAll(".fa-file-o").length).to.equals(2); + }); + }); + + it("loads correctly, shows datasets and folders", () => { + wrapper = mount(DataDialog, { + propsData: mockOptions + }); + emitted = wrapper.emitted(); + expect(wrapper.classes()).contain("data-dialog-modal"); + expect(wrapper.find(".fa-spinner").text()).to.equals(""); + expect(wrapper.find(".btn-secondary").text()).to.equals("Clear"); + expect(wrapper.find(".btn-primary").text()).to.equals("Ok"); + expect(wrapper.contains(".fa-spinner")).to.equals(true); + return Vue.nextTick().then(() => { + expect(wrapper.findAll(".fa-copy").length).to.equals(2); + expect(wrapper.findAll(".fa-file-o").length).to.equals(2); + }); + }); +}); diff --git a/client/galaxy/scripts/components/DataDialog/DataDialog.vue b/client/galaxy/scripts/components/DataDialog/DataDialog.vue new file mode 100644 index 00000000000..04abd4d86a2 --- /dev/null +++ b/client/galaxy/scripts/components/DataDialog/DataDialog.vue @@ -0,0 +1,160 @@ + + + + diff --git a/client/galaxy/scripts/components/DataDialog/DataDialogSearch.vue b/client/galaxy/scripts/components/DataDialog/DataDialogSearch.vue new file mode 100644 index 00000000000..fc6a6f4ec07 --- /dev/null +++ b/client/galaxy/scripts/components/DataDialog/DataDialogSearch.vue @@ -0,0 +1,29 @@ + + + diff --git a/client/galaxy/scripts/components/DataDialog/DataDialogTable.vue b/client/galaxy/scripts/components/DataDialog/DataDialogTable.vue new file mode 100644 index 00000000000..c3ad745bdab --- /dev/null +++ b/client/galaxy/scripts/components/DataDialog/DataDialogTable.vue @@ -0,0 +1,115 @@ + + + diff --git a/client/galaxy/scripts/components/DataDialog/model.js b/client/galaxy/scripts/components/DataDialog/model.js new file mode 100644 index 00000000000..072445a0f5f --- /dev/null +++ b/client/galaxy/scripts/components/DataDialog/model.js @@ -0,0 +1,52 @@ +export class Model { + constructor(options = {}) { + this.values = {}; + this.multiple = options.multiple || false; + this.format = options.format || null; + } + + /** Adds a new record to the value stack **/ + add(record) { + if (!this.multiple) { + this.values = {}; + } + let key = record && record.id; + if (key) { + if (!this.values[key]) { + this.values[key] = record; + } else { + delete this.values[key]; + } + } else { + throw "Invalid record with no ."; + } + } + + /** Returns the number of added records **/ + count() { + return Object.keys(this.values).length; + } + + /** Returns true if a record is available for a given key **/ + exists(key) { + return !!this.values[key]; + } + + /** Finalizes the results from added records **/ + finalize() { + let results = []; + Object.values(this.values).forEach(v => { + let value = null; + if (this.format) { + value = v[this.format]; + } else { + value = v; + } + results.push(value); + }); + if (results.length > 0 && !this.multiple) { + results = results[0]; + } + return results; + } +} diff --git a/client/galaxy/scripts/components/DataDialog/services.js b/client/galaxy/scripts/components/DataDialog/services.js new file mode 100644 index 00000000000..debf80724c7 --- /dev/null +++ b/client/galaxy/scripts/components/DataDialog/services.js @@ -0,0 +1,82 @@ +import axios from "axios"; + +/** Data populator traverses raw server responses **/ +export class Services { + constructor(options = {}) { + this.root = options.root; + this.host = options.host; + } + + get(url) { + return new Promise((resolve, reject) => { + axios + .get(url) + .then(response => { + let items = this.getItems(response.data); + resolve(items); + }) + .catch(e => { + let errorMessage = "Request failed."; + if (e.response) { + errorMessage = e.response.data.err_msg || `${e.response.statusText} (${e.response.status})`; + } + reject(errorMessage); + }); + }); + } + + /** Returns the formatted results **/ + getItems(data) { + let items = []; + let stack = [data]; + while (stack.length > 0) { + let root = stack.pop(); + if (Array.isArray(root)) { + root.forEach(element => { + stack.push(element); + }); + } else if (root.elements) { + stack.push(root.elements); + } else if (root.object) { + stack.push(root.object); + } else { + let record = this.getRecord(root); + if (record) { + items.push(record); + } + } + } + return items; + } + + /** Populate record data from raw record source **/ + getRecord(record) { + record.details = record.extension || record.description; + record.time = record.update_time || record.create_time; + record.isDataset = this.isDataset(record); + if (record.time) { + record.time = record.time.substring(0, 16).replace("T", " "); + } + if (record.model_class == "Library") { + record.url = `${this.root}api/libraries/${record.id}/contents`; + return record; + } else if (record.hid) { + record.name = `${record.hid}: ${record.name}`; + record.download = `${this.host}/api/histories/${record.history_id}/contents/${record.id}/display`; + return record; + } else if (record.type == "file") { + if (record.name && record.name[0] === "/") { + record.name = record.name.substring(1); + } + record.download = `${this.host}${this.root}api/libraries/datasets/download/uncompressed?ld_ids=${ + record.id + }`; + return record; + } + } + + /** Checks if record is a dataset or drillable **/ + isDataset(record) { + return record.history_content_type == "dataset" || record.type == "file"; + } +} diff --git a/client/galaxy/scripts/components/DataDialog/utilities.js b/client/galaxy/scripts/components/DataDialog/utilities.js new file mode 100644 index 00000000000..8ebcd1c3132 --- /dev/null +++ b/client/galaxy/scripts/components/DataDialog/utilities.js @@ -0,0 +1,28 @@ +/** This helps track urls for data drilling **/ +export class UrlTracker { + constructor(root) { + this.root = root; + this.navigation = []; + } + + /** Returns urls for data drilling **/ + getUrl(url) { + if (url) { + this.navigation.push(url); + } else { + this.navigation.pop(); + let navigationLength = this.navigation.length; + if (navigationLength > 0) { + url = this.navigation[navigationLength - 1]; + } else { + url = this.root; + } + } + return url; + } + + /** Returns true if the last data is at navigation root **/ + atRoot() { + return this.navigation.length == 0; + } +} diff --git a/client/galaxy/scripts/components/Nametags/Nametag.vue b/client/galaxy/scripts/components/Nametags/Nametag.vue new file mode 100644 index 00000000000..183db221f7b --- /dev/null +++ b/client/galaxy/scripts/components/Nametags/Nametag.vue @@ -0,0 +1,36 @@ + + + + + diff --git a/client/galaxy/scripts/components/Nametags/Nametags.vue b/client/galaxy/scripts/components/Nametags/Nametags.vue new file mode 100644 index 00000000000..adb4078a773 --- /dev/null +++ b/client/galaxy/scripts/components/Nametags/Nametags.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/client/galaxy/scripts/components/Nametags/index.js b/client/galaxy/scripts/components/Nametags/index.js new file mode 100644 index 00000000000..84f010168a6 --- /dev/null +++ b/client/galaxy/scripts/components/Nametags/index.js @@ -0,0 +1,6 @@ +import Nametags from "./Nametags"; +import { mountVueComponent } from "utils/mountVueComponent"; + +export { default as Nametag } from "./Nametag.vue"; +export { default as Nametags } from "./Nametags.vue"; +export const mountNametags = mountVueComponent(Nametags); diff --git a/client/galaxy/scripts/components/RuleCollectionBuilder.vue b/client/galaxy/scripts/components/RuleCollectionBuilder.vue index 7136d150a54..dccaac7e71a 100644 --- a/client/galaxy/scripts/components/RuleCollectionBuilder.vue +++ b/client/galaxy/scripts/components/RuleCollectionBuilder.vue @@ -448,7 +448,7 @@ - - `, - mounted: function() { - var vm = this; - $(this.$el) - // init select2 - .select2({ data: this.options, placeholder: this.placeholder, allowClear: this.placeholder }) - .val(this.value) - .trigger("change") - // emit event on change. - .on("change", function(event) { - vm.$emit("input", event.val); - }); - }, - watch: { - value: function(value) { - // update value - $(this.$el).val(value); - }, - options: function(options) { - // update options - $(this.$el) - .empty() - .select2({ data: options }); - } - }, - destroyed: function() { - $(this.$el) - .off() - .select2("destroy"); - } -}; - const ColumnSelector = { template: `
@@ -1029,7 +990,6 @@ export default { } return { rules: rules, - colHeadersPerRule: [], mapping: mapping, state: "build", // 'build', 'error', 'wait', ruleView: "normal", // 'normal' or 'source' @@ -1257,8 +1217,7 @@ export default { return targets; }, colHeaders() { - const data = this.hotData["data"]; - const columns = this.hotData["columns"]; + let { data, columns } = this.hotData; return RuleDefs.colHeadersFor(data, columns); }, colHeadersDisplay() { @@ -1374,6 +1333,44 @@ export default { valid = false; } return valid; + }, + hotData() { + let data, sources, columns; + if ( + this.elementsType == "datasets" || + this.elementsType == "library_datasets" || + this.elementsType == "ftp" + ) { + sources = this.initialElements.slice(); + data = sources.map(el => []); + columns = []; + } else if (this.elementsType == "collection_contents") { + const collection = this.initialElements; + if (collection) { + const obj = this.populateElementsFromCollectionDescription( + collection.elements, + collection.collection_type + ); + data = obj.data; + sources = obj.sources; + columns = []; + } else { + data = []; + sources = []; + columns = []; + } + } else { + data = this.initialElements.slice(); + sources = data.map(el => null); + columns = []; + if (this.initialElements) { + this.initialElements[0].forEach(() => columns.push("new")); + } + } + return RuleDefs.applyRules(data, sources, columns, this.rules); + }, + colHeadersPerRule() { + return this.hotData.colHeadersPerRule; } }, methods: { @@ -1412,42 +1409,6 @@ export default { this.rules.push(rule); } }, - hotData() { - let data, sources, columns; - if ( - this.elementsType == "datasets" || - this.elementsType == "library_datasets" || - this.elementsType == "ftp" - ) { - data = this.initialElements.map(el => []); - sources = this.initialElements.slice(); - columns = []; - } else if (this.elementsType == "collection_contents") { - const collection = this.initialElements; - if (collection) { - const obj = this.populateElementsFromCollectionDescription( - collection.elements, - collection.collection_type - ); - data = obj.data; - sources = obj.sources; - columns = []; - } else { - data = []; - sources = []; - columns = []; - } - } else { - data = this.initialElements.slice(); - sources = data.map(el => null); - columns = []; - if (this.initialElements) { - this.initialElements[0].forEach(() => columns.push("new")); - } - } - this.colHeadersPerRule = []; - return RuleDefs.applyRules(data, sources, columns, this.rules, this.colHeadersPerRule); - }, viewSource() { this.resetSource(); this.ruleView = "source"; @@ -1684,7 +1645,7 @@ export default { return identifierColumns; }, buildRequestElements(createDatasetDescription, createSubcollectionDescription, subElementProp) { - const data = this.hotData["data"]; + const data = this.hotData.data; const identifierColumns = this.identifierColumns(); if (identifierColumns.length < 1) { console.log("Error but this shouldn't have happened, create button should have been disabled."); @@ -1791,8 +1752,7 @@ export default { return elementsByName; }, creationElementsFromDatasets() { - const sources = this.hotData["sources"]; - const data = this.hotData["data"]; + const { sources, data } = this.hotData; const mappingAsDict = this.mappingAsDict; const elementsByCollectionName = this.buildRequestElements( @@ -1811,7 +1771,7 @@ export default { }, creationElementsForFetch() { // fetch elements for HDCA - const data = this.hotData["data"]; + const data = this.hotData.data; const mappingAsDict = this.mappingAsDict; const elementsByCollectionName = this.buildRequestElements( @@ -1830,7 +1790,7 @@ export default { }, creationDatasetsForFetch() { // fetch elements for HDAs if not collection information specified. - const data = this.hotData["data"]; + const data = this.hotData.data; const mappingAsDict = this.mappingAsDict; const datasets = []; diff --git a/client/galaxy/scripts/components/Select2.js b/client/galaxy/scripts/components/Select2.js new file mode 100644 index 00000000000..639511faeaf --- /dev/null +++ b/client/galaxy/scripts/components/Select2.js @@ -0,0 +1,37 @@ +// Based on https://vuejs.org/v2/examples/select2.html but adapted to handle list values +// with "multiple: true" set. +import $ from "jquery"; + +export default { + props: ["options", "value", "placeholder"], + template: ``, + mounted: function() { + var vm = this; + $(this.$el) + // init select2 + .select2({ data: this.options, placeholder: this.placeholder, allowClear: this.placeholder }) + .val(this.value) + .trigger("change") + // emit event on change. + .on("change", function(event) { + vm.$emit("input", event.val); + }); + }, + watch: { + value: function(value) { + // update value + $(this.$el).val(value); + }, + options: function(options) { + // update options + $(this.$el) + .empty() + .select2({ data: options }); + } + }, + destroyed: function() { + $(this.$el) + .off() + .select2("destroy"); + } +}; diff --git a/client/galaxy/scripts/components/Tags/StatelessTags.test.js b/client/galaxy/scripts/components/Tags/StatelessTags.test.js new file mode 100644 index 00000000000..fa8ca82a523 --- /dev/null +++ b/client/galaxy/scripts/components/Tags/StatelessTags.test.js @@ -0,0 +1,52 @@ +import { mount, createLocalVue } from "@vue/test-utils"; +import StatelessTags from "./StatelessTags"; +import _l from "utils/localization"; +import Vue from "vue"; + +describe("Tags/StatelessTags.vue", () => { + const localVue = createLocalVue(); + localVue.filter("localize", value => _l(value)); + + let testTags = ["abc", "def", "ghi"]; + let wrapper, emitted; + + beforeEach(async () => { + wrapper = mount(StatelessTags, { localVue }); + wrapper.setProps({ + value: testTags + }); + emitted = wrapper.emitted(); + await Vue.nextTick(); + }); + + it("should render a div for each tag", () => { + let tags = wrapper.findAll(".ti-tag-center"); + assert(tags.length == testTags.length, "Wrong number of tags"); + for (let i = 0; i < testTags.length; i++) { + assert(tags.at(i).is("div"), "button not a div"); + assert(tags.at(i).text() == testTags[i], "rendered tag label doesn't match test data"); + } + }); + + it("should emit a click event when the tag is clicked", () => { + let tags = wrapper.findAll(".ti-tag-center > div"); + tags.at(0).trigger("click"); + assert(emitted["tag-click"], "click event not detected"); + assert(emitted["tag-click"].length == 1, "wrong event count"); + }); + + it("should emit a tag model payload when tag is clicked", () => { + let tags = wrapper.findAll(".ti-tag-center > div"); + tags.at(0).trigger("click"); + let firstEvent = emitted["tag-click"][0]; + let firstArg = firstEvent[0]; + assert((firstArg.text = testTags[0]), "returned tag model doesn't match test data"); + }); + + it("should change internal model representation when new tag list assigned", async () => { + assert(wrapper.vm.tagModels.length == 3); + let newTags = ["floob", "clown", "hoohah", "doodoo"]; + wrapper.setProps({ value: newTags }); + assert(wrapper.vm.tagModels.length == newTags.length); + }); +}); diff --git a/client/galaxy/scripts/components/Tags/StatelessTags.vue b/client/galaxy/scripts/components/Tags/StatelessTags.vue new file mode 100644 index 00000000000..8cbaac11041 --- /dev/null +++ b/client/galaxy/scripts/components/Tags/StatelessTags.vue @@ -0,0 +1,188 @@ + + + + + + + diff --git a/client/galaxy/scripts/components/Tags/Tags.test.js b/client/galaxy/scripts/components/Tags/Tags.test.js new file mode 100644 index 00000000000..4c2fbf4b189 --- /dev/null +++ b/client/galaxy/scripts/components/Tags/Tags.test.js @@ -0,0 +1,149 @@ +/* global expect */ + +import Vuex from "vuex"; +import { mount, createLocalVue } from "@vue/test-utils"; +import Tags from "./Tags"; +import _l from "utils/localization"; +import store from "../../store"; +import { TagService } from "./tagService"; +import { createTag } from "./model"; + +describe("Tags/Tags.vue", () => { + const localVue = createLocalVue(); + localVue.filter("localize", value => _l(value)); + localVue.use(Vuex); + + let id = "testId", + itemClass = "TestObject", + context = "testing", + tagService, + wrapper, + emitted, + startingTags = ["a", "b", "c"], + storeKey = "testingTagSet"; + + let clickFirstTag = () => { + if (wrapper) { + let firstTag = wrapper.find(".ti-tag-center > div"); + firstTag.trigger("click"); + } else { + console.log("missing tag"); + } + }; + + // Run bef oreEach as async so the lifecycle methods can run + beforeEach(async () => { + // TODO: this mocking mechanism is no good. + + tagService = new TagService({ id, itemClass, context }); + + tagService.save = async function(tag) { + return createTag(tag); + }; + + tagService.delete = async function(tag) { + return createTag(tag); + }; + + tagService.autocomplete = async function(txt) { + return [txt].map(createTag); + }; + + // Mount the tags with sample props + + wrapper = mount(Tags, { + store, + localVue, + propsData: { + tags: startingTags, + storeKey, + tagService + } + }); + + emitted = wrapper.emitted(); + + // Waits for lifecycle handlers to execute + + await wrapper.vm.$nextTick(); + }); + + afterEach(() => { + tagService = null; + }); + + it("should display the tags I give it", async () => { + let tags = wrapper.findAll(".ti-tag-center .tag-name"); + expect(tags.length).to.equal(3); + expect(tags.at(0).text()).to.equal(startingTags[0]); + }); + + it("should put the initialized tags into the store at initialization", async () => { + let storedTags = store.getters.getTagsById(storeKey); + expect(storedTags.length).to.equal(3); + startingTags.forEach((tag, i) => { + expect(tag).to.equal(storedTags[i]); + }); + }); + + it("should respond to click events on the tags", async () => { + clickFirstTag(); + assert(emitted["tag-click"], "click event not detected"); + assert(emitted["tag-click"].length == 1, "wrong event count"); + }); + + it("should reflect changes in the store", async () => { + let newTags = ["asdfadsadf", "gfhjfghjf"]; + store.dispatch("updateTags", { key: storeKey, tags: newTags }); + + // TODO: figure out how to make the computed observableTags + // prop update when the store does. This works in the real code, + // but does not update in this test environment. The following + // brute force mechanism of changing a different dependency works + // and effectively recalculates the computed value, but it should + // not be necessary + wrapper.setProps({ storeKey: "thisshouldbeunnecessary" }); + wrapper.setProps({ storeKey }); + + let observed = wrapper.vm.observedTags; + expect(observed.length).to.equal(newTags.length); + observed.forEach((tagLabel, i) => { + expect(newTags[i]).to.equal(tagLabel); + }); + }); + + describe("autocomplete list", () => { + let subscription; + + afterEach(() => { + if (subscription) { + subscription.unsubscribe(); + } + }); + + it("should generate autocomplete items when you set the search text", done => { + let sampleTxt = "floobar"; + subscription = tagService.autocompleteOptions.subscribe(results => { + expect(results.length).equal(1); + results.forEach(r => { + expect(r.text).equal(sampleTxt); + }); + done(); + }); + tagService.autocompleteSearchText = sampleTxt; + }); + + it("should debounce autocomplete requests", done => { + let sampleTxt = "floobar"; + subscription = tagService.autocompleteOptions.subscribe(results => { + expect(results.length).equal(1); + results.forEach(r => { + expect(r.text).equal(sampleTxt); + }); + done(); + }); + tagService.autocompleteSearchText = "foo"; + tagService.autocompleteSearchText = sampleTxt; + }); + }); +}); diff --git a/client/galaxy/scripts/components/Tags/Tags.vue b/client/galaxy/scripts/components/Tags/Tags.vue new file mode 100644 index 00000000000..2fd42ced629 --- /dev/null +++ b/client/galaxy/scripts/components/Tags/Tags.vue @@ -0,0 +1,97 @@ + + + diff --git a/client/galaxy/scripts/components/Tags/backboneTagService.js b/client/galaxy/scripts/components/Tags/backboneTagService.js new file mode 100644 index 00000000000..7b162b5fd1c --- /dev/null +++ b/client/galaxy/scripts/components/Tags/backboneTagService.js @@ -0,0 +1,39 @@ +import { createTag } from "./model"; +import { TagService } from "./tagService"; + +// Some parts of the application save tags by saving model so that +// it will update other parts of the app, which is a rudimentary way +// of recreating reactivity, so I expect this will probably die soon + +export class BackboneTagService extends TagService { + constructor(props) { + super(props); + this.model = props.model; + } + + async save(rawTag) { + let tag = createTag(rawTag); + if (!tag.valid) { + throw new Error("Invalid tag"); + } + + // update model + let tags = new Set(this.model.attributes.tags); + tags.add(tag.text); + tags = Array.from(tags); + + await this.model.save({ tags }); + return tag; + } + + async delete(rawTag) { + let tag = createTag(rawTag); + + let tags = new Set(this.model.attributes.tags); + tags.delete(tag.text); + tags = Array.from(tags); + + await this.model.save({ tags }); + return tag; + } +} diff --git a/client/galaxy/scripts/components/Tags/index.js b/client/galaxy/scripts/components/Tags/index.js new file mode 100644 index 00000000000..d508d536d27 --- /dev/null +++ b/client/galaxy/scripts/components/Tags/index.js @@ -0,0 +1,9 @@ +// Low level stateless UI component +export { default as StatelessTags } from "./StatelessTags"; + +// Implements data storage logic and click event handling +// This is usually what you'll want to use. +export { default as Tags } from "./Tags"; + +// functions for mounting the tag editor in non-Vue environments +export { mountMakoTags, mountModelTags } from "./mounts"; diff --git a/client/galaxy/scripts/components/Tags/model.js b/client/galaxy/scripts/components/Tags/model.js new file mode 100644 index 00000000000..2449e22ce0e --- /dev/null +++ b/client/galaxy/scripts/components/Tags/model.js @@ -0,0 +1,88 @@ +/** + * The tag model is pretty simple, so this extra file might be overkill, but + * it's good practice to separate data modeling from data retrieval + */ + +import { keyedColorScheme } from "utils/color"; + +function TagModel(props = {}) { + this.text = ""; + + // special handling for name:thing tags + if (props.text && props.text.startsWith("#")) { + props.text = props.text.replace("#", "name:"); + } + + Object.assign(this, props); + + // Need to do Object.defineProperty instead of a class getter to make + // style enumerable for vue-tags-input + Object.defineProperty(this, "style", { + enumerable: true, + get: function() { + if (this.text.startsWith("name:")) { + let { primary, contrasting, darker } = keyedColorScheme(this.text); + + let styles = { + "background-color": primary, + color: contrasting, + "border-color": darker + }; + + return Object.keys(styles) + .map(prop => `${prop}: ${styles[prop]}`) + .join(";"); + } + return ""; + } + }); + + // Changes name:foo to #foo + Object.defineProperty(this, "label", { + enumerable: true, + get: function() { + return this.text.startsWith("name:") ? this.text.replace("name:", "#") : this.text; + } + }); + + // valid flag + Object.defineProperty(this, "valid", { + enumerable: false, + get: function() { + if (!this.text.length) return false; + if (this.text == "name:") return false; + return true; + } + }); +} + +TagModel.prototype.equals = function(otherTag) { + return this.text == otherTag.text; +}; + +TagModel.prototype.toString = function() { + return this.text; +}; + +// Public factory + +export function createTag(data) { + let props = {}; + switch (typeof data) { + case "string": + props = { text: data }; + break; + case "object": + props = Object.assign({}, data); + break; + } + return new TagModel(props); +} + +// Returns tags in "newTags" that aren't present in "existingTags" + +export const diffTags = (newTags, existingTags) => { + let newModels = newTags.map(createTag); + let existingModels = existingTags.map(createTag); + return newModels.filter(tag => !existingModels.some(st => st.equals(tag))); +}; diff --git a/client/galaxy/scripts/components/Tags/model.test.js b/client/galaxy/scripts/components/Tags/model.test.js new file mode 100644 index 00000000000..49b44fb485e --- /dev/null +++ b/client/galaxy/scripts/components/Tags/model.test.js @@ -0,0 +1,64 @@ +/* global expect */ +import { createTag, diffTags } from "./model"; + +describe("Tags/model.js", () => { + // Basic props + + describe("tag model", () => { + it("should have a string representation equal to text prop", () => { + let testLabel = "abc"; + let model = createTag(testLabel); + assert.equal(model, testLabel); + assert.equal(model.text, testLabel); + assert.equal(model.toString(), testLabel); + }); + }); + + // Factory Function + + describe("createTag", () => { + it("should build a model from a string", () => { + let label = "floob"; + let model = createTag(label); + expect(model.text).to.equal(label); + }); + + it("should build a model from an object", () => { + let data = { text: "floob" }; + let model = createTag(data); + expect(model.text).to.equal(data.text); + }); + }); + + // Filtered select function, currently used in component to remove + // selected items from a list of returned autocomplete options + + describe("diffTags", () => { + let source, selected; + + beforeEach(() => { + source = ["a", "b", "c", "d"].map(createTag); + selected = ["a", "d", "f"].map(createTag); + }); + + it("should remove duplicates from a passed array", () => { + let result = diffTags(source, selected); + expect(result.length).to.equal(2); + assert(result[0].equals(source[1]), true); + assert(result[0].equals(createTag("b")), true); + assert(result[1].equals(source[2]), true); + assert(result[1].equals(createTag("c")), true); + }); + }); + + describe("handles name tags", () => { + it("should accept a #label", () => { + let testLabel = "#abc"; + let expectedLabel = "name:abc"; + let model = createTag(testLabel); + assert.equal(model, expectedLabel); + assert.equal(model.text, expectedLabel); + assert.equal(model.toString(), expectedLabel); + }); + }); +}); diff --git a/client/galaxy/scripts/components/Tags/mounts.js b/client/galaxy/scripts/components/Tags/mounts.js new file mode 100644 index 00000000000..ae502d87e9b --- /dev/null +++ b/client/galaxy/scripts/components/Tags/mounts.js @@ -0,0 +1,84 @@ +/** + * These functions are for mounting the tag display/editor in non-Vue + * environments such as the existing python scripts and Backbone views. + */ + +import Tags from "./Tags"; +import { mountVueComponent } from "utils/mountVueComponent"; +import { redirectToUrl } from "utils/redirect"; +import { TagService } from "./tagService"; +import { BackboneTagService } from "./backboneTagService"; + +/** + * General mount function for the tags that were previously rendered + * by the tagging_common.mako file + */ +export const mountMakoTags = (options = {}, el) => { + let { id, itemClass, tags = [], disabled = false, context = "unspecified" } = options; + + let propData = { + storeKey: `${itemClass}-${id}`, + tagService: new TagService({ id, itemClass, context }), + tags, + disabled + }; + + let fn = mountVueComponent(Tags); + let vm = fn(propData, el); + vm.$on("tag-click", makoClickHandler(options, vm)); + return vm; +}; + +/** + * Generate a click handler for the tags + * + * @param {object} options Passed options from mount fn + */ +const makoClickHandler = (options, vm) => + function(tag) { + if (!tag) { + return; + } + + let { tagClickFn = "none", clickUrl } = options; + + switch (tagClickFn) { + // I made this match the existing behavior, but I am not clear on + // the reason why this link redirects to a raw json page + case "community_tag_click": + if (undefined !== clickUrl) { + let suffix = tag.value ? `:${tag.value}` : ""; + let href = `${clickUrl}?f-tags=${tag.text}${suffix}`; + redirectToUrl(href); + } + break; + + case "add_tag_to_grid_filter": + vm.$store.dispatch("toggleSearchTag", tag); + break; + } + }; + +/** + * Mount function when a backbone model is provided. + */ +export const mountModelTags = (options = {}, el) => { + let { model, disabled = false, context = "unspecified" } = options; + + if (!model) { + console.warn("Missing model in mountModelTags"); + return; + } + + let { id, model_class: itemClass, tags = [] } = model.attributes; + + let propData = { + storeKey: `${itemClass}-${id}`, + tagService: new BackboneTagService({ id, itemClass, context, model }), + tags, + disabled + }; + + let fn = mountVueComponent(Tags); + return fn(propData, el); +}; diff --git a/client/galaxy/scripts/components/Tags/tagService.js b/client/galaxy/scripts/components/Tags/tagService.js new file mode 100644 index 00000000000..9a0e6f36228 --- /dev/null +++ b/client/galaxy/scripts/components/Tags/tagService.js @@ -0,0 +1,112 @@ +/** + * Generates a service object used by the tagging component to save, delete and + * lookup potential tag options for the autocomplete feature. Standard typeahead + * debouncing and ajax cancelling functionality is also provided here, though an + * argument can be made that it more properly belongs in the component that is + * handling the inputs. + * + * TODO: convert the associated python endpoint to a legit json REST service + */ + +import axios from "axios"; +import { createTag } from "./model"; +import { Subject } from "rxjs"; +import { map, filter, debounceTime, switchMap, distinctUntilChanged } from "rxjs/operators"; + +export class TagService { + constructor({ id, itemClass, context, debounceInterval = 150 }) { + this.id = id; + this.itemClass = itemClass; + this.context = context; + this.debounceInterval = debounceInterval; + + // Buffer for autocomplete text changes + this._searchText = new Subject(); + } + + /** + * Autocomplete observable. Subscribe to this object to get updates to + * matching autocomplete results as the autocompleteSearchText property is + * changed + */ + get autocompleteOptions() { + return this._searchText.pipe( + map(txt => txt.replace("name:", "")), + filter(txt => txt.length), + debounceTime(this.debounceInterval), + distinctUntilChanged(), + switchMap(txt => this.autocomplete(txt)) + ); + } + + /** + * Set this property to start process of retrieving autocomplete results. + */ + set autocompleteSearchText(txt) { + this._searchText.next(txt); + } + + /** + * Save tag, input can be text string or tag model + * @param {string|Tag} tag + * @returns Promise yielding new tag model + */ + async save(rawTag) { + let { id, itemClass, context } = this; + let tag = createTag(rawTag); + if (!tag.valid) { + throw new Error("Invalid tag"); + } + let url = `/tag/add_tag_async?item_id=${id}&item_class=${itemClass}&context=${context}&new_tag=${tag.text}`; + let response = await axios.get(url); + if (response.status !== 200) { + throw new Error(`Unable to save tag: ${tag}`); + } + return createTag(tag); + } + + /** + * Delete tag, input can be text string or tag model + * @param {string|Tag} tag + * @returns Promise yielding deleted tag model + */ + async delete(rawTag) { + let { id, itemClass, context } = this; + let tag = createTag(rawTag); + let url = `/tag/remove_tag_async?item_id=${id}&item_class=${itemClass}&context=${context}&tag_name=${tag.text}`; + let response = await axios.get(url); + if (response.status !== 200) { + throw new Error(`Unable to delete tag: ${tag}`); + } + return tag; + } + + /** + * Looks up autocomplete options based on search text + * @param {string} searchText + * @returns Promise yielding an array of tag models + */ + async autocomplete(searchText) { + let { id, itemClass } = this; + let url = `/tag/tag_autocomplete_data?item_id=${id}&item_class=${itemClass}&q=${searchText}`; + let response = await axios.get(url); + if (response.status !== 200) { + throw new Error(`Unable to retrieve autocomplete tags for search string: ${searchText}`); + } + return parseAutocompleteResults(response.data).map(createTag); + } +} + +/** + * Utility function parser for the archaic result format in the current API. See + * testData/autocompleteResponse.txt for a sample. + * @param {string} rawResponse + */ +export function parseAutocompleteResults(rawResponse) { + return rawResponse + .split("\n") + .filter(line => line.includes("|")) + .map(line => line.split("|")[0]) + .filter(label => label.length) + .filter(label => label !== "#Header"); +} diff --git a/client/galaxy/scripts/components/Tags/tagService.test.js b/client/galaxy/scripts/components/Tags/tagService.test.js new file mode 100644 index 00000000000..82169efd590 --- /dev/null +++ b/client/galaxy/scripts/components/Tags/tagService.test.js @@ -0,0 +1,139 @@ +/* global expect */ +import sinon from "sinon"; +import { TagService, __RewireAPI__ as rewire } from "./tagService"; +import { createTag } from "./model"; +import { interval } from "rxjs"; +import { take, takeUntil } from "rxjs/operators"; + +// test response +import autocompleteResponse from "./testData/autocompleteResponse.txt"; + +describe("Tags/tagService.js", () => { + let svcParams = { + id: 123, + itemClass: "fooClass", + context: "", + debounceInterval: 50 // shorter value than default for unit tests + }; + + let svc = new TagService(svcParams); + + let mockAxios = { + get: () => null + }; + + let stub; + + beforeEach(() => { + rewire.__Rewire__("axios", mockAxios); + }); + + afterEach(() => { + if (stub) stub.restore(); + }); + + describe("save", () => { + let testLabel = "fo0bar123"; + let testTag = createTag(testLabel); + + let { id, itemClass, context } = svcParams; + let expectedSaveUrl = `/tag/add_tag_async?item_id=${id}&item_class=${itemClass}&context=${context}&new_tag=${testLabel}`; + + it("should save a string tag", async () => { + stub = sinon.stub(mockAxios, "get").resolves({ status: 200 }); + let savedTag = await svc.save(testLabel); + expect(savedTag.text).to.equal(testLabel); + assert(stub.calledWith(expectedSaveUrl)); + }); + + it("should save an object tag", async () => { + stub = sinon.stub(mockAxios, "get").resolves({ status: 200 }); + let savedTag = await svc.save(testTag); + expect(savedTag.text).to.equal(testLabel); + assert(stub.calledWith(expectedSaveUrl)); + }); + + // TODO: test error conditions + }); + + describe("delete", () => { + let testLabel = "fo0bar123"; + let testTag = createTag(testLabel); + + let { id, itemClass, context } = svcParams; + let expectedDeleteUrl = `/tag/remove_tag_async?item_id=${id}&item_class=${itemClass}&context=${context}&tag_name=${testLabel}`; + + it("should delete a text tag", async () => { + stub = sinon.stub(mockAxios, "get").resolves({ status: 200 }); + let result = await svc.delete(testTag); + assert(result); + assert(stub.calledWith(expectedDeleteUrl)); + }); + + it("should delete an object tag", async () => { + stub = sinon.stub(mockAxios, "get").resolves({ status: 200 }); + let result = await svc.delete(testTag); + assert(result); + assert(stub.calledWith(expectedDeleteUrl)); + }); + + // TODO: test error conditions + }); + + describe("autocomplete", () => { + let searchString = "foo"; + let { id, itemClass } = svcParams; + let expectedSearchUrl = `/tag/tag_autocomplete_data?item_id=${id}&item_class=${itemClass}&q=${searchString}`; + + let successResponse = { + status: 200, + data: autocompleteResponse + }; + + let checkAutocompleteResult = result => { + assert(result); + assert(result instanceof Array); + assert(result.length == 2); + assert(result[0] instanceof Object); + assert((result[0].text = "abc")); + }; + + // straight ajax request, unused in practice, but it's easier to + // test this call if we just expose it + it("ajax call should return tag objects", async () => { + stub = sinon.stub(mockAxios, "get").resolves(successResponse); + let result = await svc.autocomplete(searchString); + assert(stub.calledWith(expectedSearchUrl), "Called with wrong search url"); + checkAutocompleteResult(result); + }); + + // hit the search input with multiple entries, only one ajax call + // should result because of debouncing + it("should debounce autocomplete search inputs", done => { + let searchResult; + + // ends subscription to observable so test doesn't go on forever + let spamCount = Math.floor(Math.random() * 10); + let timer = interval(1500).pipe(take(1)); + + // stub ajax request to return the success response if the + // searchString is the expected input + stub = sinon.stub(mockAxios, "get").resolves(successResponse); + + svc.autocompleteOptions.pipe(takeUntil(timer)).subscribe( + result => (searchResult = result), + err => console.log("error", err), + () => { + assert(stub.called, "Ajax call not made"); + assert(stub.callCount == 1, `Wrong number of ajax calls: ${stub.callCount}`); + checkAutocompleteResult(searchResult); + done(); + } + ); + + // spam a bunch of key inputs + for (var i = 0; i < spamCount; i++) svc.autocompleteSearchText = new String(Math.random()); + svc.autocompleteSearchText = searchString; + }); + }); +}); diff --git a/client/galaxy/scripts/components/Tags/testData/autocompleteResponse.txt b/client/galaxy/scripts/components/Tags/testData/autocompleteResponse.txt new file mode 100644 index 00000000000..7d8b85f44af --- /dev/null +++ b/client/galaxy/scripts/components/Tags/testData/autocompleteResponse.txt @@ -0,0 +1,3 @@ +#Header|Your Tags +abc|abc +def|def \ No newline at end of file diff --git a/client/galaxy/scripts/components/login/Index.vue b/client/galaxy/scripts/components/login/Index.vue new file mode 100644 index 00000000000..aaa5a784f62 --- /dev/null +++ b/client/galaxy/scripts/components/login/Index.vue @@ -0,0 +1,66 @@ + + diff --git a/client/galaxy/scripts/components/login/Login.vue b/client/galaxy/scripts/components/login/Login.vue index 48dc96c35eb..82d8f9a7ff8 100644 --- a/client/galaxy/scripts/components/login/Login.vue +++ b/client/galaxy/scripts/components/login/Login.vue @@ -3,21 +3,30 @@
- - - - - - - - Forgot password? Click here to reset your - password. - - Login + + + + + + + + + Forgot password? Click here to reset your + password. + + Login + + + Don't have an account? + Register here. + + + Sign in with {{ idp.charAt(0).toUpperCase() + idp.slice(1) }} +
@@ -46,7 +55,14 @@ export default { } }, data() { - let Galaxy = getGalaxyInstance(); + let galaxy = getGalaxyInstance(); + let oidc_idps = galaxy.config.oidc; + // Icons to use for each IdP + let oidc_idps_icons = {'google': 'fa fa-google'}; + // Add default icons to IdPs without icons + oidc_idps.filter(function(key) { return oidc_idps_icons[key] === undefined; }).forEach(function(idp) { + oidc_idps_icons[idp] = 'fa fa-id-card' + }); return { login: null, password: null, @@ -54,7 +70,12 @@ export default { provider: null, messageText: null, messageVariant: null, - redirect: Galaxy.params.redirect + redirect: galaxy.params.redirect, + session_csrf_token: galaxy.session_csrf_token, + enable_oidc: galaxy.config.enable_oidc, + oidc_idps: oidc_idps, + oidc_idps_icons: oidc_idps_icons + }; }, computed: { @@ -63,11 +84,15 @@ export default { } }, methods: { - submit: function(method) { + toggleLogin: function() { + if (this.$root.toggleLogin) { + this.$root.toggleLogin(); + } + }, + submitGalaxyLogin: function(method) { let rootUrl = getAppRoot(); - let data = { login: this.login, password: this.password, redirect: this.redirect }; axios - .post(`${rootUrl}user/login`, data) + .post(`${rootUrl}user/login`, this.$data) .then(response => { if (response.data.message && response.data.status) { alert(response.data.message); @@ -86,6 +111,22 @@ export default { this.messageText = message || "Login failed for an unknown reason."; }); }, + submitOIDCLogin: function(idp) { + let rootUrl = getAppRoot(); + axios + .post(`${rootUrl}authnz/${idp}/login`) + .then(response => { + if (response.data.redirect_uri) { + window.location = response.data.redirect_uri; + } + // Else do something intelligent or maybe throw an error -- what else does this endpoint possibly return? + }) + .catch(error => { + this.messageVariant = "danger"; + let message = error.response.data && error.response.data.err_msg; + this.messageText = message || "Login failed for an unknown reason."; + }); + }, reset: function(ev) { let rootUrl = getAppRoot(); ev.preventDefault(); diff --git a/client/galaxy/scripts/components/login/Register.vue b/client/galaxy/scripts/components/login/Register.vue new file mode 100644 index 00000000000..2009c783a1f --- /dev/null +++ b/client/galaxy/scripts/components/login/Register.vue @@ -0,0 +1,115 @@ + + diff --git a/client/galaxy/scripts/entry/admin/AdminRouter.js b/client/galaxy/scripts/entry/admin/AdminRouter.js index 27dfc413142..1eeae6a2138 100644 --- a/client/galaxy/scripts/entry/admin/AdminRouter.js +++ b/client/galaxy/scripts/entry/admin/AdminRouter.js @@ -9,6 +9,7 @@ import DataTables from "components/admin/DataTables.vue"; import DataTypes from "components/admin/DataTypes.vue"; import DataManagerView from "components/admin/DataManager/DataManagerView.vue"; import DataManagerRouter from "components/admin/DataManager/DataManagerRouter.vue"; +import Register from "components/login/Register.vue"; import ErrorStack from "components/admin/ErrorStack.vue"; import DisplayApplications from "components/admin/DisplayApplications.vue"; import Vue from "vue"; @@ -20,6 +21,7 @@ export const getAdminRouter = (Galaxy, options) => { routes: { "(/)admin(/)": "home", "(/)admin(/)users": "show_users", + "(/)admin(/)users(/)create": "show_users_create", "(/)admin(/)roles": "show_roles", "(/)admin(/)groups": "show_groups", "(/)admin(/)error_stack": "show_error_stack", @@ -54,6 +56,20 @@ export const getAdminRouter = (Galaxy, options) => { this._show_grid_view("admin/users_list"); }, + show_users_create: function() { + let instance = Vue.extend(Register); + let vm = document.createElement("div"); + this.page.display(vm); + new instance({ + propsData: { + redirect: "/admin/users", + registration_warning_message: options.config.registration_warning_message, + mailing_join_addr: options.config.mailing_join_addr, + smtp_server: options.config.smtp_server + } + }).$mount(vm); + }, + show_roles: function() { this._show_grid_view("admin/roles_list"); }, diff --git a/client/galaxy/scripts/entry/analysis/AnalysisRouter.js b/client/galaxy/scripts/entry/analysis/AnalysisRouter.js index 2d8b1eb123e..3fc93a5cb39 100644 --- a/client/galaxy/scripts/entry/analysis/AnalysisRouter.js +++ b/client/galaxy/scripts/entry/analysis/AnalysisRouter.js @@ -172,7 +172,8 @@ export const getAnalysisRouter = Galaxy => }, show_histories: function(action_id) { - this.page.display(new HistoryList.View({ action_id: action_id })); + let view = new HistoryList.View({ action_id: action_id }); + this.page.display(view); }, show_history_citations: function() { diff --git a/client/galaxy/scripts/entry/login/index.js b/client/galaxy/scripts/entry/login/index.js index c9bc1b0d652..151c5567210 100644 --- a/client/galaxy/scripts/entry/login/index.js +++ b/client/galaxy/scripts/entry/login/index.js @@ -1,6 +1,6 @@ import { standardInit, addInitialization } from "onload"; import Page from "layout/page"; -import Login from "components/login/Login.vue"; +import Index from "components/login/Index.vue"; import Password from "components/login/Password.vue"; import Vue from "vue"; @@ -9,7 +9,7 @@ export function initLoginView(Galaxy, { options }) { Galaxy.page = new Page.View(options); var vm = document.createElement("div"); Galaxy.display(vm); - var component = Galaxy.params.token || Galaxy.params.expired_user ? Password : Login; + var component = Galaxy.params.token || Galaxy.params.expired_user ? Password : Index; var loginInstance = Vue.extend(component); new loginInstance({ propsData: options }).$mount(vm); } diff --git a/client/galaxy/scripts/entry/panels/history-panel.js b/client/galaxy/scripts/entry/panels/history-panel.js index cbca070a873..ac15e5b3c4d 100644 --- a/client/galaxy/scripts/entry/panels/history-panel.js +++ b/client/galaxy/scripts/entry/panels/history-panel.js @@ -8,8 +8,8 @@ import CurrentHistoryView from "mvc/history/history-view-edit-current"; /** the right hand panel in the analysis page that shows the current history */ var HistoryPanel = Backbone.View.extend({ initialize: function(page, options) { - let Galaxy = getGalaxyInstance(); - let self = this; + const Galaxy = getGalaxyInstance(); + const panelHeaderButtons = []; this.userIsAnonymous = Galaxy.user.isAnonymous(); this.allow_user_dataset_purge = options.config.allow_user_dataset_purge; @@ -32,10 +32,34 @@ var HistoryPanel = Backbone.View.extend({ title: _l("Refresh history"), cls: "panel-header-button", icon: "fa fa-refresh", - onclick: function() { - self.historyView.loadCurrentHistory(); + onclick: () => { + this.historyView.loadCurrentHistory(); } }); + panelHeaderButtons.push(this.buttonRefresh); + + if (!this.userIsAnonymous){ + this.buttonNew = new Ui.ButtonLink({ + id: "history-new-button", + title: _l("Create new history"), + cls: "panel-header-button", + icon: "fa fa-plus", + onclick: function() { + Galaxy.currHistoryPanel.createNewHistory(); + } + }); + panelHeaderButtons.push(this.buttonNew); + } + + this.buttonViewMulti = new Ui.ButtonLink({ + id: "history-view-multi-button", + title: _l("View all histories"), + cls: "panel-header-button", + icon: "fa fa-columns", + href: `${this.root}history/view_multiple` + }); + panelHeaderButtons.push(this.buttonViewMulti); + this.buttonOptions = new Ui.ButtonLink({ id: "history-options-button", title: _l("History options"), @@ -44,19 +68,13 @@ var HistoryPanel = Backbone.View.extend({ icon: "fa fa-cog", href: `${this.root}root/history_options` }); - this.buttonViewMulti = new Ui.ButtonLink({ - id: "history-view-multi-button", - title: _l("View all histories"), - cls: "panel-header-button", - icon: "fa fa-columns", - href: `${this.root}history/view_multiple` - }); + panelHeaderButtons.push(this.buttonOptions); - // define components this.model = new Backbone.Model({ + // define components cls: "history-right-panel", title: _l("History"), - buttons: [this.buttonRefresh, this.buttonOptions, this.buttonViewMulti] + buttons: panelHeaderButtons }); // build body template and connect history view diff --git a/client/galaxy/scripts/entry/panels/tool-panel.js b/client/galaxy/scripts/entry/panels/tool-panel.js index 62efacb7947..49b36eb9414 100644 --- a/client/galaxy/scripts/entry/panels/tool-panel.js +++ b/client/galaxy/scripts/entry/panels/tool-panel.js @@ -1,4 +1,5 @@ import Backbone from "backbone"; +import $ from "jquery"; import Tools from "mvc/tool/tools"; import Upload from "mvc/upload/upload-view"; import _l from "utils/localization"; @@ -6,6 +7,7 @@ import _l from "utils/localization"; import _ from "libs/underscore"; import { getGalaxyInstance } from "app"; import { getAppRoot } from "onload"; +import Buttons from "mvc/ui/ui-buttons"; var ToolPanel = Backbone.View.extend({ initialize: function(page, options) { @@ -42,13 +44,25 @@ var ToolPanel = Backbone.View.extend({ default_extension: config.default_extension }); + // add favorite filter button + this.favorite_button = new Buttons.ButtonLink({ + cls: "panel-header-button", + title: _l("Show favorites"), + icon: "fa fa-star-o", + onclick: e => { + $("#tool-search-query") + .val("#favorites") + .trigger("change"); + } + }); + // add uploader button to Galaxy object Galaxy.upload = this.upload_button; // components for panel definition this.model = new Backbone.Model({ title: _l("Tools"), - buttons: [this.upload_button] + buttons: [this.upload_button, this.favorite_button] }); // build body template diff --git a/client/galaxy/scripts/galaxy.interactive_environments.js b/client/galaxy/scripts/galaxy.interactive_environments.js index 430bf463a1d..6ae025c5bb7 100644 --- a/client/galaxy/scripts/galaxy.interactive_environments.js +++ b/client/galaxy/scripts/galaxy.interactive_environments.js @@ -210,3 +210,38 @@ export function load_when_ready(url, success_callback) { var spin_state = make_spin_state("IE container readiness"); spin_until(url, true, messages, success_callback, spin_state); } + +/** + * Keep the container alive by pinging `notebookAccessURL` every 10 seconds. + * If the user leaves this site this function is not constantly pinging the + * container, the container will terminate itself. + */ +export function keepAlive(notebookAccessURL) { + var request_count = 0; + var interval = window.setInterval(function() { + $.ajax({ + url: notebookAccessURL, + xhrFields: { + withCredentials: true + }, + type: "GET", + timeout: 500, + success: function() { + console.log("Connected to IE, returning"); + }, + error: function(jqxhr, status, error) { + request_count++; + console.log("Request " + request_count); + if (request_count > 30) { + window.clearInterval(interval); + clear_main_area(); + toastr.error("Could not connect to IE, contact your administrator", "Error", { + closeButton: true, + timeOut: 20000, + tapToDismiss: false + }); + } + } + }); + }, 10000); +} diff --git a/client/galaxy/scripts/galaxy.library.js b/client/galaxy/scripts/galaxy.library.js index 2cf7cad9164..3cf8fb8f63c 100644 --- a/client/galaxy/scripts/galaxy.library.js +++ b/client/galaxy/scripts/galaxy.library.js @@ -8,7 +8,7 @@ import Backbone from "backbone"; import { getAppRoot } from "onload/loadConfig"; import { getGalaxyInstance } from "app"; // import mod_utils from "utils/utils"; -import mod_toastr from "libs/toastr"; +import { Toast } from "ui/toast"; import mod_baseMVC from "mvc/base-mvc"; // import mod_library_model from "mvc/library/library-model"; import mod_folderlist_view from "mvc/library/library-folderlist-view"; @@ -179,7 +179,7 @@ var GalaxyLibrary = Backbone.View.extend({ this.library_router.on("route:download", (folder_id, format) => { if ($("#folder_list_body").find(":checked").length === 0) { - mod_toastr.info("You must select at least one dataset to download"); + Toast.info("You must select at least one dataset to download"); Galaxy.libraries.library_router.navigate(`folders/${folder_id}`, { trigger: true, replace: true }); } else { Galaxy.libraries.folderToolbarView.download(format); diff --git a/client/galaxy/scripts/layout/communication-server-view.js b/client/galaxy/scripts/layout/communication-server-view.js index a3baf9758c2..b54030873a6 100644 --- a/client/galaxy/scripts/layout/communication-server-view.js +++ b/client/galaxy/scripts/layout/communication-server-view.js @@ -28,7 +28,7 @@ export var CommunicationServerView = Backbone.View.extend({ var $el_chat_modal_header = null; var $el_chat_modal_body = null; - var iframe_template = ``; + var iframe_template = ``; var header_template = '' + diff --git a/client/galaxy/scripts/layout/data.js b/client/galaxy/scripts/layout/data.js index 362f9486ba7..44f4cad5b6e 100644 --- a/client/galaxy/scripts/layout/data.js +++ b/client/galaxy/scripts/layout/data.js @@ -1,5 +1,5 @@ import $ from "jquery"; -import DataDialog from "components/DataDialog.vue"; +import DataDialog from "components/DataDialog/DataDialog.vue"; import Vue from "vue"; import { getGalaxyInstance } from "app"; import { getAppRoot } from "onload/loadConfig"; @@ -9,14 +9,20 @@ export default class Data { * Opens a modal dialog for data selection * @param {function} callback - Result function called with selection */ - dialog(callback) { + dialog(callback, options = {}) { + let galaxy = getGalaxyInstance(); + let host = `${window.location.protocol}//${window.location.hostname}:${window.location.port}`; + Object.assign(options, { + callback: callback, + history: galaxy.currHistoryPanel && galaxy.currHistoryPanel.model.id, + root: galaxy.root, + host: host + }); var instance = Vue.extend(DataDialog); var vm = document.createElement("div"); $("body").append(vm); new instance({ - propsData: { - callback: callback - } + propsData: options }).$mount(vm); } diff --git a/client/galaxy/scripts/layout/menu.js b/client/galaxy/scripts/layout/menu.js index 0a3e1b05827..4eb07f03985 100644 --- a/client/galaxy/scripts/layout/menu.js +++ b/client/galaxy/scripts/layout/menu.js @@ -1,6 +1,7 @@ /** Masthead Collection **/ import _ from "underscore"; import $ from "jquery"; +import axios from "axios"; import Backbone from "backbone"; import { getAppRoot } from "onload/loadConfig"; import { getGalaxyInstance } from "app"; @@ -9,6 +10,18 @@ import { CommunicationServerView } from "layout/communication-server-view"; import Webhooks from "mvc/webhooks"; import Utils from "utils/utils"; +function logoutClick() { + let galaxy = getGalaxyInstance(); + let session_csrf_token = galaxy.session_csrf_token; + let url = `${galaxy.root}user/logout?session_csrf_token=${session_csrf_token}`; + axios.get(url).then(() => { + if (galaxy.user) { + galaxy.user.clearSessionStorage(); + } + window.top.location.href = `${galaxy.root}login`; + }); +} + var Collection = Backbone.Collection.extend({ model: Backbone.Model.extend({ defaults: { @@ -35,8 +48,7 @@ var Collection = Backbone.Collection.extend({ id: "analysis", title: _l("Analyze Data"), url: "", - tooltip: _l("Analysis home view"), - target: "__use_router__" + tooltip: _l("Analysis home view") }); // @@ -208,20 +220,6 @@ var Collection = Backbone.Collection.extend({ target: "_blank" }); } - if (options.biostar_url) { - helpTab.menu.unshift({ - title: _l("Ask a question"), - url: "biostar/biostar_question_redirect", - target: "_blank" - }); - } - if (options.biostar_url) { - helpTab.menu.unshift({ - title: _l("Galaxy Biostar"), - url: options.biostar_url_redirect, - target: "_blank" - }); - } if (options.helpsite_url) { helpTab.menu.unshift({ title: _l("Galaxy Help"), @@ -241,21 +239,8 @@ var Collection = Backbone.Collection.extend({ id: "user", title: _l("Login or Register"), cls: "loggedout-only", - tooltip: _l("Account registration or login"), - menu: [ - { - title: _l("Login"), - url: "login", - target: "_top", - noscratchbook: true - }, - { - title: _l("Register"), - url: "user/create", - target: "galaxy_main", - noscratchbook: true - } - ] + url: "login", + tooltip: _l("Account registration or login") }; } else { userTab = { @@ -290,9 +275,8 @@ var Collection = Backbone.Collection.extend({ }, { title: _l("Logout"), - url: `user/logout?session_csrf_token=${Galaxy.session_csrf_token}`, - target: "_top", - divider: true + divider: true, + onclick: logoutClick }, { title: _l("Saved Datasets"), @@ -421,7 +405,7 @@ var Tab = Backbone.View.extend({ } else { let Galaxy = getGalaxyInstance(); if (options.target == "__use_router__" && typeof Galaxy.page != "undefined") { - Galaxy.page.router.push(options.url); + Galaxy.page.router.executeUseRouter(options.url); } else { try { Galaxy.frame.add(options); @@ -460,7 +444,7 @@ var Tab = Backbone.View.extend({ } else { let Galaxy = getGalaxyInstance(); if (model.attributes.target == "__use_router__" && typeof Galaxy.page != "undefined") { - Galaxy.page.router.push(model.attributes.url); + Galaxy.page.router.executeUseRouter(model.attributes.url); } else { Galaxy.frame.add(model.attributes); } @@ -476,10 +460,7 @@ var Tab = Backbone.View.extend({ .popover({ html: true, placement: "bottom", - content: `Please ${this.buildLink("login", "login")} or ${this.buildLink( - "register", - "user/create?use_panels=True" - )} to use this feature.` + content: `Please ${this.buildLink("login or register", "login")} to use this feature.` }) .popover("show"); window.setTimeout(() => { diff --git a/client/galaxy/scripts/layout/panel.js b/client/galaxy/scripts/layout/panel.js index af891ca3bf5..d277923eb98 100644 --- a/client/galaxy/scripts/layout/panel.js +++ b/client/galaxy/scripts/layout/panel.js @@ -231,9 +231,18 @@ var CenterPanel = Backbone.View.extend({ /** Display a view in the center panel, hide iframe */ display: function(view) { - var contentWindow = this.$frame[0].contentWindow || {}; - var message = contentWindow.onbeforeunload && contentWindow.onbeforeunload(); - var Galaxy = getGalaxyInstance(); + let Galaxy = getGalaxyInstance(); + let contentWindow = this.$frame[0].contentWindow || {}; + let message; + try { + message = contentWindow.onbeforeunload && contentWindow.onbeforeunload(); + } catch (err) { + // This can happen when external content is displayed in this iframe // CORS violation + contentWindow = {}; + console.warn( + "Iframe unload exception. This can happen when external content is displayed in the page iframe and causes a CORS violation -- likely harmless." + ); + } if (!message || confirm(message)) { contentWindow.onbeforeunload = undefined; this.$frame.attr("src", "about:blank").hide(); diff --git a/client/galaxy/scripts/layout/router.js b/client/galaxy/scripts/layout/router.js index 8b6a8cb518d..dd5f6a6df3d 100644 --- a/client/galaxy/scripts/layout/router.js +++ b/client/galaxy/scripts/layout/router.js @@ -1,5 +1,6 @@ import $ from "jquery"; import Backbone from "backbone"; +import { getAppRoot } from "onload/loadConfig"; import { getGalaxyInstance } from "app"; import QUERY_STRING from "utils/query-string-parsing"; import Ui from "mvc/ui/ui-misc"; @@ -12,6 +13,14 @@ var Router = Backbone.Router.extend({ this.options = options; }, + executeUseRouter: function(url) { + let prefix = getAppRoot(); + if (url.startsWith(prefix)) { + url = url.replace(prefix, "/"); + } + return this.push(url); + }, + /** helper to push a new navigation state */ push: function(url, data) { data = data || {}; @@ -19,10 +28,12 @@ var Router = Backbone.Router.extend({ .toString(36) .substr(2); url += url.indexOf("?") == -1 ? "?" : "&"; - url += $.param(data, true); + let bustParam = $.param(data, true); + url += bustParam; let Galaxy = getGalaxyInstance(); Galaxy.params = data; this.navigate(url, { trigger: true }); + window.history.replaceState(window.history.state, "", window.location.pathname.replace(bustParam, "")); }, /** override to parse query string into obj and send to each route */ diff --git a/client/galaxy/scripts/libs/jquery/jquery.event.drag.js b/client/galaxy/scripts/libs/jquery/jquery.event.drag.js index 8648e94b6de..8f8e9c581b7 100644 --- a/client/galaxy/scripts/libs/jquery/jquery.event.drag.js +++ b/client/galaxy/scripts/libs/jquery/jquery.event.drag.js @@ -178,7 +178,7 @@ drag = $special.drag = { case !dd.dragging && 'touchmove': event.preventDefault(); case !dd.dragging && 'mousemove': - // drag tolerance, x² + y² = distance² + // drag tolerance, x² + y² = distance² if ( Math.pow( event.pageX-dd.pageX, 2 ) + Math.pow( event.pageY-dd.pageY, 2 ) < Math.pow( dd.distance, 2 ) ) break; // distance tolerance not reached event.target = dd.target; // force target from "mousedown" event (fix distance issue) diff --git a/client/galaxy/scripts/libs/jquery/jquery.event.hover.js b/client/galaxy/scripts/libs/jquery/jquery.event.hover.js index c607990ef59..ebe7c1cd4a3 100644 --- a/client/galaxy/scripts/libs/jquery/jquery.event.hover.js +++ b/client/galaxy/scripts/libs/jquery/jquery.event.hover.js @@ -1,84 +1,84 @@ -;(function($){ // secure $ jQuery alias -/*******************************************************************************************/ -// jquery.event.hover.js -// Copyright (c) 2008, Three Dub Media (http://threedubmedia.com) -// Licensed under the MIT License (MIT-LICENSE.txt) -// http://www.opensource.org/licenses/mit-license.php -// -// JQuery 1.9+ compatible version -// -// Optional settings : -// $.event.special.hover.delay = 100; -// Defines the delay (msec) while mouse is inside the element before checking the speed -// $.event.special.hover.speed = 100; -// Defines the maximum speed (px/sec) the mouse may be moving to trigger the hover event -/*******************************************************************************************/ - -// save the old jquery "hover" method -$.fn._hover = $.fn.hover; - -// jquery method -$.fn.hover = function( fn1, fn2, fn3 ) { - if ( fn3 ) this.bind('hoverstart', fn1 ); // 3 args - if ( fn2 ) this.bind('hoverend', fn3 ? fn3 : fn2 ); // 2+ args - return !fn1 ? this.trigger('hover') // 0 args - : this.bind('hover', fn3 ? fn2 : fn1 ); // 1+ args - }; - -// special event configuration -var hover = $.event.special.hover = { - delay: 100, // milliseconds - speed: 100, // pixels per second - setup: function( data ){ - data = $.extend({ speed: hover.speed, delay: hover.delay, hovered:0 }, data||{} ); - $.event.add( this, "mouseenter mouseleave", hoverHandler, data ); - }, - teardown: function(){ - $.event.remove( this, "mouseenter mouseleave", hoverHandler ); - } - }; - -// shared event handler -function hoverHandler( event ){ - var data = event.data || event; - switch ( event.type ){ - case 'mouseenter': // mouseover - data.dist2 = 0; // init mouse distance² - data.event = event; // store the event - event.type = "hoverstart"; // hijack event - if($.event.dispatch.call(this, event) !== false) { - data.elem = this; // ref to the current element - $.event.add( this, "mousemove", hoverHandler, data ); // track the mouse - data.timer = setTimeout( compare, data.delay ); // start async compare - } - break; - case 'mousemove': // track the event, mouse distance² = x² + y² - data.dist2 += Math.pow( event.pageX-data.event.pageX, 2 ) - + Math.pow( event.pageY-data.event.pageY, 2 ); - data.event = event; // store current event - break; - case 'mouseleave': // mouseout - clearTimeout( data.timer ); // uncompare - if ( data.hovered ){ - event.type = "hoverend"; // hijack event - $.event.dispatch.call(this, event); // handle "hoverend" - data.hovered--; // reset flag - } - else $.event.remove( data.elem, "mousemove", hoverHandler ); // untrack - break; - default: // timeout compare // distance² = x² + y² = ( speed * time )² - if ( data.dist2 <= Math.pow( data.speed*( data.delay/1e3 ), 2 ) ){ // speed acceptable - $.event.remove( data.elem, "mousemove", hoverHandler ); // untrack - data.event.type = "hover"; // hijack event - if($.event.dispatch.call(data.elem, data.event) !== false) // handle "hover" - data.hovered++; // flag for "hoverend" - } - else data.timer = setTimeout( compare, data.delay ); // async recurse - data.dist2 = 0; // reset distance² for next compare - break; - } - function compare(){ hoverHandler( data ); }; // timeout/recursive function - }; - -/*******************************************************************************************/ +;(function($){ // secure $ jQuery alias +/*******************************************************************************************/ +// jquery.event.hover.js +// Copyright (c) 2008, Three Dub Media (http://threedubmedia.com) +// Licensed under the MIT License (MIT-LICENSE.txt) +// http://www.opensource.org/licenses/mit-license.php +// +// JQuery 1.9+ compatible version +// +// Optional settings : +// $.event.special.hover.delay = 100; +// Defines the delay (msec) while mouse is inside the element before checking the speed +// $.event.special.hover.speed = 100; +// Defines the maximum speed (px/sec) the mouse may be moving to trigger the hover event +/*******************************************************************************************/ + +// save the old jquery "hover" method +$.fn._hover = $.fn.hover; + +// jquery method +$.fn.hover = function( fn1, fn2, fn3 ) { + if ( fn3 ) this.bind('hoverstart', fn1 ); // 3 args + if ( fn2 ) this.bind('hoverend', fn3 ? fn3 : fn2 ); // 2+ args + return !fn1 ? this.trigger('hover') // 0 args + : this.bind('hover', fn3 ? fn2 : fn1 ); // 1+ args + }; + +// special event configuration +var hover = $.event.special.hover = { + delay: 100, // milliseconds + speed: 100, // pixels per second + setup: function( data ){ + data = $.extend({ speed: hover.speed, delay: hover.delay, hovered:0 }, data||{} ); + $.event.add( this, "mouseenter mouseleave", hoverHandler, data ); + }, + teardown: function(){ + $.event.remove( this, "mouseenter mouseleave", hoverHandler ); + } + }; + +// shared event handler +function hoverHandler( event ){ + var data = event.data || event; + switch ( event.type ){ + case 'mouseenter': // mouseover + data.dist2 = 0; // init mouse distance² + data.event = event; // store the event + event.type = "hoverstart"; // hijack event + if($.event.dispatch.call(this, event) !== false) { + data.elem = this; // ref to the current element + $.event.add( this, "mousemove", hoverHandler, data ); // track the mouse + data.timer = setTimeout( compare, data.delay ); // start async compare + } + break; + case 'mousemove': // track the event, mouse distance² = x² + y² + data.dist2 += Math.pow( event.pageX-data.event.pageX, 2 ) + + Math.pow( event.pageY-data.event.pageY, 2 ); + data.event = event; // store current event + break; + case 'mouseleave': // mouseout + clearTimeout( data.timer ); // uncompare + if ( data.hovered ){ + event.type = "hoverend"; // hijack event + $.event.dispatch.call(this, event); // handle "hoverend" + data.hovered--; // reset flag + } + else $.event.remove( data.elem, "mousemove", hoverHandler ); // untrack + break; + default: // timeout compare // distance² = x² + y² = ( speed * time )² + if ( data.dist2 <= Math.pow( data.speed*( data.delay/1e3 ), 2 ) ){ // speed acceptable + $.event.remove( data.elem, "mousemove", hoverHandler ); // untrack + data.event.type = "hover"; // hijack event + if($.event.dispatch.call(data.elem, data.event) !== false) // handle "hover" + data.hovered++; // flag for "hoverend" + } + else data.timer = setTimeout( compare, data.delay ); // async recurse + data.dist2 = 0; // reset distance² for next compare + break; + } + function compare(){ hoverHandler( data ); }; // timeout/recursive function + }; + +/*******************************************************************************************/ })(jQuery); // confine scope \ No newline at end of file diff --git a/client/galaxy/scripts/libs/toastr.js b/client/galaxy/scripts/libs/toastr.js deleted file mode 100644 index ff2046b7ca9..00000000000 --- a/client/galaxy/scripts/libs/toastr.js +++ /dev/null @@ -1,307 +0,0 @@ -/* - * Toastr - * Version 2.0.1 - * Copyright 2012 John Papa and Hans Fjällemark. - * All Rights Reserved. - * Use, reproduction, distribution, and modification of this code is subject to the terms and - * conditions of the MIT license, available at http://www.opensource.org/licenses/mit-license.php - * - * Author: John Papa and Hans Fjällemark - * Project: https://github.com/CodeSeven/toastr - */ -; (function (define) { - define([], function () { - var $ = jQuery; - return (function () { - var version = '2.0.1'; - var $container; - var listener; - var toastId = 0; - var toastType = { - error: 'error', - info: 'info', - success: 'success', - warning: 'warning' - }; - - var toastr = { - clear: clear, - error: error, - getContainer: getContainer, - info: info, - options: {}, - subscribe: subscribe, - success: success, - version: version, - warning: warning - }; - - return toastr; - - //#region Accessible Methods - function error(message, title, optionsOverride) { - return notify({ - type: toastType.error, - iconClass: getOptions().iconClasses.error, - message: message, - optionsOverride: optionsOverride, - title: title - }); - } - - function info(message, title, optionsOverride) { - return notify({ - type: toastType.info, - iconClass: getOptions().iconClasses.info, - message: message, - optionsOverride: optionsOverride, - title: title - }); - } - - function subscribe(callback) { - listener = callback; - } - - function success(message, title, optionsOverride) { - return notify({ - type: toastType.success, - iconClass: getOptions().iconClasses.success, - message: message, - optionsOverride: optionsOverride, - title: title - }); - } - - function warning(message, title, optionsOverride) { - return notify({ - type: toastType.warning, - iconClass: getOptions().iconClasses.warning, - message: message, - optionsOverride: optionsOverride, - title: title - }); - } - - function clear($toastElement) { - var options = getOptions(); - if (!$container) { getContainer(options); } - if ($toastElement && $(':focus', $toastElement).length === 0) { - $toastElement[options.hideMethod]({ - duration: options.hideDuration, - easing: options.hideEasing, - complete: function () { removeToast($toastElement); } - }); - return; - } - if ($container.children().length) { - $container[options.hideMethod]({ - duration: options.hideDuration, - easing: options.hideEasing, - complete: function () { $container.remove(); } - }); - } - } - //#endregion - - //#region Internal Methods - - function getDefaults() { - return { - tapToDismiss: true, - toastClass: 'toast', - containerId: 'toast-container', - debug: false, - - showMethod: 'fadeIn', //fadeIn, slideDown, and show are built into jQuery - showDuration: 300, - showEasing: 'swing', //swing and linear are built into jQuery - onShown: undefined, - hideMethod: 'fadeOut', - hideDuration: 1000, - hideEasing: 'swing', - onHidden: undefined, - - extendedTimeOut: 1000, - iconClasses: { - error: 'toast-error', - info: 'toast-info', - success: 'toast-success', - warning: 'toast-warning' - }, - iconClass: 'toast-info', - positionClass: 'toast-top-right', - timeOut: 5000, // Set timeOut and extendedTimeout to 0 to make it sticky - titleClass: 'toast-title', - messageClass: 'toast-message', - target: 'body', - closeHtml: '', - newestOnTop: true - }; - } - - function publish(args) { - if (!listener) { - return; - } - listener(args); - } - - function notify(map) { - var - options = getOptions(), - iconClass = map.iconClass || options.iconClass; - - if (typeof (map.optionsOverride) !== 'undefined') { - options = $.extend(options, map.optionsOverride); - iconClass = map.optionsOverride.iconClass || iconClass; - } - - toastId++; - - $container = getContainer(options); - var - intervalId = null, - $toastElement = $('
'), - $titleElement = $('
'), - $messageElement = $('
'), - $closeElement = $(options.closeHtml), - response = { - toastId: toastId, - state: 'visible', - startTime: new Date(), - options: options, - map: map - }; - - if (map.iconClass) { - $toastElement.addClass(options.toastClass).addClass(iconClass); - } - - if (map.title) { - $titleElement.append(map.title).addClass(options.titleClass); - $toastElement.append($titleElement); - } - - if (map.message) { - $messageElement.append(map.message).addClass(options.messageClass); - $toastElement.append($messageElement); - } - - if (options.closeButton) { - $closeElement.addClass('toast-close-button'); - $toastElement.prepend($closeElement); - } - - $toastElement.hide(); - if (options.newestOnTop) { - $container.prepend($toastElement); - } else { - $container.append($toastElement); - } - - - $toastElement[options.showMethod]( - { duration: options.showDuration, easing: options.showEasing, complete: options.onShown } - ); - if (options.timeOut > 0) { - intervalId = setTimeout(hideToast, options.timeOut); - } - - $toastElement.hover(stickAround, delayedhideToast); - if (!options.onclick && options.tapToDismiss) { - $toastElement.click(hideToast); - } - if (options.closeButton && $closeElement) { - $closeElement.click(function (event) { - event.stopPropagation(); - hideToast(true); - }); - } - - if (options.onclick) { - $toastElement.click(function () { - options.onclick(); - hideToast(); - }); - } - - publish(response); - - if (options.debug && console) { - console.log(response); - } - - return $toastElement; - - function hideToast(override) { - if ($(':focus', $toastElement).length && !override) { - return; - } - return $toastElement[options.hideMethod]({ - duration: options.hideDuration, - easing: options.hideEasing, - complete: function () { - removeToast($toastElement); - if (options.onHidden) { - options.onHidden(); - } - response.state = 'hidden'; - response.endTime = new Date(), - publish(response); - } - }); - } - - function delayedhideToast() { - if (options.timeOut > 0 || options.extendedTimeOut > 0) { - intervalId = setTimeout(hideToast, options.extendedTimeOut); - } - } - - function stickAround() { - clearTimeout(intervalId); - $toastElement.stop(true, true)[options.showMethod]( - { duration: options.showDuration, easing: options.showEasing } - ); - } - } - function getContainer(options) { - if (!options) { options = getOptions(); } - $container = $('#' + options.containerId); - if ($container.length) { - return $container; - } - $container = $('
') - .attr('id', options.containerId) - .addClass(options.positionClass); - $container.appendTo($(options.target)); - return $container; - } - - function getOptions() { - return $.extend({}, getDefaults(), toastr.options); - } - - function removeToast($toastElement) { - if (!$container) { $container = getContainer(); } - if ($toastElement.is(':visible')) { - return; - } - $toastElement.remove(); - $toastElement = null; - if ($container.children().length === 0) { - $container.remove(); - } - } - //#endregion - - })(); - }); -}(typeof define === 'function' && define.amd ? define : function (deps, factory) { - if (typeof module !== 'undefined' && module.exports) { //Node - module.exports = factory(require(deps[0])); - } else { - window['toastr'] = factory(window['jQuery']); - } -})); diff --git a/client/galaxy/scripts/mvc/collection/collection-view-edit.js b/client/galaxy/scripts/mvc/collection/collection-view-edit.js index 93e117d6055..f90e13f37ef 100644 --- a/client/galaxy/scripts/mvc/collection/collection-view-edit.js +++ b/client/galaxy/scripts/mvc/collection/collection-view-edit.js @@ -1,7 +1,8 @@ +import $ from "jquery"; import { getGalaxyInstance } from "app"; import DC_VIEW from "mvc/collection/collection-view"; import DC_EDIT from "mvc/collection/collection-li-edit"; -import TAGS from "mvc/tag"; +import { mountModelTags } from "components/Tags"; import _l from "utils/localization"; import "ui/editable-text"; @@ -76,15 +77,27 @@ var CollectionViewEdit = _super.extend( } } }); - this.tagsEditor = new TAGS.TagsEditor({ + + let el = $where.find(".tags-display")[0]; + let propsData = { model: this.model, - el: $where.find(".tags-display"), - onshowFirstTime: function() { - this.render(); - }, - usePrompt: false - }); - this.tagsEditor.toggle(true); + disabled: false, + context: "collection-view-edit" + }; + + let vm = mountModelTags(propsData, el); + + let toggleEditor = () => { + $(vm.$el).toggleClass("active"); + this.tagsEditorShown = $(vm.$el).hasClass("active"); + }; + + if (this.tagsEditorShown) { + let editorIsOpen = $(vm.$el).hasClass("active"); + if (!editorIsOpen) { + toggleEditor(); + } + } }, // ........................................................................ misc diff --git a/client/galaxy/scripts/mvc/collection/list-collection-creator.js b/client/galaxy/scripts/mvc/collection/list-collection-creator.js index 822f2f57638..ba77f2d039e 100644 --- a/client/galaxy/scripts/mvc/collection/list-collection-creator.js +++ b/client/galaxy/scripts/mvc/collection/list-collection-creator.js @@ -7,7 +7,6 @@ import BASE_MVC from "mvc/base-mvc"; import baseCreator from "mvc/collection/base-creator"; import UI_MODAL from "mvc/ui/ui-modal"; import _l from "utils/localization"; -import RuleCollectionBuilder from "components/RuleCollectionBuilder.vue"; import Vue from "vue"; import { getGalaxyInstance } from "app"; @@ -1077,24 +1076,28 @@ var ruleBasedCollectionCreatorModal = function _ruleBasedCollectionCreatorModal( title: title }); const { deferred, creatorOptions, showEl } = collectionCreatorModalSetup(options); // eslint-disable-line no-unused-vars - var ruleCollectionBuilderInstance = Vue.extend(RuleCollectionBuilder); - var vm = document.createElement("div"); - showEl(vm); - new ruleCollectionBuilderInstance({ - propsData: { - initialElements: elements, - elementsType: elementsType, - importType: importType, - ftpUploadSite: options.ftpUploadSite, - creationFn: options.creationFn, - oncancel: options.oncancel, - oncreate: options.oncreate, - defaultHideSourceItems: options.defaultHideSourceItems, - saveRulesFn: options.saveRulesFn, - initialRules: options.initialRules + return import(/* webpackChunkName: "ruleCollectionBuilder" */ "components/RuleCollectionBuilder.vue").then( + module => { + var ruleCollectionBuilderInstance = Vue.extend(module.default); + var vm = document.createElement("div"); + showEl(vm); + new ruleCollectionBuilderInstance({ + propsData: { + initialElements: elements, + elementsType: elementsType, + importType: importType, + ftpUploadSite: options.ftpUploadSite, + creationFn: options.creationFn, + oncancel: options.oncancel, + oncreate: options.oncreate, + defaultHideSourceItems: options.defaultHideSourceItems, + saveRulesFn: options.saveRulesFn, + initialRules: options.initialRules + } + }).$mount(vm); + return deferred; } - }).$mount(vm); - return deferred; + ); }; /** List collection flavor of collectionCreatorModal. */ diff --git a/client/galaxy/scripts/mvc/dataset/data.js b/client/galaxy/scripts/mvc/dataset/data.js index aae9edb60e2..18b1f3b330b 100644 --- a/client/galaxy/scripts/mvc/dataset/data.js +++ b/client/galaxy/scripts/mvc/dataset/data.js @@ -267,8 +267,30 @@ var TabularDatasetChunkedView = Backbone.View.extend({ ); row.append(this._renderCell(cells.slice(num_columns - 1).join("\t"), num_columns - 1)); } else if (cells.length === 1) { - // Comment line, just return the one cell. - row.append(this._renderCell(line, 0, num_columns)); + cells = line.split(","); + if (cells.length === num_columns) { + _.each( + cells, + function(cell_contents, index) { + row.append(this._renderCell(cell_contents, index)); + }, + this + ); + } else { + cells = line.split(" "); + if (cells.length === num_columns) { + _.each( + cells, + function(cell_contents, index) { + row.append(this._renderCell(cell_contents, index)); + }, + this + ); + } else { + // Comment line, just return the one cell. + row.append(this._renderCell(line, 0, num_columns)); + } + } } else { // cells.length is greater than one, but less than num_columns. Render cells and pad tds. // Possibly a SAM file or like format with optional metadata missing. diff --git a/client/galaxy/scripts/mvc/dataset/dataset-edit-attributes.js b/client/galaxy/scripts/mvc/dataset/dataset-edit-attributes.js index f80d1058f59..fca53d24b16 100644 --- a/client/galaxy/scripts/mvc/dataset/dataset-edit-attributes.js +++ b/client/galaxy/scripts/mvc/dataset/dataset-edit-attributes.js @@ -48,7 +48,7 @@ var View = Backbone.View.extend({ var err_msg = response.responseJSON && response.responseJSON.err_msg; self.message.update({ status: "danger", - message: err_msg || "Error occured while loading the dataset." + message: err_msg || "Error occurred while loading the dataset." }); } }); @@ -73,7 +73,7 @@ var View = Backbone.View.extend({ var err_msg = response.responseJSON && response.responseJSON.err_msg; self.message.update({ status: "danger", - message: err_msg || "Error occured while editing the dataset attributes." + message: err_msg || "Error occurred while editing the dataset attributes." }); } }); diff --git a/client/galaxy/scripts/mvc/dataset/dataset-error.js b/client/galaxy/scripts/mvc/dataset/dataset-error.js index 33e20b5b0d3..15ed502ce27 100644 --- a/client/galaxy/scripts/mvc/dataset/dataset-error.js +++ b/client/galaxy/scripts/mvc/dataset/dataset-error.js @@ -36,7 +36,7 @@ var View = Backbone.View.extend({ error: response => { var error_response = { status: "error", - message: "Error occured while loading the job.", + message: "Error occurred while loading the job.", persistent: true, cls: "errormessage" }; @@ -47,7 +47,7 @@ var View = Backbone.View.extend({ error: response => { var error_response = { status: "error", - message: "Error occured while loading the dataset.", + message: "Error occurred while loading the dataset.", persistent: true, cls: "errormessage" }; @@ -61,7 +61,7 @@ var View = Backbone.View.extend({ this.$el.empty().append(` ${this._templateHeader()}

Dataset Error

-

An error occured while running the tool ${job.tool_id}.

+

An error occurred while running the tool ${job.tool_id}.

Tool execution generated the following messages:

${_.escape(job.stderr)}
@@ -194,7 +194,7 @@ var View = Backbone.View.extend({ error: response => { var error_response = { status: "error", - message: "Error occured while saving. Please fill all the required fields and try again.", + message: "Error occurred while saving. Please fill all the required fields and try again.", persistent: true, cls: "errormessage" }; diff --git a/client/galaxy/scripts/mvc/dataset/dataset-li-edit.js b/client/galaxy/scripts/mvc/dataset/dataset-li-edit.js index 7b303b528aa..26928c06936 100644 --- a/client/galaxy/scripts/mvc/dataset/dataset-li-edit.js +++ b/client/galaxy/scripts/mvc/dataset/dataset-li-edit.js @@ -4,7 +4,7 @@ import { getAppRoot } from "onload/loadConfig"; import { getGalaxyInstance } from "app"; import STATES from "mvc/dataset/states"; import DATASET_LI from "mvc/dataset/dataset-li"; -import TAGS from "mvc/tag"; +import { mountModelTags } from "components/Tags"; import ANNOTATIONS from "mvc/annotation"; import faIconButton from "ui/fa-icon-button"; import BASE_MVC from "mvc/base-mvc"; @@ -297,35 +297,44 @@ var DatasetListItemEdit = _super.extend( } }, - //TODO: if possible move these to readonly view - but display the owner's tags/annotation (no edit) /** Render the tags list/control */ _renderTags: function($where) { if (!this.hasUser) { return; } - var view = this; - this.tagsEditor = new TAGS.TagsEditor({ + + let el = $where.find(".tags-display")[0]; + + let propsData = { model: this.model, - el: $where.find(".tags-display"), - onshowFirstTime: function() { - this.render(); - }, - // persist state on the hda view (and not the editor) since these are currently re-created each time - onshow: function() { - view.tagsEditorShown = true; - }, - onhide: function() { - view.tagsEditorShown = false; - }, - $activator: faIconButton({ - title: _l("Edit dataset tags"), - classes: "tag-btn", - faIcon: "fa-tags" - }).appendTo($where.find(".actions .right")) - }); + disabled: false, + context: "dataset-li-edit" + }; + + let vm = mountModelTags(propsData, el); + + // tag icon button open/closes + let activator = faIconButton({ + title: _l("Edit dataset tags"), + classes: "tag-btn", + faIcon: "fa-tags" + }).appendTo($where.find(".actions .right")); + + let toggleEditor = () => { + $(vm.$el).toggleClass("active"); + this.tagsEditorShown = $(vm.$el).hasClass("active"); + }; + + activator.on("click", toggleEditor); + if (this.tagsEditorShown) { - this.tagsEditor.toggle(true); + let editorIsOpen = $(vm.$el).hasClass("active"); + if (!editorIsOpen) { + toggleEditor(); + } } + + return vm; }, /** Render the annotation display/control */ diff --git a/client/galaxy/scripts/mvc/dataset/dataset-li.js b/client/galaxy/scripts/mvc/dataset/dataset-li.js index d12fa457005..99a01609568 100644 --- a/client/galaxy/scripts/mvc/dataset/dataset-li.js +++ b/client/galaxy/scripts/mvc/dataset/dataset-li.js @@ -7,6 +7,7 @@ import STATES from "mvc/dataset/states"; import faIconButton from "ui/fa-icon-button"; import BASE_MVC from "mvc/base-mvc"; import _l from "utils/localization"; +import { mountNametags } from "components/Nametags"; var logNamespace = "dataset"; /*============================================================================== @@ -42,6 +43,20 @@ export var DatasetListItemView = _super.extend( this.linkTarget = attributes.linkTarget || "_blank"; }, + // mount new vue component for tags + render: function() { + let result = _super.prototype.render.apply(this, arguments); + this._mountVueNametags(); + return result; + }, + + _mountVueNametags: function() { + let container = this.$(".nametags")[0]; + let { id, model_class, tags } = this.model.attributes; + let storeKey = `${model_class}-${id}`; + mountNametags({ storeKey, tags }, container); + }, + /** event listeners */ _setUpListeners: function() { _super.prototype._setUpListeners.call(this); @@ -64,11 +79,11 @@ export var DatasetListItemView = _super.extend( self.render(); }); } else { - if (_.has(model.changed, "tags") && _.keys(model.changed).length === 1) { - // If only the tags have changed, rerender specifically - // the titlebar region. Otherwise default to the full - // render. - self.$(".nametags").html(self._renderNametags()); + if (_.has(self.model.changed, "tags") && _.keys(self.model.changed).length === 2) { + // If only the tags and update time have changed, + // rerender specifically the titlebar region. + // Otherwise default to the full render. + self._mountVueNametags(); } else { self.render(); } @@ -327,19 +342,6 @@ export var DatasetListItemView = _super.extend(
`); }, - _renderNametags: function() { - var tpl = _.template( - [ - "<% _.each(_.sortBy(_.uniq(tags), function(x) { return x }), function(tag){ %>", - '<% if (tag.indexOf("name:") == 0){ %>', - '<%- tag.slice(5) %>', - "<% } %>", - "<% }); %>" - ].join("") - ); - return tpl({ tags: this.model.get("tags") }); - }, - // ......................................................................... misc events: _.extend(_.clone(_super.prototype.events), { "click .display-btn": function(ev) { diff --git a/client/galaxy/scripts/mvc/form/form-input.js b/client/galaxy/scripts/mvc/form/form-input.js index 86c978e936c..89ecd872df1 100644 --- a/client/galaxy/scripts/mvc/form/form-input.js +++ b/client/galaxy/scripts/mvc/form/form-input.js @@ -203,7 +203,7 @@ export default Backbone.View.extend({ .append( $("
") .addClass("ui-form-field") - .append($("").addClass("ui-form-info form-text text-muted")) + .append($("").addClass("ui-form-info form-text text-muted mt-2")) .append($("
").addClass("ui-form-backdrop")) ) .append($("
").addClass("ui-form-preview")); diff --git a/client/galaxy/scripts/mvc/form/form-parameters.js b/client/galaxy/scripts/mvc/form/form-parameters.js index 55f0a8d8df3..4082e01d419 100644 --- a/client/galaxy/scripts/mvc/form/form-parameters.js +++ b/client/galaxy/scripts/mvc/form/form-parameters.js @@ -29,6 +29,7 @@ export default Backbone.Model.extend({ boolean: "_fieldBoolean", drill_down: "_fieldDrilldown", color: "_fieldColor", + group_tag: "_fieldSelect", hidden: "_fieldHidden", hidden_data: "_fieldHidden", baseurl: "_fieldHidden", @@ -125,7 +126,7 @@ export default Backbone.Model.extend({ /** Text input field */ _fieldText: function(input_def) { // field replaces e.g. a select field - if (input_def.options && input_def.data) { + if (input_def.model_class === 'SelectTagParameter' || input_def.options && input_def.data) { input_def.area = input_def.multiple; if (Utils.isEmpty(input_def.value)) { input_def.value = null; diff --git a/client/galaxy/scripts/mvc/grid/grid-view.js b/client/galaxy/scripts/mvc/grid/grid-view.js index 63f5cf0922b..45be9958211 100644 --- a/client/galaxy/scripts/mvc/grid/grid-view.js +++ b/client/galaxy/scripts/mvc/grid/grid-view.js @@ -8,6 +8,8 @@ import Templates from "mvc/grid/grid-template"; import PopupMenu from "mvc/ui/popup-menu"; import LoadingIndicator from "ui/loading-indicator"; import { init_refresh_on_change } from "onload/globalInits/init_refresh_on_change"; +import store from "../../store"; +import slugify from "slugify"; // This is necessary so that, when nested arrays are used in ajax/post/get methods, square brackets ('[]') are // not appended to the identifier of a nested array. @@ -25,17 +27,21 @@ export default Backbone.View.extend({ this.active_tab = grid_config.active_tab; var self = this; - // Why is this a global? - window.add_tag_to_grid_filter = (tag_name, tag_value) => { - // Put tag name and value together. - var tag = tag_name + (tag_value !== undefined && tag_value !== "" ? `:${tag_value}` : ""); - var advanced_search = $("#advanced-search").is(":visible"); - if (!advanced_search) { - $("#standard-search").slideToggle("fast"); - $("#advanced-search").slideToggle("fast"); + // Subscribe to changes in the store, currently just storing + // tag changes from the tagging components, but that will change + // when we rework the grid. This subscription ties this older grid + // code to the new vue components + store.watch( + state => state.gridSearch.searchTags, + newTags => { + let tagArray = Array.from(newTags); + self.grid.add_filter("tags", tagArray, false); + self.openAdvancedSearch(); + self.render_filter_button("tags", tagArray); + self.go_page_one(); + self.execute(); } - self.add_filter_condition("tags", tag); - }; + ); if (grid_config.url_base && !grid_config.items) { LoadingIndicator.markViewAsLoading(this); @@ -66,6 +72,14 @@ export default Backbone.View.extend({ } }, + openAdvancedSearch: function() { + var isOpen = $("#advanced-search").is(":visible"); + if (!isOpen) { + $("#standard-search").slideToggle("fast"); + $("#advanced-search").slideToggle("fast"); + } + }, + // refresh frames handle_refresh: function(refresh_frames) { if (refresh_frames) { @@ -99,6 +113,9 @@ export default Backbone.View.extend({ // append main template this.$el.html(Templates.grid(options)); + // add a class identifier for styling purposes + this.$el.addClass(this.getRootClassName(grid_config)); + // update div contents this.$el.find("#grid-table-header").html(Templates.header(options)); this.$el.find("#grid-table-body").html(Templates.body(options)); @@ -310,6 +327,14 @@ export default Backbone.View.extend({ // Add condition to grid. this.grid.add_filter(name, value, true); + this.render_filter_button(name, value); + + // execute + this.go_page_one(); + this.execute(); + }, + + render_filter_button: function(name, value) { // Add button that displays filter and provides a button to delete it. var t = $(Templates.filter_element(name, value)); var self = this; @@ -324,10 +349,6 @@ export default Backbone.View.extend({ // append to container var container = this.$el.find(`#${name}-filtering-criteria`); container.append(t); - - // execute - this.go_page_one(); - this.execute(); }, // Remove a condition to the grid filter; this adds the condition and refreshes the grid. @@ -335,6 +356,11 @@ export default Backbone.View.extend({ // Remove filter condition. this.grid.remove_filter(name, value); + // update vuex if the one criteria we're currently tracking changes + if (name == "tags") { + store.dispatch("removeSearchTag", { text: value }); + } + // Execute this.go_page_one(); this.execute(); @@ -665,5 +691,12 @@ export default Backbone.View.extend({ }); } }); + }, + + // Generates a class name at the root of the view that we can + // use for conditional styling in the various kinds of grids + // instead of acres of if/then statements in javascript + getRootClassName({ title = "grid" }) { + return slugify(title).toLowerCase(); } }); diff --git a/client/galaxy/scripts/mvc/history/hda-li.js b/client/galaxy/scripts/mvc/history/hda-li.js index 18962fa7f03..b814b452a9e 100644 --- a/client/galaxy/scripts/mvc/history/hda-li.js +++ b/client/galaxy/scripts/mvc/history/hda-li.js @@ -1,7 +1,6 @@ import _ from "underscore"; import DATASET_LI from "mvc/dataset/dataset-li"; import BASE_MVC from "mvc/base-mvc"; -import HISTORY_ITEM_LI from "mvc/history/history-item-li"; import _l from "utils/localization"; //============================================================================== @@ -39,7 +38,7 @@ HDAListItemView.prototype.templates = (() => { ${_.escape(dataset.name)}

- ${HISTORY_ITEM_LI.nametagTemplate(dataset)} +
`; diff --git a/client/galaxy/scripts/mvc/history/hdca-li.js b/client/galaxy/scripts/mvc/history/hdca-li.js index 8b5383ba526..3744648c613 100644 --- a/client/galaxy/scripts/mvc/history/hdca-li.js +++ b/client/galaxy/scripts/mvc/history/hdca-li.js @@ -2,8 +2,8 @@ import _ from "underscore"; import STATES from "mvc/dataset/states"; import DC_LI from "mvc/collection/collection-li"; import DC_VIEW from "mvc/collection/collection-view"; -import HISTORY_ITEM_LI from "mvc/history/history-item-li"; import _l from "utils/localization"; +import { mountNametags } from "components/Nametags"; //============================================================================== var _super = DC_LI.DCListItemView; @@ -13,12 +13,19 @@ var HDCAListItemView = _super.extend( /** @lends HDCAListItemView.prototype */ { className: `${_super.prototype.className} history-content`, + render: function() { + let result = _super.prototype.render.apply(this, arguments); + this._mountNametags("initialize"); + return result; + }, + /** event listeners */ _setUpListeners: function() { _super.prototype._setUpListeners.call(this); var renderListen = (model, options) => { // We want this to swap immediately without extra animations. this.render(0); + this._mountNametags("listener"); }; if (this.model.jobStatesSummary) { this.listenTo(this.model.jobStatesSummary, "change", renderListen); @@ -28,6 +35,15 @@ var HDCAListItemView = _super.extend( }); }, + _mountNametags(context) { + let container = this.$el.find(".nametags")[0]; + if (container) { + let { id, model_class, tags } = this.model.attributes; + let storeKey = `${model_class}-${id}`; + mountNametags({ storeKey, tags }, container); + } + }, + /** Override to provide the proper collections panels as the foldout */ _getFoldoutPanelClass: function() { return DC_VIEW.CollectionView; @@ -137,7 +153,7 @@ HDCAListItemView.prototype.templates = (() => {
- ${HISTORY_ITEM_LI.nametagTemplate(collection)} +
`; diff --git a/client/galaxy/scripts/mvc/history/history-item-li.js b/client/galaxy/scripts/mvc/history/history-item-li.js deleted file mode 100644 index 268fd0226f7..00000000000 --- a/client/galaxy/scripts/mvc/history/history-item-li.js +++ /dev/null @@ -1,21 +0,0 @@ -import _ from "underscore"; -import Utils from "utils/utils"; - -function _templateNametag(tag) { - return `${_.escape( - tag.slice(5) - )}`; -} - -function nametagTemplate(historyItem) { - let uniqueNametags = _.filter(_.uniq(historyItem.tags), t => t.indexOf("name:") === 0); - let nametagsDisplay = _.sortBy(uniqueNametags).map(_templateNametag); - return ` -
- ${nametagsDisplay.join("")} -
`; -} - -export default { - nametagTemplate: nametagTemplate -}; diff --git a/client/galaxy/scripts/mvc/history/history-view-edit-current.js b/client/galaxy/scripts/mvc/history/history-view-edit-current.js index c3fd521c760..15b58baf202 100644 --- a/client/galaxy/scripts/mvc/history/history-view-edit-current.js +++ b/client/galaxy/scripts/mvc/history/history-view-edit-current.js @@ -224,16 +224,7 @@ var CurrentHistoryView = _super.extend( /** In this override, get and set current panel preferences when editor is used */ _renderTags: function($where) { - var panel = this; - // render tags and show/hide based on preferences - _super.prototype._renderTags.call(panel, $where); - if (panel.preferences.get("tagsEditorShown")) { - panel.tagsEditor.toggle(true); - } - // store preference when shown or hidden - panel.listenTo(panel.tagsEditor, "hiddenUntilActivated:shown hiddenUntilActivated:hidden", tagsEditor => { - panel.preferences.set("tagsEditorShown", tagsEditor.hidden); - }); + return _super.prototype._renderTags.call(this, $where); }, /** In this override, get and set current panel preferences when editor is used */ diff --git a/client/galaxy/scripts/mvc/history/history-view-edit.js b/client/galaxy/scripts/mvc/history/history-view-edit.js index 342518c34bd..0f9c17612b9 100644 --- a/client/galaxy/scripts/mvc/history/history-view-edit.js +++ b/client/galaxy/scripts/mvc/history/history-view-edit.js @@ -5,7 +5,7 @@ import HISTORY_VIEW from "mvc/history/history-view"; import HDA_MODEL from "mvc/history/hda-model"; import HDA_LI_EDIT from "mvc/history/hda-li-edit"; import HDCA_LI_EDIT from "mvc/history/hdca-li-edit"; -import TAGS from "mvc/tag"; +import { mountModelTags } from "components/Tags"; import ANNOTATIONS from "mvc/annotation"; import LIST_COLLECTION_CREATOR from "mvc/collection/list-collection-creator"; import PAIR_COLLECTION_CREATOR from "mvc/collection/pair-collection-creator"; @@ -155,28 +155,31 @@ var HistoryViewEdit = _super.extend( /** render the tags sub-view controller */ _renderTags: function($where) { - var panel = this; - this.tagsEditor = new TAGS.TagsEditor({ + let el = $where.find(".controls .tags-display")[0]; + + let propsData = { model: this.model, - el: $where.find(".controls .tags-display"), - onshowFirstTime: function() { - this.render(); - }, - // show hide sub-view tag editors when this is shown/hidden - onshow: function() { - panel.toggleHDATagEditors(true, panel.fxSpeed); - }, - onhide: function() { - panel.toggleHDATagEditors(false, panel.fxSpeed); - }, - $activator: faIconButton({ - title: _l("Edit history tags"), - classes: "history-tag-btn", - faIcon: "fa-tags", - tooltipConfig: { placement: "top" } - }).appendTo($where.find(".controls .actions")) + disabled: false, + context: "history-view-edit" + }; + + let vm = mountModelTags(propsData, el); + + // tag icon button open/closes + let activator = faIconButton({ + title: _l("Edit history tags"), + classes: "history-tag-btn", + faIcon: "fa-tags", + tooltipConfig: { placement: "top" } + }).appendTo($where.find(".controls .actions")); + + activator.on("click", () => { + $(vm.$el).toggleClass("active"); }); + + return vm; }, + /** render the annotation sub-view controller */ _renderAnnotation: function($where) { var panel = this; @@ -244,35 +247,76 @@ var HistoryViewEdit = _super.extend( * ajax calls made for multiple datasets are queued */ multiselectActions: function() { + var Galaxy = getGalaxyInstance(); var panel = this; + const batchUpdate = (key, value) => { + const actionModels = {}; + const items = panel + .getSelectedModels() + .filter(model => { + console.log(model.attributes[key]); + return model.attributes[key] != value; + }) + .map(model => { + const res = { + history_content_type: model.attributes.history_content_type, + id: model.attributes.id + }; + actionModels[model.id] = model; + // Tried "optimistically" deleting the dataset - but it didn't quite work - + // maybe history polling restored the previous state? + return res; + }); + + if (items.length == 0) { + return; + } + + const options = {}; + options.url = `${Galaxy.root}api/histories/${this.model.id}/contents`; + options.type = "PUT"; + const requestData = { items: items }; + requestData[key] = value; + options.data = JSON.stringify(requestData); + options.contentType = "application/json"; + + var xhr = jQuery.ajax(options); + xhr.done((message, status, responseObj) => { + for (let updated of message) { + const typeId = updated.history_content_type + "-" + updated.id; + actionModels[typeId].set(updated); + } + }); + xhr.fail((xhr, status, message) => { + console.error(message); + }); + return xhr; + }; + var actions = [ { html: _l("Hide datasets"), func: function() { - var action = HDA_MODEL.HistoryDatasetAssociation.prototype.hide; - panel.getSelectedModels().ajaxQueue(action); + batchUpdate("visible", false); } }, { html: _l("Unhide datasets"), func: function() { - var action = HDA_MODEL.HistoryDatasetAssociation.prototype.unhide; - panel.getSelectedModels().ajaxQueue(action); + batchUpdate("visible", true); } }, { html: _l("Delete datasets"), - func: function() { - var action = HDA_MODEL.HistoryDatasetAssociation.prototype["delete"]; - panel.getSelectedModels().ajaxQueue(action); + func: () => { + batchUpdate("deleted", true); } }, { html: _l("Undelete datasets"), func: function() { - var action = HDA_MODEL.HistoryDatasetAssociation.prototype.undelete; - panel.getSelectedModels().ajaxQueue(action); + batchUpdate("deleted", false); } } ]; @@ -338,7 +382,7 @@ var HistoryViewEdit = _super.extend( } else { console.warn(`Unknown collectionType encountered ${collectionType}`); } - createFunc(selection, hideSourceItems).done(() => { + createFunc(selection, hideSourceItems).then(() => { panel.model.refresh(); }); }, @@ -418,13 +462,13 @@ var HistoryViewEdit = _super.extend( }, /** toggle the visibility of each content's tagsEditor applying all the args sent to this function */ - toggleHDATagEditors: function(showOrHide, speed) { - _.each(this.views, view => { - if (view.tagsEditor) { - view.tagsEditor.toggle(showOrHide, speed); - } - }); - }, + // toggleHDATagEditors: function(showOrHide, speed) { + // _.each(this.views, view => { + // if (view.tagsEditor) { + // view.tagsEditor.toggle(showOrHide, speed); + // } + // }); + // }, /** toggle the visibility of each content's annotationEditor applying all the args sent to this function */ toggleHDAAnnotationEditors: function(showOrHide, speed) { @@ -483,8 +527,7 @@ var HistoryViewEdit = _super.extend( this.$list().before([this._renderDropTargetHelp(), $dropTarget]); for (var evName in dropHandlers) { if (dropHandlers.hasOwnProperty(evName)) { - //console.debug( evName, dropHandlers[ evName ] ); - $dropTarget.on(evName, dropHandlers[evName]); + $dropTarget.get(0).addEventListener(evName, dropHandlers[evName]); } } return this; @@ -551,8 +594,7 @@ var HistoryViewEdit = _super.extend( drop: function(ev) { ev.preventDefault(); //ev.stopPropagation(); - - var dataTransfer = ev.originalEvent.dataTransfer; + var dataTransfer = ev.dataTransfer; var data = dataTransfer.getData("text"); dataTransfer.dropEffect = "move"; diff --git a/client/galaxy/scripts/mvc/history/options-menu.js b/client/galaxy/scripts/mvc/history/options-menu.js index 639504990a7..d40885d1d93 100644 --- a/client/galaxy/scripts/mvc/history/options-menu.js +++ b/client/galaxy/scripts/mvc/history/options-menu.js @@ -28,15 +28,6 @@ var menu = [ header: true, anon: true }, - { - html: _l("Create New"), - func: function() { - let Galaxy = getGalaxyInstance(); - if (Galaxy && Galaxy.currHistoryPanel) { - Galaxy.currHistoryPanel.createNewHistory(); - } - } - }, { html: _l("Copy History"), func: function() { @@ -202,16 +193,6 @@ var menu = [ html: _l("Export History to File"), href: "history/export_archive?preview=True", anon: true - }, - - { - html: _l("Other Actions"), - header: true - }, - { - html: _l("Import from File"), - href: "histories/import", - target: "_top" } ]; @@ -260,8 +241,9 @@ function buildMenu(isAnon, purgeAllowed, urlRoot) { if (menuOption.confirm) { menuOption.func = () => { - if (confirm(menuOption.confirm) && window.parent.frames && window.parent.frames.galaxy_main) { - window.parent.frames.galaxy_main = menuOption.href; + const galaxy_main = window.parent.document.getElementById("galaxy_main"); + if (confirm(menuOption.confirm) && galaxy_main){ + galaxy_main.src = menuOption.href; } }; } @@ -274,7 +256,6 @@ var create = ($button, options) => { var isAnon = options.anonymous === undefined ? true : options.anonymous; var purgeAllowed = options.purgeAllowed || false; var menu = buildMenu(isAnon, purgeAllowed, getAppRoot()); - //console.debug( 'menu:', menu ); return new PopupMenu($button, menu); }; diff --git a/client/galaxy/scripts/mvc/library/library-dataset-view.js b/client/galaxy/scripts/mvc/library/library-dataset-view.js index 1869f0666ba..c357e05e3d0 100644 --- a/client/galaxy/scripts/mvc/library/library-dataset-view.js +++ b/client/galaxy/scripts/mvc/library/library-dataset-view.js @@ -4,7 +4,7 @@ import Backbone from "backbone"; import { getAppRoot } from "onload/loadConfig"; import { getGalaxyInstance } from "app"; import _l from "utils/localization"; -import mod_toastr from "libs/toastr"; +import { Toast } from "ui/toast"; import mod_library_model from "mvc/library/library-model"; import mod_utils from "utils/utils"; import mod_select from "mvc/ui/ui-select"; @@ -29,7 +29,8 @@ var LibraryDatasetView = Backbone.View.extend({ "click .make-private": "makeDatasetPrivate", "click .remove-restrictions": "removeDatasetRestrictions", "click .toolbtn_save_permissions": "savePermissions", - "click .toolbtn_save_modifications": "saveModifications" + "click .toolbtn_save_modifications": "saveModifications", + "click .toolbtn_detect_datatype": "detectDatatype" }, // genome select @@ -84,13 +85,13 @@ var LibraryDatasetView = Backbone.View.extend({ }, error: function(model, response) { if (typeof response.responseJSON !== "undefined") { - mod_toastr.error(`${response.responseJSON.err_msg} Click this to go back.`, "", { + Toast.error(`${response.responseJSON.err_msg} Click this to go back.`, "", { onclick: function() { Galaxy.libraries.library_router.back(); } }); } else { - mod_toastr.error("An error occurred. Click this to go back.", "", { + Toast.error("An error occurred. Click this to go back.", "", { onclick: function() { Galaxy.libraries.library_router.back(); } @@ -114,7 +115,7 @@ var LibraryDatasetView = Backbone.View.extend({ var self = this; if (!this.options.ldda_id) { this.render(); - mod_toastr.error("Library dataset version requested but no id provided."); + Toast.error("Library dataset version requested but no id provided."); } else { this.ldda = new mod_library_model.Ldda({ id: this.options.ldda_id @@ -126,9 +127,9 @@ var LibraryDatasetView = Backbone.View.extend({ }, error: function(model, response) { if (typeof response.responseJSON !== "undefined") { - mod_toastr.error(response.responseJSON.err_msg); + Toast.error(response.responseJSON.err_msg); } else { - mod_toastr.error("An error occurred."); + Toast.error("An error occurred."); } } }); @@ -177,7 +178,7 @@ var LibraryDatasetView = Backbone.View.extend({ .submit() .remove(); - mod_toastr.info("Your download will begin soon."); + Toast.info("Your download will begin soon."); } }, @@ -208,7 +209,7 @@ var LibraryDatasetView = Backbone.View.extend({ this.histories.fetch({ success: function(histories) { if (histories.length === 0) { - mod_toastr.warning("You have to create history first. Click this to do so.", "", { + Toast.warning("You have to create history first. Click this to do so.", "", { onclick: function() { window.location = getAppRoot(); } @@ -219,9 +220,9 @@ var LibraryDatasetView = Backbone.View.extend({ }, error: function(model, response) { if (typeof response.responseJSON !== "undefined") { - mod_toastr.error(response.responseJSON.err_msg); + Toast.error(response.responseJSON.err_msg); } else { - mod_toastr.error("An error occurred."); + Toast.error("An error occurred."); } } }); @@ -239,7 +240,7 @@ var LibraryDatasetView = Backbone.View.extend({ self.processImportToHistory(new_history.id); }) .fail((xhr, status, error) => { - mod_toastr.error("An error occurred."); + Toast.error("An error occurred."); }) .always(() => { self.modal.enableButton("Import"); @@ -266,7 +267,7 @@ var LibraryDatasetView = Backbone.View.extend({ { success: function() { Galaxy.modal.hide(); - mod_toastr.success("Dataset imported. Click this to start analyzing it.", "", { + Toast.success("Dataset imported. Click this to start analyzing it.", "", { onclick: function() { window.location = getAppRoot(); } @@ -274,9 +275,9 @@ var LibraryDatasetView = Backbone.View.extend({ }, error: function(model, response) { if (typeof response.responseJSON !== "undefined") { - mod_toastr.error(`Dataset not imported. ${response.responseJSON.err_msg}`); + Toast.error(`Dataset not imported. ${response.responseJSON.err_msg}`); } else { - mod_toastr.error("An error occured. Dataset not imported. Please try again."); + Toast.error("An error occurred. Dataset not imported. Please try again."); } } } @@ -310,7 +311,7 @@ var LibraryDatasetView = Backbone.View.extend({ }); }) .fail(() => { - mod_toastr.error("An error occurred while attempting to fetch dataset permissions."); + Toast.error("An error occurred while attempting to fetch dataset permissions."); }); $('#center [data-toggle="tooltip"]').tooltip({ trigger: "hover" }); $("#center").css("overflow", "auto"); @@ -398,6 +399,12 @@ var LibraryDatasetView = Backbone.View.extend({ return select_options; }, + detectDatatype: function(options) { + let ld = this.model; + ld.set("file_ext", "auto"); + this._submitModification(ld); + }, + /** * Save the changes made to the library dataset. */ @@ -410,7 +417,7 @@ var LibraryDatasetView = Backbone.View.extend({ ld.set("name", new_name); is_changed = true; } else { - mod_toastr.warning("Library dataset name has to be at least 1 character long."); + Toast.warning("Library dataset name has to be at least 1 character long."); return; } } @@ -434,28 +441,31 @@ var LibraryDatasetView = Backbone.View.extend({ ld.set("file_ext", new_ext); is_changed = true; } - var dataset_view = this; if (is_changed) { - ld.save(null, { - patch: true, - success: function(ld) { - dataset_view.render(); - mod_toastr.success("Changes to library dataset saved."); - }, - error: function(model, response) { - if (typeof response.responseJSON !== "undefined") { - mod_toastr.error(response.responseJSON.err_msg); - } else { - mod_toastr.error("An error occured while attempting to update the library dataset."); - } - } - }); + this._submitModification(ld); } else { - dataset_view.render(); - mod_toastr.info("Nothing has changed."); + this.render(); + Toast.info("Nothing has changed."); } }, + _submitModification(library_dataset) { + library_dataset.save(null, { + patch: true, + success: library_dataset => { + this.render(); + Toast.success("Changes to library dataset saved."); + }, + error: function(model, response) { + if (typeof response.responseJSON !== "undefined") { + Toast.error(response.responseJSON.err_msg); + } else { + Toast.error("An error occurred while attempting to update the library dataset."); + } + } + }); + }, + copyToClipboard: function(e) { e.preventDefault(); var href = Backbone.history.location.href; @@ -473,10 +483,10 @@ var LibraryDatasetView = Backbone.View.extend({ self.showPermissions({ fetched_permissions: fetched_permissions }); - mod_toastr.success("The dataset is now private to you."); + Toast.success("The dataset is now private to you."); }) .fail(() => { - mod_toastr.error("An error occurred while attempting to make dataset private."); + Toast.error("An error occurred while attempting to make dataset private."); }); }, @@ -488,10 +498,10 @@ var LibraryDatasetView = Backbone.View.extend({ self.showPermissions({ fetched_permissions: fetched_permissions }); - mod_toastr.success("Access to this dataset is now unrestricted."); + Toast.success("Access to this dataset is now unrestricted."); }) .fail(() => { - mod_toastr.error("An error occurred while attempting to make dataset unrestricted."); + Toast.error("An error occurred while attempting to make dataset unrestricted."); }); }, @@ -523,10 +533,10 @@ var LibraryDatasetView = Backbone.View.extend({ self.showPermissions({ fetched_permissions: fetched_permissions }); - mod_toastr.success("Permissions saved."); + Toast.success("Permissions saved."); }) .fail(() => { - mod_toastr.error("An error occurred while attempting to set dataset permissions."); + Toast.error("An error occurred while attempting to set dataset permissions."); }); }, @@ -618,6 +628,10 @@ var LibraryDatasetView = Backbone.View.extend({ '', " Modify", "", + '", "<% } %>", '<% if (item.get("can_user_manage")) { %>', '/datasets/<%- item.id %>/permissions">', diff --git a/client/galaxy/scripts/mvc/library/library-folder-view.js b/client/galaxy/scripts/mvc/library/library-folder-view.js index 4175c01e5e3..c040f323903 100644 --- a/client/galaxy/scripts/mvc/library/library-folder-view.js +++ b/client/galaxy/scripts/mvc/library/library-folder-view.js @@ -3,7 +3,7 @@ import $ from "jquery"; import Backbone from "backbone"; import { getGalaxyInstance } from "app"; import { getAppRoot } from "onload/loadConfig"; -import mod_toastr from "libs/toastr"; +import { Toast } from "ui/toast"; import mod_library_model from "mvc/library/library-model"; import mod_select from "mvc/ui/ui-select"; @@ -40,13 +40,13 @@ var FolderView = Backbone.View.extend({ }, error: function(model, response) { if (typeof response.responseJSON !== "undefined") { - mod_toastr.error(`${response.responseJSON.err_msg} Click this to go back.`, "", { + Toast.error(`${response.responseJSON.err_msg} Click this to go back.`, "", { onclick: function() { Galaxy.libraries.library_router.back(); } }); } else { - mod_toastr.error("An error occurred. Click this to go back.", "", { + Toast.error("An error occurred. Click this to go back.", "", { onclick: function() { Galaxy.libraries.library_router.back(); } @@ -76,7 +76,7 @@ var FolderView = Backbone.View.extend({ }); }) .fail(() => { - mod_toastr.error("An error occurred while attempting to fetch folder permissions."); + Toast.error("An error occurred while attempting to fetch folder permissions."); }); $('#center [data-toggle="tooltip"]').tooltip({ trigger: "hover" }); @@ -193,10 +193,10 @@ var FolderView = Backbone.View.extend({ self.showPermissions({ fetched_permissions: fetched_permissions }); - mod_toastr.success("Permissions saved."); + Toast.success("Permissions saved."); }) .fail(() => { - mod_toastr.error("An error occurred while attempting to set folder permissions."); + Toast.error("An error occurred while attempting to set folder permissions."); }); }, diff --git a/client/galaxy/scripts/mvc/library/library-folderlist-view.js b/client/galaxy/scripts/mvc/library/library-folderlist-view.js index 807d64db56d..07e04e66958 100644 --- a/client/galaxy/scripts/mvc/library/library-folderlist-view.js +++ b/client/galaxy/scripts/mvc/library/library-folderlist-view.js @@ -1,7 +1,7 @@ import $ from "jquery"; import _ from "underscore"; import Backbone from "backbone"; -import mod_toastr from "libs/toastr"; +import { Toast } from "ui/toast"; import mod_library_model from "mvc/library/library-model"; import mod_library_folderrow_view from "mvc/library/library-folderrow-view"; import { getGalaxyInstance } from "app"; @@ -79,13 +79,13 @@ var FolderListView = Backbone.View.extend({ error: function(model, response) { let Galaxy = getGalaxyInstance(); if (typeof response.responseJSON !== "undefined") { - mod_toastr.error(`${response.responseJSON.err_msg} Click this to go back.`, "", { + Toast.error(`${response.responseJSON.err_msg} Click this to go back.`, "", { onclick: function() { Galaxy.libraries.library_router.back(); } }); } else { - mod_toastr.error("An error occurred. Click this to go back.", "", { + Toast.error("An error occurred. Click this to go back.", "", { onclick: function() { Galaxy.libraries.library_router.back(); } @@ -128,7 +128,7 @@ var FolderListView = Backbone.View.extend({ if (row) { row.showDatasetDetails(); } else { - mod_toastr.error("Requested dataset not found. Showing folder instead."); + Toast.error("Requested dataset not found. Showing folder instead."); } } else { if (this.options.show_page === null || this.options.show_page < 1) { @@ -410,8 +410,7 @@ var FolderListView = Backbone.View.extend({ "", '" ].join("") ); diff --git a/client/galaxy/scripts/mvc/library/library-folderrow-view.js b/client/galaxy/scripts/mvc/library/library-folderrow-view.js index 5398a5260f9..2493f11402e 100644 --- a/client/galaxy/scripts/mvc/library/library-folderrow-view.js +++ b/client/galaxy/scripts/mvc/library/library-folderrow-view.js @@ -3,7 +3,7 @@ import $ from "jquery"; import Backbone from "backbone"; import { getAppRoot } from "onload/loadConfig"; import { getGalaxyInstance } from "app"; -import mod_toastr from "libs/toastr"; +import { Toast } from "ui/toast"; import mod_library_model from "mvc/library/library-model"; import mod_library_dataset_view from "mvc/library/library-dataset-view"; @@ -125,7 +125,7 @@ var FolderRowView = Backbone.View.extend({ var updated_dataset = new mod_library_model.Item(response); Galaxy.libraries.folderListView.collection.add(updated_dataset); Galaxy.libraries.folderListView.collection.sortFolder("name", "asc"); - mod_toastr.success("Dataset undeleted. Click this to see it.", "", { + Toast.success("Dataset undeleted. Click this to see it.", "", { onclick: function() { var folder_id = that.model.get("folder_id"); window.location = `${getAppRoot()}library/list#folders/${folder_id}/datasets/${that.id}`; @@ -134,9 +134,9 @@ var FolderRowView = Backbone.View.extend({ }, error: function(model, response) { if (typeof response.responseJSON !== "undefined") { - mod_toastr.error(`Dataset was not undeleted. ${response.responseJSON.err_msg}`); + Toast.error(`Dataset was not undeleted. ${response.responseJSON.err_msg}`); } else { - mod_toastr.error("An error occured! Dataset was not undeleted. Please try again."); + Toast.error("An error occurred! Dataset was not undeleted. Please try again."); } } }); @@ -157,13 +157,13 @@ var FolderRowView = Backbone.View.extend({ var updated_folder = new mod_library_model.FolderAsModel(response); Galaxy.libraries.folderListView.collection.add(updated_folder); Galaxy.libraries.folderListView.collection.sortFolder("name", "asc"); - mod_toastr.success("Folder undeleted."); + Toast.success("Folder undeleted."); }, error: function(model, response) { if (typeof response.responseJSON !== "undefined") { - mod_toastr.error(`Folder was not undeleted. ${response.responseJSON.err_msg}`); + Toast.error(`Folder was not undeleted. ${response.responseJSON.err_msg}`); } else { - mod_toastr.error("An error occured! Folder was not undeleted. Please try again."); + Toast.error("An error occurred! Folder was not undeleted. Please try again."); } } }); @@ -191,7 +191,7 @@ var FolderRowView = Backbone.View.extend({ folder.set("name", new_name); is_changed = true; } else { - mod_toastr.warning("Folder name has to be at least 3 characters long."); + Toast.warning("Folder name has to be at least 3 characters long."); return; } } @@ -207,20 +207,20 @@ var FolderRowView = Backbone.View.extend({ success: function(folder) { row_view.options.edit_mode = false; row_view.repaint(folder); - mod_toastr.success("Changes to folder saved."); + Toast.success("Changes to folder saved."); }, error: function(model, response) { if (typeof response.responseJSON !== "undefined") { - mod_toastr.error(response.responseJSON.err_msg); + Toast.error(response.responseJSON.err_msg); } else { - mod_toastr.error("An error occured while attempting to update the folder."); + Toast.error("An error occurred while attempting to update the folder."); } } }); } else { this.options.edit_mode = false; this.repaint(folder); - mod_toastr.info("Nothing has changed."); + Toast.info("Nothing has changed."); } }, diff --git a/client/galaxy/scripts/mvc/library/library-foldertoolbar-view.js b/client/galaxy/scripts/mvc/library/library-foldertoolbar-view.js index 4176034ce73..5af390bd69c 100644 --- a/client/galaxy/scripts/mvc/library/library-foldertoolbar-view.js +++ b/client/galaxy/scripts/mvc/library/library-foldertoolbar-view.js @@ -5,7 +5,7 @@ import { getAppRoot } from "onload/loadConfig"; import { getGalaxyInstance } from "app"; import _l from "utils/localization"; import mod_utils from "utils/utils"; -import mod_toastr from "libs/toastr"; +import { Toast } from "ui/toast"; import mod_library_model from "mvc/library/library-model"; import mod_select from "mvc/ui/ui-select"; import LIST_CREATOR from "mvc/collection/list-collection-creator"; @@ -190,21 +190,21 @@ var FolderToolbarView = Backbone.View.extend({ folder.save(folderDetails, { success: function(folder) { Galaxy.modal.hide(); - mod_toastr.success("Folder created."); + Toast.success("Folder created."); folder.set({ type: "folder" }); Galaxy.libraries.folderListView.collection.add(folder); }, error: function(model, response) { Galaxy.modal.hide(); if (typeof response.responseJSON !== "undefined") { - mod_toastr.error(response.responseJSON.err_msg); + Toast.error(response.responseJSON.err_msg); } else { - mod_toastr.error("An error occurred."); + Toast.error("An error occurred."); } } }); } else { - mod_toastr.error("Folder's name is missing."); + Toast.error("Folder's name is missing."); } return false; }, @@ -226,7 +226,7 @@ var FolderToolbarView = Backbone.View.extend({ var $checkedValues = this.findCheckedRows(); var template = this.templateImportIntoHistoryModal(); if ($checkedValues.length === 0) { - mod_toastr.info("You must select some datasets first."); + Toast.info("You must select some datasets first."); } else { var promise = this.fetchUserHistories(); promise @@ -250,9 +250,9 @@ var FolderToolbarView = Backbone.View.extend({ }) .fail((model, response) => { if (typeof response.responseJSON !== "undefined") { - mod_toastr.error(response.responseJSON.err_msg); + Toast.error(response.responseJSON.err_msg); } else { - mod_toastr.error("An error occurred."); + Toast.error("An error occurred."); } }); } @@ -273,7 +273,7 @@ var FolderToolbarView = Backbone.View.extend({ this.processImportToHistory(new_history.id, new_history.name); }) .fail((xhr, status, error) => { - mod_toastr.error("An error occurred."); + Toast.error("An error occurred."); }) .always(() => { this.modal.enableButton("Import"); @@ -366,9 +366,9 @@ var FolderToolbarView = Backbone.View.extend({ .appendTo("body") .submit() .remove(); - mod_toastr.info("Your download will begin soon."); + Toast.info("Your download will begin soon."); } else { - mod_toastr.error("An error occurred."); + Toast.error("An error occurred."); } }, @@ -405,9 +405,9 @@ var FolderToolbarView = Backbone.View.extend({ }) .fail((model, response) => { if (typeof response.responseJSON !== "undefined") { - mod_toastr.error(response.responseJSON.err_msg); + Toast.error(response.responseJSON.err_msg); } else { - mod_toastr.error("An error occurred."); + Toast.error("An error occurred."); } }); }, @@ -589,12 +589,12 @@ var FolderToolbarView = Backbone.View.extend({ error: (model, response) => { if (typeof response.responseJSON !== "undefined") { if (response.responseJSON.err_code === 404001) { - mod_toastr.warning(response.responseJSON.err_msg); + Toast.warning(response.responseJSON.err_msg); } else { - mod_toastr.error(response.responseJSON.err_msg); + Toast.error(response.responseJSON.err_msg); } } else { - mod_toastr.error("An error occurred."); + Toast.error("An error occurred."); } } }); @@ -616,7 +616,7 @@ var FolderToolbarView = Backbone.View.extend({ var paths = $("textarea#import_paths").val(); var valid_paths = []; if (!paths) { - mod_toastr.info("Please enter a path relative to Galaxy root."); + Toast.info("Please enter a path relative to Galaxy root."); } else { this.modal.disableButton("Import"); paths = paths.split("\n"); @@ -706,7 +706,7 @@ var FolderToolbarView = Backbone.View.extend({ var selection_type = selected_nodes[0].type; var paths = []; if (selected_nodes.length < 1) { - mod_toastr.info("Please select some items first."); + Toast.info("Please select some items first."); } else { this.modal.disableButton("Import"); for (let i = selected_nodes.length - 1; i >= 0; i--) { @@ -769,9 +769,9 @@ var FolderToolbarView = Backbone.View.extend({ }, error: (model, response) => { if (typeof response.responseJSON !== "undefined") { - mod_toastr.error(response.responseJSON.err_msg); + Toast.error(response.responseJSON.err_msg); } else { - mod_toastr.error("An error occurred."); + Toast.error("An error occurred."); } } }); @@ -786,7 +786,7 @@ var FolderToolbarView = Backbone.View.extend({ var history_item_types = []; var items_to_add = []; if (checked_hdas.length < 1) { - mod_toastr.info("You must select some datasets first."); + Toast.info("You must select some datasets first."); } else { this.modal.disableButton("Add"); checked_hdas.each(function() { @@ -831,15 +831,15 @@ var FolderToolbarView = Backbone.View.extend({ var popped_item = history_item_set.pop(); if (typeof popped_item == "undefined") { if (this.options.chain_call_control.failed_number === 0) { - mod_toastr.success("Selected datasets imported into history. Click this to start analyzing it.", "", { + Toast.success("Selected datasets imported into history. Click this to start analyzing it.", "", { onclick: () => { window.location = getAppRoot(); } }); } else if (this.options.chain_call_control.failed_number === this.options.chain_call_control.total_number) { - mod_toastr.error("There was an error and no datasets were imported into history."); + Toast.error("There was an error and no datasets were imported into history."); } else if (this.options.chain_call_control.failed_number < this.options.chain_call_control.total_number) { - mod_toastr.warning( + Toast.warning( "Some of the datasets could not be imported into history. Click this to see what was imported.", "", { @@ -879,27 +879,29 @@ var FolderToolbarView = Backbone.View.extend({ */ chainCallImportingUserdirFiles: function(options) { let Galaxy = getGalaxyInstance(); - var popped_item = options.paths.pop(); + let popped_item = options.paths.pop(); if (typeof popped_item === "undefined") { if (this.options.chain_call_control.failed_number === 0) { - mod_toastr.success("Selected files imported into the current folder"); + Toast.success("Selected files imported into the current folder"); Galaxy.modal.hide(); } else { - mod_toastr.error("An error occured."); + Toast.error("An error occurred."); } return true; } - var promise = $.when( - $.post( - `${getAppRoot()}api/libraries/datasets?encoded_folder_id=${this.id}&source=${ - options.source - }&path=${popped_item}&file_type=${options.file_type}&link_data=${options.link_data}&space_to_tab=${ - options.space_to_tab - }&to_posix_lines=${options.to_posix_lines}&dbkey=${options.dbkey}&tag_using_filenames=${ - options.tag_using_filenames - }` - ) - ); + let post_url = `${getAppRoot()}api/libraries/datasets`; + let post_data = { + encoded_folder_id: this.id, + source: options.source, + path: popped_item, + file_type: options.file_type, + link_data: options.link_data, + space_to_tab: options.space_to_tab, + to_posix_lines: options.to_posix_lines, + dbkey: options.dbkey, + tag_using_filenames: options.tag_using_filenames + }; + let promise = $.when($.post(post_url, post_data)); promise .done(response => { this.updateProgress(); @@ -927,28 +929,31 @@ var FolderToolbarView = Backbone.View.extend({ chainCallImportingFolders: function(options) { let Galaxy = getGalaxyInstance(); // TODO need to check which paths to call - var popped_item = options.paths.pop(); + let popped_item = options.paths.pop(); if (typeof popped_item == "undefined") { if (this.options.chain_call_control.failed_number === 0) { - mod_toastr.success("Selected folders and their contents imported into the current folder."); + Toast.success("Selected folders and their contents imported into the current folder."); Galaxy.modal.hide(); } else { // TODO better error report - mod_toastr.error("An error occured."); + Toast.error("An error occurred."); } return true; } - var promise = $.when( - $.post( - `${getAppRoot()}api/libraries/datasets?encoded_folder_id=${this.id}&source=${ - options.source - }&path=${popped_item}&preserve_dirs=${options.preserve_dirs}&link_data=${ - options.link_data - }&to_posix_lines=${options.to_posix_lines}&space_to_tab=${options.space_to_tab}&file_type=${ - options.file_type - }&dbkey=${options.dbkey}&tag_using_filenames=${options.tag_using_filenames}` - ) - ); + let post_url = `${getAppRoot()}api/libraries/datasets`; + let post_data = { + encoded_folder_id: this.id, + source: options.source, + path: popped_item, + preserve_dirs: options.preserve_dirs, + link_data: options.link_data, + to_posix_lines: options.to_posix_lines, + space_to_tab: options.space_to_tab, + file_type: options.file_type, + dbkey: options.dbkey, + tag_using_filenames: options.tag_using_filenames + }; + let promise = $.when($.post(post_url, post_data)); promise .done(response => { this.updateProgress(); @@ -972,11 +977,11 @@ var FolderToolbarView = Backbone.View.extend({ var popped_item = hdas_set.pop(); if (typeof popped_item == "undefined") { if (this.options.chain_call_control.failed_number === 0) { - mod_toastr.success("Selected datasets from history added to the folder"); + Toast.success("Selected datasets from history added to the folder"); } else if (this.options.chain_call_control.failed_number === this.options.chain_call_control.total_number) { - mod_toastr.error("There was an error and no datasets were added to the folder."); + Toast.error("There was an error and no datasets were added to the folder."); } else if (this.options.chain_call_control.failed_number < this.options.chain_call_control.total_number) { - mod_toastr.warning("Some of the datasets could not be added to the folder"); + Toast.warning("Some of the datasets could not be added to the folder"); } Galaxy.modal.hide(); return this.added_hdas; @@ -1011,13 +1016,13 @@ var FolderToolbarView = Backbone.View.extend({ var item_to_delete = items_to_delete.pop(); if (typeof item_to_delete === "undefined") { if (this.options.chain_call_control.failed_number === 0) { - mod_toastr.success("Selected items were deleted."); + Toast.success("Selected items were deleted."); } else if (this.options.chain_call_control.failed_number === this.options.chain_call_control.total_number) { - mod_toastr.error( + Toast.error( "There was an error and no items were deleted. Please make sure you have sufficient permissions." ); } else if (this.options.chain_call_control.failed_number < this.options.chain_call_control.total_number) { - mod_toastr.warning( + Toast.warning( "Some of the items could not be deleted. Please make sure you have sufficient permissions." ); } @@ -1076,7 +1081,7 @@ var FolderToolbarView = Backbone.View.extend({ var folder_ids = []; var $checkedValues = this.findCheckedRows(); if ($checkedValues.length === 0) { - mod_toastr.info("You must select at least one item for deletion."); + Toast.info("You must select at least one item for deletion."); } else { var template = this.templateDeletingItemsProgressBar(); this.modal = Galaxy.modal; @@ -1148,9 +1153,9 @@ var FolderToolbarView = Backbone.View.extend({ }, error: function(model, response) { if (typeof response.responseJSON !== "undefined") { - mod_toastr.error(response.responseJSON.err_msg); + Toast.error(response.responseJSON.err_msg); } else { - mod_toastr.error("An error occurred."); + Toast.error("An error occurred."); } } }); @@ -1194,7 +1199,7 @@ var FolderToolbarView = Backbone.View.extend({ break; default: Galaxy.libraries.library_router.back(); - mod_toastr.error("Invalid import source."); + Toast.error("Invalid import source."); break; } }, @@ -1253,7 +1258,7 @@ var FolderToolbarView = Backbone.View.extend({ body: template({ selected_datasets: checked_items.dataset_ids.length }), buttons: { Continue: () => { - this.showColectionBuilder(checked_items.dataset_ids); + this.showCollectionBuilder(checked_items.dataset_ids); }, Close: () => { Galaxy.modal.hide(); @@ -1306,7 +1311,12 @@ var FolderToolbarView = Backbone.View.extend({ this.collectionType = collectionType; }, - showColectionBuilder: function(checked_items) { + /** + * Note: The collection creation process expects ldda_ids as ids + * in the collection_elements array but we operate on ld_ids in libraries. + * The code below overwrites the id with ldda_id for this reason. + */ + showCollectionBuilder: function(checked_items) { let Galaxy = getGalaxyInstance(); let collection_elements = []; let elements_source = this.modal.$('input[type="radio"]:checked').val(); @@ -1314,26 +1324,28 @@ var FolderToolbarView = Backbone.View.extend({ for (let i = checked_items.length - 1; i >= 0; i--) { let collection_item = {}; let dataset = Galaxy.libraries.folderListView.folder_container.get("folder").get(checked_items[i]); - collection_item.id = checked_items[i]; + collection_item.id = dataset.get("ldda_id"); collection_item.name = dataset.get("name"); collection_item.deleted = dataset.get("deleted"); collection_item.state = dataset.get("state"); collection_elements.push(collection_item); } } else if (elements_source === "folder") { - collection_elements = new Backbone.Collection( - Galaxy.libraries.folderListView.folder_container.get("folder").where({ type: "file" }) - ).toJSON(); + let all_datasets = Galaxy.libraries.folderListView.folder_container.get("folder").where({ type: "file" }); + collection_elements = new Backbone.Collection(all_datasets).toJSON(); + for (var i = collection_elements.length - 1; i >= 0; i--) { + collection_elements[i].id = collection_elements[i].ldda_id; + } } let new_history_name = this.modal.$("input[name=history_name]").val(); if (new_history_name !== "") { this.createNewHistory(new_history_name) .done(new_history => { - mod_toastr.success("History created"); + Toast.success("History created"); this.collectionImport(collection_elements, new_history.id, new_history.name); }) .fail((xhr, status, error) => { - mod_toastr.error("An error occurred."); + Toast.error("An error occurred."); }); } else { let selected_history_id = this.select_collection_history.value(); @@ -1411,92 +1423,81 @@ var FolderToolbarView = Backbone.View.extend({ templateToolBar: function() { return _.template( - [ - '
', // container start - '
', - 'DATA LIBRARIES', - "
", // toolbar start - '
', - '", - '", - "<% if(multiple_add_dataset_options) { %>", // add datasets button - '", - - "<% } else { %>", - '", - "<% } %>", - // import to history button - '", - // download button - '", - // delete button - '", - '', // location button - '", - "", - '', // help button - '', - '", - "", - "", - "
", - "
", // toolbar end - "
", // end flex - '
', // folder contents will append here - '
', // paginator will append here - "
" // container end - ].join("") + `
+
+ + + + + + +
+
+ + + <% if(multiple_add_dataset_options) { %> + + <% } else { %> + + <% } %> + + + + + + +
+
+
+
+
+
` ); }, diff --git a/client/galaxy/scripts/mvc/library/library-library-view.js b/client/galaxy/scripts/mvc/library/library-library-view.js index 70d491e5c60..f86aff62a0b 100644 --- a/client/galaxy/scripts/mvc/library/library-library-view.js +++ b/client/galaxy/scripts/mvc/library/library-library-view.js @@ -3,7 +3,7 @@ import $ from "jquery"; import Backbone from "backbone"; import { getAppRoot } from "onload/loadConfig"; import { getGalaxyInstance } from "app"; -import mod_toastr from "libs/toastr"; +import { Toast } from "ui/toast"; import mod_library_model from "mvc/library/library-model"; import mod_select from "mvc/ui/ui-select"; @@ -40,13 +40,13 @@ var LibraryView = Backbone.View.extend({ error: function(model, response) { let Galaxy = getGalaxyInstance(); if (typeof response.responseJSON !== "undefined") { - mod_toastr.error(`${response.responseJSON.err_msg} Click this to go back.`, "", { + Toast.error(`${response.responseJSON.err_msg} Click this to go back.`, "", { onclick: function() { Galaxy.libraries.library_router.back(); } }); } else { - mod_toastr.error("An error occurred. Click this to go back.", "", { + Toast.error("An error occurred. Click this to go back.", "", { onclick: function() { Galaxy.libraries.library_router.back(); } @@ -83,7 +83,7 @@ var LibraryView = Backbone.View.extend({ }); }) .fail(() => { - mod_toastr.error("An error occurred while attempting to fetch library permissions."); + Toast.error("An error occurred while attempting to fetch library permissions."); }); $('#center [data-toggle="tooltip"]').tooltip({ trigger: "hover" }); @@ -188,10 +188,10 @@ var LibraryView = Backbone.View.extend({ self.showPermissions({ fetched_permissions: fetched_permissions }); - mod_toastr.success("The dataset is now private to you."); + Toast.success("The dataset is now private to you."); }) .fail(() => { - mod_toastr.error("An error occurred while attempting to make dataset private."); + Toast.error("An error occurred while attempting to make dataset private."); }); }, @@ -203,10 +203,10 @@ var LibraryView = Backbone.View.extend({ self.showPermissions({ fetched_permissions: fetched_permissions }); - mod_toastr.success("Access to this dataset is now unrestricted."); + Toast.success("Access to this dataset is now unrestricted."); }) .fail(() => { - mod_toastr.error("An error occurred while attempting to make dataset unrestricted."); + Toast.error("An error occurred while attempting to make dataset unrestricted."); }); }, @@ -236,10 +236,10 @@ var LibraryView = Backbone.View.extend({ self.showPermissions({ fetched_permissions: fetched_permissions }); - mod_toastr.success("Permissions saved."); + Toast.success("Permissions saved."); }) .fail(() => { - mod_toastr.error("An error occurred while attempting to set library permissions."); + Toast.error("An error occurred while attempting to set library permissions."); }); }, diff --git a/client/galaxy/scripts/mvc/library/library-librarylist-view.js b/client/galaxy/scripts/mvc/library/library-librarylist-view.js index f3a2bd893b4..7e36cf370b1 100644 --- a/client/galaxy/scripts/mvc/library/library-librarylist-view.js +++ b/client/galaxy/scripts/mvc/library/library-librarylist-view.js @@ -2,7 +2,7 @@ import $ from "jquery"; import _ from "libs/underscore"; import Backbone from "backbone"; import { getGalaxyInstance } from "app"; -import mod_toastr from "libs/toastr"; +import { Toast } from "ui/toast"; import mod_library_model from "mvc/library/library-model"; import mod_library_libraryrow_view from "mvc/library/library-libraryrow-view"; @@ -37,9 +37,9 @@ var LibraryListView = Backbone.View.extend({ }, error: function(model, response) { if (typeof response.responseJSON !== "undefined") { - mod_toastr.error(response.responseJSON.err_msg); + Toast.error(response.responseJSON.err_msg); } else { - mod_toastr.error("An error occurred."); + Toast.error("An error occurred."); } } }); @@ -147,9 +147,9 @@ var LibraryListView = Backbone.View.extend({ }, error: function(model, response) { if (typeof response.responseJSON !== "undefined") { - mod_toastr.error(response.responseJSON.err_msg); + Toast.error(response.responseJSON.err_msg); } else { - mod_toastr.error("An error occurred."); + Toast.error("An error occurred."); } } }); @@ -240,8 +240,7 @@ var LibraryListView = Backbone.View.extend({ "<% } else{ %>", "
", "There are no libraries visible to you here. If you expected some to show up please consult the", - ' library security wikipage', - ' or visit the Galaxy support site.', + ' library security wikipage.', "
", "<% }%>", "<% } else{ %>", diff --git a/client/galaxy/scripts/mvc/library/library-libraryrow-view.js b/client/galaxy/scripts/mvc/library/library-libraryrow-view.js index fbead05760d..15315685052 100644 --- a/client/galaxy/scripts/mvc/library/library-libraryrow-view.js +++ b/client/galaxy/scripts/mvc/library/library-libraryrow-view.js @@ -1,7 +1,7 @@ import _ from "underscore"; import $ from "jquery"; import Backbone from "backbone"; -import mod_toastr from "libs/toastr"; +import { Toast } from "ui/toast"; import { getGalaxyInstance } from "app"; // galaxy library row view @@ -116,7 +116,7 @@ var LibraryRowView = Backbone.View.extend({ /* User clicked the 'cancel' button so we render normal rowView */ cancel_library_modification: function() { - // mod_toastr.info('Modifications canceled'); + // Toast.info('Modifications canceled'); this.edit_mode = false; this.repaint(); }, @@ -132,7 +132,7 @@ var LibraryRowView = Backbone.View.extend({ library.set("name", new_name); is_changed = true; } else { - mod_toastr.warning("Library name has to be at least 3 characters long."); + Toast.warning("Library name has to be at least 3 characters long."); return; } } @@ -156,20 +156,20 @@ var LibraryRowView = Backbone.View.extend({ success: function(library) { row_view.edit_mode = false; row_view.repaint(library); - mod_toastr.success("Changes to library saved."); + Toast.success("Changes to library saved."); }, error: function(model, response) { if (typeof response.responseJSON !== "undefined") { - mod_toastr.error(response.responseJSON.err_msg); + Toast.error(response.responseJSON.err_msg); } else { - mod_toastr.error("An error occured while attempting to update the library."); + Toast.error("An error occurred while attempting to update the library."); } } }); } else { this.edit_mode = false; this.repaint(library); - mod_toastr.info("Nothing has changed."); + Toast.info("Nothing has changed."); } }, @@ -191,13 +191,13 @@ var LibraryRowView = Backbone.View.extend({ } else if (Galaxy.libraries.preferences.get("with_deleted") === true) { row_view.repaint(library); } - mod_toastr.success("Library has been marked deleted."); + Toast.success("Library has been marked deleted."); }, error: function(model, response) { if (typeof response.responseJSON !== "undefined") { - mod_toastr.error(response.responseJSON.err_msg); + Toast.error(response.responseJSON.err_msg); } else { - mod_toastr.error("An error occured during deleting the library."); + Toast.error("An error occurred during deleting the library."); } } }); @@ -218,13 +218,13 @@ var LibraryRowView = Backbone.View.extend({ Galaxy.libraries.libraryListView.collection.add(library); row_view.edit_mode = false; row_view.repaint(library); - mod_toastr.success("Library has been undeleted."); + Toast.success("Library has been undeleted."); }, error: function(model, response) { if (typeof response.responseJSON !== "undefined") { - mod_toastr.error(response.responseJSON.err_msg); + Toast.error(response.responseJSON.err_msg); } else { - mod_toastr.error("An error occured while undeleting the library."); + Toast.error("An error occurred while undeleting the library."); } } }); diff --git a/client/galaxy/scripts/mvc/library/library-librarytoolbar-view.js b/client/galaxy/scripts/mvc/library/library-librarytoolbar-view.js index d0e7a1d69e2..543f52e3254 100644 --- a/client/galaxy/scripts/mvc/library/library-librarytoolbar-view.js +++ b/client/galaxy/scripts/mvc/library/library-librarytoolbar-view.js @@ -2,7 +2,7 @@ import _ from "underscore"; import $ from "jquery"; import Backbone from "backbone"; import _l from "utils/localization"; -import mod_toastr from "libs/toastr"; +import { Toast } from "ui/toast"; import mod_library_model from "mvc/library/library-model"; import { getGalaxyInstance } from "app"; @@ -106,18 +106,18 @@ var LibraryToolbarView = Backbone.View.extend({ self.modal.hide(); self.clearLibraryModal(); Galaxy.libraries.libraryListView.render(); - mod_toastr.success("Library created."); + Toast.success("Library created."); }, error: function(model, response) { if (typeof response.responseJSON !== "undefined") { - mod_toastr.error(response.responseJSON.err_msg); + Toast.error(response.responseJSON.err_msg); } else { - mod_toastr.error("An error occured."); + Toast.error("An error occurred."); } } }); } else { - mod_toastr.error("Library's name is missing."); + Toast.error("Library's name is missing."); } return false; }, @@ -207,32 +207,37 @@ var LibraryToolbarView = Backbone.View.extend({ templateToolBar: function() { return _.template( - [ - '
', - '
', - 'DATA LIBRARIES', - '
', // paginator will append here - '
', - '', - "<% if(admin_user === true) { %>", // only admins - '
', - '', - '', - "
", - '
', - '', - '', - "
", - '', - "<% } %>", // end admins - "
", - '', - '', - "", - "
", // end flex - '
', // table with libraries will append here - "
" // end library_style_container - ].join("") + `
+
+ + + + + + + <% if(admin_user === true) { %> + + <% } %> +
+
+ + <% if(admin_user === true) { %> +
+ + +
+
+ + +
+ <% } %> +
+
+
+
+
` ); }, diff --git a/client/galaxy/scripts/mvc/rules/rule-definitions.js b/client/galaxy/scripts/mvc/rules/rule-definitions.js index 4b410cb2e55..c0cb365f121 100644 --- a/client/galaxy/scripts/mvc/rules/rule-definitions.js +++ b/client/galaxy/scripts/mvc/rules/rule-definitions.js @@ -861,8 +861,8 @@ const colHeadersFor = function(data, columns) { } }; -const applyRules = function(data, sources, columns, rules, colHeadersPerRule) { - colHeadersPerRule = colHeadersPerRule || []; +const applyRules = function(data, sources, columns, rules, headersPerRule = []) { + let colHeadersPerRule = Array.from(headersPerRule); let hasRuleError = false; for (var ruleIndex in rules) { const ruleHeaders = colHeadersFor(data, columns); @@ -889,7 +889,7 @@ const applyRules = function(data, sources, columns, rules, colHeadersPerRule) { columns = res.columns || columns; } } - return { data, sources, columns }; + return { data, sources, columns, colHeadersPerRule }; }; export default { diff --git a/client/galaxy/scripts/mvc/tool/tool-form-base.js b/client/galaxy/scripts/mvc/tool/tool-form-base.js index 08d79e4f368..c4e29fea8d8 100644 --- a/client/galaxy/scripts/mvc/tool/tool-form-base.js +++ b/client/galaxy/scripts/mvc/tool/tool-form-base.js @@ -13,6 +13,7 @@ import FormBase from "mvc/form/form-view"; import Webhooks from "mvc/webhooks"; import Citations from "components/Citations.vue"; import Vue from "vue"; +import axios from "axios"; export default FormBase.extend({ initialize: function(options) { @@ -113,13 +114,51 @@ export default FormBase.extend({ _operations: function() { var self = this; var options = this.model.attributes; + let Galaxy = getGalaxyInstance(); + + // Buttons for adding and removing favorite. + let in_favorites = Galaxy.user.getFavorites().tools.indexOf(options.id) >= 0; + var favorite_button = new Ui.Button({ + icon: "fa-star-o", + title: options.narrow ? null : "Favorite", + tooltip: "Add to favorites", + visible: !Galaxy.user.isAnonymous() && !in_favorites, + onclick: () => { + axios + .put(`${Galaxy.root}api/users/${Galaxy.user.id}/favorites/tools`, { object_id: options.id }) + .then(response => { + favorite_button.hide(); + remove_favorite_button.show(); + Galaxy.user.updateFavorites("tools", response.data); + }); + } + }); + + var remove_favorite_button = new Ui.Button({ + icon: "fa-star", + title: options.narrow ? null : "Added", + tooltip: "Remove from favorites", + visible: !Galaxy.user.isAnonymous() && in_favorites, + onclick: () => { + axios + .delete( + `${Galaxy.root}api/users/${Galaxy.user.id}/favorites/tools/${encodeURIComponent(options.id)}` + ) + .then(response => { + remove_favorite_button.hide(); + favorite_button.show(); + Galaxy.user.updateFavorites("tools", response.data); + }); + } + }); // button for version selection var versions_button = new Ui.ButtonMenu({ icon: "fa-cubes", - title: (!options.narrow && "Versions") || null, + title: options.narrow ? null : "Versions", tooltip: "Select another tool version" }); + if (!options.sustain_version && options.versions && options.versions.length > 1) { for (var i in options.versions) { var version = options.versions[i]; @@ -145,25 +184,9 @@ export default FormBase.extend({ var menu_button = new Ui.ButtonMenu({ id: "options", icon: "fa-caret-down", - title: (!options.narrow && "Options") || null, + title: options.narrow ? null : "Options", tooltip: "View available options" }); - if (options.biostar_url) { - menu_button.addMenu({ - icon: "fa-question-circle", - title: "Question?", - onclick: function() { - window.open(`${options.biostar_url}/p/new/post/`); - } - }); - menu_button.addMenu({ - icon: "fa-search", - title: _l("Search"), - onclick: function() { - window.open(`${options.biostar_url}/local/search/page/?q=${options.name}`); - } - }); - } menu_button.addMenu({ icon: "fa-share", title: _l("Share"), @@ -176,7 +199,6 @@ export default FormBase.extend({ }); // add admin operations - let Galaxy = getGalaxyInstance(); if (Galaxy.user && Galaxy.user.get("is_admin")) { menu_button.addMenu({ icon: "fa-download", @@ -242,7 +264,9 @@ export default FormBase.extend({ return { menu: menu_button, - versions: versions_button + versions: versions_button, + favorite: favorite_button, + remove_favorite: remove_favorite_button }; }, @@ -267,8 +291,7 @@ export default FormBase.extend({ /** Templates */ _templateHelp: function(options) { var $tmpl = $("
") - .addClass("form-help") - .addClass("form-text") + .addClass("form-help form-text mt-4") .append(options.help); $tmpl.find("a").attr("target", "_blank"); $tmpl.find("img").each(function() { diff --git a/client/galaxy/scripts/mvc/tool/tools.js b/client/galaxy/scripts/mvc/tool/tools.js index b1806e4280f..034b0e5c2d2 100644 --- a/client/galaxy/scripts/mvc/tool/tools.js +++ b/client/galaxy/scripts/mvc/tool/tools.js @@ -344,6 +344,8 @@ _.extend(ToolSection.prototype, VisibilityMixin); * query. */ var ToolSearch = Backbone.Model.extend({ + SEARCH_RESERVED_TERMS_FAVORITES: ["#favs", "#favorites", "#favourites"], + defaults: { search_hint_string: "search tools", min_chars_for_search: 3, @@ -365,6 +367,7 @@ var ToolSearch = Backbone.Model.extend({ * Do the search and update the results. */ do_search: function() { + let Galaxy = getGalaxyInstance(); var query = this.attributes.query; // If query is too short, do not search. @@ -379,26 +382,31 @@ var ToolSearch = Backbone.Model.extend({ if (this.timer) { clearTimeout(this.timer); } - // Start a new ajax-request in X ms - $("#search-clear-btn").hide(); - $("#search-spinner").show(); - var self = this; - this.timer = setTimeout(() => { - // log the search to analytics if present - if (typeof ga !== "undefined") { - ga("send", "pageview", `${getAppRoot()}?q=${q}`); - } - $.get( - self.urlRoot, - { q: q }, - data => { - self.set("results", data); - $("#search-spinner").hide(); - $("#search-clear-btn").show(); - }, - "json" - ); - }, 400); + // Catch reserved words + if (this.SEARCH_RESERVED_TERMS_FAVORITES.indexOf(q) >= 0) { + this.set("results", Galaxy.user.getFavorites().tools); + } else { + // Start a new ajax-request in X ms + $("#search-clear-btn").hide(); + $("#search-spinner").show(); + var self = this; + this.timer = setTimeout(() => { + // log the search to analytics if present + if (typeof ga !== "undefined") { + ga("send", "pageview", `${getAppRoot()}?q=${q}`); + } + $.get( + self.urlRoot, + { q: q }, + data => { + self.set("results", data); + $("#search-spinner").hide(); + $("#search-clear-btn").show(); + }, + "json" + ); + }, 400); + } }, clear_search: function() { diff --git a/client/galaxy/scripts/mvc/toolshed/categories-view.js b/client/galaxy/scripts/mvc/toolshed/categories-view.js index 8ecb17d9729..f54d0375609 100644 --- a/client/galaxy/scripts/mvc/toolshed/categories-view.js +++ b/client/galaxy/scripts/mvc/toolshed/categories-view.js @@ -87,7 +87,7 @@ var ToolShedCategories = Backbone.View.extend({ "<% _.each(categories, function(category) { %>", "", "", - '"><%= category.get("name") %>', + '/k/name/p/1/t/asc"><%= category.get("name") %>', "", '<%= category.get("description") %>', '<%= category.get("repositories") %>', diff --git a/client/galaxy/scripts/mvc/toolshed/repositories-view.js b/client/galaxy/scripts/mvc/toolshed/repositories-view.js index faae6e3681f..3ee678dab4a 100644 --- a/client/galaxy/scripts/mvc/toolshed/repositories-view.js +++ b/client/galaxy/scripts/mvc/toolshed/repositories-view.js @@ -10,23 +10,56 @@ var ToolShedCategoryContentsView = Backbone.View.extend({ el: "#center", initialize: function(params) { + this.params = params; this.model = new toolshed_model.CategoryCollection(); this.listenTo(this.model, "sync", this.render); var shed = params.tool_shed.replace(/\//g, "%2f"); - this.model.url += `?tool_shed_url=${shed}&category_id=${params.category_id}`; + this.model.url += `?tool_shed_url=${shed}&category_id=${params.category_id}&sort_key=${ + params.sort_key + }&sort_order=${params.sort_order}&page=${params.page}`; this.model.tool_shed = shed; this.model.category = params.category_id; this.model.fetch(); }, render: function(options) { - this.options = _.extend(this.options, options); + this.options = _.defaults(this.options || {}, options); var category_contents_template = this.templateCategoryContents; + var page_navigation_template = this.templatePageNavigation; + var sorting = { + class: { owner: "fa-sort", description: "fa-sort", name: "fa-sort" }, + direction: { owner: "asc", description: "asc", name: "asc" } + }; + sorting.class[this.params.sort_key] = this.params.sort_order == "desc" ? "fa-sort-down" : "fa-sort-up"; + sorting.direction[this.params.sort_key] = this.params.sort_order == "asc" ? "desc" : "asc"; + var previous_page = Math.max(this.params.page - 1, 1); + var pages = parseInt(this.model.models[0].get("repository_count") % 25) + 1; + var next_page = Math.min(parseInt(this.params.page) + 1, pages); + var pagination_params = { + page: this.params.page, + next: next_page, + previous: previous_page, + pages: pages + }; + if (pages > 5) { + var page = parseInt(this.params.page); + var page_slice = Array(); + var slice_start = Math.max(page - 2, 2); + var i = slice_start; + var slice_end = Math.min(Math.min(page + 2, slice_start + 4), pages); + for (i = slice_start; i <= slice_end; i++) { + page_slice.push(i); + } + pagination_params["page_slice"] = page_slice; + } this.$el.html( category_contents_template({ + page_navigation: page_navigation_template(pagination_params), category: this.model.models[0], tool_shed: this.model.tool_shed, - queue: toolshed_util.queueLength() + queue: toolshed_util.queueLength(), + sorting: sorting, + page: this.params.page }) ); $("#center").css("overflow", "auto"); @@ -57,13 +90,89 @@ var ToolShedCategoryContentsView = Backbone.View.extend({ }); } }); + $(".fa-fw").on("click", ev => { + var parameters = {}; + parameters.field = $(ev.target).attr("data-field"); + parameters.direction = $(ev.target).attr("data-direction"); + var new_route = `category/s/${this.model.tool_shed}/c/${this.model.category}/k/${parameters.field}/p/${ + this.params.page + }/t/${parameters.direction}`; + Backbone.history.navigate(new_route, { + trigger: true, + replace: true + }); + }); + $(".pagenav").on("click", ev => { + var page = $(ev.target).attr("data-page"); + var new_route = `category/s/${this.model.tool_shed}/c/${this.model.category}/k/${ + this.params.sort_key + }/p/${page}/t/${this.params.sort_order}`; + Backbone.history.navigate(new_route, { + trigger: true, + replace: true + }); + }); }, + templatePageNavigation: _.template( + [ + '" + ].join("") + ), + templateCategoryContents: _.template( [ '", '
', '', @@ -74,13 +183,16 @@ var ToolShedCategoryContentsView = Backbone.View.extend({ '" placeholder="Search repositories by name or id" size="60" type="text" />', "", "
", + "<% if (category.get('repository_count') > 25) { %>", + "<%= page_navigation %>", + "<% } %>", '
', - '', + '
', '', "", - '', - '', - "", + '', + '', + '', '', "", "", diff --git a/client/galaxy/scripts/mvc/tours.js b/client/galaxy/scripts/mvc/tours.js index 2040a3b09f8..8bf7559d064 100644 --- a/client/galaxy/scripts/mvc/tours.js +++ b/client/galaxy/scripts/mvc/tours.js @@ -14,6 +14,9 @@ import "libs/bootstrap-tour"; // bootstrap-tour configures a window.Tour object; keep a local ref. let Tour = window.Tour; +// For buttons to show, we need to add them to bootstrap/popper's sanitizing filters. +$.fn.tooltip.Constructor.Default.whiteList.button = ["data-role"]; + var gxy_root = getAppRoot(); const TOURPAGE_TEMPLATE = ` @@ -24,23 +27,19 @@ const TOURPAGE_TEMPLATE = `
<% _.each(tourtagorder, function(tag) { %> - <% }); %>
-<% _.each(tourtagorder, function(tourtagkey) { %> -
+
- <% var tourtag = tourtags[tourtagkey]; %> -

- <%- tourtag.name %> -

+

Tours

-
-<% }); %>`; +
`; var tour_opts = { storage: window.sessionStorage, @@ -139,22 +137,28 @@ export var ToursView = Backbone.View.extend({ var tourtags = {}; _.each(this.model.models, tour => { + tour.attributes.tags_lc = []; if (tour.attributes.tags === null) { if (tourtags.Untagged === undefined) { tourtags.Untagged = { name: "Untagged", tours: [] }; } tourtags.Untagged.tours.push(tour); } else { - _.each(tour.attributes.tags, tag => { - tag = tag.charAt(0).toUpperCase() + tag.slice(1); + _.each(tour.attributes.tags, otag => { + var tag = otag.charAt(0).toUpperCase() + otag.slice(1); if (tourtags[tag] === undefined) { tourtags[tag] = { name: tag, tours: [] }; } + tour.attributes.tags_lc.push(otag.toLowerCase()); tourtags[tag].tours.push(tour); }); } }); - var tourtagorder = Object.keys(tourtags).sort(); + //var tourtagorder = Object.keys(tourtags).sort(); + var tourtagorder = []; + Object.keys(tourtags).forEach(function(tag, index) { + tourtagorder.push({ name: tag, key: tag.toLowerCase() }); + }); this.$el .html( @@ -170,16 +174,33 @@ export var ToursView = Backbone.View.extend({ }) .on("click", ".tag-selector-button", e => { var elem = $(e.target); - var display = "block"; - var tag = elem.attr("tag-selector-button"); + var active_tags = []; + // Switch classes for the buttons elem.toggleClass("btn-primary"); elem.toggleClass("btn-secondary"); - if (elem.hasClass("btn-secondary")) { - display = "none"; - } - $(`div[tag='${tag}']`).css({ display: display }); + // Get all non-disabled tags + $(`.tag-selector-button.btn-primary`).each(function() { + active_tags.push($(this).attr("tag-selector-button")); + }); + + // Loop over all list items, subsequently determine these are + // only the tours (tags should be unique). Then use the non-disabled tags to + // determien whether or not to display this specific tour. + $(`li.list-group-item`).each(function() { + if ($(this).attr("tags")) { + var tour_tags = []; + var tour_tags_html = $(this).attr("tags"); + + tour_tags = tour_tags_html.split(","); + var fil_tour_tags = tour_tags.filter(function(tag) { + return active_tags.indexOf(tag.toLowerCase()) > -1; + }); + + $(this).css("display", fil_tour_tags.length > 0 ? "block" : "none"); + } + }); }); } }); diff --git a/client/galaxy/scripts/mvc/ui/popup-menu.js b/client/galaxy/scripts/mvc/ui/popup-menu.js index 4f9b72e2156..5e27f02dca1 100644 --- a/client/galaxy/scripts/mvc/ui/popup-menu.js +++ b/client/galaxy/scripts/mvc/ui/popup-menu.js @@ -130,18 +130,20 @@ var PopupMenu = Backbone.View.extend({ // function to close popup and unbind itself function closePopup(event) { $(document).off("click.close_popup"); - if (window && window.parent !== window) { - try { + try { + if (window && window.parent !== window) { $(window.parent.document).off("click.close_popup"); - } catch (err) { - console.debug(err); - } - } else { - try { + } else { $("iframe#galaxy_main") .contents() .off("click.close_popup"); - } catch (err) { + } + } catch (err) { + if (err instanceof DOMException) { + console.debug( + "Error clearing parent popups, likely cross-origin frame access from the toolshed and not problematic." + ); + } else { console.debug(err); } } @@ -149,20 +151,22 @@ var PopupMenu = Backbone.View.extend({ } $("html").one("click.close_popup", closePopup); - if (window && window.parent !== window) { - try { + try { + if (window && window.parent !== window) { $(window.parent.document) .find("html") .one("click.close_popup", closePopup); - } catch (err) { - console.debug(err); - } - } else { - try { + } else { $("iframe#galaxy_main") .contents() .one("click.close_popup", closePopup); - } catch (err) { + } + } catch (err) { + if (err instanceof DOMException) { + console.debug( + "Error clearing parent popups, likely cross-origin frame access from the toolshed and not problematic." + ); + } else { console.debug(err); } } diff --git a/client/galaxy/scripts/mvc/ui/ui-color-picker.js b/client/galaxy/scripts/mvc/ui/ui-color-picker.js index 4d6f8f2553c..8621b6d58cb 100644 --- a/client/galaxy/scripts/mvc/ui/ui-color-picker.js +++ b/client/galaxy/scripts/mvc/ui/ui-color-picker.js @@ -117,7 +117,7 @@ export default Backbone.View.extend({ /** Content template */ _templateContent: function() { - return '
' + '
' + "
"; + return '
'; }, /** Box template */ diff --git a/client/galaxy/scripts/mvc/ui/ui-options.js b/client/galaxy/scripts/mvc/ui/ui-options.js index 7b7ab975cba..03fe661287a 100644 --- a/client/galaxy/scripts/mvc/ui/ui-options.js +++ b/client/galaxy/scripts/mvc/ui/ui-options.js @@ -12,6 +12,7 @@ var Base = Backbone.View.extend({ (options && options.model) || new Backbone.Model({ visible: true, + cls: null, data: [], id: Utils.uid(), error_text: "No options available.", @@ -36,6 +37,7 @@ var Base = Backbone.View.extend({ .empty() .removeClass() .addClass("ui-options") + .addClass(this.model.get("cls")) .append((this.$message = $("
").addClass("mt-2"))) .append((this.$menu = $("
").addClass("ui-options-menu"))) .append((this.$options = $(this._template()))); @@ -154,6 +156,16 @@ var Base = Backbone.View.extend({ return this.$(".ui-option").length; }, + /** Shows the options */ + show: function() { + this.model.set("visible", true); + }, + + /** Hides the options */ + hide: function() { + this.model.set("visible", false); + }, + /** Set value to dom */ _setValue: function(new_value) { var self = this; @@ -260,7 +272,7 @@ RadioButton.View = Base.extend({ /** Template for a single option */ _templateOption: function(pair) { - var $el = $("
+ return ` - ` - ); + `; } }); diff --git a/client/galaxy/scripts/mvc/upload/composite/composite-view.js b/client/galaxy/scripts/mvc/upload/composite/composite-view.js index 52020fd0390..50a75706056 100644 --- a/client/galaxy/scripts/mvc/upload/composite/composite-view.js +++ b/client/galaxy/scripts/mvc/upload/composite/composite-view.js @@ -176,8 +176,7 @@ export default Backbone.View.extend({ /** Load html template */ _template: function() { - return ( - `
+ return `
 
@@ -205,7 +204,6 @@ export default Backbone.View.extend({
-
` - ); +
`; } }); diff --git a/client/galaxy/scripts/mvc/upload/upload-button.js b/client/galaxy/scripts/mvc/upload/upload-button.js index dad7c1f091a..7efabca5dd1 100644 --- a/client/galaxy/scripts/mvc/upload/upload-button.js +++ b/client/galaxy/scripts/mvc/upload/upload-button.js @@ -9,7 +9,6 @@ var View = Backbone.View.extend({ this.model = (options && options.model) || new Backbone.Model({ - icon: "fa-upload", tooltip: _l("Download from URL or upload files from disk"), label: "Load Data", percentage: 0, @@ -46,16 +45,14 @@ var View = Backbone.View.extend({ /** Template */ _template: function() { - return ( - '
' + - '
' + - '
' + - '' + - '' + - "" + - "
" + - "
" - ); + return `
+
+
+ + + +
+
`; } }); export default { View: View }; diff --git a/client/galaxy/scripts/mvc/user/user-model.js b/client/galaxy/scripts/mvc/user/user-model.js index acb4f40ea89..8de2b2e1343 100644 --- a/client/galaxy/scripts/mvc/user/user-model.js +++ b/client/galaxy/scripts/mvc/user/user-model.js @@ -28,7 +28,8 @@ var User = Backbone.Model.extend(baseMVC.LoggableMixin).extend( total_disk_usage: 0, nice_total_disk_usage: "", quota_percent: null, - is_admin: false + is_admin: false, + preferences: {} }, /** Set up and bind events @@ -53,6 +54,29 @@ var User = Backbone.Model.extend(baseMVC.LoggableMixin).extend( return this.get("is_admin"); }, + updatePreferences: function(name, new_value) { + let preferences = this.get("preferences"); + preferences[name] = JSON.stringify(new_value); + this.preferences = preferences; + }, + + getFavorites: function() { + let preferences = this.get("preferences"); + if (preferences && preferences.favorites) { + return JSON.parse(preferences.favorites); + } else { + return { + tools: [] + }; + } + }, + + updateFavorites: function(object_type, new_favorites) { + let favorites = this.getFavorites(); + favorites[object_type] = new_favorites[object_type]; + this.updatePreferences("favorites", favorites); + }, + /** Load a user with the API using an id. * If getting an anonymous user or no access to a user id, pass the User.CURRENT_ID_STR * (e.g. 'current') and the API will return the current transaction's user data. diff --git a/client/galaxy/scripts/mvc/user/user-preferences.js b/client/galaxy/scripts/mvc/user/user-preferences.js index bdfe85d2a28..70f1f88d590 100644 --- a/client/galaxy/scripts/mvc/user/user-preferences.js +++ b/client/galaxy/scripts/mvc/user/user-preferences.js @@ -106,6 +106,14 @@ var Model = Backbone.Model.extend({ Galaxy.page.router.push(`${getAppRoot()}custom_builds`); } }, + genomespace: { + title: _l("Request GenomeSpace token"), + description: _l("Requests token through OpenID."), + icon: "fa-openid", + onclick: function() { + window.location.href = `${getAppRoot()}openid/openid_auth?openid_provider=genomespace`; + } + }, logout: { title: _l("Sign out"), description: _l("Click here to sign out of all sessions."), @@ -166,6 +174,9 @@ var View = Backbone.View.extend({ self._addLink("permissions"); self._addLink("make_data_private"); self._addLink("api_key"); + if (config.enable_openid) { + self._addLink("genomespace"); + } if (config.has_user_tool_filters) { self._addLink("toolbox_filters"); } diff --git a/client/galaxy/scripts/mvc/workflow/workflow-connector.js b/client/galaxy/scripts/mvc/workflow/workflow-connector.js index 7199391cc8d..f3cd1628d2f 100644 --- a/client/galaxy/scripts/mvc/workflow/workflow-connector.js +++ b/client/galaxy/scripts/mvc/workflow/workflow-connector.js @@ -1,6 +1,6 @@ import $ from "jquery"; -import * as Toastr from "libs/toastr"; +import { Toast } from "ui/toast"; function Connector(handle1, handle2) { this.canvas = null; @@ -8,10 +8,7 @@ function Connector(handle1, handle2) { this.inner_color = "#FFFFFF"; this.outer_color = "#25537b"; if (handle1 && handle2) { - this.connect( - handle1, - handle2 - ); + this.connect(handle1, handle2); } } $.extend(Connector.prototype, { @@ -35,9 +32,9 @@ $.extend(Connector.prototype, { $(this.canvas).remove(); }, destroyIfInvalid: function(warn) { - if (this.handle1 && this.handle2 && !this.handle2.attachable(this.handle1)) { + if (this.handle1 && this.handle2 && !this.handle2.attachable(this.handle1).canAccept) { if (warn) { - Toastr.warning("Destroying a connection because collection type has changed."); + Toast.warning("Destroying a connection because collection type has changed."); } this.destroy(); } diff --git a/client/galaxy/scripts/mvc/workflow/workflow-manager.js b/client/galaxy/scripts/mvc/workflow/workflow-manager.js index 60b4b494725..a9db9f71352 100644 --- a/client/galaxy/scripts/mvc/workflow/workflow-manager.js +++ b/client/galaxy/scripts/mvc/workflow/workflow-manager.js @@ -1,6 +1,6 @@ import $ from "jquery"; import Connector from "mvc/workflow/workflow-connector"; -import * as Toastr from "libs/toastr"; +import { Toast } from "ui/toast"; class Workflow { constructor(app, canvas_container) { @@ -37,7 +37,7 @@ class Workflow { this.unregisterOutputLabel(fromLabel); } if (!this.canLabelOutputWith(toLabel)) { - Toastr.warning( + Toast.warning( `Workflow contains duplicate workflow output labels ${toLabel}. This must be fixed before it can be saved.` ); } @@ -90,7 +90,7 @@ class Workflow { var using_workflow_outputs = false; var has_existing_pjas = false; $.each(this.nodes, (k, node) => { - if (node.workflow_outputs && node.workflow_outputs.length > 0) { + if (node.type === "tool" && node.workflow_outputs && node.workflow_outputs.length > 0) { using_workflow_outputs = true; } $.each(node.post_job_actions, (pja_id, pja) => { @@ -102,43 +102,41 @@ class Workflow { if (using_workflow_outputs !== false || has_existing_pjas !== false) { // Using workflow outputs, or has existing pjas. Remove all PJAs and recreate based on outputs. $.each(this.nodes, (k, node) => { - if (node.type === "tool") { - var node_changed = false; - if (node.post_job_actions === null) { - node.post_job_actions = {}; - node_changed = true; + var node_changed = false; + if (node.post_job_actions === null) { + node.post_job_actions = {}; + node_changed = true; + } + var pjas_to_rem = []; + $.each(node.post_job_actions, (pja_id, pja) => { + if (pja.action_type == "HideDatasetAction") { + pjas_to_rem.push(pja_id); } - var pjas_to_rem = []; - $.each(node.post_job_actions, (pja_id, pja) => { - if (pja.action_type == "HideDatasetAction") { - pjas_to_rem.push(pja_id); + }); + if (pjas_to_rem.length > 0) { + $.each(pjas_to_rem, (i, pja_name) => { + node_changed = true; + delete node.post_job_actions[pja_name]; + }); + } + if (using_workflow_outputs) { + $.each(node.output_terminals, (ot_id, ot) => { + var create_pja = !node.isWorkflowOutput(ot.name); + if (create_pja === true) { + node_changed = true; + var pja = { + action_type: "HideDatasetAction", + output_name: ot.name, + action_arguments: {} + }; + node.post_job_actions[`HideDatasetAction${ot.name}`] = null; + node.post_job_actions[`HideDatasetAction${ot.name}`] = pja; } }); - if (pjas_to_rem.length > 0) { - $.each(pjas_to_rem, (i, pja_name) => { - node_changed = true; - delete node.post_job_actions[pja_name]; - }); - } - if (using_workflow_outputs) { - $.each(node.output_terminals, (ot_id, ot) => { - var create_pja = !node.isWorkflowOutput(ot.name); - if (create_pja === true) { - node_changed = true; - var pja = { - action_type: "HideDatasetAction", - output_name: ot.name, - action_arguments: {} - }; - node.post_job_actions[`HideDatasetAction${ot.name}`] = null; - node.post_job_actions[`HideDatasetAction${ot.name}`] = pja; - } - }); - } - // lastly, if this is the active node, and we made changes, reload the display at right. - if (this.active_node == node && node_changed === true) { - this.reload_active_node(); - } + } + // lastly, if this is the active node, and we made changes, reload the display at right. + if (this.active_node == node && node_changed === true) { + this.reload_active_node(); } }); } @@ -187,7 +185,7 @@ class Workflow { id: node.id, type: node.type, content_id: node.content_id, - tool_version: node.config_form.version, + tool_version: node.config_form ? node.config_form.version : null, tool_state: node.tool_state, errors: node.errors, input_connections: input_connections, @@ -263,10 +261,7 @@ class Workflow { $.each(v, (l, x) => { var other_node = wf.nodes[parseInt(x.id) + offset]; var c = new Connector(); - c.connect( - other_node.output_terminals[x.output_name], - node.input_terminals[k] - ); + c.connect(other_node.output_terminals[x.output_name], node.input_terminals[k]); c.redraw(); }); } diff --git a/client/galaxy/scripts/mvc/workflow/workflow-terminals.js b/client/galaxy/scripts/mvc/workflow/workflow-terminals.js index 577cfa93e15..550149d40ba 100644 --- a/client/galaxy/scripts/mvc/workflow/workflow-terminals.js +++ b/client/galaxy/scripts/mvc/workflow/workflow-terminals.js @@ -11,6 +11,11 @@ function CollectionTypeDescription(collectionType) { this.rank = collectionType.split(":").length; } +function ConnectionAcceptable(canAccept, reason) { + this.canAccept = canAccept; + this.reason = reason; +} + var NULL_COLLECTION_TYPE_DESCRIPTION = { isCollection: false, canMatch: function() { @@ -157,14 +162,21 @@ var Terminal = Backbone.Model.extend({ }); }, setMapOver: function(val) { + let output_val = val; if (this.multiple) { - return; // Cannot set this to be multirun... + // emulate list input + let description = new CollectionTypeDescription("list"); + if (val.collectionType === description.collectionType) { + // No mapping over necessary + return; + } + output_val = val.effectiveMapOver ? val.effectiveMapOver(description) : val; } if (!this.mapOver().equal(val)) { this.terminalMapping.setMapOver(val); _.each(this.node.output_terminals, outputTerminal => { - outputTerminal.setMapOver(val); + outputTerminal.setMapOver(output_val); }); } }, @@ -239,7 +251,10 @@ var BaseInputTerminal = Terminal.extend({ }, canAccept: function(other) { if (this._inputFilled()) { - return false; + return new ConnectionAcceptable( + false, + "Input already filled with another connection, delete it before connecting another output." + ); } else { return this.attachable(other); } @@ -336,7 +351,7 @@ var BaseInputTerminal = Terminal.extend({ for (var t in this.datatypes) { var thisDatatype = this.datatypes[t]; if (thisDatatype == "input") { - return true; + return new ConnectionAcceptable(true, null); } var cat_outputs = []; cat_outputs = cat_outputs.concat(other.datatypes); @@ -361,11 +376,14 @@ var BaseInputTerminal = Terminal.extend({ other_datatype == "input_collection" || window.workflow_globals.app.isSubType(cat_outputs[other_datatype_i], thisDatatype) ) { - return true; + return new ConnectionAcceptable(true, null); } } } - return false; + return new ConnectionAcceptable( + false, + `Effective output data type(s) [${cat_outputs}] do not appear to match input type(s) [${this.datatypes}].` + ); }, _otherCollectionType: function(other) { var otherCollectionType = NULL_COLLECTION_TYPE_DESCRIPTION; @@ -403,16 +421,16 @@ var InputTerminal = BaseInputTerminal.extend({ if (otherCollectionType.isCollection) { if (this.multiple) { if (this.connected() && !this._collectionAttached()) { - // if single inputs attached, cannot also attach a - // collection (yet...) - return false; + return new ConnectionAcceptable( + false, + "Cannot attach collections to data parameters with individual data inputs already attached." + ); } - if (otherCollectionType.rank == 1) { - return this._producesAcceptableDatatype(other); - } else { - // TODO: Allow subcollection mapping over this as if it were - // a list collection input. - return false; + if (otherCollectionType.collectionType.endsWith("paired")) { + return new ConnectionAcceptable( + false, + "Cannot attach paired inputs to multiple data parameters, only lists may be treated this way." + ); } } if (thisMapOver.isCollection && thisMapOver.canMatch(otherCollectionType)) { @@ -423,13 +441,32 @@ var InputTerminal = BaseInputTerminal.extend({ if (mappingConstraints.every(_.bind(otherCollectionType.canMatch, otherCollectionType))) { return this._producesAcceptableDatatype(other); } else { - return false; + if (thisMapOver.isCollection) { + // incompatible collection type attached + if (this.node.hasConnectedMappedInputTerminals()) { + return new ConnectionAcceptable( + false, + "Can't map over this input with output collection type - other inputs have an incompatible map over collection type. Disconnect inputs (and potentially outputs) and retry." + ); + } else { + return new ConnectionAcceptable( + false, + "Can't map over this input with output collection type - this step has outputs defined constraining the mapping of this tool. Disconnect outputs and retry." + ); + } + } else { + return new ConnectionAcceptable( + false, + "Can't map over this input with output collection type - an output of this tool is not mapped over constraining this input. Disconnect output(s) and retry." + ); + } } } } else if (thisMapOver.isCollection) { - // Attempting to match a non-collection output to an - // explicitly collection input. - return false; + return new ConnectionAcceptable( + false, + "Cannot attach non-collection outputs to mapped over inputs, consider disconnecting inputs and outputs to reset this input's mapping." + ); } return this._producesAcceptableDatatype(other); } @@ -443,7 +480,7 @@ var InputParameterTerminal = BaseInputTerminal.extend({ BaseInputTerminal.prototype.connect.call(this, connector); }, attachable: function(other) { - return this.type == other.attributes.type; + return new ConnectionAcceptable(this.type == other.attributes.type, ""); } }); @@ -451,6 +488,7 @@ var InputCollectionTerminal = BaseInputTerminal.extend({ update: function(input) { this.multiple = false; this.collection = true; + this.collection_type = input.collection_type; this.datatypes = input.extensions; var collectionTypes = []; if (input.collection_types) { @@ -526,20 +564,46 @@ var InputCollectionTerminal = BaseInputTerminal.extend({ // Otherwise we need to mapOver } else if (thisMapOver.isCollection) { // In this case, mapOver already set and we didn't match skipping... - return false; + if (this.node.hasConnectedMappedInputTerminals()) { + return new ConnectionAcceptable( + false, + "Can't map over this input with output collection type - other inputs have an incompatible map over collection type. Disconnect inputs (and potentially outputs) and retry." + ); + } else { + return new ConnectionAcceptable( + false, + "Can't map over this input with output collection type - this step has outputs defined constraining the mapping of this tool. Disconnect outputs and retry." + ); + } } else if (_.some(this.collectionTypes, collectionType => otherCollectionType.canMapOver(collectionType))) { + // we're not mapped over - but hey maybe we could be... lets check. var effectiveMapOver = this._effectiveMapOver(other); if (!effectiveMapOver.isCollection) { - return false; + return new ConnectionAcceptable(false, "Incompatible collection type(s) for attachment."); } // Need to check if this would break constraints... var mappingConstraints = this._mappingConstraints(); - if (mappingConstraints.every(effectiveMapOver.canMatch)) { + if (mappingConstraints.every(d => effectiveMapOver.canMatch(d))) { return this._producesAcceptableDatatype(other); + } else { + if (this.node.hasConnectedMappedInputTerminals()) { + return new ConnectionAcceptable( + false, + "Can't map over this input with output collection type - other inputs have an incompatible map over collection type. Disconnect inputs (and potentially outputs) and retry." + ); + } else { + return new ConnectionAcceptable( + false, + "Can't map over this input with output collection type - this step has outputs defined constraining the mapping of this tool. Disconnect outputs and retry." + ); + } } + } else { + return new ConnectionAcceptable(false, "Incompatible collection type(s) for attachment."); } + } else { + return new ConnectionAcceptable(false, "Cannot attach a data output to a collection input."); } - return false; } }); diff --git a/client/galaxy/scripts/mvc/workflow/workflow-view-terminals.js b/client/galaxy/scripts/mvc/workflow/workflow-view-terminals.js index a43e1be25b5..460f1f3198f 100644 --- a/client/galaxy/scripts/mvc/workflow/workflow-view-terminals.js +++ b/client/galaxy/scripts/mvc/workflow/workflow-view-terminals.js @@ -77,9 +77,7 @@ var BaseInputTerminalView = TerminalView.extend({ const name = input.name; const id = `node-${node.cid}-input-${name}`; const terminal = this.terminalForInput(input); - if (!terminal.multiple) { - this.setupMappingView(terminal); - } + this.setupMappingView(terminal); this.el.terminal = terminal; this.$el.attr("input-name", name); this.$el.attr("id", id); @@ -102,21 +100,47 @@ var BaseInputTerminalView = TerminalView.extend({ var terminal = this.el.terminal; // Accept a dragable if it is an output terminal and has a // compatible type - return $(d.drag).hasClass("output-terminal") && terminal.canAccept(d.drag.terminal); + var connectionAcceptable = $(d.drag).hasClass("output-terminal") && terminal.canAccept(d.drag.terminal); + if (connectionAcceptable.canAccept) { + this.$el.addClass("can-accept"); + this.$el.removeClass("cannot-accept"); + this.reason = null; + } else { + this.$el.addClass("cannot-accept"); + this.$el.removeClass("can-accept"); + this.reason = connectionAcceptable.reason; + } + + return true; }, onDropStart: function(e, d) { if (d.proxy.terminal) { - d.proxy.terminal.connectors[0].inner_color = "#BBFFBB"; + if (this.$el.hasClass("can-accept")) { + d.proxy.terminal.connectors[0].inner_color = "#BBFFBB"; + d.proxy.dropTooltip = ""; + } else { + d.proxy.terminal.connectors[0].inner_color = "#fe7f02"; + if (this.reason) { + d.proxy.dropTooltip = this.reason; + $(d.proxy).tooltip("show"); + } else { + d.proxy.dropTooltip = ""; + } + } } }, onDropEnd: function(e, d) { + d.proxy.dropTooltip = ""; if (d.proxy.terminal) { d.proxy.terminal.connectors[0].inner_color = "#FFFFFF"; } }, onDrop: function(e, d) { - var terminal = this.el.terminal; - new Connector(d.drag.terminal, terminal).redraw(); + d.proxy.dropTooltip = ""; + if (this.$el.hasClass("can-accept")) { + const terminal = this.el.terminal; + new Connector(d.drag.terminal, terminal).redraw(); + } }, onHover: function() { let element = this.el; @@ -227,14 +251,19 @@ var BaseOutputTerminalView = TerminalView.extend({ var h = $("
") .appendTo("#canvas-container") .get(0); + + h.dropTooltip = ""; + // Terminal and connection to display noodle while dragging + $(h).tooltip({ + title: function() { + return h.dropTooltip || ""; + } + }); h.terminal = new Terminals.OutputTerminal({ element: h }); var c = new Connector(); c.dragging = true; - c.connect( - this.el.terminal, - h.terminal - ); + c.connect(this.el.terminal, h.terminal); return h; }, onDragEnd: function(e, d) { @@ -246,6 +275,7 @@ var BaseOutputTerminalView = TerminalView.extend({ if (connector) { connector.destroy(); } + $(d.proxy).tooltip("dispose"); $(d.proxy).remove(); $(d.available).removeClass("input-terminal-active"); $("#canvas-container") diff --git a/client/galaxy/scripts/mvc/workflow/workflow-view.js b/client/galaxy/scripts/mvc/workflow/workflow-view.js index 49669842af7..7c166b83eae 100644 --- a/client/galaxy/scripts/mvc/workflow/workflow-view.js +++ b/client/galaxy/scripts/mvc/workflow/workflow-view.js @@ -641,8 +641,19 @@ export default Backbone.View.extend({ url: `${getAppRoot()}api/workflows/build_module`, data: request_data, success: function(data) { + const Galaxy = getGalaxyInstance(); node.init_field_data(data); node.update_field_data(data); + // Post init/update, for new modules we want to default to + // nodes being outputs + // TODO: Overhaul the handling of all this when we modernize + // the editor, replace callout image manipulation with a simple + // class toggle, etc. + $.each(node.output_terminals, (ot_id, ot) => { + node.addWorkflowOutput(ot.name); + var callout = $(node.element).find(`.callout.${ot.name.replace(/(?=[()])/g, "\\")}`); + callout.find("img").attr("src", `${Galaxy.root}static/images/fugue/asterisk-small.png`); + }); self.workflow.activate_node(node); } }); diff --git a/client/galaxy/scripts/mvc/workflow/workflow.js b/client/galaxy/scripts/mvc/workflow/workflow.js index dcc9f6e3dd6..720d9bea4dc 100644 --- a/client/galaxy/scripts/mvc/workflow/workflow.js +++ b/client/galaxy/scripts/mvc/workflow/workflow.js @@ -4,12 +4,12 @@ import $ from "jquery"; import Backbone from "backbone"; import { getAppRoot } from "onload/loadConfig"; import { getGalaxyInstance } from "app"; -import * as mod_toastr from "libs/toastr"; -import TAGS from "mvc/tag"; +import { Toast } from "ui/toast"; import WORKFLOWS from "mvc/workflow/workflow-model"; import QueryStringParsing from "utils/query-string-parsing"; import _l from "utils/localization"; import LoadingIndicator from "ui/loading-indicator"; +import { mountModelTags } from "components/Tags"; /** View of the individual workflows */ const WorkflowItemView = Backbone.View.extend({ @@ -24,7 +24,7 @@ const WorkflowItemView = Backbone.View.extend({ "removeWorkflow", "copyWorkflow" ); // every function that uses 'this' as the current object should be in here - mod_toastr.options.timeOut = 1500; + Toast.options.timeOut = 1500; }, events: { @@ -57,7 +57,7 @@ const WorkflowItemView = Backbone.View.extend({ if (window.confirm(`Are you sure you want to delete workflow '${wfName}'?`)) { this.model.destroy({ success: function() { - mod_toastr.success(`Successfully deleted workflow '${wfName}'`); + Toast.success(`Successfully deleted workflow '${wfName}'`); } }); this.remove(); @@ -72,7 +72,7 @@ const WorkflowItemView = Backbone.View.extend({ { name: newName }, { success: function() { - mod_toastr.success(`Successfully renamed workflow '${oldName}' to '${newName}'`); + Toast.success(`Successfully renamed workflow '${oldName}' to '${newName}'`); } } ); @@ -94,16 +94,16 @@ const WorkflowItemView = Backbone.View.extend({ at: 0, wait: true, success: function() { - mod_toastr.success(`Successfully copied workflow '${oldName}' to '${newName}'`); + Toast.success(`Successfully copied workflow '${oldName}' to '${newName}'`); }, error: function(model, resp, options) { // signature seems to have changed over the course of backbone dev // see https://github.com/jashkenas/backbone/issues/2606#issuecomment-19289483 - mod_toastr.error(options.errorThrown); + Toast.error(options.errorThrown); } }); }).error((jqXHR, textStatus, errorThrown) => { - mod_toastr.error(jqXHR.responseJSON.err_msg); + Toast.error(jqXHR.responseJSON.err_msg); }); }, @@ -127,8 +127,7 @@ const WorkflowItemView = Backbone.View.extend({
", "", "\n"+e+"\n"},i.prototype.tablecell=function(e,t){var r=t.header?"th":"td",a=t.align?"<"+r+' style="text-align:'+t.align+'">':"<"+r+">";return a+e+"\n"},i.prototype.strong=function(e){return""+e+""},i.prototype.em=function(e){return""+e+""},i.prototype.codespan=function(e){return""+e+""},i.prototype.br=function(){return"
"},i.prototype.del=function(e){return""+e+""},i.prototype.link=function(e,t,r){if(this.options.sanitize){try{var a=decodeURIComponent(l(e)).replace(/[^\w:]/g,"").toLowerCase()}catch(i){return""}if(0===a.indexOf("javascript:"))return""}var n='"},i.prototype.image=function(e,t,r){var a=''+r+' 1: + raise Exception("User is associated more than once with provider {}".format(self.config["provider"])) + trans.sa_session.delete(provider_tokens[0]) + trans.sa_session.flush() + return True, "", disconnect_redirect_url + except Exception as e: + return False, "Failed to disconnect provider {}: {}".format(provider, str(e)), None + + def _create_oauth2_session(self, state=None, scope=None): + client_id = self.config['client_id'] + redirect_uri = self.config['redirect_uri'] + if (redirect_uri.startswith('http://localhost') + and os.environ.get("OAUTHLIB_INSECURE_TRANSPORT", None) != "1"): + log.warn("Setting OAUTHLIB_INSECURE_TRANSPORT to '1' to " + "allow plain HTTP (non-SSL) callback") + os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = "1" + session = OAuth2Session(client_id, + scope=scope, + redirect_uri=redirect_uri, + state=state) + session.verify = self._get_verify_param() + return session + + def _fetch_token(self, oauth2_session, trans): + client_secret = self.config['client_secret'] + token_endpoint = self.config['token_endpoint'] + return oauth2_session.fetch_token( + token_endpoint, + client_secret=client_secret, + authorization_response=trans.request.url, + verify=self._get_verify_param()) + + def _get_userinfo(self, oauth2_session): + userinfo_endpoint = self.config['userinfo_endpoint'] + return oauth2_session.get(userinfo_endpoint, + verify=self._get_verify_param()).json() + + def _get_custos_authnz_token(self, sa_session, user_id, provider): + return sa_session.query(CustosAuthnzToken).filter_by( + external_user_id=user_id, provider=provider).one_or_none() + + def _get_current_user(self, trans): + return trans.user if trans.user else None + + def _create_user(self, sa_session, username, email): + user = User(email=email, username=username) + user.set_random_password() + sa_session.add(user) + sa_session.flush() + return user + + def _hash_nonce(self, nonce): + return hashlib.sha256(util.smart_str(nonce)).hexdigest() + + def _validate_nonce(self, trans, nonce_hash): + nonce_cookie = trans.get_cookie(name=NONCE_COOKIE_NAME) + # Delete the nonce cookie + trans.set_cookie('', name=NONCE_COOKIE_NAME, age=-1) + nonce_cookie_hash = self._hash_nonce(nonce_cookie) + if nonce_hash != nonce_cookie_hash: + raise Exception("Nonce mismatch!") + + def _load_config_for_provider_and_realm(self, provider, realm): + self.config['well_known_oidc_config_uri'] = self._get_well_known_uri_for_provider_and_realm(provider, realm) + well_known_oidc_config = self._load_well_known_oidc_config(self.config['well_known_oidc_config_uri']) + self.config['authorization_endpoint'] = well_known_oidc_config['authorization_endpoint'] + self.config['token_endpoint'] = well_known_oidc_config['token_endpoint'] + self.config['userinfo_endpoint'] = well_known_oidc_config['userinfo_endpoint'] + + def _get_well_known_uri_for_provider_and_realm(self, provider, realm): + # TODO: Look up this URL from a Python library + if provider == 'custos': + return "{}/realms/{}/.well-known/openid-configuration".format(self.config["url"], realm) + else: + raise Exception("Unknown Custos provider name: {}".format(provider)) + + def _load_well_known_oidc_config(self, well_known_uri): + try: + return requests.get(well_known_uri, + verify=self._get_verify_param()).json() + except Exception: + log.error("Failed to load well-known OIDC config URI: {}".format(well_known_uri)) + raise + + def _get_verify_param(self): + """Return 'ca_bundle' if 'verify_ssl' is true and 'ca_bundle' is configured.""" + # in requests_oauthlib, the verify param can either be a boolean or a CA bundle path + if self.config['ca_bundle'] is not None and self.config['verify_ssl']: + return self.config['ca_bundle'] + else: + return self.config['verify_ssl'] diff --git a/lib/galaxy/authnz/managers.py b/lib/galaxy/authnz/managers.py index d31b5c52b23..7a8bb69cabd 100644 --- a/lib/galaxy/authnz/managers.py +++ b/lib/galaxy/authnz/managers.py @@ -17,6 +17,7 @@ from cloudauthz.exceptions import ( from galaxy import exceptions from galaxy import model +from .custos_authnz import CustosAuthnz from .psa_authnz import ( BACKENDS_NAME, on_the_fly_config, @@ -39,6 +40,7 @@ class AuthnzManager(object): :param config: sets the path for OIDC configuration file (e.g., oidc_backends_config.xml). """ + self.app = app self._parse_oidc_config(oidc_config_file) self._parse_oidc_backends_config(oidc_backends_config_file) @@ -73,6 +75,7 @@ class AuthnzManager(object): def _parse_oidc_backends_config(self, config_file): self.oidc_backends_config = {} + self.oidc_backends_implementation = {} try: tree = ET.parse(config_file) root = tree.getroot() @@ -88,8 +91,14 @@ class AuthnzManager(object): log.error("Could not find a node attribute 'name'; skipping the node '{}'.".format(child.tag)) continue idp = child.get('name').lower() - if idp == 'google': - self.oidc_backends_config[idp] = self._parse_google_config(child) + if idp in BACKENDS_NAME: + self.oidc_backends_config[idp] = self._parse_idp_config(child) + self.oidc_backends_implementation[idp] = 'psa' + self.app.config.oidc.append(idp) + elif idp == 'custos': + self.oidc_backends_config[idp] = self._parse_custos_config(child) + self.oidc_backends_implementation[idp] = 'custos' + self.app.config.oidc.append(idp) if len(self.oidc_backends_config) == 0: raise ParseError("No valid provider configuration parsed.") except ImportError: @@ -97,7 +106,7 @@ class AuthnzManager(object): except ParseError as e: raise ParseError("Invalid configuration at `{}`: {} -- unable to continue.".format(config_file, e)) - def _parse_google_config(self, config_xml): + def _parse_idp_config(self, config_xml): rtv = { 'client_id': config_xml.find('client_id').text, 'client_secret': config_xml.find('client_secret').text, @@ -106,26 +115,53 @@ class AuthnzManager(object): rtv['prompt'] = config_xml.find('prompt').text return rtv + def _parse_custos_config(self, config_xml): + rtv = { + 'url': config_xml.find('url').text, + 'client_id': config_xml.find('client_id').text, + 'client_secret': config_xml.find('client_secret').text, + 'redirect_uri': config_xml.find('redirect_uri').text, + 'realm': config_xml.find('realm').text} + if config_xml.find('well_known_oidc_config_uri') is not None: + rtv['well_known_oidc_config_uri'] = config_xml.find('well_known_oidc_config_uri').text + if config_xml.find('idphint') is not None: + rtv['idphint'] = config_xml.find('idphint').text + if config_xml.find('ca_bundle') is not None: + rtv['ca_bundle'] = config_xml.find('ca_bundle').text + return rtv + def _unify_provider_name(self, provider): if provider.lower() in self.oidc_backends_config: return provider.lower() for k, v in BACKENDS_NAME.iteritems(): if v == provider: return k.lower() + return None def _get_authnz_backend(self, provider): - provider = self._unify_provider_name(provider) - if provider in self.oidc_backends_config: + unified_provider_name = self._unify_provider_name(provider) + if unified_provider_name in self.oidc_backends_config: + provider = unified_provider_name + identity_provider_class = self._get_identity_provider_class(self.oidc_backends_implementation[provider]) try: - return True, "", PSAAuthnz(provider, self.oidc_config, self.oidc_backends_config[provider]) + return True, "", identity_provider_class(unified_provider_name, self.oidc_config, self.oidc_backends_config[unified_provider_name]) except Exception as e: - log.exception('An error occurred when loading PSAAuthnz') + log.exception('An error occurred when loading {}'.format(identity_provider_class.__name__)) return False, str(e), None else: - msg = 'The requested identity provider, `{}`, is not a recognized/expected provider'.format(provider) + msg = 'The requested identity provider, `{}`, is not a recognized/expected provider.'.format(provider) log.debug(msg) return False, msg, None + @staticmethod + def _get_identity_provider_class(implementation): + if implementation == 'psa': + return PSAAuthnz + elif implementation == 'custos': + return CustosAuthnz + else: + return None + def _extend_cloudauthz_config(self, cloudauthz, request, sa_session, user_id): config = copy.deepcopy(cloudauthz.config) if cloudauthz.provider == "aws": diff --git a/lib/galaxy/authnz/psa_authnz.py b/lib/galaxy/authnz/psa_authnz.py index c4448ec3b27..881313e9f22 100644 --- a/lib/galaxy/authnz/psa_authnz.py +++ b/lib/galaxy/authnz/psa_authnz.py @@ -18,11 +18,13 @@ DEFAULTS = { } BACKENDS = { - 'google': 'social_core.backends.google_openidconnect.GoogleOpenIdConnect' + 'google': 'social_core.backends.google_openidconnect.GoogleOpenIdConnect', + "globus": "social_core.backends.globus.GlobusOpenIdConnect" } BACKENDS_NAME = { - 'google': 'google-openidconnect' + 'google': 'google-openidconnect', + "globus": "globus" } AUTH_PIPELINE = ( @@ -96,13 +98,13 @@ class PSAAuthnz(IdentityProvider): # the just logged-in user. self.config[setting_name('INACTIVE_USER_LOGIN')] = True - if provider == 'google': - self._setup_google_backend(oidc_backend_config) + if provider in BACKENDS_NAME: + self._setup_idp(oidc_backend_config) - def _setup_google_backend(self, oidc_backend_config): + def _setup_idp(self, oidc_backend_config): self.config[setting_name('AUTH_EXTRA_ARGUMENTS')] = {'access_type': 'offline'} - self.config['SOCIAL_AUTH_GOOGLE_OPENIDCONNECT_KEY'] = oidc_backend_config.get('client_id') - self.config['SOCIAL_AUTH_GOOGLE_OPENIDCONNECT_SECRET'] = oidc_backend_config.get('client_secret') + self.config['KEY'] = oidc_backend_config.get('client_id') + self.config['SECRET'] = oidc_backend_config.get('client_secret') self.config['redirect_uri'] = oidc_backend_config.get('redirect_uri') if oidc_backend_config.get('prompt') is not None: self.config[setting_name('AUTH_EXTRA_ARGUMENTS')]['prompt'] = oidc_backend_config.get('prompt') diff --git a/lib/galaxy/config.py b/lib/galaxy/config.py index 3eed86893dc..20abecb9850 100644 --- a/lib/galaxy/config.py +++ b/lib/galaxy/config.py @@ -19,6 +19,7 @@ import threading import time from datetime import timedelta +import yaml from six import string_types from six.moves import configparser @@ -47,6 +48,8 @@ PATH_DEFAULTS = dict( tool_destinations_config_file=['config/tool_destinations.yml', 'config/tool_destinations.yml.sample'], job_metrics_config_file=['config/job_metrics_conf.xml', 'job_metrics_conf.xml', 'config/job_metrics_conf.xml.sample'], error_report_file=['config/error_report.yml', 'config/error_report.yml.sample'], + oidc_config_file=['config/oidc_config.yml', 'config/oidc_config.yml.sample'], + oidc_backends_config_file=['config/oidc_backends_config.yml', 'config/oidc_backends_config.yml.sample'], dependency_resolvers_config_file=['config/dependency_resolvers_conf.xml', 'dependency_resolvers_conf.xml'], job_resource_params_file=['config/job_resource_params_conf.xml', 'job_resource_params_conf.xml'], workflow_resource_params_file=['config/workflow_resource_params_conf.xml', 'workflow_resource_params_conf.xml'], @@ -58,7 +61,6 @@ PATH_DEFAULTS = dict( workflow_schedulers_config_file=['config/workflow_schedulers_conf.xml', 'config/workflow_schedulers_conf.xml.sample'], modules_mapping_files=['config/environment_modules_mapping.yml', 'config/environment_modules_mapping.yml.sample'], local_conda_mapping_file=['config/local_conda_mapping.yml', 'config/local_conda_mapping.yml.sample'], - user_preferences_extra_config_file=['config/user_preferences_extra_conf.yml'], containers_config_file=['config/containers_conf.yml'], ) @@ -171,6 +173,7 @@ class Configuration(object): self.version_major = VERSION_MAJOR # Database related configuration + self.check_migrate_databases = kwargs.get('check_migrate_databases', True) self.database = resolve_path(kwargs.get("database_file", "database/universe.sqlite"), self.root) self.database_connection = kwargs.get("database_connection", False) self.database_engine_options = get_database_engine_options(kwargs) @@ -199,6 +202,7 @@ class Configuration(object): if override_tempdir: tempfile.tempdir = self.new_file_path self.shared_home_dir = kwargs.get("shared_home_dir", None) + self.openid_consumer_cache_path = resolve_path(kwargs.get("openid_consumer_cache_path", "database/openid_consumer_cache"), self.root) self.cookie_path = kwargs.get("cookie_path", "/") self.enable_quotas = string_as_bool(kwargs.get('enable_quotas', False)) self.enable_unique_workflow_defaults = string_as_bool(kwargs.get('enable_unique_workflow_defaults', False)) @@ -208,8 +212,9 @@ class Configuration(object): self.len_file_path = resolve_path(kwargs.get("len_file_path", os.path.join(self.tool_data_path, 'shared', 'ucsc', 'chrom')), self.root) # Galaxy OIDC settings. self.enable_oidc = kwargs.get("enable_oidc", False) - self.oidc_config = kwargs.get('oidc_config_file', None) - self.oidc_backends_config = kwargs.get("oidc_backends_config_file", None) + self.oidc_config = kwargs.get("oidc_config_file", self.oidc_config_file) + self.oidc_backends_config = kwargs.get("oidc_backends_config_file", self.oidc_backends_config_file) + self.oidc = [] # The value of migrated_tools_config is the file reserved for containing only those tools that have been eliminated from the distribution # and moved to the tool shed. self.integrated_tool_panel_config = resolve_path(kwargs.get('integrated_tool_panel_config', 'integrated_tool_panel.xml'), self.root) @@ -238,7 +243,7 @@ class Configuration(object): # Check for tools defined in the above non-shed tool configs (i.e., tool_conf.xml) tht have # been migrated from the Galaxy code distribution to the Tool Shed. - self.check_migrate_tools = string_as_bool(kwargs.get('check_migrate_tools', True)) + self.check_migrate_tools = string_as_bool(kwargs.get('check_migrate_tools', False)) self.shed_tool_data_path = kwargs.get("shed_tool_data_path", None) self.x_frame_options = kwargs.get("x_frame_options", "SAMEORIGIN") if self.shed_tool_data_path: @@ -274,6 +279,7 @@ class Configuration(object): self.id_secret = kwargs.get("id_secret", "USING THE DEFAULT IS NOT SECURE!") self.retry_metadata_internally = string_as_bool(kwargs.get("retry_metadata_internally", "True")) self.max_metadata_value_size = int(kwargs.get("max_metadata_value_size", 5242880)) + self.metadata_strategy = kwargs.get("metadata_strategy", "directory") self.single_user = kwargs.get("single_user", None) self.use_remote_user = string_as_bool(kwargs.get("use_remote_user", "False")) or self.single_user self.normalize_remote_user_email = string_as_bool(kwargs.get("normalize_remote_user_email", "False")) @@ -310,6 +316,7 @@ class Configuration(object): log.warning("preserve_python_environment set to unknown value [%s], defaulting to legacy_only") preserve_python_environment = "legacy_only" self.preserve_python_environment = preserve_python_environment + self.nodejs_path = kwargs.get("nodejs_path", None) # Older default container cache path, I don't think anyone is using it anymore and it wasn't documented - we # should probably drop the backward compatiblity to save the path check. self.container_image_cache_path = self.resolve_path(kwargs.get("container_image_cache_path", "database/container_images")) @@ -358,12 +365,11 @@ class Configuration(object): self.communication_server_host = kwargs.get('communication_server_host', 'http://localhost') self.communication_server_port = int(kwargs.get('communication_server_port', '7070')) self.persistent_communication_rooms = listify(kwargs.get("persistent_communication_rooms", []), do_strip=True) + self.enable_openid = string_as_bool(kwargs.get('enable_openid', 'False')) self.enable_quotas = string_as_bool(kwargs.get('enable_quotas', 'False')) # Tasked job runner. self.use_tasked_jobs = string_as_bool(kwargs.get('use_tasked_jobs', False)) self.local_task_queue_workers = int(kwargs.get("local_task_queue_workers", 2)) - self.tool_submission_burst_threads = int(kwargs.get('tool_submission_burst_threads', '1')) - self.tool_submission_burst_at = int(kwargs.get('tool_submission_burst_at', '10')) # Enable new interface for API installations from TS. # Admin menu will list both if enabled. @@ -378,8 +384,8 @@ class Configuration(object): # workflows built using these modules may not function in the # future. self.enable_beta_workflow_modules = string_as_bool(kwargs.get('enable_beta_workflow_modules', 'False')) - # Enable use of gxformat2 workflows. - self.enable_beta_workflow_format = string_as_bool(kwargs.get('enable_beta_workflow_format', 'False')) + # Enable default export of gxformat2 workflows. + self.enable_beta_export_format2_default = string_as_bool(kwargs.get('enable_beta_export_format2_default', 'False')) # These are not even beta - just experiments - don't use them unless # you want yours tools to be broken in the future. self.enable_beta_tool_formats = string_as_bool(kwargs.get('enable_beta_tool_formats', 'False')) @@ -633,12 +639,15 @@ class Configuration(object): self.amqp_internal_connection = "sqlalchemy+" + self.database_connection else: self.amqp_internal_connection = "sqlalchemy+sqlite:///%s?isolation_level=IMMEDIATE" % resolve_path("database/control.sqlite", self.root) - self.biostar_url = kwargs.get('biostar_url', None) - self.biostar_key_name = kwargs.get('biostar_key_name', None) - self.biostar_key = kwargs.get('biostar_key', None) - self.biostar_enable_bug_reports = string_as_bool(kwargs.get('biostar_enable_bug_reports', True)) - self.biostar_never_authenticate = string_as_bool(kwargs.get('biostar_never_authenticate', False)) self.pretty_datetime_format = expand_pretty_datetime_format(kwargs.get('pretty_datetime_format', '$locale (UTC)')) + self.user_preferences_extra_config_file = kwargs.get('user_preferences_extra_conf_path', 'config/user_preferences_extra_conf.yml') + try: + with open(self.user_preferences_extra_config_file, 'r') as stream: + self.user_preferences_extra = yaml.safe_load(stream) + except Exception: + log.warning('Config file (%s) could not be found or is malformed.' % self.user_preferences_extra_config_file) + self.user_preferences_extra = {'preferences': {}} + self.default_locale = kwargs.get('default_locale', None) self.master_api_key = kwargs.get('master_api_key', None) if self.master_api_key == "changethis": # default in sample config file @@ -1043,6 +1052,8 @@ class ConfiguresGalaxyMixin(object): self.citations_manager = CitationsManager(self) + from galaxy.managers.tools import DynamicToolManager + self.dynamic_tools_manager = DynamicToolManager(self) self._toolbox_lock = threading.RLock() # Initialize the tools, making sure the list of tool configs includes the reserved migrated_tools_conf.xml file. tool_configs = self.config.tool_configs @@ -1120,8 +1131,8 @@ class ConfiguresGalaxyMixin(object): self.object_store = build_object_store_from_config(self.config, **kwds) def _configure_security(self): - from galaxy.web import security - self.security = security.SecurityHelper(id_secret=self.config.id_secret) + from galaxy.security import idencoding + self.security = idencoding.IdEncodingHelper(id_secret=self.config.id_secret) def _configure_tool_shed_registry(self): import tool_shed.tool_shed_registry diff --git a/lib/galaxy/datatypes/anvio.py b/lib/galaxy/datatypes/anvio.py new file mode 100644 index 00000000000..7cdc42ff250 --- /dev/null +++ b/lib/galaxy/datatypes/anvio.py @@ -0,0 +1,178 @@ +""" +Datatypes for Anvi'o +https://github.com/merenlab/anvio +""" +import glob +import logging +import os +import sys + +from galaxy.datatypes.metadata import MetadataElement +from galaxy.datatypes.text import Html + +log = logging.getLogger(__name__) + + +class AnvioComposite(Html): + """ + Base class to use for Anvi'o composite datatypes. + Generally consist of a sqlite database, plus optional additional files + """ + file_ext = "anvio_composite" + composite_type = 'auto_primary_file' + + def generate_primary_file(self, dataset=None): + """ + This is called only at upload to write the html file + cannot rename the datasets here - they come with the default unfortunately + """ + defined_files = self.get_composite_files(dataset=dataset).items() + rval = ["Files for Anvi'o Composite Dataset (%s)" % (self.file_ext)] + if defined_files: + rval.append("

This composite dataset is composed of the following defined files:

") + defined_files = map(lambda x: x[0], defined_files) + extra_files = [] + for (dirpath, dirnames, filenames) in os.walk(dataset.extra_files_path, followlinks=True): + for filename in filenames: + rel_path = os.path.relpath(os.path.join(dirpath, filename), dataset.extra_files_path) + if rel_path not in defined_files: + extra_files.append(rel_path) + if extra_files: + rval.append("

This composite dataset contains these undefined files:

    ") + for rel_path in extra_files: + rval.append('
  • %s
  • ' % (rel_path, rel_path)) + rval.append('
') + if not (defined_files or extra_files): + rval.append("

This composite dataset does not contain any files!

    ") + rval.append('') + return "\n".join(rval) + + def get_mime(self): + """Returns the mime type of the datatype""" + return 'text/html' + + def set_peek(self, dataset, is_multi_byte=False): + """Set the peek and blurb text""" + if not dataset.dataset.purged: + dataset.peek = 'Anvio database (multiple files)' + dataset.blurb = 'Anvio database (multiple files)' + else: + dataset.peek = 'file does not exist' + dataset.blurb = 'file purged from disk' + + def display_peek(self, dataset): + """Create HTML content, used for displaying peek.""" + try: + return dataset.peek + except Exception: + return "Anvio database (multiple files)" + + +class AnvioDB(AnvioComposite): + """Class for AnvioDB database files.""" + _anvio_basename = None + MetadataElement(name="anvio_basename", default=_anvio_basename, desc="Basename", readonly=True) + file_ext = 'anvio_db' + composite_type = 'auto_primary_file' + allow_datatype_change = False + + def __init__(self, *args, **kwd): + super(AnvioDB, self).__init__(*args, **kwd) + if self._anvio_basename is not None: + self.add_composite_file(self._anvio_basename, is_binary=True, optional=False) + + def set_meta(self, dataset, **kwd): + """ + Set the anvio_basename based upon actual extra_files_path contents. + """ + super(AnvioDB, self).set_meta(dataset, **kwd) + if dataset.metadata.anvio_basename is not None and os.path.exists(os.path.join(dataset.extra_files_path, dataset.metadata.anvio_basename)): + return + found = False + for basename in [dataset.metadata.anvio_basename, self._anvio_basename]: + if found: + break + if basename is not None and not os.path.exists(os.path.join(dataset.extra_files_path, basename)): + for name in glob.glob(os.path.join(dataset.extra_files_path, "*%s" % (basename))): + dataset.metadata.anvio_basename = os.path.basename(name) + found = True + break + + +class AnvioStructureDB(AnvioDB): + """Class for Anvio Structure DB database files.""" + _anvio_basename = 'STRUCTURE.db' + MetadataElement(name="anvio_basename", default=_anvio_basename, desc="Basename", readonly=True) + file_ext = 'anvio_structure_db' + composite_type = 'auto_primary_file' + allow_datatype_change = False + + +class AnvioGenomesDB(AnvioDB): + """Class for Anvio Genomes DB database files.""" + _anvio_basename = '-GENOMES.db' + MetadataElement(name="anvio_basename", default=_anvio_basename, desc="Basename", readonly=True) + file_ext = 'anvio_genomes_db' + composite_type = 'auto_primary_file' + allow_datatype_change = False + + +class AnvioContigsDB(AnvioDB): + """Class for Anvio Contigs DB database files.""" + _anvio_basename = 'CONTIGS.db' + MetadataElement(name="anvio_basename", default=_anvio_basename, desc="Basename", readonly=True) + file_ext = 'anvio_contigs_db' + composite_type = 'auto_primary_file' + allow_datatype_change = False + + def __init__(self, *args, **kwd): + super(AnvioContigsDB, self).__init__(*args, **kwd) + self.add_composite_file('CONTIGS.h5', is_binary=True, optional=True) + + +class AnvioProfileDB(AnvioDB): + """Class for Anvio Profile DB database files.""" + _anvio_basename = 'PROFILE.db' + MetadataElement(name="anvio_basename", default=_anvio_basename, desc="Basename", readonly=True) + file_ext = 'anvio_profile_db' + composite_type = 'auto_primary_file' + allow_datatype_change = False + + def __init__(self, *args, **kwd): + super(AnvioProfileDB, self).__init__(*args, **kwd) + self.add_composite_file('RUNINFO.cp', is_binary=True, optional=True) + self.add_composite_file('RUNINFO.mcp', is_binary=True, optional=True) + self.add_composite_file('AUXILIARY_DATA.db', is_binary=True, optional=True) + self.add_composite_file('RUNLOG.txt', is_binary=False, optional=True) + + +class AnvioPanDB(AnvioDB): + """Class for Anvio Pan DB database files.""" + _anvio_basename = 'PAN.db' + MetadataElement(name="anvio_basename", default=_anvio_basename, desc="Basename", readonly=True) + file_ext = 'anvio_pan_db' + composite_type = 'auto_primary_file' + allow_datatype_change = False + + +class AnvioSamplesDB(AnvioDB): + """Class for Anvio Samples DB database files.""" + _anvio_basename = 'SAMPLES.db' + MetadataElement(name="anvio_basename", default=_anvio_basename, desc="Basename", readonly=True) + file_ext = 'anvio_samples_db' + composite_type = 'auto_primary_file' + allow_datatype_change = False + + +if __name__ == '__main__': + import doctest + doctest.testmod(sys.modules[__name__]) diff --git a/lib/galaxy/datatypes/binary.py b/lib/galaxy/datatypes/binary.py index b03e2618c0d..f80639f954c 100644 --- a/lib/galaxy/datatypes/binary.py +++ b/lib/galaxy/datatypes/binary.py @@ -22,6 +22,7 @@ from bx.seq.twobit import TWOBIT_MAGIC_NUMBER, TWOBIT_MAGIC_NUMBER_SWAP, TWOBIT_ from galaxy import util from galaxy.datatypes import metadata +from galaxy.datatypes.data import get_file_peek from galaxy.datatypes.metadata import DictParameter, ListParameter, MetadataElement, MetadataParameter from galaxy.util import nice_size, sqlite from galaxy.util.checkers import is_bz2, is_gzip @@ -98,32 +99,66 @@ class Idat(Binary): class Cel(Binary): + """ Cel File format described at: + http://media.affymetrix.com/support/developer/powertools/changelog/gcos-agcc/cel.html + """ - """Binary data in CEL format.""" file_ext = "cel" edam_format = "format_1638" edam_data = "data_3110" + MetadataElement(name="version", default="3", desc="Version", readonly=True, visible=True, + optional=True, no_value="3") def sniff(self, filename): """ - Try to guess if the file is a CEL file. - + Try to guess if the file is a Cel file. >>> from galaxy.datatypes.sniff import get_test_fname - >>> fname = get_test_fname('test.cel') + >>> fname = get_test_fname('affy_v_agcc.cel') >>> Cel().sniff(fname) True - - >>> fname = get_test_fname('drugbank_drugs.mz5') + >>> fname = get_test_fname('affy_v_3.cel') + >>> Cel().sniff(fname) + True + >>> fname = get_test_fname('affy_v_4.cel') + >>> Cel().sniff(fname) + True + >>> fname = get_test_fname('test.gal') >>> Cel().sniff(fname) False """ - try: - header = open(filename, 'rb').read(4) - if header == b';\x01\x00\x00': - return True - return False - except Exception: - return False + with open(filename, 'rb') as handle: + header_bytes = handle.read(8) + found_cel_4 = False + found_cel_3 = False + found_cel_agcc = False + if struct.unpack("bb", header_bytes[:2]) == (59, 1): + found_cel_agcc = True + elif header_bytes.decode("utf8", errors="ignore").startswith('[CEL]'): + found_cel_3 = True + return found_cel_3 or found_cel_4 or found_cel_agcc + + def set_meta(self, dataset, **kwd): + """ + Set metadata for Cel file. + """ + with open(dataset.file_name, 'rb') as handle: + header_bytes = handle.read(8) + if struct.unpack("bb", header_bytes[:2]) == (59, 1): + dataset.metadata.version = "agcc" + elif header_bytes.decode("utf8", errors="ignore").startswith('[CEL]'): + dataset.metadata.version = "3" + + def set_peek(self, dataset, is_multi_byte=False): + if not dataset.dataset.purged: + dataset.blurb = "Cel version: %s" % dataset.metadata.version + dataset.peek = get_file_peek(dataset.file_name) + else: + dataset.peek = 'file does not exist' + dataset.blurb = 'file purged from disk' class MashSketch(Binary): @@ -831,6 +866,27 @@ class Loom(H5): log.warning('%s, set_meta Exception: %s', self, e) +class Anndata(H5): + """ + Class describing an HDF5 anndata files: http://anndata.rtfd.io + >>> from galaxy.datatypes.sniff import get_test_fname + >>> Anndata().sniff(get_test_fname('pbmc3k_tiny.h5ad')) + True + >>> Anndata().sniff(get_test_fname('test.mz5')) + False + """ + file_ext = 'h5ad' + + def sniff(self, filename): + if super(Anndata, self).sniff(filename): + try: + with h5py.File(filename) as f: + return all(attr in f for attr in ['X', 'obs', 'var']) + except Exception: + return False + return False + + class GmxBinary(Binary): """ Base class for GROMACS binary files - xtc, trr, cpt @@ -2239,6 +2295,118 @@ class ICM(Binary): return False +class BafTar(CompressedArchive): + """ + Base class for common behavior of tar files of directory-based raw file formats + >>> from galaxy.datatypes.sniff import get_test_fname + >>> fname = get_test_fname('brukerbaf.d.tar') + >>> BafTar().sniff(fname) + True + >>> fname = get_test_fname('test.fast5.tar') + >>> BafTar().sniff(fname) + False + """ + edam_data = "data_2536" # mass spectrometry data + edam_format = "format_3712" # TODO: add more raw formats to EDAM? + file_ext = "brukerbaf.d.tar" + + def get_signature_file(self): + return "analysis.baf" + + def sniff(self, filename): + if tarfile.is_tarfile(filename): + with tarfile.open(filename) as rawtar: + return self.get_signature_file() in [os.path.basename(f).lower() for f in rawtar.getnames()] + return False + + def get_type(self): + return "Bruker BAF directory archive" + + def set_peek(self, dataset, is_multi_byte=False): + if not dataset.dataset.purged: + dataset.peek = self.get_type() + dataset.blurb = nice_size(dataset.get_size()) + else: + dataset.peek = 'file does not exist' + dataset.blurb = 'file purged from disk' + + def display_peek(self, dataset): + try: + return dataset.peek + except Exception: + return "%s (%s)" % (self.get_type(), nice_size(dataset.get_size())) + + +class YepTar(BafTar): + """ A tar'd up .d directory containing Agilent/Bruker YEP format data """ + file_ext = "agilentbrukeryep.d.tar" + + def get_signature_file(self): + return "analysis.yep" + + def get_type(self): + return "Agilent/Bruker YEP directory archive" + + +class TdfTar(BafTar): + """ A tar'd up .d directory containing Bruker TDF format data """ + file_ext = "brukertdf.d.tar" + + def get_signature_file(self): + return "analysis.tdf" + + def get_type(self): + return "Bruker TDF directory archive" + + +class MassHunterTar(BafTar): + """ A tar'd up .d directory containing Agilent MassHunter format data """ + file_ext = "agilentmasshunter.d.tar" + + def get_signature_file(self): + return "msscan.bin" + + def get_type(self): + return "Agilent MassHunter directory archive" + + +class MassLynxTar(BafTar): + """ A tar'd up .d directory containing Waters MassLynx format data """ + file_ext = "watersmasslynx.raw.tar" + + def get_signature_file(self): + return "_func001.dat" + + def get_type(self): + return "Waters MassLynx RAW directory archive" + + +class WiffTar(BafTar): + """ + A tar'd up .wiff/.scan pair containing Sciex WIFF format data + >>> from galaxy.datatypes.sniff import get_test_fname + >>> fname = get_test_fname('some.wiff.tar') + >>> WiffTar().sniff(fname) + True + >>> fname = get_test_fname('brukerbaf.d.tar') + >>> WiffTar().sniff(fname) + False + >>> fname = get_test_fname('test.fast5.tar') + >>> WiffTar().sniff(fname) + False + """ + file_ext = "wiff.tar" + + def sniff(self, filename): + if tarfile.is_tarfile(filename): + with tarfile.open(filename) as rawtar: + return ".wiff" in [os.path.splitext(os.path.basename(f).lower())[1] for f in rawtar.getnames()] + return False + + def get_type(self): + return "Sciex WIFF/SCAN archive" + + if __name__ == '__main__': import doctest doctest.testmod(sys.modules[__name__]) diff --git a/lib/galaxy/datatypes/data.py b/lib/galaxy/datatypes/data.py index e17b6f68be5..8cdd9fcf91c 100644 --- a/lib/galaxy/datatypes/data.py +++ b/lib/galaxy/datatypes/data.py @@ -372,7 +372,9 @@ class Data(object): return smart_str(data) if filename and filename != "index": # For files in extra_files_path - file_path = trans.app.object_store.get_filename(data.dataset, extra_dir='dataset_%s_files' % data.dataset.id, alt_name=filename) + store_by = data.dataset.object_store.store_by + extra_dir = 'dataset_%s_files' % getattr(data.dataset, store_by) + file_path = trans.app.object_store.get_filename(data.dataset, extra_dir=extra_dir, alt_name=filename) if os.path.exists(file_path): if os.path.isdir(file_path): with tempfile.NamedTemporaryFile(mode='w', delete=False, dir=trans.app.config.new_file_path, prefix='gx_html_autocreate_') as tmp_fh: @@ -430,7 +432,7 @@ class Data(object): else: trans.response.set_content_type("text/html") return trans.stream_template_mako("/dataset/large_file.mako", - truncated_data=open(data.file_name).read(max_peek_size), + truncated_data=open(data.file_name, 'rb').read(max_peek_size), data=data) def _yield_user_file_content(self, trans, from_dataset, filename): @@ -802,10 +804,16 @@ class Text(Data): """ data_lines = 0 with compression_utils.get_fileobj(dataset.file_name) as in_file: - for line in in_file: - line = line.strip() - if line and not line.startswith('#'): - data_lines += 1 + # FIXME: Potential encoding issue can prevent the ability to iterate over lines + # causing set_meta process to fail otherwise OK jobs. A better solution than + # a silent try/except is desirable. + try: + for line in in_file: + line = line.strip() + if line and not line.startswith('#'): + data_lines += 1 + except Exception: + pass return data_lines def set_peek(self, dataset, line_count=None, is_multi_byte=False, WIDTH=256, skipchars=None, line_wrap=True): diff --git a/lib/galaxy/datatypes/display_applications/application.py b/lib/galaxy/datatypes/display_applications/application.py index e0bc7484148..6cd9acb3517 100644 --- a/lib/galaxy/datatypes/display_applications/application.py +++ b/lib/galaxy/datatypes/display_applications/application.py @@ -11,7 +11,6 @@ from galaxy.util import ( ) from galaxy.util.odict import odict from galaxy.util.template import fill_template -from galaxy.web import url_for from .parameters import ( DEFAULT_DATASET_NAME, DisplayApplicationDataParameter, @@ -21,9 +20,6 @@ from .util import encode_dataset_user log = logging.getLogger(__name__) -# Any basic functions that we want to provide as a basic part of parameter dict should be added to this dict -BASE_PARAMS = {'qp': quote_plus, 'url_for': url_for} - class DisplayApplicationLink(object): @classmethod @@ -53,13 +49,13 @@ class DisplayApplicationLink(object): def get_display_url(self, data, trans): dataset_hash, user_hash = encode_dataset_user(trans, data, None) - return url_for(controller='dataset', - action="display_application", - dataset_id=dataset_hash, - user_id=user_hash, - app_name=quote_plus(self.display_application.id), - link_name=quote_plus(self.id), - app_action=None) + return trans.app.url_for(controller='dataset', + action="display_application", + dataset_id=dataset_hash, + user_id=user_hash, + app_name=quote_plus(self.display_application.id), + link_name=quote_plus(self.id), + app_action=None) def get_inital_values(self, data, trans): if self.other_values: @@ -67,6 +63,7 @@ class DisplayApplicationLink(object): else: rval = odict() rval.update({'BASE_URL': trans.request.base, 'APP': trans.app}) # trans automatically appears as a response, need to add properties of trans that we want here + BASE_PARAMS = {'qp': quote_plus, 'url_for': trans.app.url_for} for key, value in BASE_PARAMS.items(): # add helper functions/variables rval[key] = value rval[DEFAULT_DATASET_NAME] = data # always have the display dataset name available diff --git a/lib/galaxy/datatypes/display_applications/parameters.py b/lib/galaxy/datatypes/display_applications/parameters.py index 40e5a77c379..a7473059591 100644 --- a/lib/galaxy/datatypes/display_applications/parameters.py +++ b/lib/galaxy/datatypes/display_applications/parameters.py @@ -6,7 +6,6 @@ from six.moves.urllib.parse import quote_plus from galaxy.util import string_as_bool from galaxy.util.bunch import Bunch from galaxy.util.template import fill_template -from galaxy.web import url_for DEFAULT_DATASET_NAME = 'dataset' @@ -191,14 +190,14 @@ class DisplayParameterValueWrapper(object): if self.parameter.strip_https and base_url[: 5].lower() == 'https': base_url = "http%s" % base_url[5:] return "%s%s" % (base_url, - url_for(controller='dataset', - action="display_application", - dataset_id=self._dataset_hash, - user_id=self._user_hash, - app_name=quote_plus(self.parameter.link.display_application.id), - link_name=quote_plus(self.parameter.link.id), - app_action=self.action_name, - action_param=self._url)) + self.trans.app.url_for(controller='dataset', + action="display_application", + dataset_id=self._dataset_hash, + user_id=self._user_hash, + app_name=quote_plus(self.parameter.link.display_application.id), + link_name=quote_plus(self.parameter.link.id), + app_action=self.action_name, + action_param=self._url)) @property def action_name(self): diff --git a/lib/galaxy/datatypes/genetics.py b/lib/galaxy/datatypes/genetics.py index ab22d49e13d..3af65de2e35 100644 --- a/lib/galaxy/datatypes/genetics.py +++ b/lib/galaxy/datatypes/genetics.py @@ -26,7 +26,6 @@ from galaxy.datatypes.sniff import build_sniff_from_prefix from galaxy.datatypes.tabular import Tabular from galaxy.datatypes.text import Html from galaxy.util import nice_size -from galaxy.web import url_for gal_Log = logging.getLogger(__name__) verbose = False @@ -68,7 +67,7 @@ class GenomeGraphs(Tabular): """ Returns file """ - return open(dataset.file_name, 'r') + return open(dataset.file_name, 'rb') def ucsc_links(self, dataset, type, app, base_url): """ @@ -94,11 +93,11 @@ class GenomeGraphs(Tabular): for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('ucsc', dataset.dbkey): if site_name in app.datatypes_registry.get_display_sites('ucsc'): site_url = site_url.replace('/hgTracks?', '/hgGenome?') # for genome graphs - internal_url = "%s" % url_for(controller='dataset', - dataset_id=dataset.id, - action='display_at', - filename='ucsc_' + site_name) - display_url = "%s%s/display_as?id=%i&display_app=%s&authz_method=display_at" % (base_url, url_for(controller='root'), dataset.id, type) + internal_url = "%s" % app.url_for(controller='dataset', + dataset_id=dataset.id, + action='display_at', + filename='ucsc_' + site_name) + display_url = "%s%s/display_as?id=%i&display_app=%s&authz_method=display_at" % (base_url, app.url_for(controller='root'), dataset.id, type) display_url = quote_plus(display_url) # was display_url = quote_plus( "%s/display_as?id=%i&display_app=%s" % (base_url, dataset.id, type) ) # redirect_url = quote_plus( "%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" % (site_url, dataset.dbkey, chrom, start, stop) ) diff --git a/lib/galaxy/datatypes/interval.py b/lib/galaxy/datatypes/interval.py index 63c664db2b5..ee747aa91b0 100644 --- a/lib/galaxy/datatypes/interval.py +++ b/lib/galaxy/datatypes/interval.py @@ -19,7 +19,6 @@ from galaxy.datatypes.sniff import ( ) from galaxy.datatypes.tabular import Tabular from galaxy.datatypes.util.gff_util import parse_gff3_attributes, parse_gff_attributes -from galaxy.web import url_for from . import ( data, dataproviders @@ -100,42 +99,38 @@ class Interval(Tabular): else: # Header lines in Interval files are optional. For example, BED is Interval but has no header. # We'll make a best guess at the location of the metadata columns. - metadata_is_set = False elems = line.split('\t') if len(elems) > 2: - for str in data.col1_startswith: - if line.lower().startswith(str): - if overwrite or not dataset.metadata.element_is_set('chromCol'): - dataset.metadata.chromCol = 1 - try: - int(elems[1]) - if overwrite or not dataset.metadata.element_is_set('startCol'): - dataset.metadata.startCol = 2 - except Exception: - pass # Metadata default will be used - try: - int(elems[2]) - if overwrite or not dataset.metadata.element_is_set('endCol'): - dataset.metadata.endCol = 3 - except Exception: - pass # Metadata default will be used - # we no longer want to guess that this column is the 'name', name must now be set manually for interval files - # we will still guess at the strand, as we can make a more educated guess - # if len( elems ) > 3: - # try: - # int( elems[3] ) - # except Exception: - # if overwrite or not dataset.metadata.element_is_set( 'nameCol' ): - # dataset.metadata.nameCol = 4 - if len(elems) < 6 or elems[5] not in data.valid_strand: - if overwrite or not dataset.metadata.element_is_set('strandCol'): - dataset.metadata.strandCol = 0 - else: - if overwrite or not dataset.metadata.element_is_set('strandCol'): - dataset.metadata.strandCol = 6 - metadata_is_set = True - break - if metadata_is_set or (i - empty_line_count) > num_check_lines: + if overwrite or not dataset.metadata.element_is_set('chromCol'): + dataset.metadata.chromCol = 1 + try: + int(elems[1]) + if overwrite or not dataset.metadata.element_is_set('startCol'): + dataset.metadata.startCol = 2 + except Exception: + pass # Metadata default will be used + try: + int(elems[2]) + if overwrite or not dataset.metadata.element_is_set('endCol'): + dataset.metadata.endCol = 3 + except Exception: + pass # Metadata default will be used + # we no longer want to guess that this column is the 'name', name must now be set manually for interval files + # we will still guess at the strand, as we can make a more educated guess + # if len( elems ) > 3: + # try: + # int( elems[3] ) + # except Exception: + # if overwrite or not dataset.metadata.element_is_set( 'nameCol' ): + # dataset.metadata.nameCol = 4 + if len(elems) < 6 or elems[5] not in data.valid_strand: + if overwrite or not dataset.metadata.element_is_set('strandCol'): + dataset.metadata.strandCol = 0 + else: + if overwrite or not dataset.metadata.element_is_set('strandCol'): + dataset.metadata.strandCol = 6 + break + if (i - empty_line_count) > num_check_lines: break # Our metadata is set or we examined 100 non-empty lines, so break out of the outer loop else: empty_line_count += 1 @@ -238,7 +233,7 @@ class Interval(Tabular): for elems in util.file_iter(dataset.file_name): tmp = [elems[c], elems[s], elems[e]] fh.write('%s\n' % '\t'.join(tmp)) - return open(fh.name) + return open(fh.name, 'rb') def display_peek(self, dataset): """Returns formated html of peek""" @@ -264,10 +259,10 @@ class Interval(Tabular): # Accumulate links for valid sites ret_val = [] for site_name, site_url in valid_sites: - internal_url = url_for(controller='dataset', dataset_id=dataset.id, - action='display_at', filename='ucsc_' + site_name) + internal_url = app.url_for(controller='dataset', dataset_id=dataset.id, + action='display_at', filename='ucsc_' + site_name) display_url = quote_plus("%s%s/display_as?id=%i&display_app=%s&authz_method=display_at" % - (base_url, url_for(controller='root'), dataset.id, type)) + (base_url, app.url_for(controller='root'), dataset.id, type)) redirect_url = quote_plus("%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" % (site_url, dataset.dbkey, chrom, start, stop)) link = '%s?redirect_url=%s&display_url=%s' % (internal_url, redirect_url, display_url) @@ -371,7 +366,7 @@ class BedGraph(Interval): 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. """ - return open(dataset.file_name) + return open(dataset.file_name, 'rb') def get_estimated_display_viewport(self, dataset, chrom_col=0, start_col=1, end_col=2): """ @@ -403,26 +398,20 @@ class Bed(Interval): i = 0 if dataset.has_data(): for i, line in enumerate(open(dataset.file_name)): - metadata_set = False line = line.rstrip('\r\n') if line and not line.startswith('#'): elems = line.split('\t') if len(elems) > 2: - for startswith in data.col1_startswith: - if line.lower().startswith(startswith): - if len(elems) > 3: - if overwrite or not dataset.metadata.element_is_set('nameCol'): - dataset.metadata.nameCol = 4 - if len(elems) < 6: - if overwrite or not dataset.metadata.element_is_set('strandCol'): - dataset.metadata.strandCol = 0 - else: - if overwrite or not dataset.metadata.element_is_set('strandCol'): - dataset.metadata.strandCol = 6 - metadata_set = True - break - if metadata_set: - break + if len(elems) > 3: + if overwrite or not dataset.metadata.element_is_set('nameCol'): + dataset.metadata.nameCol = 4 + if len(elems) < 6: + if overwrite or not dataset.metadata.element_is_set('strandCol'): + dataset.metadata.strandCol = 0 + else: + if overwrite or not dataset.metadata.element_is_set('strandCol'): + dataset.metadata.strandCol = 6 + break Tabular.set_meta(self, dataset, overwrite=overwrite, skip=i) def as_ucsc_display_file(self, dataset, **kwd): @@ -459,7 +448,7 @@ class Bed(Interval): break try: - return open(dataset.file_name) + return open(dataset.file_name, 'rb') except Exception: return "This item contains no content" @@ -494,75 +483,67 @@ class Bed(Interval): for hdr in headers: if hdr[0] == '': continue - valid_col1 = False if len(hdr) < 3 or len(hdr) > 12: return False - for str in data.col1_startswith: - if hdr[0].lower().startswith(str): - valid_col1 = True - break - if valid_col1: + try: + int(hdr[1]) + int(hdr[2]) + except Exception: + return False + if len(hdr) > 4: + # hdr[3] is a string, 'name', which defines the name of the BED line - difficult to test for this. + # hdr[4] is an int, 'score', a score between 0 and 1000. try: - int(hdr[1]) - int(hdr[2]) + if int(hdr[4]) < 0 or int(hdr[4]) > 1000: + return False except Exception: return False - if len(hdr) > 4: - # hdr[3] is a string, 'name', which defines the name of the BED line - difficult to test for this. - # hdr[4] is an int, 'score', a score between 0 and 1000. + if len(hdr) > 5: + # hdr[5] is strand + if hdr[5] not in data.valid_strand: + return False + if len(hdr) > 6: + # hdr[6] is thickStart, the starting position at which the feature is drawn thickly. + try: + int(hdr[6]) + except Exception: + return False + if len(hdr) > 7: + # hdr[7] is thickEnd, the ending position at which the feature is drawn thickly + try: + int(hdr[7]) + except Exception: + return False + if len(hdr) > 8: + # hdr[8] is itemRgb, an RGB value of the form R,G,B (e.g. 255,0,0). However, this could also be an int (e.g., 0) + try: + int(hdr[8]) + except Exception: try: - if int(hdr[4]) < 0 or int(hdr[4]) > 1000: - return False + hdr[8].split(',') except Exception: return False - if len(hdr) > 5: - # hdr[5] is strand - if hdr[5] not in data.valid_strand: - return False - if len(hdr) > 6: - # hdr[6] is thickStart, the starting position at which the feature is drawn thickly. - try: - int(hdr[6]) - except Exception: - return False - if len(hdr) > 7: - # hdr[7] is thickEnd, the ending position at which the feature is drawn thickly - try: - int(hdr[7]) - except Exception: - return False - if len(hdr) > 8: - # hdr[8] is itemRgb, an RGB value of the form R,G,B (e.g. 255,0,0). However, this could also be an int (e.g., 0) - try: - int(hdr[8]) - except Exception: - try: - hdr[8].split(',') - except Exception: - return False - if len(hdr) > 9: - # hdr[9] is blockCount, the number of blocks (exons) in the BED line. - try: - block_count = int(hdr[9]) - except Exception: - return False - if len(hdr) > 10: - # hdr[10] is blockSizes - A comma-separated list of the block sizes. - # Sometimes the blosck_sizes and block_starts lists end in extra commas - try: - block_sizes = hdr[10].rstrip(',').split(',') - except Exception: - return False - if len(hdr) > 11: - # hdr[11] is blockStarts - A comma-separated list of block starts. - try: - block_starts = hdr[11].rstrip(',').split(',') - except Exception: - return False - if len(block_sizes) != block_count or len(block_starts) != block_count: - return False - else: - return False + if len(hdr) > 9: + # hdr[9] is blockCount, the number of blocks (exons) in the BED line. + try: + block_count = int(hdr[9]) + except Exception: + return False + if len(hdr) > 10: + # hdr[10] is blockSizes - A comma-separated list of the block sizes. + # Sometimes the blosck_sizes and block_starts lists end in extra commas + try: + block_sizes = hdr[10].rstrip(',').split(',') + except Exception: + return False + if len(hdr) > 11: + # hdr[11] is blockStarts - A comma-separated list of block starts. + try: + block_starts = hdr[11].rstrip(',').split(',') + except Exception: + return False + if len(block_sizes) != block_count or len(block_starts) != block_count: + return False return True except Exception: return False @@ -625,10 +606,10 @@ class _RemoteCallMixin(object): the data available, followed by redirecting to the remote site with a link back to the available information. """ - internal_url = "%s" % url_for(controller='dataset', dataset_id=dataset.id, action='display_at', filename='%s_%s' % (type, site_name)) + internal_url = "%s" % app.url_for(controller='dataset', dataset_id=dataset.id, action='display_at', filename='%s_%s' % (type, site_name)) base_url = app.config.get("display_at_callback", base_url) display_url = quote_plus("%s%s/display_as?id=%i&display_app=%s&authz_method=display_at" % - (base_url, url_for(controller='root'), dataset.id, type)) + (base_url, app.url_for(controller='root'), dataset.id, type)) link = '%s?redirect_url=%s&display_url=%s' % (internal_url, redirect_url, display_url) return link @@ -1201,16 +1182,14 @@ class Wiggle(Tabular, _RemoteCallMixin): if line and not line.startswith('#'): elems = line.split('\t') try: - float(elems[0]) # "Wiggle track data values can be integer or real, positive or negative values" + # variableStep format is nucleotide position\tvalue\n, + # fixedStep is value\n + # "Wiggle track data values can be integer or real, positive or negative values" + float(elems[0]) break except Exception: - do_break = False - for col_startswith in data.col1_startswith: - if elems[0].lower().startswith(col_startswith): - do_break = True - break - if do_break: - break + # We are either in the track definition line or in a declaration line + pass if self.max_optional_metadata_filesize >= 0 and dataset.get_size() > self.max_optional_metadata_filesize: # we'll arbitrarily only use the first 100 data lines in this wig file to calculate tabular attributes (column types) # this should be sufficient, except when we have mixed wig track types (bed, variable, fixed), @@ -1355,8 +1334,8 @@ class CustomTrack(Tabular): if chrom is not None: for site_name, site_url in app.datatypes_registry.get_legacy_sites_by_build('ucsc', dataset.dbkey): if site_name in app.datatypes_registry.get_display_sites('ucsc'): - internal_url = "%s" % url_for(controller='dataset', dataset_id=dataset.id, action='display_at', filename='ucsc_' + site_name) - display_url = quote_plus("%s%s/display_as?id=%i&display_app=%s&authz_method=display_at" % (base_url, url_for(controller='root'), dataset.id, type)) + internal_url = "%s" % app.url_for(controller='dataset', dataset_id=dataset.id, action='display_at', filename='ucsc_' + site_name) + display_url = quote_plus("%s%s/display_as?id=%i&display_app=%s&authz_method=display_at" % (base_url, app.url_for(controller='root'), dataset.id, type)) redirect_url = quote_plus("%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" % (site_url, dataset.dbkey, chrom, start, stop)) link = '%s?redirect_url=%s&display_url=%s' % (internal_url, redirect_url, display_url) ret_val.append((site_name, link)) diff --git a/lib/galaxy/datatypes/isa.py b/lib/galaxy/datatypes/isa.py index 0089fd326f6..d11f6454612 100644 --- a/lib/galaxy/datatypes/isa.py +++ b/lib/galaxy/datatypes/isa.py @@ -271,7 +271,7 @@ class _Isa(data.Data): if investigation is None: html = """
    Error while reading ISA archive.
    -

    An error occured while reading content of ISA archive.

    +

    An error occurred while reading content of ISA archive.

    If you have tried to load your archive with the uploader by selecting isa-tab as composite data type, then try to load it again with isa-json instead. Conversely, if you have tried to load your archive with the uploader by selecting isa-json as composite data type, then try isa-tab instead.

    You may also try to look into your zip file in order to find out if this is a proper ISA archive. If you see a file i_Investigation.txt inside, then it is an ISA-Tab archive. If you see a file with extension .json inside, then it is an ISA-JSON archive. If you see nothing like that, then either your ISA archive is corrupted, or it is not an ISA archive.

    """ diff --git a/lib/galaxy/datatypes/microarrays.py b/lib/galaxy/datatypes/microarrays.py new file mode 100644 index 00000000000..bedf6e2bac4 --- /dev/null +++ b/lib/galaxy/datatypes/microarrays.py @@ -0,0 +1,128 @@ +import logging + +from galaxy.datatypes import data +from galaxy.datatypes.binary import Cel # noqa: F401 +from galaxy.datatypes.data import get_file_peek +from galaxy.datatypes.metadata import MetadataElement +from galaxy.datatypes.sniff import ( + build_sniff_from_prefix, + get_headers +) + +log = logging.getLogger(__name__) + + +class GenericMicroarrayFile(data.Text): + """ + Abstract class for most of the microarray files. + """ + MetadataElement(name="version_number", default="1.0", desc="Version number", readonly=True, visible=True, + optional=True, no_value="1.0") + MetadataElement(name="file_format", default="ATF", desc="File format", readonly=True, visible=True, + optional=True, no_value="ATF") + MetadataElement(name="number_of_optional_header_records", default=1, desc="Number of optional header records", + readonly=True, visible=True, optional=True, no_value=1) + MetadataElement(name="number_of_data_columns", default=1, desc="Number of data columns", + readonly=True, visible=True, + optional=True, no_value=1) + MetadataElement(name="file_type", default="GenePix", desc="File type", + readonly=True, visible=True, + optional=True, no_value="GenePix") + MetadataElement(name="block_count", default=1, desc="Number of blocks described in the file", + readonly=True, visible=True, + optional=True, no_value=1) + MetadataElement(name="block_type", default=0, desc="Type of block", + readonly=True, visible=True, + optional=True, no_value=0) + + def set_peek(self, dataset, is_multi_byte=False): + if not dataset.dataset.purged: + if dataset.metadata.block_count == 1: + dataset.blurb = "%s %s: Format %s, 1 block, %s headers and %s columns" % (dataset.metadata.file_type, dataset.metadata.version_number, dataset.metadata.file_format, dataset.metadata.number_of_optional_header_records, dataset.metadata.number_of_data_columns) + else: + dataset.blurb = "%s %s: Format %s, %s blocks, %s headers and %s columns" % (dataset.metadata.file_type, dataset.metadata.version_number, dataset.metadata.file_format, dataset.metadata.block_count, dataset.metadata.number_of_optional_header_records, dataset.metadata.number_of_data_columns) + dataset.peek = get_file_peek(dataset.file_name) + else: + dataset.peek = 'file does not exist' + dataset.blurb = 'file purged from disk' + + def get_mime(self): + return 'text/plain' + + +@build_sniff_from_prefix +class Gal(GenericMicroarrayFile): + """ Gal File format described at: + http://mdc.custhelp.com/app/answers/detail/a_id/18883/#gal + """ + + edam_format = "format_3829" + edam_data = "data_3110" + file_ext = "gal" + + def sniff_prefix(self, file_prefix): + """ + Try to guess if the file is a Gal file. + >>> from galaxy.datatypes.sniff import get_test_fname + >>> fname = get_test_fname('test.gal') + >>> Gal().sniff(fname) + True + >>> fname = get_test_fname('test.gpr') + >>> Gal().sniff(fname) + False + """ + 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, **kwd): + """ + Set metadata for Gal file. + """ + super(Gal, self).set_meta(dataset, **kwd) + headers = get_headers(dataset.file_name, sep="\t", count=5) + dataset.metadata.file_format = headers[0][0] + dataset.metadata.version_number = headers[0][1] + dataset.metadata.number_of_optional_header_records = int(headers[1][0]) + dataset.metadata.number_of_data_columns = int(headers[1][1]) + dataset.metadata.file_type = headers[2][0].strip().strip('"').split("=")[1] + if "BlockCount" in headers[3][0]: + dataset.metadata.block_count = int(headers[3][0].strip().strip('"').split("=")[1]) + if "BlockType" in headers[4][0]: + dataset.metadata.block_type = int(headers[4][0].strip().strip('"').split("=")[1]) + + +@build_sniff_from_prefix +class Gpr(GenericMicroarrayFile): + """ Gpr File format described at: + http://mdc.custhelp.com/app/answers/detail/a_id/18883/#gpr + """ + + edam_format = "format_3829" + edam_data = "data_3110" + file_ext = "gpr" + + def sniff_prefix(self, file_prefix): + """ + Try to guess if the file is a Gpr file. + >>> from galaxy.datatypes.sniff import get_test_fname + >>> fname = get_test_fname('test.gpr') + >>> Gpr().sniff(fname) + True + >>> fname = get_test_fname('test.gal') + >>> Gpr().sniff(fname) + False + """ + 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, **kwd): + """ + Set metadata for Gpr file. + """ + super(Gpr, self).set_meta(dataset, **kwd) + headers = get_headers(dataset.file_name, sep="\t", count=5) + dataset.metadata.file_format = headers[0][0] + dataset.metadata.version_number = headers[0][1] + dataset.metadata.number_of_optional_header_records = int(headers[1][0]) + dataset.metadata.number_of_data_columns = int(headers[1][1]) + dataset.metadata.file_type = headers[2][0].strip().strip('"').split("=")[1] diff --git a/lib/galaxy/datatypes/molecules.py b/lib/galaxy/datatypes/molecules.py index a4aaebe70f9..b4556e5d366 100644 --- a/lib/galaxy/datatypes/molecules.py +++ b/lib/galaxy/datatypes/molecules.py @@ -17,29 +17,12 @@ from galaxy.datatypes.sniff import ( iter_headers ) from galaxy.datatypes.tabular import Tabular +from galaxy.datatypes.util.generic_util import count_special_lines from galaxy.datatypes.xml import GenericXml log = logging.getLogger(__name__) -def count_special_lines(word, filename, invert=False): - """ - searching for special 'words' using the grep tool - grep is used to speed up the searching and counting - The number of hits is returned. - """ - try: - cmd = ["grep", "-c"] - if invert: - cmd.append('-v') - cmd.extend([word, filename]) - out = subprocess.Popen(cmd, stdout=subprocess.PIPE) - return int(out.communicate()[0].split()[0]) - except Exception: - pass - return 0 - - def count_lines(filename, non_empty=False): """ counting the number of lines from the 'filename' file diff --git a/lib/galaxy/datatypes/proteomics.py b/lib/galaxy/datatypes/proteomics.py index edd636500bb..ee6c4bc8d7b 100644 --- a/lib/galaxy/datatypes/proteomics.py +++ b/lib/galaxy/datatypes/proteomics.py @@ -488,7 +488,7 @@ class Analyze75(Binary): self.add_composite_file( 'hdr', description='The Analyze75 header file.', - is_binary=False) + is_binary=True) """The image file. Image data, whose data type and ordering are described by the header file.""" self.add_composite_file( @@ -500,7 +500,8 @@ class Analyze75(Binary): self.add_composite_file( 't2m', description='The Analyze75 t2m file.', - optional='True', is_binary=True) + optional=True, + is_binary=True) def generate_primary_file(self, dataset=None): rval = ['Analyze75 Composite Dataset.

    '] diff --git a/lib/galaxy/datatypes/sequence.py b/lib/galaxy/datatypes/sequence.py index 4aacb73e582..ee03f53e21d 100644 --- a/lib/galaxy/datatypes/sequence.py +++ b/lib/galaxy/datatypes/sequence.py @@ -98,18 +98,19 @@ class Sequence(data.Text): """ data_lines = 0 sequences = 0 - for line in open(dataset.file_name): - line = line.strip() - if line and line.startswith('#'): - # We don't count comment lines for sequence data types - continue - if line and line.startswith('>'): - sequences += 1 - data_lines += 1 - else: - data_lines += 1 - dataset.metadata.data_lines = data_lines - dataset.metadata.sequences = sequences + with compression_utils.get_fileobj(dataset.file_name) as fh: + for line in fh: + line = line.strip() + if line and line.startswith('#'): + # We don't count comment lines for sequence data types + continue + if line and line.startswith('>'): + sequences += 1 + data_lines += 1 + else: + data_lines += 1 + dataset.metadata.data_lines = data_lines + dataset.metadata.sequences = sequences def set_peek(self, dataset, is_multi_byte=False): if not dataset.dataset.purged: diff --git a/lib/galaxy/datatypes/set_metadata_tool.xml b/lib/galaxy/datatypes/set_metadata_tool.xml index 32e0e6fdd59..f61009a80a8 100644 --- a/lib/galaxy/datatypes/set_metadata_tool.xml +++ b/lib/galaxy/datatypes/set_metadata_tool.xml @@ -5,7 +5,7 @@ -"\${GALAXY_PYTHON:-python}" '${set_metadata}' ${__SET_EXTERNAL_METADATA_COMMAND_LINE__} +cd ..; "\${GALAXY_PYTHON:-python}" '${set_metadata}' ${__SET_EXTERNAL_METADATA_COMMAND_LINE__} from galaxy_ext.metadata.set_metadata import set_metadata; set_metadata() diff --git a/lib/galaxy/datatypes/sniff.py b/lib/galaxy/datatypes/sniff.py index 8c8c9aea3fd..404f9c53a11 100644 --- a/lib/galaxy/datatypes/sniff.py +++ b/lib/galaxy/datatypes/sniff.py @@ -119,7 +119,7 @@ def convert_newlines(fname, in_place=True, tmp_dir=None, tmp_prefix="gxupload"): fd, temp_name = tempfile.mkstemp(prefix=tmp_prefix, dir=tmp_dir) with io.open(fd, mode="wt", encoding='utf-8') as fp: i = None - for i, line in enumerate(io.open(fname, mode="U", encoding='utf-8')): + for i, line in enumerate(io.open(fname, encoding='utf-8')): fp.write("%s\n" % line.rstrip("\r\n")) if i is None: i = 0 @@ -186,7 +186,7 @@ def convert_newlines_sep2tabs(fname, in_place=True, patt=r"\s+", tmp_dir=None, t regexp = re.compile(patt) fd, temp_name = tempfile.mkstemp(prefix=tmp_prefix, dir=tmp_dir) with io.open(fd, mode="wt", encoding='utf-8') as fp: - for i, line in enumerate(codecs.open(fname, mode="U", encoding='utf-8')): + for i, line in enumerate(io.open(fname, encoding='utf-8')): line = line.rstrip('\r\n') elems = regexp.split(line) fp.write(u"%s\n" % '\t'.join(elems)) @@ -199,27 +199,19 @@ def convert_newlines_sep2tabs(fname, in_place=True, patt=r"\s+", tmp_dir=None, t def iter_headers(fname_or_file_prefix, sep, count=60, comment_designator=None): + idx = 0 if isinstance(fname_or_file_prefix, FilePrefix): - idx = 0 - for line in fname_or_file_prefix.line_iterator(): - line = line.rstrip('\n\r') - if comment_designator is not None and comment_designator != '' and line.startswith(comment_designator): - continue - yield line.split(sep) - idx += 1 - if idx == count: - break + file_iterator = fname_or_file_prefix.line_iterator() else: - with compression_utils.get_fileobj(fname_or_file_prefix) as in_file: - idx = 0 - for line in in_file: - line = line.rstrip('\n\r') - if comment_designator is not None and comment_designator != '' and line.startswith(comment_designator): - continue - yield line.split(sep) - idx += 1 - if idx == count: - break + file_iterator = compression_utils.get_fileobj(fname_or_file_prefix) + for line in file_iterator: + line = line.rstrip('\n\r') + if comment_designator is not None and comment_designator != '' and line.startswith(comment_designator): + continue + yield line.split(sep) + idx += 1 + if idx == count: + break def get_headers(fname_or_file_prefix, sep, count=60, comment_designator=None): @@ -479,6 +471,9 @@ def guess_ext(fname, sniff_order, is_binary=False): >>> fname = get_test_fname('1.fastqsanger.gz') >>> guess_ext(fname, sniff_order) # See test_datatype_registry for more compressed type tests. 'fastqsanger.gz' + >>> fname = get_test_fname('1.mtx') + >>> guess_ext(fname, sniff_order) + 'mtx' """ file_prefix = FilePrefix(fname) file_ext = run_sniffers_raw(file_prefix, sniff_order, is_binary) diff --git a/lib/galaxy/datatypes/tabular.py b/lib/galaxy/datatypes/tabular.py index 5fd331627d3..1d04a03a4a6 100644 --- a/lib/galaxy/datatypes/tabular.py +++ b/lib/galaxy/datatypes/tabular.py @@ -394,10 +394,10 @@ class Tabular(TabularData): dataset.metadata.delimiter = '\t' def as_gbrowse_display_file(self, dataset, **kwd): - return open(dataset.file_name) + return open(dataset.file_name, 'rb') def as_ucsc_display_file(self, dataset, **kwd): - return open(dataset.file_name) + return open(dataset.file_name, 'rb') class Taxonomy(Tabular): @@ -1161,3 +1161,67 @@ class ConnectivityTable(Tabular): ck_data_body = re.sub('[ ]+', '\t', ck_data_body) return dumps({'ck_data': util.unicodify(ck_data_header + "\n" + ck_data_body), 'ck_index': ck_index + 1}) + + +@build_sniff_from_prefix +class MatrixMarket(TabularData): + """ + The Matrix Market (MM) exchange formats provide a simple mechanism + to facilitate the exchange of matrix data. MM coordinate format is + suitable for representing sparse matrices. Only nonzero entries need + be encoded, and the coordinates of each are given explicitly. + + The tabular file format is defined as follows:: + + %%MatrixMarket matrix coordinate real general <--- header line + % <--+ + % comments |-- 0 or more comment lines + % <--+ + M N L <--- rows, columns, entries + I1 J1 A(I1, J1) <--+ + I2 J2 A(I2, J2) | + I3 J3 A(I3, J3) |-- L lines + . . . | + IL JL A(IL, JL) <--+ + + Indices are 1-based, i.e. A(1,1) is the first element. + + >>> from galaxy.datatypes.sniff import get_test_fname + >>> MatrixMarket().sniff( get_test_fname( 'sequence.maf' ) ) + False + >>> MatrixMarket().sniff( get_test_fname( '1.mtx' ) ) + True + >>> MatrixMarket().sniff( get_test_fname( '2.mtx' ) ) + True + >>> MatrixMarket().sniff( get_test_fname( '3.mtx' ) ) + True + """ + file_ext = "mtx" + + def __init__(self, **kwd): + super(MatrixMarket, self).__init__(**kwd) + + def sniff_prefix(self, file_prefix): + return file_prefix.startswith('%%MatrixMarket matrix coordinate') + + def set_meta(self, dataset, overwrite=True, skip=None, max_data_lines=5, **kwd): + if dataset.has_data(): + with open(dataset.file_name) as dataset_fh: + comment_lines = 0 + for i, l in enumerate(dataset_fh): + if l.startswith('%'): + comment_lines += 1 + elif self.max_optional_metadata_filesize >= 0 and dataset.get_size() > self.max_optional_metadata_filesize: + # If the dataset is larger than optional_metadata, just count comment lines. + # No more comments, and the file is too big to look at the whole thing. Give up. + dataset.metadata.data_lines = None + break + if ' ' in l: + dataset.metadata.delimiter = ' ' + else: + dataset.metadata.delimiter = '\t' + if not (self.max_optional_metadata_filesize >= 0 and dataset.get_size() > self.max_optional_metadata_filesize): + dataset.metadata.data_lines = i + 1 - comment_lines + dataset.metadata.comment_lines = comment_lines + dataset.metadata.columns = 3 + dataset.metadata.column_types = ['int', 'int', 'float'] diff --git a/lib/galaxy/datatypes/test/1.fasta.gz b/lib/galaxy/datatypes/test/1.fasta.gz new file mode 120000 index 00000000000..5dfa130d9eb --- /dev/null +++ b/lib/galaxy/datatypes/test/1.fasta.gz @@ -0,0 +1 @@ +../../../../test-data/1.fasta.gz \ No newline at end of file diff --git a/lib/galaxy/datatypes/test/1.mtx b/lib/galaxy/datatypes/test/1.mtx new file mode 100644 index 00000000000..66e5f7d2453 --- /dev/null +++ b/lib/galaxy/datatypes/test/1.mtx @@ -0,0 +1,31 @@ +%%MatrixMarket matrix coordinate real general +%================================================================================= +% +% This ASCII file represents a sparse MxN matrix with L +% nonzeros in the following Matrix Market format: +% +% +----------------------------------------------+ +% |%%MatrixMarket matrix coordinate real general | <--- header line +% |% | <--+ +% |% comments | |-- 0 or more comment lines +% |% | <--+ +% | M N L | <--- rows, columns, entries +% | I1 J1 A(I1, J1) | <--+ +% | I2 J2 A(I2, J2) | | +% | I3 J3 A(I3, J3) | |-- L lines +% | . . . | | +% | IL JL A(IL, JL) | <--+ +% +----------------------------------------------+ +% +% Indices are 1-based, i.e. A(1,1) is the first element. +% +%================================================================================= +5 5 8 +1 1 1.000e+00 +2 2 1.050e+01 +3 3 1.500e-02 +1 4 6.000e+00 +4 2 2.505e+02 +4 4 -2.800e+02 +4 5 3.332e+01 +5 5 1.200e+01 diff --git a/lib/galaxy/datatypes/test/2.mtx b/lib/galaxy/datatypes/test/2.mtx new file mode 100644 index 00000000000..b998dd32780 --- /dev/null +++ b/lib/galaxy/datatypes/test/2.mtx @@ -0,0 +1,11 @@ +%%MatrixMarket matrix coordinate real general +% +5 5 8 +1 1 1.000e+00 +2 2 1.050e+01 +3 3 1.500e-02 +1 4 6.000e+00 +4 2 2.505e+02 +4 4 -2.800e+02 +4 5 3.332e+01 +5 5 1.200e+01 diff --git a/lib/galaxy/datatypes/test/3.mtx b/lib/galaxy/datatypes/test/3.mtx new file mode 100644 index 00000000000..cc785471635 --- /dev/null +++ b/lib/galaxy/datatypes/test/3.mtx @@ -0,0 +1,15 @@ +%%MatrixMarket matrix coordinate integer general +% +343 12 12 +201 1 1 +123 2 1 +283 3 1 +318 4 1 +175 5 1 +12 6 1 +132 7 1 +197 8 1 +222 9 1 +236 10 1 +317 11 1 +127 12 1 diff --git a/lib/galaxy/datatypes/test/affy_v_3.cel b/lib/galaxy/datatypes/test/affy_v_3.cel new file mode 100644 index 00000000000..be5811726f8 --- /dev/null +++ b/lib/galaxy/datatypes/test/affy_v_3.cel @@ -0,0 +1,74 @@ +[CEL] +Version=3 + +[HEADER] +Cols=5 +Rows=5 +TotalX=5 +TotalY=5 +OffsetX=0 +OffsetY=0 +GridCornerUL=206 129 +GridCornerUR=3570 107 +GridCornerLR=3597 3470 +GridCornerLL=234 3492 +Axis-invertX=0 +AxisInvertY=0 +swapXY=0 +DatHeader=[11..65533] 1g_A9AF:CLS=3684 RWS=3684 XIN=1 YIN=1 VE=30 2.0 08/23/07 11:23:24 50205880 M10   Tgondii_SNP1.1sq      570  25356.509766  3.500000  1.5600  6 +Algorithm=Percentile +AlgorithmParameters=Percentile:75;CellMargin:2;OutlierHigh:1.500;OutlierLow:1.004;AlgVersion:6.0;FixedCellSize:TRUE;FullFeatureWidth:7;FullFeatureHeight:7;IgnoreOutliersInShiftRows:FALSE;FeatureExtraction:TRUE;PoolWidthExtenstion:2;PoolHeightExtension:2;UseSubgrids:FALSE;RandomizePixels:FALSE;ErrorBasis:StdvMean;StdMult:1.000000 + +[INTENSITY] +NumberCells=25 +CellHeader=X Y MEAN STDV NPIXELS + 0 0 234.0 24.0 25 + 1 0 22428.0 2378.1 25 + 2 0 249.0 27.7 25 + 3 0 22339.0 2264.8 25 + 4 0 156.0 27.2 25 + 1 0 170.0 34.5 25 + 2 0 22177.0 2669.0 25 + 3 0 164.0 19.7 25 + 4 0 22104.0 3661.2 25 + 0 1 188.0 29.8 25 + 1 1 188.0 29.8 25 + 2 1 21871.0 2795.9 25 + 3 1 168.0 67.9 25 + 4 1 21883.0 2792.4 25 + 0 2 188.0 29.8 25 + 1 2 193.0 88.7 25 + 2 2 21455.0 2976.5 25 + 3 2 198.0 62.0 25 + 4 2 21300.0 2914.5 25 + 0 3 188.0 29.8 25 + 1 3 182.0 76.2 25 + 2 3 21438.0 2759.5 25 + 3 3 188.0 49.2 25 + 4 3 20945.0 2762.0 25 + 0 4 193.0 38.8 25 + 1 4 20370.0 2611.8 25 + 2 4 174.0 26.6 25 + 3 4 20605.0 2810.7 25 + 4 4 168.0 24.1 25 + +[MASKS] +NumberCells=3 +CellHeader=X Y +4 1 +4 2 +3 1 + +[OUTLIERS] +NumberCells=3 +CellHeader=X Y +1 1 +2 1 +1 3 + +[MODIFIED] +NumberCells=3 +CellHeader=X Y ORIGMEAN +3 1 189.0 +3 2 21775.0 +4 1 220.0 diff --git a/lib/galaxy/datatypes/test/affy_v_4.cel b/lib/galaxy/datatypes/test/affy_v_4.cel new file mode 100644 index 00000000000..9dc05e342d5 Binary files /dev/null and b/lib/galaxy/datatypes/test/affy_v_4.cel differ diff --git a/lib/galaxy/datatypes/test/affy_v_agcc.cel b/lib/galaxy/datatypes/test/affy_v_agcc.cel new file mode 100644 index 00000000000..2a7346daac2 Binary files /dev/null and b/lib/galaxy/datatypes/test/affy_v_agcc.cel differ diff --git a/lib/galaxy/datatypes/test/brukerbaf.d.tar b/lib/galaxy/datatypes/test/brukerbaf.d.tar new file mode 100644 index 00000000000..9e27e71aeff Binary files /dev/null and b/lib/galaxy/datatypes/test/brukerbaf.d.tar differ diff --git a/lib/galaxy/datatypes/test/imgt.json b/lib/galaxy/datatypes/test/imgt.json new file mode 100644 index 00000000000..57583753678 --- /dev/null +++ b/lib/galaxy/datatypes/test/imgt.json @@ -0,0 +1,8833 @@ +[ { + "taxonId": 9615, + "speciesNames": [ "conis_lupus", "dog" ], + "genes": [ { + "baseSequence": "file://dog_V_IGH.fasta#IGHV1-15*01", + "name": "IGHV1-15*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV1-15*01|Canis lupus familiaris|P|V-REGION|976750..977043|294 nt|1| | | | |294+24=318| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 294 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV1-30*01", + "name": "IGHV1-30*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV1-30*01|Canis lupus familiaris|F|V-REGION|714117..714410|294 nt|1| | | | |294+24=318| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 294 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-2*01", + "name": "IGHV3-2*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-2*01|Canis lupus familiaris|F|V-REGION|1233843..1234138|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-3*01", + "name": "IGHV3-3*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-3*01|Canis lupus familiaris|F|V-REGION|1215699..1215994|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-5*01", + "name": "IGHV3-5*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-5*01|Canis lupus familiaris|F|V-REGION|1163008..1163303|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-6*01", + "name": "IGHV3-6*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-6*01|Canis lupus familiaris|F|V-REGION|1135209..1135501|293 nt|1| | | | |293+24=317| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 293 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-7*01", + "name": "IGHV3-7*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-7*01|Canis lupus familiaris|F|V-REGION|1118779..1119074|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-8*01", + "name": "IGHV3-8*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-8*01|Canis lupus familiaris|F|V-REGION|1112259..1112554|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-9*01", + "name": "IGHV3-9*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-9*01|Canis lupus familiaris|F|V-REGION|1087006..1087301|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-10*01", + "name": "IGHV3-10*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-10*01|Canis lupus familiaris|F|V-REGION|1077081..1077376|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-13*01", + "name": "IGHV3-13*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-13*01|Canis lupus familiaris|F|V-REGION|1005163..1005458|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-14*01", + "name": "IGHV3-14*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-14*01|Canis lupus familiaris|P|V-REGION|999692..999987|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-16*01", + "name": "IGHV3-16*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-16*01|Canis lupus familiaris|F|V-REGION|965853..966148|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-18*01", + "name": "IGHV3-18*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-18*01|Canis lupus familiaris|F|V-REGION|906835..907130|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-19*01", + "name": "IGHV3-19*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-19*01|Canis lupus familiaris|F|V-REGION|884529..884824|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-21*01", + "name": "IGHV3-21*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-21*01|Canis lupus familiaris|P|V-REGION|841592..841887|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-21-1*01", + "name": "IGHV3-21-1*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-21-1*01|Canis lupus familiaris|P|V-REGION|829178..829461|284 nt|1| | | | |284+36=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 273, + "VEnd": 284 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-23*01", + "name": "IGHV3-23*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-23*01|Canis lupus familiaris|F|V-REGION|805772..806067|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-24*01", + "name": "IGHV3-24*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-24*01|Canis lupus familiaris|F|V-REGION|794014..794309|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-25*01", + "name": "IGHV3-25*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-25*01|Canis lupus familiaris|P|V-REGION|785012..785307|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-26*01", + "name": "IGHV3-26*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-26*01|Canis lupus familiaris|P|V-REGION|762762..763057|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-31*01", + "name": "IGHV3-31*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-31*01|Canis lupus familiaris|P|V-REGION|706521..706816|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-32*01", + "name": "IGHV3-32*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-32*01|Canis lupus familiaris|ORF|V-REGION|695678..695973|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-33*01", + "name": "IGHV3-33*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-33*01|Canis lupus familiaris|P|V-REGION|692222..692515|294 nt|1| | | | |294+24=318| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 294 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-34*01", + "name": "IGHV3-34*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-34*01|Canis lupus familiaris|F|V-REGION|685238..685352|115 nt|1| | || |115+205=320|partial in 5'| |" + }, + "anchorPoints": { + "CDR3Begin": 104, + "VEnd": 115 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-35*01", + "name": "IGHV3-35*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-35*01|Canis lupus familiaris|F|V-REGION|676291..676586|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-36*01", + "name": "IGHV3-36*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-36*01|Canis lupus familiaris|P|V-REGION|671952..672247|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-37*01", + "name": "IGHV3-37*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-37*01|Canis lupus familiaris|F|V-REGION|652203..652495|293 nt|1| | | | |293+24=317| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 293 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-38*01", + "name": "IGHV3-38*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-38*01|Canis lupus familiaris|F|V-REGION|632668..632963|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-39*01", + "name": "IGHV3-39*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-39*01|Canis lupus familiaris|F|V-REGION|616746..617041|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-41*01", + "name": "IGHV3-41*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-41*01|Canis lupus familiaris|F|V-REGION|586047..586342|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-43*01", + "name": "IGHV3-43*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-43*01|Canis lupus familiaris|P|V-REGION|558635..558930|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-44*01", + "name": "IGHV3-44*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-44*01|Canis lupus familiaris|ORF|V-REGION|542441..542736|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-45*01", + "name": "IGHV3-45*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-45*01|Canis lupus familiaris|P|V-REGION|536792..537086|295 nt|1| | | | |295+24=319| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 295 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-46*01", + "name": "IGHV3-46*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-46*01|Canis lupus familiaris|F|V-REGION|527739..528031|293 nt|1| | | | |293+24=317| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 293 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-47*01", + "name": "IGHV3-47*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-47*01|Canis lupus familiaris|F|V-REGION|520717..521009|293 nt|1| | | | |293+24=317| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 293 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-47-1*01", + "name": "IGHV3-47-1*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-47-1*01|Canis lupus familiaris|P|V-REGION|497588..497880|293 nt|1| | | | |293+24=317| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 293 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-50*01", + "name": "IGHV3-50*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-50*01|Canis lupus familiaris|F|V-REGION|434142..434437|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-52*01", + "name": "IGHV3-52*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-52*01|Canis lupus familiaris|P|V-REGION|420977..421272|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-54*01", + "name": "IGHV3-54*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-54*01|Canis lupus familiaris|F|V-REGION|385679..385975|297 nt|1| | | | |297+24=321| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 297 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-58*01", + "name": "IGHV3-58*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-58*01|Canis lupus familiaris|F|V-REGION|315906..316201|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-60*01", + "name": "IGHV3-60*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-60*01|Canis lupus familiaris|P|V-REGION|297702..297985|284 nt|1| | | | |284+36=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 273, + "VEnd": 284 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-61*01", + "name": "IGHV3-61*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-61*01|Canis lupus familiaris|F|V-REGION|291231..291526|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-67*01", + "name": "IGHV3-67*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-67*01|Canis lupus familiaris|F|V-REGION|219729..220024|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-69*01", + "name": "IGHV3-69*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-69*01|Canis lupus familiaris|F|V-REGION|203949..204244|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-70*01", + "name": "IGHV3-70*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-70*01|Canis lupus familiaris|F|V-REGION|192208..192503|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-72*01", + "name": "IGHV3-72*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-72*01|Canis lupus familiaris|P|V-REGION|170231..170526|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-75*01", + "name": "IGHV3-75*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-75*01|Canis lupus familiaris|F|V-REGION|127588..127880|293 nt|1| | | | |293+24=317| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 293 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-76*01", + "name": "IGHV3-76*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-76*01|Canis lupus familiaris|F|V-REGION|105886..106178|293 nt|1| | | | |293+24=317| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 293 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-78*01", + "name": "IGHV3-78*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-78*01|Canis lupus familiaris|P|V-REGION|59377..59672|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-80*01", + "name": "IGHV3-80*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-80*01|Canis lupus familiaris|F|V-REGION|47828..48123|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-81*01", + "name": "IGHV3-81*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-81*01|Canis lupus familiaris|F|V-REGION|29307..29602|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-82*01", + "name": "IGHV3-82*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-82*01|Canis lupus familiaris|F|V-REGION|24021..24316|296 nt|1| | | | |296+24=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV3-83*01", + "name": "IGHV3-83*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV3-83*01|Canis lupus familiaris|P|V-REGION|1011..1307|297 nt|1| | | | |297+24=321| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 285, + "VEnd": 297 + } + }, { + "baseSequence": "file://dog_V_IGH.fasta#IGHV4-1*01", + "name": "IGHV4-1*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHV4-1*01|Canis lupus familiaris|F|V-REGION|1243371..1243660|290 nt|1| | | | |290+30=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 102, + "CDR2Begin": 153, + "FR3Begin": 168, + "CDR3Begin": 279, + "VEnd": 290 + } + }, { + "baseSequence": "file://dog_D_IGH.fasta#IGHD1*01", + "name": "IGHD1*01", + "geneType": "D", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHD1*01|Canis lupus familiaris|F|D-REGION|1247360..1247390|31 nt|1| | | | |31+0=31| | |" + }, + "anchorPoints": { + "DBegin": 0, + "DEnd": 31 + } + }, { + "baseSequence": "file://dog_D_IGH.fasta#IGHD2*01", + "name": "IGHD2*01", + "geneType": "D", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHD2*01|Canis lupus familiaris|F|D-REGION|1247950..1247968|19 nt|1| | | | |19+0=19| | |" + }, + "anchorPoints": { + "DBegin": 0, + "DEnd": 19 + } + }, { + "baseSequence": "file://dog_D_IGH.fasta#IGHD3*01", + "name": "IGHD3*01", + "geneType": "D", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHD3*01|Canis lupus familiaris|F|D-REGION|1249343..1249359|17 nt|1| | | | |17+0=17| | |" + }, + "anchorPoints": { + "DBegin": 0, + "DEnd": 17 + } + }, { + "baseSequence": "file://dog_D_IGH.fasta#IGHD4*01", + "name": "IGHD4*01", + "geneType": "D", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHD4*01|Canis lupus familiaris|F|D-REGION|1251017..1251035|19 nt|1| | | | |19+0=19| | |" + }, + "anchorPoints": { + "DBegin": 0, + "DEnd": 19 + } + }, { + "baseSequence": "file://dog_D_IGH.fasta#IGHD5*01", + "name": "IGHD5*01", + "geneType": "D", + "isFunctional": false, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHD5*01|Canis lupus familiaris|ORF|D-REGION|1251280..1251298|19 nt|1| | | | |19+0=19| | |" + }, + "anchorPoints": { + "DBegin": 0, + "DEnd": 19 + } + }, { + "baseSequence": "file://dog_D_IGH.fasta#IGHD6*01", + "name": "IGHD6*01", + "geneType": "D", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHD6*01|Canis lupus familiaris|F|D-REGION|1280507..1280517|11 nt|1| | | | |11+0=11| | |" + }, + "anchorPoints": { + "DBegin": 0, + "DEnd": 11 + } + }, { + "baseSequence": "file://dog_J_IGH.fasta#IGHJ1*01", + "name": "IGHJ1*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHJ1*01|Canis lupus familiaris|ORF|J-REGION|1281009..1281061|53 nt|2| | || |53+0=53| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 22, + "FR4End": 53 + } + }, { + "baseSequence": "file://dog_J_IGH.fasta#IGHJ2*01", + "name": "IGHJ2*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHJ2*01|Canis lupus familiaris|F|J-REGION|1281345..1281398|54 nt|3| | || |54+0=54| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 23, + "FR4End": 54 + } + }, { + "baseSequence": "file://dog_J_IGH.fasta#IGHJ3*01", + "name": "IGHJ3*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHJ3*01|Canis lupus familiaris|F|J-REGION|1281557..1281606|50 nt|2| | || |50+0=50| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 19, + "FR4End": 50 + } + }, { + "baseSequence": "file://dog_J_IGH.fasta#IGHJ4*01", + "name": "IGHJ4*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHJ4*01|Canis lupus familiaris|F|J-REGION|1281909..1281956|48 nt|3| | || |48+0=48| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 17, + "FR4End": 48 + } + }, { + "baseSequence": "file://dog_J_IGH.fasta#IGHJ5*01", + "name": "IGHJ5*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHJ5*01|Canis lupus familiaris|F|J-REGION|1282219..1282269|51 nt|3| | || |51+0=51| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 20, + "FR4End": 51 + } + }, { + "baseSequence": "file://dog_J_IGH.fasta#IGHJ6*01", + "name": "IGHJ6*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "IGH" ], + "meta": { + "comments": "IMGT000001|IGHJ6*01|Canis lupus familiaris|F|J-REGION|1282783..1282836|54 nt|3| | || |54+0=54| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 23, + "FR4End": 54 + } + }, { + "baseSequence": "file://dog_V_IGK.fasta#IGKV2-4*01", + "name": "IGKV2-4*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGK" ], + "meta": { + "comments": "IMGT000002|IGKV2-4*01|Canis lupus familiaris_boxer|F|V-REGION|87583..87884|302 nt|1| | | | |302+33=335| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 111, + "CDR2Begin": 162, + "FR3Begin": 171, + "CDR3Begin": 276, + "VEnd": 302 + } + }, { + "baseSequence": "file://dog_V_IGK.fasta#IGKV2-5*01", + "name": "IGKV2-5*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGK" ], + "meta": { + "comments": "IMGT000002|IGKV2-5*01|Canis lupus familiaris_boxer|F|V-REGION|76296..76597|302 nt|1| | | | |302+33=335| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 111, + "CDR2Begin": 162, + "FR3Begin": 171, + "CDR3Begin": 276, + "VEnd": 302 + } + }, { + "baseSequence": "file://dog_V_IGK.fasta#IGKV2-6*01", + "name": "IGKV2-6*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGK" ], + "meta": { + "comments": "IMGT000002|IGKV2-6*01|Canis lupus familiaris_boxer|F|V-REGION|69194..69495|302 nt|1| | | | |302+33=335| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 111, + "CDR2Begin": 162, + "FR3Begin": 171, + "CDR3Begin": 276, + "VEnd": 302 + } + }, { + "baseSequence": "file://dog_V_IGK.fasta#IGKV2-7*01", + "name": "IGKV2-7*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGK" ], + "meta": { + "comments": "IMGT000002|IGKV2-7*01|Canis lupus familiaris_boxer|F|V-REGION|56253..56554|302 nt|1| | | | |302+33=335| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 111, + "CDR2Begin": 162, + "FR3Begin": 171, + "CDR3Begin": 276, + "VEnd": 302 + } + }, { + "baseSequence": "file://dog_V_IGK.fasta#IGKV2-8*01", + "name": "IGKV2-8*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGK" ], + "meta": { + "comments": "IMGT000002|IGKV2-8*01|Canis lupus familiaris_boxer|F|V-REGION|37622..37923|302 nt|1| | | | |302+33=335| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 111, + "CDR2Begin": 162, + "FR3Begin": 171, + "CDR3Begin": 276, + "VEnd": 302 + } + }, { + "baseSequence": "file://dog_V_IGK.fasta#IGKV2-9*01", + "name": "IGKV2-9*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGK" ], + "meta": { + "comments": "IMGT000002|IGKV2-9*01|Canis lupus familiaris_boxer|F|V-REGION|23078..23379|302 nt|1| | | | |302+33=335| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 111, + "CDR2Begin": 162, + "FR3Begin": 171, + "CDR3Begin": 276, + "VEnd": 302 + } + }, { + "baseSequence": "file://dog_V_IGK.fasta#IGKV2-10*01", + "name": "IGKV2-10*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGK" ], + "meta": { + "comments": "IMGT000002|IGKV2-10*01|Canis lupus familiaris_boxer|F|V-REGION|13334..13635|302 nt|1| | | | |302+33=335| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 111, + "CDR2Begin": 162, + "FR3Begin": 171, + "CDR3Begin": 276, + "VEnd": 302 + } + }, { + "baseSequence": "file://dog_V_IGK.fasta#IGKV2-11*01", + "name": "IGKV2-11*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGK" ], + "meta": { + "comments": "IMGT000002|IGKV2-11*01|Canis lupus familiaris_boxer|F|V-REGION|1012..1313|302 nt|1| | | | |302+33=335| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 111, + "CDR2Begin": 162, + "FR3Begin": 171, + "CDR3Begin": 276, + "VEnd": 302 + } + }, { + "baseSequence": "file://dog_V_IGK.fasta#IGKV2S13*01", + "name": "IGKV2S13*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGK" ], + "meta": { + "comments": "IMGT000002|IGKV2S13*01|Canis lupus familiaris_boxer|F|V-REGION|342391..342692|302 nt|1| | | | |302+33=335| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 111, + "CDR2Begin": 162, + "FR3Begin": 171, + "CDR3Begin": 276, + "VEnd": 302 + } + }, { + "baseSequence": "file://dog_V_IGK.fasta#IGKV2S14*01", + "name": "IGKV2S14*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGK" ], + "meta": { + "comments": "IMGT000002|IGKV2S14*01|Canis lupus familiaris_boxer|P|V-REGION|328480..328781|302 nt|1| | | | |302+33=335| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 111, + "CDR2Begin": 162, + "FR3Begin": 171, + "CDR3Begin": 276, + "VEnd": 302 + } + }, { + "baseSequence": "file://dog_V_IGK.fasta#IGKV2S16*01", + "name": "IGKV2S16*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGK" ], + "meta": { + "comments": "IMGT000002|IGKV2S16*01|Canis lupus familiaris_boxer|F|V-REGION|290540..290841|302 nt|1| | | | |302+33=335| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 111, + "CDR2Begin": 162, + "FR3Begin": 171, + "CDR3Begin": 276, + "VEnd": 302 + } + }, { + "baseSequence": "file://dog_V_IGK.fasta#IGKV2S18*01", + "name": "IGKV2S18*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGK" ], + "meta": { + "comments": "IMGT000002|IGKV2S18*01|Canis lupus familiaris_boxer|P|V-REGION|232719..233020|302 nt|1| | | | |302+33=335| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 111, + "CDR2Begin": 162, + "FR3Begin": 171, + "CDR3Begin": 276, + "VEnd": 302 + } + }, { + "baseSequence": "file://dog_V_IGK.fasta#IGKV2S19*01", + "name": "IGKV2S19*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGK" ], + "meta": { + "comments": "IMGT000002|IGKV2S19*01|Canis lupus familiaris_boxer|F|V-REGION|216751..217052|302 nt|1| | | | |302+33=335| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 111, + "CDR2Begin": 162, + "FR3Begin": 171, + "CDR3Begin": 276, + "VEnd": 302 + } + }, { + "baseSequence": "file://dog_V_IGK.fasta#IGKV3S1*01", + "name": "IGKV3S1*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGK" ], + "meta": { + "comments": "IMGT000002|IGKV3S1*01|Canis lupus familiaris_boxer|F|V-REGION|307021..307306|286 nt|1| | | | |286+48=334| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 156, + "CDR3Begin": 261, + "VEnd": 286 + } + }, { + "baseSequence": "file://dog_V_IGK.fasta#IGKV4-1*01", + "name": "IGKV4-1*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGK" ], + "meta": { + "comments": "IMGT000002|IGKV4-1*01|Canis lupus familiaris_boxer|ORF|V-REGION|112219..112505|287 nt|1| | | | |287+48=335| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 156, + "CDR3Begin": 261, + "VEnd": 287 + } + }, { + "baseSequence": "file://dog_V_IGK.fasta#IGKV4S1*01", + "name": "IGKV4S1*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "IGK" ], + "meta": { + "comments": "IMGT000002|IGKV4S1*01|Canis lupus familiaris_boxer|F|V-REGION|283808..284112|305 nt|1| | | | |305+30=335| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 114, + "CDR2Begin": 165, + "FR3Begin": 174, + "CDR3Begin": 279, + "VEnd": 305 + } + }, { + "baseSequence": "file://dog_J_IGK.fasta#IGKJ1*01", + "name": "IGKJ1*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "IGK" ], + "meta": { + "comments": "IMGT000002|IGKJ1*01|Canis lupus familiaris_boxer|F|J-REGION|134511..134548|38 nt|2| | | | |38+0=38| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 10, + "FR4End": 38 + } + }, { + "baseSequence": "file://dog_J_IGK.fasta#IGKJ2*01", + "name": "IGKJ2*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "IGK" ], + "meta": { + "comments": "IMGT000002|IGKJ2*01|Canis lupus familiaris_boxer|ORF|J-REGION|134863..134901|39 nt|3| | || |39+0=39| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 12, + "FR4End": 39 + } + }, { + "baseSequence": "file://dog_J_IGK.fasta#IGKJ3*01", + "name": "IGKJ3*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "IGK" ], + "meta": { + "comments": "IMGT000002|IGKJ3*01|Canis lupus familiaris_boxer|F|J-REGION|135172..135209|38 nt|2| | | | |38+0=38| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 10, + "FR4End": 38 + } + }, { + "baseSequence": "file://dog_J_IGK.fasta#IGKJ4*01", + "name": "IGKJ4*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "IGK" ], + "meta": { + "comments": "IMGT000002|IGKJ4*01|Canis lupus familiaris_boxer|F|J-REGION|135504..135541|38 nt|2| | | | |38+0=38| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 10, + "FR4End": 38 + } + }, { + "baseSequence": "file://dog_J_IGK.fasta#IGKJ5*01", + "name": "IGKJ5*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "IGK" ], + "meta": { + "comments": "IMGT000002|IGKJ5*01|Canis lupus familiaris_boxer|F|J-REGION|135820..135857|38 nt|2| | | | |38+0=38| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 10, + "FR4End": 38 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-41*01", + "name": "Canlupfam_IGLV1-41*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-41*01|Canis lupus familiaris|IGLV1|ORF|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 299 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-44*01", + "name": "Canlupfam_IGLV1-44*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-44*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-45*01", + "name": "Canlupfam_IGLV1-45*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-45*01|Canis lupus familiaris|IGLV1|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 294 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-46*01", + "name": "Canlupfam_IGLV1-46*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-46*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-48*01", + "name": "Canlupfam_IGLV1-48*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-48*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-49*01", + "name": "Canlupfam_IGLV1-49*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-49*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 299 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-50*01", + "name": "Canlupfam_IGLV1-50*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-50*01|Canis lupus familiaris|IGLV1|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-52*01", + "name": "Canlupfam_IGLV1-52*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-52*01|Canis lupus familiaris|IGLV1|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-54*01", + "name": "Canlupfam_IGLV1-54*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-54*01|Canis lupus familiaris|IGLV1|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-55*01", + "name": "Canlupfam_IGLV1-55*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-55*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-56*01", + "name": "Canlupfam_IGLV1-56*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-56*01|Canis lupus familiaris|IGLV1|ORF|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-57*01", + "name": "Canlupfam_IGLV1-57*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-57*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 299 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-58*01", + "name": "Canlupfam_IGLV1-58*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-58*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-66*01", + "name": "Canlupfam_IGLV1-66*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-66*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-67*01", + "name": "Canlupfam_IGLV1-67*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-67*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-69*01", + "name": "Canlupfam_IGLV1-69*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-69*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 292 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-70*01", + "name": "Canlupfam_IGLV1-70*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-70*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 299 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-72*01", + "name": "Canlupfam_IGLV1-72*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-72*01|Canis lupus familiaris|IGLV1|ORF|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 72, + "FR2Begin": 90, + "CDR2Begin": 138, + "CDR3Begin": 252, + "VEnd": 293 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-73*01", + "name": "Canlupfam_IGLV1-73*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-73*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-75*01", + "name": "Canlupfam_IGLV1-75*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-75*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-78*01", + "name": "Canlupfam_IGLV1-78*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-78*01|Canis lupus familiaris|IGLV1|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-80*01", + "name": "Canlupfam_IGLV1-80*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-80*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-82*01", + "name": "Canlupfam_IGLV1-82*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-82*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 90, + "CDR2Begin": 138, + "CDR3Begin": 252, + "VEnd": 293 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-84*01", + "name": "Canlupfam_IGLV1-84*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-84*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 299 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-84-1*01", + "name": "Canlupfam_IGLV1-84-1*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-84-1*01|Canis lupus familiaris|IGLV1|ORF|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 294 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-86*01", + "name": "Canlupfam_IGLV1-86*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-86*01|Canis lupus familiaris|IGLV1|ORF|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 111, + "CDR2Begin": 159, + "CDR3Begin": 273, + "VEnd": 314 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-87*01", + "name": "Canlupfam_IGLV1-87*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-87*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 299 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-89*01", + "name": "Canlupfam_IGLV1-89*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-89*01|Canis lupus familiaris|IGLV1|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-92*01", + "name": "Canlupfam_IGLV1-92*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-92*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 299 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-94*01", + "name": "Canlupfam_IGLV1-94*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-94*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-96*01", + "name": "Canlupfam_IGLV1-96*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-96*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 299 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-97-1*01", + "name": "Canlupfam_IGLV1-97-1*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-97-1*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-98*01", + "name": "Canlupfam_IGLV1-98*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-98*01|Canis lupus familiaris|IGLV1|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 299 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-100*01", + "name": "Canlupfam_IGLV1-100*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-100*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-103*01", + "name": "Canlupfam_IGLV1-103*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-103*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 299 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-104*01", + "name": "Canlupfam_IGLV1-104*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-104*01|Canis lupus familiaris|IGLV1|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 138, + "CDR3Begin": 252, + "VEnd": 293 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-106*01", + "name": "Canlupfam_IGLV1-106*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-106*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-111*01", + "name": "Canlupfam_IGLV1-111*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-111*01|Canis lupus familiaris|IGLV1|ORF|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-112*01", + "name": "Canlupfam_IGLV1-112*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-112*01|Canis lupus familiaris|IGLV1|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 299 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-114*01", + "name": "Canlupfam_IGLV1-114*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-114*01|Canis lupus familiaris|IGLV1|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 293 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-115*01", + "name": "Canlupfam_IGLV1-115*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-115*01|Canis lupus familiaris|IGLV1|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-116*01", + "name": "Canlupfam_IGLV1-116*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-116*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-118*01", + "name": "Canlupfam_IGLV1-118*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-118*01|Canis lupus familiaris|IGLV1|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-125*01", + "name": "Canlupfam_IGLV1-125*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-125*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-129*01", + "name": "Canlupfam_IGLV1-129*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-129*01|Canis lupus familiaris|IGLV1|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 299 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-130*01", + "name": "Canlupfam_IGLV1-130*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-130*01|Canis lupus familiaris|IGLV1|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-135*01", + "name": "Canlupfam_IGLV1-135*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-135*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-136*01", + "name": "Canlupfam_IGLV1-136*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-136*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-138*01", + "name": "Canlupfam_IGLV1-138*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-138*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 299 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-139*01", + "name": "Canlupfam_IGLV1-139*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-139*01|Canis lupus familiaris|IGLV1|ORF|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 297 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-141*01", + "name": "Canlupfam_IGLV1-141*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-141*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-143*01", + "name": "Canlupfam_IGLV1-143*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-143*01|Canis lupus familiaris|IGLV1|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 299 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-144*01", + "name": "Canlupfam_IGLV1-144*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-144*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-146*01", + "name": "Canlupfam_IGLV1-146*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-146*01|Canis lupus familiaris|IGLV1|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-147*01", + "name": "Canlupfam_IGLV1-147*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-147*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-149*01", + "name": "Canlupfam_IGLV1-149*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-149*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 299 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-150*01", + "name": "Canlupfam_IGLV1-150*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-150*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 299 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-151*01", + "name": "Canlupfam_IGLV1-151*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-151*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 299 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-152*01", + "name": "Canlupfam_IGLV1-152*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-152*01|Canis lupus familiaris|IGLV1|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-155*01", + "name": "Canlupfam_IGLV1-155*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-155*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-157*01", + "name": "Canlupfam_IGLV1-157*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-157*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-158*01", + "name": "Canlupfam_IGLV1-158*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-158*01|Canis lupus familiaris|IGLV1|ORF|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-159*01", + "name": "Canlupfam_IGLV1-159*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-159*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-160*01", + "name": "Canlupfam_IGLV1-160*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-160*01|Canis lupus familiaris|IGLV1|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 299 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-162*01", + "name": "Canlupfam_IGLV1-162*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV1-162*01|Canis lupus familiaris|IGLV1|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 141, + "CDR3Begin": 255, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV2-31*01", + "name": "Canlupfam_IGLV2-31*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV2-31*01|Canis lupus familiaris|IGLV2|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 297 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV2-32*01", + "name": "Canlupfam_IGLV2-32*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV2-32*01|Canis lupus familiaris|IGLV2|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-2*01", + "name": "Canlupfam_IGLV3-2*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV3-2*01|Canis lupus familiaris|IGLV3|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 87, + "CDR2Begin": 135, + "CDR3Begin": 249, + "VEnd": 288 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-3*01", + "name": "Canlupfam_IGLV3-3*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV3-3*01|Canis lupus familiaris|IGLV3|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 87, + "CDR2Begin": 135, + "CDR3Begin": 249, + "VEnd": 288 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-4*01", + "name": "Canlupfam_IGLV3-4*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV3-4*01|Canis lupus familiaris|IGLV3|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 87, + "CDR2Begin": 135, + "CDR3Begin": 249, + "VEnd": 288 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-7*01", + "name": "Canlupfam_IGLV3-7*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV3-7*01|Canis lupus familiaris|IGLV3|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 87, + "CDR2Begin": 135, + "CDR3Begin": 249, + "VEnd": 288 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-8*01", + "name": "Canlupfam_IGLV3-8*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV3-8*01|Canis lupus familiaris|IGLV3|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 87, + "CDR2Begin": 135, + "CDR3Begin": 249, + "VEnd": 288 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-11*01", + "name": "Canlupfam_IGLV3-11*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV3-11*01|Canis lupus familiaris|IGLV3|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 87, + "CDR2Begin": 135, + "CDR3Begin": 249, + "VEnd": 288 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-13*01", + "name": "Canlupfam_IGLV3-13*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV3-13*01|Canis lupus familiaris|IGLV3|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 87, + "CDR2Begin": 135, + "CDR3Begin": 249, + "VEnd": 288 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-14*01", + "name": "Canlupfam_IGLV3-14*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV3-14*01|Canis lupus familiaris|IGLV3|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 87, + "CDR2Begin": 135, + "CDR3Begin": 249, + "VEnd": 288 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-15*01", + "name": "Canlupfam_IGLV3-15*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV3-15*01|Canis lupus familiaris|IGLV3|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 87, + "CDR2Begin": 135, + "CDR3Begin": 249, + "VEnd": 288 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-19*01", + "name": "Canlupfam_IGLV3-19*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV3-19*01|Canis lupus familiaris|IGLV3|ORF|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 87, + "CDR2Begin": 135, + "CDR3Begin": 249, + "VEnd": 288 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-21*01", + "name": "Canlupfam_IGLV3-21*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV3-21*01|Canis lupus familiaris|IGLV3|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 87, + "CDR2Begin": 135, + "CDR3Begin": 249, + "VEnd": 288 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-23*01", + "name": "Canlupfam_IGLV3-23*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV3-23*01|Canis lupus familiaris|IGLV3|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 87, + "CDR2Begin": 135, + "CDR3Begin": 249, + "VEnd": 288 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-24*01", + "name": "Canlupfam_IGLV3-24*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV3-24*01|Canis lupus familiaris|IGLV3|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 87, + "CDR2Begin": 135, + "CDR3Begin": 249, + "VEnd": 288 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-25*01", + "name": "Canlupfam_IGLV3-25*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV3-25*01|Canis lupus familiaris|IGLV3|ORF|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 87, + "CDR2Begin": 135, + "CDR3Begin": 249, + "VEnd": 288 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-26*01", + "name": "Canlupfam_IGLV3-26*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV3-26*01|Canis lupus familiaris|IGLV3|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 87, + "CDR2Begin": 135, + "CDR3Begin": 249, + "VEnd": 288 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-27*01", + "name": "Canlupfam_IGLV3-27*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV3-27*01|Canis lupus familiaris|IGLV3|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 87, + "CDR2Begin": 135, + "CDR3Begin": 249, + "VEnd": 288 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-28*01", + "name": "Canlupfam_IGLV3-28*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV3-28*01|Canis lupus familiaris|IGLV3|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 87, + "CDR2Begin": 135, + "CDR3Begin": 249, + "VEnd": 288 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-29*01", + "name": "Canlupfam_IGLV3-29*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV3-29*01|Canis lupus familiaris|IGLV3|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 87, + "CDR2Begin": 135, + "CDR3Begin": 249, + "VEnd": 288 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-30*01", + "name": "Canlupfam_IGLV3-30*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV3-30*01|Canis lupus familiaris|IGLV3|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 87, + "CDR2Begin": 135, + "CDR3Begin": 249, + "VEnd": 288 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV4-5*01", + "name": "Canlupfam_IGLV4-5*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV4-5*01|Canis lupus familiaris|IGLV4|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 90, + "CDR2Begin": 138, + "FR3Begin": 159, + "CDR3Begin": 264, + "VEnd": 317 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV4-6*01", + "name": "Canlupfam_IGLV4-6*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV4-6*01|Canis lupus familiaris|IGLV4|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 90, + "CDR2Begin": 141, + "FR3Begin": 162, + "CDR3Begin": 267, + "VEnd": 303 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV4-10*01", + "name": "Canlupfam_IGLV4-10*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV4-10*01|Canis lupus familiaris|IGLV4|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 90, + "CDR2Begin": 138, + "FR3Begin": 159, + "CDR3Begin": 264, + "VEnd": 317 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV4-16*01", + "name": "Canlupfam_IGLV4-16*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV4-16*01|Canis lupus familiaris|IGLV4|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 90, + "CDR2Begin": 138, + "FR3Begin": 159, + "CDR3Begin": 264, + "VEnd": 317 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV4-20*01", + "name": "Canlupfam_IGLV4-20*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV4-20*01|Canis lupus familiaris|IGLV4|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 90, + "CDR2Begin": 138, + "FR3Begin": 159, + "CDR3Begin": 264, + "VEnd": 317 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV4-22*01", + "name": "Canlupfam_IGLV4-22*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV4-22*01|Canis lupus familiaris|IGLV4|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 90, + "CDR2Begin": 141, + "FR3Begin": 162, + "CDR3Begin": 267, + "VEnd": 303 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV5-64*01", + "name": "Canlupfam_IGLV5-64*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV5-64*01|Canis lupus familiaris|IGLV5|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "FR3Begin": 165, + "CDR3Begin": 276, + "VEnd": 325 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV5-85*01", + "name": "Canlupfam_IGLV5-85*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV5-85*01|Canis lupus familiaris|IGLV5|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "FR3Begin": 165, + "CDR3Begin": 276, + "VEnd": 325 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV5-109*01", + "name": "Canlupfam_IGLV5-109*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV5-109*01|Canis lupus familiaris|IGLV5|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 270, + "VEnd": 319 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV5-131*01", + "name": "Canlupfam_IGLV5-131*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV5-131*01|Canis lupus familiaris|IGLV5|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "FR3Begin": 165, + "CDR3Begin": 276, + "VEnd": 325 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-36*01", + "name": "Canlupfam_IGLV8-36*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV8-36*01|Canis lupus familiaris|IGLV8|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 299 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-39*01", + "name": "Canlupfam_IGLV8-39*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV8-39*01|Canis lupus familiaris|IGLV8|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-43*01", + "name": "Canlupfam_IGLV8-43*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV8-43*01|Canis lupus familiaris|IGLV8|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-93*01", + "name": "Canlupfam_IGLV8-93*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV8-93*01|Canis lupus familiaris|IGLV8|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-99*01", + "name": "Canlupfam_IGLV8-99*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV8-99*01|Canis lupus familiaris|IGLV8|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-102*01", + "name": "Canlupfam_IGLV8-102*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV8-102*01|Canis lupus familiaris|IGLV8|ORF|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 69, + "FR2Begin": 90, + "CDR2Begin": 138, + "CDR3Begin": 252, + "VEnd": 300 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-108*01", + "name": "Canlupfam_IGLV8-108*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV8-108*01|Canis lupus familiaris|IGLV8|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 307 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-113*01", + "name": "Canlupfam_IGLV8-113*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV8-113*01|Canis lupus familiaris|IGLV8|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-119*01", + "name": "Canlupfam_IGLV8-119*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV8-119*01|Canis lupus familiaris|IGLV8|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-121*01", + "name": "Canlupfam_IGLV8-121*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV8-121*01|Canis lupus familiaris|IGLV8|P|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 307 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-128*01", + "name": "Canlupfam_IGLV8-128*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV8-128*01|Canis lupus familiaris|IGLV8|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-142*01", + "name": "Canlupfam_IGLV8-142*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV8-142*01|Canis lupus familiaris|IGLV8|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-153*01", + "name": "Canlupfam_IGLV8-153*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV8-153*01|Canis lupus familiaris|IGLV8|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-161*01", + "name": "Canlupfam_IGLV8-161*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLV8-161*01|Canis lupus familiaris|IGLV8|F|V-REGION" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 144, + "CDR3Begin": 258, + "VEnd": 296 + } + }, { + "baseSequence": "file://dog_J_IGL.fasta#Canlupfam_IGLJ1*01", + "name": "Canlupfam_IGLJ1*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLJ1*01|Canis lupus familiaris|F|J-REGION" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 10, + "FR4End": 38 + } + }, { + "baseSequence": "file://dog_J_IGL.fasta#Canlupfam_IGLJ2*01", + "name": "Canlupfam_IGLJ2*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLJ2*01|Canis lupus familiaris|F|J-REGION" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 10, + "FR4End": 38 + } + }, { + "baseSequence": "file://dog_J_IGL.fasta#Canlupfam_IGLJ3*01", + "name": "Canlupfam_IGLJ3*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLJ3*01|Canis lupus familiaris|F|J-REGION" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 10, + "FR4End": 38 + } + }, { + "baseSequence": "file://dog_J_IGL.fasta#Canlupfam_IGLJ4*01", + "name": "Canlupfam_IGLJ4*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLJ4*01|Canis lupus familiaris|F|J-REGION" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 10, + "FR4End": 38 + } + }, { + "baseSequence": "file://dog_J_IGL.fasta#Canlupfam_IGLJ5*01", + "name": "Canlupfam_IGLJ5*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLJ5*01|Canis lupus familiaris|F|J-REGION" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 10, + "FR4End": 38 + } + }, { + "baseSequence": "file://dog_J_IGL.fasta#Canlupfam_IGLJ6*01", + "name": "Canlupfam_IGLJ6*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLJ6*01|Canis lupus familiaris|F|J-REGION" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 10, + "FR4End": 38 + } + }, { + "baseSequence": "file://dog_J_IGL.fasta#Canlupfam_IGLJ7*01", + "name": "Canlupfam_IGLJ7*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLJ7*01|Canis lupus familiaris|F|J-REGION" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 10, + "FR4End": 38 + } + }, { + "baseSequence": "file://dog_J_IGL.fasta#Canlupfam_IGLJ8*01", + "name": "Canlupfam_IGLJ8*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLJ8*01|Canis lupus familiaris|F|J-REGION" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 10, + "FR4End": 38 + } + }, { + "baseSequence": "file://dog_J_IGL.fasta#Canlupfam_IGLJ9*01", + "name": "Canlupfam_IGLJ9*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "IGL" ], + "meta": { + "comments": "IMGT000003|Canlupfam_IGLJ9*01|Canis lupus familiaris|F|J-REGION" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 10, + "FR4End": 38 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV8-1*01", + "name": "TRAV8-1*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV8-1*01|Canis lupus familiaris_boxer|F|V-REGION|245069..245352|284 nt|1| | | | |284+39=323| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 171, + "CDR3Begin": 270, + "VEnd": 284 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV8-2*01", + "name": "TRAV8-2*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV8-2*01|Canis lupus familiaris_boxer|F|V-REGION|301190..301467|278 nt|1| | | | |278+45=323| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 165, + "CDR3Begin": 264, + "VEnd": 278 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV9-1*01", + "name": "TRAV9-1*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV9-1*01|Canis lupus familiaris_boxer|F|V-REGION|10534..10814|281 nt|1| | | | |281+42=323| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 168, + "CDR3Begin": 267, + "VEnd": 281 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV9-3*01", + "name": "TRAV9-3*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV9-3*01|Canis lupus familiaris_boxer|F|V-REGION|55186..55466|281 nt|1| | | | |281+42=323| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 168, + "CDR3Begin": 267, + "VEnd": 281 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV9-4*01", + "name": "TRAV9-4*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV9-4*01|Canis lupus familiaris_boxer|F|V-REGION|63912..64192|281 nt|1| | | | |281+42=323| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 168, + "CDR3Begin": 267, + "VEnd": 281 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV9-5*01", + "name": "TRAV9-5*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV9-5*01|Canis lupus familiaris_boxer|F|V-REGION|86037..86317|281 nt|1| | | | |281+42=323| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 168, + "CDR3Begin": 267, + "VEnd": 281 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV9-6*01", + "name": "TRAV9-6*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV9-6*01|Canis lupus familiaris_boxer|F|V-REGION|102097..102377|281 nt|1| | | | |281+42=323| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 168, + "CDR3Begin": 267, + "VEnd": 281 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV9-7*01", + "name": "TRAV9-7*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV9-7*01|Canis lupus familiaris_boxer|F|V-REGION|124679..124959|281 nt|1| | | | |281+42=323| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 168, + "CDR3Begin": 267, + "VEnd": 281 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV9-8*01", + "name": "TRAV9-8*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV9-8*01|Canis lupus familiaris_boxer|F|V-REGION|140347..140627|281 nt|1| | | | |281+42=323| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 168, + "CDR3Begin": 267, + "VEnd": 281 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV9-9*01", + "name": "TRAV9-9*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV9-9*01|Canis lupus familiaris_boxer|F|V-REGION|147510..147790|281 nt|1| | | | |281+42=323| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 168, + "CDR3Begin": 267, + "VEnd": 281 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV9-10*01", + "name": "TRAV9-10*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV9-10*01|Canis lupus familiaris_boxer|F|V-REGION|170231..170511|281 nt|1| | | | |281+42=323| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 168, + "CDR3Begin": 267, + "VEnd": 281 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV9-11*01", + "name": "TRAV9-11*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV9-11*01|Canis lupus familiaris_boxer|F|V-REGION|188356..188636|281 nt|1| | | | |281+42=323| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 168, + "CDR3Begin": 267, + "VEnd": 281 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV9-12*01", + "name": "TRAV9-12*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV9-12*01|Canis lupus familiaris_boxer|F|V-REGION|191471..191751|281 nt|1| | | | |281+42=323| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 168, + "CDR3Begin": 267, + "VEnd": 281 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV9-13*01", + "name": "TRAV9-13*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV9-13*01|Canis lupus familiaris_boxer|F|V-REGION|208760..209040|281 nt|1| | | | |281+42=323| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 168, + "CDR3Begin": 267, + "VEnd": 281 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV10*01", + "name": "TRAV10*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV10*01|Canis lupus familiaris_boxer|F|V-REGION|225402..225681|280 nt|1| | | | |280+42=322| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 168, + "CDR3Begin": 267, + "VEnd": 280 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV12*01", + "name": "TRAV12*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV12*01|Canis lupus familiaris_boxer|F|V-REGION|240874..241151|278 nt|1| | | | |278+45=323| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 165, + "CDR3Begin": 264, + "VEnd": 278 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV17*01", + "name": "TRAV17*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV17*01|Canis lupus familiaris_boxer|P|V-REGION|251351..251627|277 nt|1| | || |277+45=322| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 165, + "CDR3Begin": 264, + "VEnd": 277 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV19*01", + "name": "TRAV19*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV19*01|Canis lupus familiaris_boxer|F|V-REGION|262891..263175|285 nt|1| | | | |285+36=321| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 273, + "VEnd": 285 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV21*01", + "name": "TRAV21*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV21*01|Canis lupus familiaris_boxer|F|V-REGION|287088..287366|279 nt|1| | | | |279+42=321| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 168, + "CDR3Begin": 267, + "VEnd": 279 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV22*01", + "name": "TRAV22*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV22*01|Canis lupus familiaris_boxer|F|V-REGION|306248..306518|271 nt|1| | | | |271+51=322| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 159, + "CDR3Begin": 258, + "VEnd": 271 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV23*01", + "name": "TRAV23*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV23*01|Canis lupus familiaris_boxer|F|V-REGION|308547..308826|280 nt|1| | | | |280+42=322| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 168, + "CDR3Begin": 267, + "VEnd": 280 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV24*01", + "name": "TRAV24*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV24*01|Canis lupus familiaris_boxer|F|V-REGION|330101..330377|277 nt|1| | | | |277+42=319| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 168, + "CDR3Begin": 267, + "VEnd": 277 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV25*01", + "name": "TRAV25*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV25*01|Canis lupus familiaris_boxer|P|V-REGION|344760..345028|269 nt|1| | | | |269+51=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 159, + "CDR3Begin": 258, + "VEnd": 269 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV26*01", + "name": "TRAV26*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV26*01|Canis lupus familiaris_boxer|P|V-REGION|403592..403868|277 nt|1| | || |277+48=325| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 162, + "CDR3Begin": 261, + "VEnd": 277 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV27*01", + "name": "TRAV27*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV27*01|Canis lupus familiaris_boxer|P|V-REGION|362324..362597|274 nt|1| | || |274+45=319| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 165, + "CDR3Begin": 264, + "VEnd": 274 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV28*01", + "name": "TRAV28*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV28*01|Canis lupus familiaris_boxer|F|V-REGION|368424..368694|271 nt|1| | | | |271+51=322| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 159, + "CDR3Begin": 258, + "VEnd": 271 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV29*01", + "name": "TRAV29*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV29*01|Canis lupus familiaris_boxer|F|V-REGION|376447..376726|280 nt|1| | | | |280+42=322| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 168, + "CDR3Begin": 267, + "VEnd": 280 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV34*01", + "name": "TRAV34*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV34*01|Canis lupus familiaris_boxer|F|V-REGION|418160..418431|272 nt|1| | | | |272+48=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 261, + "VEnd": 272 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV35*01", + "name": "TRAV35*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV35*01|Canis lupus familiaris_boxer|F|V-REGION|430697..430971|275 nt|1| | | | |275+45=320| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 165, + "CDR3Begin": 264, + "VEnd": 275 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV37*01", + "name": "TRAV37*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV37*01|Canis lupus familiaris_boxer|P|V-REGION|459335..459615|281 nt|1| | || |281+42=323| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 168, + "CDR3Begin": 267, + "VEnd": 281 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV38-1*01", + "name": "TRAV38-1*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV38-1*01|Canis lupus familiaris_boxer|F|V-REGION|463518..463803|286 nt|1| | | | |286+39=325| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 171, + "CDR3Begin": 270, + "VEnd": 286 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV38-2*01", + "name": "TRAV38-2*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV38-2*01|Canis lupus familiaris_boxer|F|V-REGION|471255..471543|289 nt|1| | | | |289+36=325| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 174, + "CDR3Begin": 273, + "VEnd": 289 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV40*01", + "name": "TRAV40*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV40*01|Canis lupus familiaris_boxer|F|V-REGION|502015..502281|267 nt|1| | | | |267+60=327| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 156, + "CDR3Begin": 249, + "VEnd": 267 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV43-1*01", + "name": "TRAV43-1*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV43-1*01|Canis lupus familiaris_boxer|F|V-REGION|1003..1276|274 nt|1| | | | |274+48=322| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 261, + "VEnd": 274 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV43-2*01", + "name": "TRAV43-2*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV43-2*01|Canis lupus familiaris_boxer|F|V-REGION|24019..24292|274 nt|1| | | | |274+48=322| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 261, + "VEnd": 274 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV43-3*01", + "name": "TRAV43-3*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV43-3*01|Canis lupus familiaris_boxer|F|V-REGION|73210..73483|274 nt|1| | | | |274+48=322| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 261, + "VEnd": 274 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV43-4*01", + "name": "TRAV43-4*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV43-4*01|Canis lupus familiaris_boxer|F|V-REGION|113363..113636|274 nt|1| | | | |274+48=322| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 261, + "VEnd": 274 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV43-5*01", + "name": "TRAV43-5*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV43-5*01|Canis lupus familiaris_boxer|F|V-REGION|155886..156159|274 nt|1| | | | |274+48=322| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 261, + "VEnd": 274 + } + }, { + "baseSequence": "file://dog_V_TRA.fasta#TRAV43-6*01", + "name": "TRAV43-6*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAV43-6*01|Canis lupus familiaris_boxer|F|V-REGION|196421..196694|274 nt|1| | | | |274+48=322| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 261, + "VEnd": 274 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ2*01", + "name": "TRAJ2*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ2*01|Canis lupus familiaris_boxer|F|J-REGION|733789..733840|52 nt|1| | | | |52+0=52| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 21, + "FR4End": 52 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ3*01", + "name": "TRAJ3*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ3*01|Canis lupus familiaris_boxer|F|J-REGION|733246..733303|58 nt|1| | || |58+0=58| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 27, + "FR4End": 58 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ4*01", + "name": "TRAJ4*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ4*01|Canis lupus familiaris_boxer|F|J-REGION|732271..732333|63 nt|3| | || |63+0=63| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 32, + "FR4End": 63 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ5*01", + "name": "TRAJ5*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ5*01|Canis lupus familiaris_boxer|F|J-REGION|729430..729489|60 nt|3| | || |60+0=60| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 29, + "FR4End": 60 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ6*01", + "name": "TRAJ6*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ6*01|Canis lupus familiaris_boxer|F|J-REGION|728024..728085|62 nt|2| | || |62+0=62| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 31, + "FR4End": 62 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ7*01", + "name": "TRAJ7*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ7*01|Canis lupus familiaris_boxer|ORF|J-REGION|726290..726348|59 nt|2| | || |59+0=59| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 28, + "FR4End": 59 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ8*01", + "name": "TRAJ8*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ8*01|Canis lupus familiaris_boxer|F|J-REGION|724717..724776|60 nt|3| | || |60+0=60| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 29, + "FR4End": 60 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ9*01", + "name": "TRAJ9*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ9*01|Canis lupus familiaris_boxer|F|J-REGION|724134..724188|55 nt|1| | | | |55+0=55| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 24, + "FR4End": 55 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ10*01", + "name": "TRAJ10*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ10*01|Canis lupus familiaris_boxer|ORF|J-REGION|722220..722283|64 nt|1| | | | |64+0=64| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 33, + "FR4End": 64 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ11*01", + "name": "TRAJ11*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ11*01|Canis lupus familiaris_boxer|ORF|J-REGION|721214..721273|60 nt|3| | || |60+0=60| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 29, + "FR4End": 60 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ12*01", + "name": "TRAJ12*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ12*01|Canis lupus familiaris_boxer|F|J-REGION|720636..720695|60 nt|3| | || |60+0=60| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 29, + "FR4End": 60 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ13*01", + "name": "TRAJ13*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ13*01|Canis lupus familiaris_boxer|F|J-REGION|719795..719855|61 nt|1| | | | |61+0=61| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 30, + "FR4End": 61 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ14*01", + "name": "TRAJ14*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ14*01|Canis lupus familiaris_boxer|P|J-REGION|718595..718651|57 nt|3| | || |57+0=57| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 26, + "FR4End": 57 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ15*01", + "name": "TRAJ15*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ15*01|Canis lupus familiaris_boxer|F|J-REGION|717846..717905|60 nt|3| | || |60+0=60| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 29, + "FR4End": 60 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ16*01", + "name": "TRAJ16*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ16*01|Canis lupus familiaris_boxer|F|J-REGION|716697..716756|60 nt|3| | || |60+0=60| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 29, + "FR4End": 60 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ18*01", + "name": "TRAJ18*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ18*01|Canis lupus familiaris_boxer|F|J-REGION|714171..714236|66 nt|3| | || |66+0=66| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 35, + "FR4End": 66 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ19*01", + "name": "TRAJ19*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ19*01|Canis lupus familiaris_boxer|ORF|J-REGION|713778..713835|58 nt|1| | | | |58+0=58| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 27, + "FR4End": 58 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ20*01", + "name": "TRAJ20*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ20*01|Canis lupus familiaris_boxer|F|J-REGION|712800..712856|57 nt|3| | || |57+0=57| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 26, + "FR4End": 57 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ21*01", + "name": "TRAJ21*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ21*01|Canis lupus familiaris_boxer|F|J-REGION|712069..712123|55 nt|1| | | | |55+0=55| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 24, + "FR4End": 55 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ22*01", + "name": "TRAJ22*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ22*01|Canis lupus familiaris_boxer|ORF|J-REGION|710308..710368|61 nt|1| | | | |61+0=61| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 31, + "FR4End": 61 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ23*01", + "name": "TRAJ23*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ23*01|Canis lupus familiaris_boxer|F|J-REGION|708681..708743|63 nt|3| | || |63+0=63| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 32, + "FR4End": 63 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ24*01", + "name": "TRAJ24*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ24*01|Canis lupus familiaris_boxer|ORF|J-REGION|708249..708309|61 nt|1| | | | |61+0=61| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 31, + "FR4End": 61 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ25*01", + "name": "TRAJ25*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ25*01|Canis lupus familiaris_boxer|F|J-REGION|707385..707444|60 nt|3| | || |60+0=60| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 29, + "FR4End": 60 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ26*01", + "name": "TRAJ26*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ26*01|Canis lupus familiaris_boxer|F|J-REGION|707047..707106|60 nt|3| | || |60+0=60| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 29, + "FR4End": 60 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ27*01", + "name": "TRAJ27*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ27*01|Canis lupus familiaris_boxer|F|J-REGION|704884..704942|59 nt|2| | || |59+0=59| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 28, + "FR4End": 59 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ28*01", + "name": "TRAJ28*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ28*01|Canis lupus familiaris_boxer|F|J-REGION|704241..704303|63 nt|3| | || |63+0=63| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 32, + "FR4End": 63 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ30*01", + "name": "TRAJ30*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ30*01|Canis lupus familiaris_boxer|F|J-REGION|702156..702214|59 nt|2| | || |59+0=59| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 28, + "FR4End": 59 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ31*01", + "name": "TRAJ31*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ31*01|Canis lupus familiaris_boxer|F|J-REGION|700140..700196|57 nt|3| | || |57+0=57| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 26, + "FR4End": 57 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ32*01", + "name": "TRAJ32*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ32*01|Canis lupus familiaris_boxer|F|J-REGION|698613..698676|64 nt|1| | | | |64+0=64| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 33, + "FR4End": 64 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ33*01", + "name": "TRAJ33*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ33*01|Canis lupus familiaris_boxer|F|J-REGION|697858..697914|57 nt|3| | || |57+0=57| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 26, + "FR4End": 57 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ34*01", + "name": "TRAJ34*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ34*01|Canis lupus familiaris_boxer|F|J-REGION|697163..697220|58 nt|1| | | | |58+0=58| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 27, + "FR4End": 58 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ35*01", + "name": "TRAJ35*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ35*01|Canis lupus familiaris_boxer|ORF|J-REGION|696232..696293|62 nt|2| | || |62+0=62| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 32, + "FR4End": 62 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ37*01", + "name": "TRAJ37*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ37*01|Canis lupus familiaris_boxer|F|J-REGION|693936..693997|62 nt|2| | || |62+0=62| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 31, + "FR4End": 62 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ38*01", + "name": "TRAJ38*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ38*01|Canis lupus familiaris_boxer|F|J-REGION|692419..692480|62 nt|2| | || |62+0=62| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 31, + "FR4End": 62 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ39*01", + "name": "TRAJ39*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ39*01|Canis lupus familiaris_boxer|F|J-REGION|691773..691835|63 nt|3| | || |63+0=63| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 32, + "FR4End": 63 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ40*01", + "name": "TRAJ40*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ40*01|Canis lupus familiaris_boxer|F|J-REGION|689660..689720|61 nt|1| | | | |61+0=61| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 30, + "FR4End": 61 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ41*01", + "name": "TRAJ41*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ41*01|Canis lupus familiaris_boxer|F|J-REGION|687619..687680|62 nt|2| | || |62+0=62| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 31, + "FR4End": 62 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ42*01", + "name": "TRAJ42*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ42*01|Canis lupus familiaris_boxer|F|J-REGION|687109..687174|66 nt|3| | || |66+0=66| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 35, + "FR4End": 66 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ43*01", + "name": "TRAJ43*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ43*01|Canis lupus familiaris_boxer|F|J-REGION|686281..686337|57 nt|3| | || |57+0=57| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 26, + "FR4End": 57 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ44*01", + "name": "TRAJ44*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ44*01|Canis lupus familiaris_boxer|F|J-REGION|685186..685248|63 nt|3| | || |63+0=63| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 32, + "FR4End": 63 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ45*01", + "name": "TRAJ45*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ45*01|Canis lupus familiaris_boxer|F|J-REGION|684074..684136|63 nt|3| | || |63+0=63| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 32, + "FR4End": 63 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ46*01", + "name": "TRAJ46*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ46*01|Canis lupus familiaris_boxer|ORF|J-REGION|683561..683623|63 nt|3| | || |63+0=63| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 33, + "FR4End": 63 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ48*01", + "name": "TRAJ48*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ48*01|Canis lupus familiaris_boxer|F|J-REGION|680985..681047|63 nt|3| | || |63+0=63| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 32, + "FR4End": 63 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ49*01", + "name": "TRAJ49*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ49*01|Canis lupus familiaris_boxer|F|J-REGION|680179..680237|59 nt|2| | || |59+0=59| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 28, + "FR4End": 59 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ50*01", + "name": "TRAJ50*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ50*01|Canis lupus familiaris_boxer|F|J-REGION|679341..679400|60 nt|3| | || |60+0=60| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 29, + "FR4End": 60 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ52*01", + "name": "TRAJ52*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ52*01|Canis lupus familiaris_boxer|ORF|J-REGION|676953..677021|69 nt|3| | || |69+0=69| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 38, + "FR4End": 69 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ53*01", + "name": "TRAJ53*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ53*01|Canis lupus familiaris_boxer|ORF|J-REGION|673986..674044|59 nt|2| | || |59+0=59| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 28, + "FR4End": 59 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ54*01", + "name": "TRAJ54*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ54*01|Canis lupus familiaris_boxer|F|J-REGION|673338..673402|65 nt|2| | || |65+0=65| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 34, + "FR4End": 65 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ56*01", + "name": "TRAJ56*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ56*01|Canis lupus familiaris_boxer|F|J-REGION|670564..670625|62 nt|2| | || |62+0=62| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 31, + "FR4End": 62 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ57*01", + "name": "TRAJ57*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ57*01|Canis lupus familiaris_boxer|ORF|J-REGION|669917..669979|63 nt|3| | || |63+0=63| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 33, + "FR4End": 63 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ58*01", + "name": "TRAJ58*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ58*01|Canis lupus familiaris_boxer|F|J-REGION|668697..668759|63 nt|3| | || |63+0=63| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 32, + "FR4End": 63 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ59*01", + "name": "TRAJ59*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ59*01|Canis lupus familiaris_boxer|ORF|J-REGION|667549..667608|60 nt|3| | || |60+0=60| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 30, + "FR4End": 60 + } + }, { + "baseSequence": "file://dog_J_TRA.fasta#TRAJ60*01", + "name": "TRAJ60*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRA" ], + "meta": { + "comments": "IMGT000004|TRAJ60*01|Canis lupus familiaris_boxer|F|J-REGION|667306..667361|56 nt|2| | || |56+0=56| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 25, + "FR4End": 56 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV1*01", + "name": "TRBV1*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV1*01|Canis lupus familiaris_boxer|F|V-REGION|990..1276|287 nt|1| | | | |287+45=332| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 165, + "CDR3Begin": 264, + "VEnd": 287 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV3-1*01", + "name": "TRBV3-1*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV3-1*01|Canis lupus familiaris_boxer|F|V-REGION|56373..56659|287 nt|1| | | | |287+45=332| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 264, + "VEnd": 287 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV3-2*01", + "name": "TRBV3-2*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV3-2*01|Canis lupus familiaris_boxer|F|V-REGION|67006..67292|287 nt|1| | | | |287+45=332| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 264, + "VEnd": 287 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV3-3*01", + "name": "TRBV3-3*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV3-3*01|Canis lupus familiaris_boxer|P|V-REGION|75352..75638|287 nt|1| | | | |287+45=332| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 264, + "VEnd": 287 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV4-1*01", + "name": "TRBV4-1*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV4-1*01|Canis lupus familiaris_boxer|F|V-REGION|52473..52759|287 nt|1| | | | |287+45=332| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 264, + "VEnd": 287 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV4-2*01", + "name": "TRBV4-2*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV4-2*01|Canis lupus familiaris_boxer|F|V-REGION|59857..60143|287 nt|1| | | | |287+45=332| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 264, + "VEnd": 287 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV4-3*01", + "name": "TRBV4-3*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV4-3*01|Canis lupus familiaris_boxer|F|V-REGION|70529..70815|287 nt|1| | | | |287+45=332| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 264, + "VEnd": 287 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV4-4*01", + "name": "TRBV4-4*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV4-4*01|Canis lupus familiaris_boxer|F|V-REGION|78755..79040|286 nt|1| | | | |286+45=331| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 264, + "VEnd": 286 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV5-2*01", + "name": "TRBV5-2*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV5-2*01|Canis lupus familiaris_boxer|F|V-REGION|91246..91531|286 nt|1| | | | |286+45=331| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 264, + "VEnd": 286 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV5-4*01", + "name": "TRBV5-4*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV5-4*01|Canis lupus familiaris_boxer|F|V-REGION|115468..115753|286 nt|1| | | | |286+45=331| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 264, + "VEnd": 286 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV7*01", + "name": "TRBV7*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV7*01|Canis lupus familiaris_boxer|F|V-REGION|100057..100346|290 nt|1| | | | |290+42=332| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 267, + "VEnd": 290 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV10*01", + "name": "TRBV10*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV10*01|Canis lupus familiaris_boxer|F|V-REGION|120266..120552|287 nt|1| | | | |287+45=332| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 264, + "VEnd": 287 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV12-1*01", + "name": "TRBV12-1*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV12-1*01|Canis lupus familiaris_boxer|P|V-REGION|134606..134895|290 nt|1| | | | |290+42=332| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 267, + "VEnd": 290 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV12-2*01", + "name": "TRBV12-2*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV12-2*01|Canis lupus familiaris_boxer|F|V-REGION|141963..142252|290 nt|1| | | | |290+42=332| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 267, + "VEnd": 290 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV15*01", + "name": "TRBV15*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV15*01|Canis lupus familiaris_boxer|ORF|V-REGION|147095..147381|287 nt|1| | | | |287+45=332| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 264, + "VEnd": 287 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV16*01", + "name": "TRBV16*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV16*01|Canis lupus familiaris_boxer|F|V-REGION|150689..150978|290 nt|1| | | | |290+42=332| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 267, + "VEnd": 290 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV18*01", + "name": "TRBV18*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV18*01|Canis lupus familiaris_boxer|F|V-REGION|161290..161579|290 nt|1| | | | |290+42=332| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 267, + "VEnd": 290 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV19*01", + "name": "TRBV19*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV19*01|Canis lupus familiaris_boxer|P|V-REGION|164573..164859|287 nt|1| | | | |287+45=332| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 264, + "VEnd": 287 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV20*01", + "name": "TRBV20*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV20*01|Canis lupus familiaris_boxer|F|V-REGION|169715..170007|293 nt|1| | | | |293+36=329| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 168, + "CDR3Begin": 273, + "VEnd": 293 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV21*01", + "name": "TRBV21*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV21*01|Canis lupus familiaris_boxer|P|V-REGION|176916..177205|290 nt|1| | | | |290+42=332| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 267, + "VEnd": 290 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV22*01", + "name": "TRBV22*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV22*01|Canis lupus familiaris_boxer|F|V-REGION|181387..181669|283 nt|1| | | | |283+48=331| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 75, + "FR2Begin": 90, + "CDR2Begin": 141, + "FR3Begin": 159, + "CDR3Begin": 261, + "VEnd": 283 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV24*01", + "name": "TRBV24*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV24*01|Canis lupus familiaris_boxer|F|V-REGION|190306..190593|288 nt|1| | | | |288+45=333| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 264, + "VEnd": 288 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV25*01", + "name": "TRBV25*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV25*01|Canis lupus familiaris_boxer|F|V-REGION|195581..195867|287 nt|1| | | | |287+45=332| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 264, + "VEnd": 287 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV26*01", + "name": "TRBV26*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV26*01|Canis lupus familiaris_boxer|F|V-REGION|202445..202730|286 nt|1| | | | |286+45=331| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 264, + "VEnd": 286 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV27*01", + "name": "TRBV27*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV27*01|Canis lupus familiaris_boxer|P|V-REGION|211739..212031|293 nt|1| | | | |293+39=332| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 270, + "VEnd": 293 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV28*01", + "name": "TRBV28*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV28*01|Canis lupus familiaris_boxer|F|V-REGION|221043..221328|286 nt|1| | | | |286+45=331| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 162, + "CDR3Begin": 264, + "VEnd": 286 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV29*01", + "name": "TRBV29*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV29*01|Canis lupus familiaris_boxer|F|V-REGION|227582..227871|290 nt|1| | | | |290+39=329| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 165, + "CDR3Begin": 270, + "VEnd": 290 + } + }, { + "baseSequence": "file://dog_V_TRB.fasta#TRBV30*01", + "name": "TRBV30*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBV30*01|Canis lupus familiaris_boxer|F|V-REGION|261226..261509|284 nt|1| | | | |284+45=329| |rev-compl|" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 96, + "CDR2Begin": 147, + "FR3Begin": 162, + "CDR3Begin": 264, + "VEnd": 284 + } + }, { + "baseSequence": "file://dog_D_TRB.fasta#TRBD1*01", + "name": "TRBD1*01", + "geneType": "D", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBD1*01|Canis lupus familiaris_boxer|F|D-REGION|243804..243815|12 nt|1| | | | |12+0=12| | |" + }, + "anchorPoints": { + "DBegin": 0, + "DEnd": 12 + } + }, { + "baseSequence": "file://dog_D_TRB.fasta#TRBD2*01", + "name": "TRBD2*01", + "geneType": "D", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "HE653929|TRBD2*01|Canis lupus familiaris|F|D-REGION|6658..6671|14 nt|1| | | | |14+0=14| | |" + }, + "anchorPoints": { + "DBegin": 0, + "DEnd": 14 + } + }, { + "baseSequence": "file://dog_J_TRB.fasta#TRBJ1-1*01", + "name": "TRBJ1-1*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBJ1-1*01|Canis lupus familiaris_boxer|F|J-REGION|244453..244500|48 nt|3| | | | |48+0=48| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 20, + "FR4End": 48 + } + }, { + "baseSequence": "file://dog_J_TRB.fasta#TRBJ1-2*01", + "name": "TRBJ1-2*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBJ1-2*01|Canis lupus familiaris_boxer|F|J-REGION|244588..244631|44 nt|2| | | | |44+0=44| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 16, + "FR4End": 44 + } + }, { + "baseSequence": "file://dog_J_TRB.fasta#TRBJ1-3*01", + "name": "TRBJ1-3*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBJ1-3*01|Canis lupus familiaris_boxer|P|J-REGION|245190..245239|50 nt|2| | | | |50+0=50| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 22, + "FR4End": 50 + } + }, { + "baseSequence": "file://dog_J_TRB.fasta#TRBJ1-4*01", + "name": "TRBJ1-4*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBJ1-4*01|Canis lupus familiaris_boxer|ORF|J-REGION|245772..245821|50 nt|2| | | | |50+0=50| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 23, + "FR4End": 50 + } + }, { + "baseSequence": "file://dog_J_TRB.fasta#TRBJ1-5*01", + "name": "TRBJ1-5*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBJ1-5*01|Canis lupus familiaris_boxer|F|J-REGION|246026..246075|50 nt|2| | | | |50+0=50| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 22, + "FR4End": 50 + } + }, { + "baseSequence": "file://dog_J_TRB.fasta#TRBJ1-6*01", + "name": "TRBJ1-6*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "IMGT000005|TRBJ1-6*01|Canis lupus familiaris_boxer|F|J-REGION|246487..246539|53 nt|2| | | | |53+0=53| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 25, + "FR4End": 53 + } + }, { + "baseSequence": "file://dog_J_TRB.fasta#TRBJ2-1*01", + "name": "TRBJ2-1*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "HE653929|TRBJ2-1*01|Canis lupus familiaris|F|J-REGION|7330..7379|50 nt|2| | | | |50+0=50| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 22, + "FR4End": 50 + } + }, { + "baseSequence": "file://dog_J_TRB.fasta#TRBJ2-2*01", + "name": "TRBJ2-2*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "HE653929|TRBJ2-2*01|Canis lupus familiaris|F|J-REGION|7510..7560|51 nt|3| | | | |51+0=51| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 23, + "FR4End": 51 + } + }, { + "baseSequence": "file://dog_J_TRB.fasta#TRBJ2-3*01", + "name": "TRBJ2-3*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "HE653929|TRBJ2-3*01|Canis lupus familiaris|F|J-REGION|7761..7809|49 nt|1| | | | |49+0=49| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 21, + "FR4End": 49 + } + }, { + "baseSequence": "file://dog_J_TRB.fasta#TRBJ2-4*01", + "name": "TRBJ2-4*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRB" ], + "meta": { + "comments": "HE653929|TRBJ2-4*01|Canis lupus familiaris|ORF|J-REGION|7913..7961|49 nt|1| | | | |49+0=49| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 21, + "FR4End": 49 + } + }, { + "baseSequence": "file://dog_J_TRB.fasta#TRBJ2-5*01", + "name": "TRBJ2-5*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "HE653929|TRBJ2-5*01|Canis lupus familiaris|F|J-REGION|8052..8100|49 nt|1| | | | |49+0=49| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 21, + "FR4End": 49 + } + }, { + "baseSequence": "file://dog_J_TRB.fasta#TRBJ2-6*01", + "name": "TRBJ2-6*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRB" ], + "meta": { + "comments": "HE653929|TRBJ2-6*01|Canis lupus familiaris|F|J-REGION|8568..8613|46 nt|1| | | | |46+0=46| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 18, + "FR4End": 46 + } + }, { + "baseSequence": "file://dog_V_TRD.fasta#TRDV2*01", + "name": "TRDV2*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "TRD" ], + "meta": { + "comments": "IMGT000004|TRDV2*01|Canis lupus familiaris_boxer|ORF|V-REGION|392191..392480|290 nt|1| | | | |290+36=326| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 102, + "CDR2Begin": 153, + "FR3Begin": 162, + "CDR3Begin": 273, + "VEnd": 290 + } + }, { + "baseSequence": "file://dog_V_TRD.fasta#TRDV3*01", + "name": "TRDV3*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRD" ], + "meta": { + "comments": "IMGT000004|TRDV3*01|Canis lupus familiaris_boxer|F|V-REGION|659399..659692|294 nt|1| | | | |294+30=324| |rev-compl|" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 168, + "CDR3Begin": 279, + "VEnd": 294 + } + }, { + "baseSequence": "file://dog_V_TRD.fasta#TRDV4*01", + "name": "TRDV4*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRD" ], + "meta": { + "comments": "IMGT000004|TRDV4*01|Canis lupus familiaris_boxer|F|V-REGION|582240..582527|288 nt|1| | | | |288+36=324| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 102, + "CDR2Begin": 153, + "FR3Begin": 162, + "CDR3Begin": 273, + "VEnd": 288 + } + }, { + "baseSequence": "file://dog_V_TRD.fasta#TRDV5*01", + "name": "TRDV5*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRD" ], + "meta": { + "comments": "IMGT000004|TRDV5*01|Canis lupus familiaris_boxer|F|V-REGION|543895..544183|289 nt|1| | | | |289+39=328| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 99, + "CDR2Begin": 150, + "FR3Begin": 159, + "CDR3Begin": 270, + "VEnd": 289 + } + }, { + "baseSequence": "file://dog_D_TRD.fasta#TRDD1*01", + "name": "TRDD1*01", + "geneType": "D", + "isFunctional": true, + "chains": [ "TRD" ], + "meta": { + "comments": "IMGT000004|TRDD1*01|Canis lupus familiaris_boxer|F|D-REGION|626229..626237|9 nt|1| | | | |9+0=9| | |" + }, + "anchorPoints": { + "DBegin": 0, + "DEnd": 9 + } + }, { + "baseSequence": "file://dog_D_TRD.fasta#TRDD2*01", + "name": "TRDD2*01", + "geneType": "D", + "isFunctional": true, + "chains": [ "TRD" ], + "meta": { + "comments": "IMGT000004|TRDD2*01|Canis lupus familiaris_boxer|F|D-REGION|638570..638583|14 nt|1| | | | |14+0=14| | |" + }, + "anchorPoints": { + "DBegin": 0, + "DEnd": 14 + } + }, { + "baseSequence": "file://dog_J_TRD.fasta#TRDJ1*01", + "name": "TRDJ1*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRD" ], + "meta": { + "comments": "IMGT000004|TRDJ1*01|Canis lupus familiaris_boxer|F|J-REGION|639557..639607|51 nt|3| | || |51+0=51| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 20, + "FR4End": 51 + } + }, { + "baseSequence": "file://dog_J_TRD.fasta#TRDJ2*01", + "name": "TRDJ2*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRD" ], + "meta": { + "comments": "IMGT000004|TRDJ2*01|Canis lupus familiaris_boxer|F|J-REGION|645679..645726|48 nt|3| | || |48+0=48| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 17, + "FR4End": 48 + } + }, { + "baseSequence": "file://dog_J_TRD.fasta#TRDJ3*01", + "name": "TRDJ3*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRD" ], + "meta": { + "comments": "IMGT000004|TRDJ3*01|Canis lupus familiaris_boxer|ORF|J-REGION|647775..647846|72 nt|3| | || |72+0=72| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 42, + "FR4End": 72 + } + }, { + "baseSequence": "file://dog_J_TRD.fasta#TRDJ4*01", + "name": "TRDJ4*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRD" ], + "meta": { + "comments": "IMGT000004|TRDJ4*01|Canis lupus familiaris_boxer|F|J-REGION|649884..649942|59 nt|2| | || |59+0=59| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 28, + "FR4End": 59 + } + }, { + "baseSequence": "file://dog_V_TRG.fasta#TRGV2-1*01", + "name": "TRGV2-1*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRG" ], + "meta": { + "comments": "IMGT000006|TRGV2-1*01|Canis lupus familiaris_boxer|F|V-REGION|50307..50606|300 nt|1| | || |300+27=327| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 168, + "CDR3Begin": 282, + "VEnd": 300 + } + }, { + "baseSequence": "file://dog_V_TRG.fasta#TRGV2-2*01", + "name": "TRGV2-2*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRG" ], + "meta": { + "comments": "IMGT000006|TRGV2-2*01|Canis lupus familiaris_boxer|F|V-REGION|104826..105125|300 nt|1| | || |300+27=327| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 168, + "CDR3Begin": 282, + "VEnd": 300 + } + }, { + "baseSequence": "file://dog_V_TRG.fasta#TRGV2-3*01", + "name": "TRGV2-3*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRG" ], + "meta": { + "comments": "IMGT000006|TRGV2-3*01|Canis lupus familiaris_boxer|F|V-REGION|163801..164100|300 nt|1| | || |300+27=327| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 168, + "CDR3Begin": 282, + "VEnd": 300 + } + }, { + "baseSequence": "file://dog_V_TRG.fasta#TRGV2-4*01", + "name": "TRGV2-4*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRG" ], + "meta": { + "comments": "IMGT000006|TRGV2-4*01|Canis lupus familiaris_boxer|F|V-REGION|219524..219823|300 nt|1| | || |300+27=327| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 168, + "CDR3Begin": 282, + "VEnd": 300 + } + }, { + "baseSequence": "file://dog_V_TRG.fasta#TRGV4-1*01", + "name": "TRGV4-1*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "TRG" ], + "meta": { + "comments": "IMGT000006|TRGV4-1*01|Canis lupus familiaris_boxer|ORF|V-REGION|238986..239285|300 nt|1| | || |300+27=327| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 168, + "CDR3Begin": 282, + "VEnd": 300 + } + }, { + "baseSequence": "file://dog_V_TRG.fasta#TRGV5-2*01", + "name": "TRGV5-2*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRG" ], + "meta": { + "comments": "IMGT000006|TRGV5-2*01|Canis lupus familiaris_boxer|F|V-REGION|321673..321979|307 nt|1| | || |307+18=325| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 105, + "CDR2Begin": 156, + "FR3Begin": 177, + "CDR3Begin": 291, + "VEnd": 307 + } + }, { + "baseSequence": "file://dog_V_TRG.fasta#TRGV6-1*01", + "name": "TRGV6-1*01", + "geneType": "V", + "isFunctional": false, + "chains": [ "TRG" ], + "meta": { + "comments": "IMGT000006|TRGV6-1*01|Canis lupus familiaris_boxer|P|V-REGION|300914..301213|300 nt|1| | || |300+27=327| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 93, + "CDR2Begin": 144, + "FR3Begin": 168, + "CDR3Begin": 282, + "VEnd": 300 + } + }, { + "baseSequence": "file://dog_V_TRG.fasta#TRGV7-2*01", + "name": "TRGV7-2*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRG" ], + "meta": { + "comments": "IMGT000006|TRGV7-2*01|Canis lupus familiaris_boxer|F|V-REGION|383828..384132|305 nt|1| | || |305+24=329| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 102, + "CDR2Begin": 153, + "FR3Begin": 171, + "CDR3Begin": 285, + "VEnd": 305 + } + }, { + "baseSequence": "file://dog_V_TRG.fasta#TRGV7-3*01", + "name": "TRGV7-3*01", + "geneType": "V", + "isFunctional": true, + "chains": [ "TRG" ], + "meta": { + "comments": "IMGT000006|TRGV7-3*01|Canis lupus familiaris_boxer|F|V-REGION|396401..396709|309 nt|1| | || |309+21=330| | |" + }, + "anchorPoints": { + "FR1Begin": 0, + "CDR1Begin": 78, + "FR2Begin": 102, + "CDR2Begin": 153, + "FR3Begin": 174, + "CDR3Begin": 288, + "VEnd": 309 + } + }, { + "baseSequence": "file://dog_J_TRG.fasta#TRGJ1-1*01", + "name": "TRGJ1-1*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRG" ], + "meta": { + "comments": "IMGT000006|TRGJ1-1*01|Canis lupus familiaris_boxer|P|J-REGION|11246..11311|66 nt|3| | || |66+0=66| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 33, + "FR4End": 66 + } + }, { + "baseSequence": "file://dog_J_TRG.fasta#TRGJ1-2*01", + "name": "TRGJ1-2*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRG" ], + "meta": { + "comments": "IMGT000006|TRGJ1-2*01|Canis lupus familiaris_boxer|ORF|J-REGION|13619..13677|59 nt|2| | || |59+0=59| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 31, + "FR4End": 59 + } + }, { + "baseSequence": "file://dog_J_TRG.fasta#TRGJ2-1*01", + "name": "TRGJ2-1*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRG" ], + "meta": { + "comments": "IMGT000006|TRGJ2-1*01|Canis lupus familiaris_boxer|P|J-REGION|68516..68581|66 nt|3| | || |66+0=66| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 33, + "FR4End": 66 + } + }, { + "baseSequence": "file://dog_J_TRG.fasta#TRGJ2-2*01", + "name": "TRGJ2-2*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRG" ], + "meta": { + "comments": "IMGT000006|TRGJ2-2*01|Canis lupus familiaris_boxer|F|J-REGION|71313..71371|59 nt|2| | || |59+0=59| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 31, + "FR4End": 59 + } + }, { + "baseSequence": "file://dog_J_TRG.fasta#TRGJ3-1*01", + "name": "TRGJ3-1*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRG" ], + "meta": { + "comments": "IMGT000006|TRGJ3-1*01|Canis lupus familiaris_boxer|P|J-REGION|123476..123541|66 nt|3| | || |66+0=66| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 33, + "FR4End": 66 + } + }, { + "baseSequence": "file://dog_J_TRG.fasta#TRGJ3-2*01", + "name": "TRGJ3-2*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRG" ], + "meta": { + "comments": "IMGT000006|TRGJ3-2*01|Canis lupus familiaris_boxer|ORF|J-REGION|125831..125889|59 nt|2| | || |59+0=59| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 31, + "FR4End": 59 + } + }, { + "baseSequence": "file://dog_J_TRG.fasta#TRGJ4-1*01", + "name": "TRGJ4-1*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRG" ], + "meta": { + "comments": "IMGT000006|TRGJ4-1*01|Canis lupus familiaris_boxer|P|J-REGION|180992..181057|66 nt|3| | || |66+0=66| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 33, + "FR4End": 66 + } + }, { + "baseSequence": "file://dog_J_TRG.fasta#TRGJ4-2*01", + "name": "TRGJ4-2*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRG" ], + "meta": { + "comments": "IMGT000006|TRGJ4-2*01|Canis lupus familiaris_boxer|F|J-REGION|183242..183300|59 nt|2| | || |59+0=59| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 31, + "FR4End": 59 + } + }, { + "baseSequence": "file://dog_J_TRG.fasta#TRGJ5-1*01", + "name": "TRGJ5-1*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRG" ], + "meta": { + "comments": "IMGT000006|TRGJ5-1*01|Canis lupus familiaris_boxer|ORF|J-REGION|266070..266130|61 nt|1| | || |61+0=61| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 28, + "FR4End": 61 + } + }, { + "baseSequence": "file://dog_J_TRG.fasta#TRGJ5-2*01", + "name": "TRGJ5-2*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRG" ], + "meta": { + "comments": "IMGT000006|TRGJ5-2*01|Canis lupus familiaris_boxer|F|J-REGION|268525..268583|59 nt|2| | || |59+0=59| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 31, + "FR4End": 59 + } + }, { + "baseSequence": "file://dog_J_TRG.fasta#TRGJ6-1*01", + "name": "TRGJ6-1*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRG" ], + "meta": { + "comments": "IMGT000006|TRGJ6-1*01|Canis lupus familiaris_boxer|F|J-REGION|324844..324903|60 nt|3| | || |60+0=60| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 26, + "FR4End": 60 + } + }, { + "baseSequence": "file://dog_J_TRG.fasta#TRGJ6-2*01", + "name": "TRGJ6-2*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRG" ], + "meta": { + "comments": "IMGT000006|TRGJ6-2*01|Canis lupus familiaris_boxer|ORF|J-REGION|327874..327934|61 nt|1| | || |61+0=61| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 28, + "FR4End": 61 + } + }, { + "baseSequence": "file://dog_J_TRG.fasta#TRGJ7-1*01", + "name": "TRGJ7-1*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRG" ], + "meta": { + "comments": "IMGT000006|TRGJ7-1*01|Canis lupus familiaris_boxer|P|J-REGION|406077..406139|63 nt|3| | || |63+0=63| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 38, + "FR4End": 63 + } + }, { + "baseSequence": "file://dog_J_TRG.fasta#TRGJ7-2*01", + "name": "TRGJ7-2*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRG" ], + "meta": { + "comments": "IMGT000006|TRGJ7-2*01|Canis lupus familiaris_boxer|F|J-REGION|407727..407786|60 nt|3| | || |60+0=60| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 32, + "FR4End": 60 + } + }, { + "baseSequence": "file://dog_J_TRG.fasta#TRGJ8-1*01", + "name": "TRGJ8-1*01", + "geneType": "J", + "isFunctional": true, + "chains": [ "TRG" ], + "meta": { + "comments": "IMGT000006|TRGJ8-1*01|Canis lupus familiaris_boxer|F|J-REGION|433098..433157|60 nt|3| | || |60+0=60| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 26, + "FR4End": 60 + } + }, { + "baseSequence": "file://dog_J_TRG.fasta#TRGJ8-2*01", + "name": "TRGJ8-2*01", + "geneType": "J", + "isFunctional": false, + "chains": [ "TRG" ], + "meta": { + "comments": "IMGT000006|TRGJ8-2*01|Canis lupus familiaris_boxer|P|J-REGION|437858..437915|58 nt|1| | || |58+0=58| | |" + }, + "anchorPoints": { + "JBegin": 0, + "FR4Begin": 30, + "FR4End": 58 + } + } ], + "meta": { + "comments": [ "Imported from: /Volumes/Data/Projects/repseqio/library-imgt/cache/dog_D_IGH.p.fasta", "Imported from: /Volumes/Data/Projects/repseqio/library-imgt/cache/dog_D_TRB.p.fasta", "Imported from: /Volumes/Data/Projects/repseqio/library-imgt/cache/dog_D_TRD.p.fasta", "Imported from: /Volumes/Data/Projects/repseqio/library-imgt/cache/dog_J_IGH.p.fasta", "Imported from: /Volumes/Data/Projects/repseqio/library-imgt/cache/dog_J_IGK.p.fasta", "Imported from: /Volumes/Data/Projects/repseqio/library-imgt/cache/dog_J_IGL.p.fasta", "Imported from: /Volumes/Data/Projects/repseqio/library-imgt/cache/dog_J_TRA.p.fasta", "Imported from: /Volumes/Data/Projects/repseqio/library-imgt/cache/dog_J_TRB.p.fasta", "Imported from: /Volumes/Data/Projects/repseqio/library-imgt/cache/dog_J_TRD.p.fasta", "Imported from: /Volumes/Data/Projects/repseqio/library-imgt/cache/dog_J_TRG.p.fasta", "Imported from: /Volumes/Data/Projects/repseqio/library-imgt/cache/dog_V_IGH.p.fasta", "Imported from: /Volumes/Data/Projects/repseqio/library-imgt/cache/dog_V_IGK.p.fasta", "Imported from: /Volumes/Data/Projects/repseqio/library-imgt/cache/dog_V_IGL.p.fasta", "Imported from: /Volumes/Data/Projects/repseqio/library-imgt/cache/dog_V_TRA.p.fasta", "Imported from: /Volumes/Data/Projects/repseqio/library-imgt/cache/dog_V_TRB.p.fasta", "Imported from: /Volumes/Data/Projects/repseqio/library-imgt/cache/dog_V_TRD.p.fasta", "Imported from: /Volumes/Data/Projects/repseqio/library-imgt/cache/dog_V_TRG.p.fasta" ] + }, + "sequenceFragments": [ { + "uri": "file://dog_D_IGH.fasta#IGHD1*01", + "range": { + "from": 0, + "to": 31 + }, + "sequence": "GTACTACTGTACTGATGATTACTGTTTCAAC" + }, { + "uri": "file://dog_D_IGH.fasta#IGHD2*01", + "range": { + "from": 0, + "to": 19 + }, + "sequence": "CTACTACGGTAGCTACTAC" + }, { + "uri": "file://dog_D_IGH.fasta#IGHD3*01", + "range": { + "from": 0, + "to": 17 + }, + "sequence": "TATATATATATGGATAC" + }, { + "uri": "file://dog_D_IGH.fasta#IGHD4*01", + "range": { + "from": 0, + "to": 19 + }, + "sequence": "GTATAGTAGCAGCTGGTAC" + }, { + "uri": "file://dog_D_IGH.fasta#IGHD5*01", + "range": { + "from": 0, + "to": 19 + }, + "sequence": "AGTTCTAGTAGTTGGGGCT" + }, { + "uri": "file://dog_D_IGH.fasta#IGHD6*01", + "range": { + "from": 0, + "to": 11 + }, + "sequence": "CTAACTGGGGC" + }, { + "uri": "file://dog_D_TRB.fasta#TRBD1*01", + "range": { + "from": 0, + "to": 12 + }, + "sequence": "GGTACAGGGGGC" + }, { + "uri": "file://dog_D_TRB.fasta#TRBD2*01", + "range": { + "from": 0, + "to": 14 + }, + "sequence": "GGAACTGGGGGACT" + }, { + "uri": "file://dog_D_TRD.fasta#TRDD1*01", + "range": { + "from": 0, + "to": 9 + }, + "sequence": "GTGGGCTAC" + }, { + "uri": "file://dog_D_TRD.fasta#TRDD2*01", + "range": { + "from": 0, + "to": 14 + }, + "sequence": "ATTGGAGGGATACC" + }, { + "uri": "file://dog_J_IGH.fasta#IGHJ1*01", + "range": { + "from": 0, + "to": 53 + }, + "sequence": "TGACATTTACTTTGACCTCTGGGGCCCGGGCACCCTGGTCACCATCTCCTCAG" + }, { + "uri": "file://dog_J_IGH.fasta#IGHJ2*01", + "range": { + "from": 0, + "to": 54 + }, + "sequence": "AACATGATTACTTAGACCTCTGGGGCCAGGGCACCCTGGTCACCGTCTCCTCAG" + }, { + "uri": "file://dog_J_IGH.fasta#IGHJ3*01", + "range": { + "from": 0, + "to": 50 + }, + "sequence": "CAATGCTTTTGGTTACTGGGGCCAGGGCACCCTGGTCACTGTCTCCTCAG" + }, { + "uri": "file://dog_J_IGH.fasta#IGHJ4*01", + "range": { + "from": 0, + "to": 48 + }, + "sequence": "ATAATTTTGACTACTGGGGCCAGGGAACCCTGGTCACCGTCTCCTCAG" + }, { + "uri": "file://dog_J_IGH.fasta#IGHJ5*01", + "range": { + "from": 0, + "to": 51 + }, + "sequence": "ACAACTGGTTCTACTACTGGGGCCAAGGGACCCTGGTCACTGTGTCCTCAG" + }, { + "uri": "file://dog_J_IGH.fasta#IGHJ6*01", + "range": { + "from": 0, + "to": 54 + }, + "sequence": "ATTACTATGGTATGGACTACTGGGGCCATGGCACCTCACTCTTCGTGTCCTCAG" + }, { + "uri": "file://dog_J_IGK.fasta#IGKJ1*01", + "range": { + "from": 0, + "to": 38 + }, + "sequence": "GTGGACGTTCGGAGCAGGAACCAAGGTGGAGCTCAAAC" + }, { + "uri": "file://dog_J_IGK.fasta#IGKJ2*01", + "range": { + "from": 0, + "to": 39 + }, + "sequence": "TTTATACTTTCAGCCAGGGAACCAAGCTGGAGATAAAAC" + }, { + "uri": "file://dog_J_IGK.fasta#IGKJ3*01", + "range": { + "from": 0, + "to": 38 + }, + "sequence": "GTTCACTTTTGGCCAAGGGACCAAACTGGAGATCAAAC" + }, { + "uri": "file://dog_J_IGK.fasta#IGKJ4*01", + "range": { + "from": 0, + "to": 38 + }, + "sequence": "GCTTACGTTCGGCCAAGGGACCAAGGTGGAGATCAAAC" + }, { + "uri": "file://dog_J_IGK.fasta#IGKJ5*01", + "range": { + "from": 0, + "to": 38 + }, + "sequence": "GATCACCTTTGGCAAAGGGACACATCTGGAGATTAAAC" + }, { + "uri": "file://dog_J_IGL.fasta#Canlupfam_IGLJ1*01", + "range": { + "from": 0, + "to": 38 + }, + "sequence": "TTGGGTATTCGGTGAAGGGACCCAGCTGACCGTCCTCG" + }, { + "uri": "file://dog_J_IGL.fasta#Canlupfam_IGLJ2*01", + "range": { + "from": 0, + "to": 38 + }, + "sequence": "TATGGTATTCGGCAGAGGGACCCAGCTGACCATCCTCG" + }, { + "uri": "file://dog_J_IGL.fasta#Canlupfam_IGLJ3*01", + "range": { + "from": 0, + "to": 38 + }, + "sequence": "TAGTGTGTTCGGCGGAGGCACCCATCTGACCGTCCTCG" + }, { + "uri": "file://dog_J_IGL.fasta#Canlupfam_IGLJ4*01", + "range": { + "from": 0, + "to": 38 + }, + "sequence": "TTACGTGTTCGGCTCAGGAACCCAACTGACCGTCCTTG" + }, { + "uri": "file://dog_J_IGL.fasta#Canlupfam_IGLJ5*01", + "range": { + "from": 0, + "to": 38 + }, + "sequence": "TATTGTGTTCGGCGGAGGCACCCATCTGACCGTCCTCG" + }, { + "uri": "file://dog_J_IGL.fasta#Canlupfam_IGLJ6*01", + "range": { + "from": 0, + "to": 38 + }, + "sequence": "TGGTGTGTTCGGCGGAGGCACCCACCTGACCGTCCTCG" + }, { + "uri": "file://dog_J_IGL.fasta#Canlupfam_IGLJ7*01", + "range": { + "from": 0, + "to": 38 + }, + "sequence": "TGCTGTGTTCGGCGGAGGCACCCACCTGACCGTCCTCG" + }, { + "uri": "file://dog_J_IGL.fasta#Canlupfam_IGLJ8*01", + "range": { + "from": 0, + "to": 38 + }, + "sequence": "TGCTGTGTTCGGCGGAGGCACCCACCTGACCGTCCTCG" + }, { + "uri": "file://dog_J_IGL.fasta#Canlupfam_IGLJ9*01", + "range": { + "from": 0, + "to": 38 + }, + "sequence": "TTACGTGTTCGGCTCAGGAACCCAACTGACCGTCCTTG" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ10*01", + "range": { + "from": 0, + "to": 64 + }, + "sequence": "GAACTGACTGTGAGAAACAACAAACTCACCTTTGGGACAGGTGCCTGGCTGAAGGTGGAGCTGG" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ11*01", + "range": { + "from": 0, + "to": 60 + }, + "sequence": "CAGAGGTAGGATACAGAAGGTTGACCTTTGGAGAGGGCACCATGCTTCTAGTCTCTCCAG" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ12*01", + "range": { + "from": 0, + "to": 60 + }, + "sequence": "GGAAGGACACAGGCTATAGATGGATCTTTGGGAGTGGGACTCAACTGCTGGTCGGGCCCG" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ13*01", + "range": { + "from": 0, + "to": 61 + }, + "sequence": "AATTCTGGGGGTAACCAAAAGTTTACCTTTGGAACTGGAACACAGCTCCAAGTCACCTCAA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ14*01", + "range": { + "from": 0, + "to": 57 + }, + "sequence": "CTGTGGTTTAGAACACATTCATCTTTGGGAGTGGGACATGATTATCAGTAAAACCCA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ15*01", + "range": { + "from": 0, + "to": 60 + }, + "sequence": "CCTACGGGGCAGGAAATGCGTTGACTTTTGGGAAGGGAACCACAATATCAGTGAGCCCCA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ16*01", + "range": { + "from": 0, + "to": 60 + }, + "sequence": "GTGTATCAAGCACCAATAATCTGGTGTTCGGAAGGGGGACCATGGTAAAAGTGGATCTTA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ18*01", + "range": { + "from": 0, + "to": 66 + }, + "sequence": "TCGACAGAGCCTCAGCCTTAGGGAAACTGCACTTCGGAAAAGGAACTCAGCTAACTGTATGGCCTG" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ19*01", + "range": { + "from": 0, + "to": 58 + }, + "sequence": "TATCAAAGTTTTCACGAGTTCAGCTTTGGAAAGGGTTCCAAACACAATGTCAATCCAA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ2*01", + "range": { + "from": 0, + "to": 52 + }, + "sequence": "GAACTGGGCAAACTCACATTTGGAAAAGGAACCCATCTGTCTGTCATATCTG" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ20*01", + "range": { + "from": 0, + "to": 57 + }, + "sequence": "GTGATAGTAGTTACAACTACATTTTTGGAGCAGGAACCACAGTAACCGTAAGAGCAA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ21*01", + "range": { + "from": 0, + "to": 55 + }, + "sequence": "TACAACTATAACAGACTTTACTTTGGATCTGGAACCAAGCTCAGTGTAAAGCCAA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ22*01", + "range": { + "from": 0, + "to": 61 + }, + "sequence": "TTTCTTGGTTCAGGCCAGCAACTGATCTTTGGATCCAGGACCCAGATGACTGTTGTACCTG" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ23*01", + "range": { + "from": 0, + "to": 63 + }, + "sequence": "TGAATTATAACCAGGGAGGAAAGCTTATCTTCGGACAAGGAACCGAGTTATCTGTGAAGCCCA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ24*01", + "range": { + "from": 0, + "to": 61 + }, + "sequence": "ACAACTGATGGCTGGTGGAAATTGAATTCTGGAGCAGGAACCCAGGCTGTGGTCATTGCAG" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ25*01", + "range": { + "from": 0, + "to": 60 + }, + "sequence": "TGCAAGGACAAGGCTTCTCTTTTATATTTGGAAAGGGGACAAGGTTGCTTGTCAAGCCAA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ26*01", + "range": { + "from": 0, + "to": 60 + }, + "sequence": "GGACTAACTCTGGCAGGGATTTGATCTTCGGTCAGGGAACCAGCTTGTCTGTGCTGCCCT" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ27*01", + "range": { + "from": 0, + "to": 59 + }, + "sequence": "TAACACCAACACAGGCAAATTAACCTTTGGGGACGGGACCATGCTCACTGTGAAGCCAA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ28*01", + "range": { + "from": 0, + "to": 63 + }, + "sequence": "TGTACTCTGGGGTTGGTAGCCAGCTCACATTCGGGAAGGGCACCAAACTCCTGGTCACACCAA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ3*01", + "range": { + "from": 0, + "to": 58 + }, + "sequence": "GGGATACAGCAGGGCATGCTAATCTTTGGAGCAGGGACCAGACTGAGTGTCCAGCCAA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ30*01", + "range": { + "from": 0, + "to": 59 + }, + "sequence": "TGTACAAAGTTCGGAAAAAATCATCTTTGGAAAGGGGACTCAACTTCATGTTCTCCCCA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ31*01", + "range": { + "from": 0, + "to": 57 + }, + "sequence": "GCAATAACAACGCCAGAATCTTCTTTGGAACAGGAACGCAGCTGGTGGTAAAGCCCA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ32*01", + "range": { + "from": 0, + "to": 64 + }, + "sequence": "AACTCTGGGAGTGCTACAAATAAGCTCATCTTTGGGACTGGAACTGTGCTTTCGGTGAAGCCAA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ33*01", + "range": { + "from": 0, + "to": 57 + }, + "sequence": "TGGATAGCAACAATCGGTTGATCTGGGGCTCTGGGACCAAGCTAATTATAAAGCCAG" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ34*01", + "range": { + "from": 0, + "to": 58 + }, + "sequence": "TTCAGCAACAATGACAGACTCTTCTTTGGGGCTGGAACCAGATTACAAGTGTTTCCAA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ35*01", + "range": { + "from": 0, + "to": 62 + }, + "sequence": "GACAGACAGGCTTGGCGAGGGTGCTGCATTTTGGGTCCTCCTCAAAGTGATTGTCACACCAC" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ37*01", + "range": { + "from": 0, + "to": 62 + }, + "sequence": "TGGCTCTAGCGACATAGGAAAACTCATCTTTGGGCAGGGGACCACTTTACGAGTCAAACCAG" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ38*01", + "range": { + "from": 0, + "to": 62 + }, + "sequence": "TAATATTGGCAACAACCGTAAGCTGATTTGGGGATTGGGAACAAGGCTGGCAGTAAATCCAA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ39*01", + "range": { + "from": 0, + "to": 63 + }, + "sequence": "TGAATAATAATGCAGGCAACAGGCTCACATTTGGAGGGGGAACAAGGTTAATGGTCAAACCCA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ4*01", + "range": { + "from": 0, + "to": 63 + }, + "sequence": "TGTCATCTGGTAGCTACAGTCAGCTGACCTTTGGAGCTGGGACCAGGCTGGTTGTGCACCCAT" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ40*01", + "range": { + "from": 0, + "to": 61 + }, + "sequence": "ACTACCTCAGGAAACTACAAATACACCTTTGGAAAAGGCACTAAGCTGAAGGTTTTACCAA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ41*01", + "range": { + "from": 0, + "to": 62 + }, + "sequence": "GAACTCAGACAACAGGTATAGCCTCAATTTTGGCAAAGGCACCTCGCTGCTGGTCATACCCG" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ42*01", + "range": { + "from": 0, + "to": 66 + }, + "sequence": "TGAATTATGGAGGCAGCCAAGGACGACTCATCTTTGGAAAAGGCACTCAACTCTCCGTTAAACCAA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ43*01", + "range": { + "from": 0, + "to": 57 + }, + "sequence": "GCAATAATAACTATGGCCTTCACTTTGGAGCAGGTACCAGACTGACTGTAAAGCCAA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ44*01", + "range": { + "from": 0, + "to": 63 + }, + "sequence": "TGACCACTGGTGGTGCCAGGAAATTCATCTTTGGGACTGGAACGAGGCTTCAGGTCACCCTGG" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ45*01", + "range": { + "from": 0, + "to": 63 + }, + "sequence": "TCACTGTAGGAACAGCTAACAAACTCATCTTTGGGAAAGGGACCCAACTCATCATCAAGCCCT" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ46*01", + "range": { + "from": 0, + "to": 63 + }, + "sequence": "AGAAGACTGGCAGCAGGGACAAGCTCACTTTTGGGACCAGGACTCGTTTAACAGTCAGGCCCA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ48*01", + "range": { + "from": 0, + "to": 63 + }, + "sequence": "TGTCTAACTATGGAAGCAACAACTTAATCTTTGGGACAGGAACTAGAGTCACTGTTACACCCA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ49*01", + "range": { + "from": 0, + "to": 59 + }, + "sequence": "GAACACCGGTAACCAGAACTTCTATTTTGGGAGAGGGACAAGTTTGACTGTCATTCCAA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ5*01", + "range": { + "from": 0, + "to": 60 + }, + "sequence": "TAGACACAGGTGCCAGAGCACTCACTTTTGGGAGTGGAACAAGACTCCAAGTGCATCCAA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ50*01", + "range": { + "from": 0, + "to": 60 + }, + "sequence": "TGACAACCTCTTCCAACAAGTTGACATTTGGGCAGGGGACAAGCTTATCAGTCATTCCAA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ52*01", + "range": { + "from": 0, + "to": 69 + }, + "sequence": "CTAATACTGGTGGCTCCAGCTATGGAAAGCTGACATTTGGACAAGGGACCTTCTTGACCGTCCATCCAA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ53*01", + "range": { + "from": 0, + "to": 59 + }, + "sequence": "AACAGTAGATTACTACAAATTCACATTTGGAAAAGGAACTCCCTTAACTGTAAATCCAA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ54*01", + "range": { + "from": 0, + "to": 65 + }, + "sequence": "TGACTCAGGGAGTGGCAGCCAAAAGTTGGTGTTTGGGCAAGGAACCAGGCTGACTGTCAATCCTG" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ56*01", + "range": { + "from": 0, + "to": 62 + }, + "sequence": "TGATATGGGAGGCAATAATAAGCTGACATTTGGCAAAGGAACGACTGTGAGTGTTCGATCAG" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ57*01", + "range": { + "from": 0, + "to": 63 + }, + "sequence": "TTCCTCAAGGTGGATCTGAACAACTCTTCTTTGGAAAAGAAATAAAACTGACAGTAAAGCCAT" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ58*01", + "range": { + "from": 0, + "to": 63 + }, + "sequence": "TCCAACAAGCCAGTGGTTTTCAAATGACATTTGGGAAAGGAACTCTGCTCACGGTGAATCTTG" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ59*01", + "range": { + "from": 0, + "to": 60 + }, + "sequence": "GGAAGGAAGGAAACAGGAAATTTACATCTGGAACCAGGACTCCAGTGAGAGTGAAACTTA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ6*01", + "range": { + "from": 0, + "to": 62 + }, + "sequence": "TGTATCAGGAGGAAACTACAGACTTACATTTGGAACAGGGACCAGGCTTGTTGTTCATCCAT" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ60*01", + "range": { + "from": 0, + "to": 56 + }, + "sequence": "TGAAGACCCTAAGAAACCCCACTTCGGGAAGGGGGTTCAGTTCTTTGTAGGCTTGG" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ7*01", + "range": { + "from": 0, + "to": 59 + }, + "sequence": "TGGCTGTGGGAACAACAGACTCACTTTTGGGAAGGGGATCCAAGTGATGGTCATACCAA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ8*01", + "range": { + "from": 0, + "to": 60 + }, + "sequence": "TGAACACAGGCTATCAGAACCTTGCATTTGGAAGTGGCACCCGACTTCTGATCAGCCCAA" + }, { + "uri": "file://dog_J_TRA.fasta#TRAJ9*01", + "range": { + "from": 0, + "to": 55 + }, + "sequence": "GGAAATAATGTGGGCTTCAAATTTGGGACAGGAACAAGACTATTTGTTAAAGCAA" + }, { + "uri": "file://dog_J_TRB.fasta#TRBJ1-1*01", + "range": { + "from": 0, + "to": 48 + }, + "sequence": "TGAACACTGAAGTGTTCTTCGGAAAAGGCACCAGGCTCACAGTTATAG" + }, { + "uri": "file://dog_J_TRB.fasta#TRBJ1-2*01", + "range": { + "from": 0, + "to": 44 + }, + "sequence": "TTATGATTTTAACTTTGGCCCAGGGACCAAGCTGACAGTTGTAG" + }, { + "uri": "file://dog_J_TRB.fasta#TRBJ1-3*01", + "range": { + "from": 0, + "to": 50 + }, + "sequence": "CTTTTGAAACACCTTGCACTTTGGGGACGGGAGCCGGCTCACTGTTGTAG" + }, { + "uri": "file://dog_J_TRB.fasta#TRBJ1-4*01", + "range": { + "from": 0, + "to": 50 + }, + "sequence": "TAACAATGAAAAATTGTATTTTGCAAGTGGAACCAAGCTTTCTGTCTTGG" + }, { + "uri": "file://dog_J_TRB.fasta#TRBJ1-5*01", + "range": { + "from": 0, + "to": 50 + }, + "sequence": "GAACAACCAGGCACAGCACTTTGGAGACGGGACTCGACTCTCTGTCCTGG" + }, { + "uri": "file://dog_J_TRB.fasta#TRBJ1-6*01", + "range": { + "from": 0, + "to": 53 + }, + "sequence": "TTTCTATAACTCGCCCCTCTACTTTGGAACAGGCACCAGGCTCACCGTGACAG" + }, { + "uri": "file://dog_J_TRB.fasta#TRBJ2-1*01", + "range": { + "from": 0, + "to": 50 + }, + "sequence": "CTAATATGGGGAGCAGCACTTTGGGCCAGGGACCCGGCTCACCGTCCTAG" + }, { + "uri": "file://dog_J_TRB.fasta#TRBJ2-2*01", + "range": { + "from": 0, + "to": 51 + }, + "sequence": "CCAACACGGGGCAGCTGTACTTCGGGGCCGGTTCCAAGCTGGCCGTGCTGG" + }, { + "uri": "file://dog_J_TRB.fasta#TRBJ2-3*01", + "range": { + "from": 0, + "to": 49 + }, + "sequence": "AGCACAGAAACTCAGTATTTCGGCGGGGGCACCCGGCTGACCGTGCTCG" + }, { + "uri": "file://dog_J_TRB.fasta#TRBJ2-4*01", + "range": { + "from": 0, + "to": 49 + }, + "sequence": "AGCCTGGACACCCAGTACTTCGGGGCGGGCACCCGGCTGACCGTGCTCG" + }, { + "uri": "file://dog_J_TRB.fasta#TRBJ2-5*01", + "range": { + "from": 0, + "to": 49 + }, + "sequence": "AGCCAAAATACCCAGTACTTCGGGGCGGGCACCCGGCTGACCGTGCTAG" + }, { + "uri": "file://dog_J_TRB.fasta#TRBJ2-6*01", + "range": { + "from": 0, + "to": 46 + }, + "sequence": "CGCTACGAGCAGTATTTCGGCGCCGGCACCAGGCTCACGGTCCTCG" + }, { + "uri": "file://dog_J_TRD.fasta#TRDJ1*01", + "range": { + "from": 0, + "to": 51 + }, + "sequence": "TTACAAATAAGCTCTTCTTTGGAAAAGGAACTCACCTGGTTGTAGAAGCAA" + }, { + "uri": "file://dog_J_TRD.fasta#TRDJ2*01", + "range": { + "from": 0, + "to": 48 + }, + "sequence": "CAGTATCCATGATCTTTGGCAAAGGAGCCTATCTGGTGGTAGAACCAT" + }, { + "uri": "file://dog_J_TRD.fasta#TRDJ3*01", + "range": { + "from": 0, + "to": 72 + }, + "sequence": "GCCTGAAGAAAGCCGTTAGTCAGGCTTTTGGTCCTCTTCTTCGTCAGAGATTGCGACCAGGGAAAAAGGCAA" + }, { + "uri": "file://dog_J_TRD.fasta#TRDJ4*01", + "range": { + "from": 0, + "to": 59 + }, + "sequence": "CTCTTGGGATACTCGACAGATGTTTTTTGGAGCTGGCACCAGACTCTTCGTGGAGCCAC" + }, { + "uri": "file://dog_J_TRG.fasta#TRGJ1-1*01", + "range": { + "from": 0, + "to": 66 + }, + "sequence": "ATAACTTCCTATGATGCTGGATCAAGAGATTAACAGAAGGAATTAGGCTCTTAGTAATTCCTCCTG" + }, { + "uri": "file://dog_J_TRG.fasta#TRGJ1-2*01", + "range": { + "from": 0, + "to": 59 + }, + "sequence": "GGTTCAGTATGGCTGGAGTACCAAAGTGTTTGGTCCTGGCAAAAAGCTTAGGGTCACAA" + }, { + "uri": "file://dog_J_TRG.fasta#TRGJ2-1*01", + "range": { + "from": 0, + "to": 66 + }, + "sequence": "GTAACATCTGACCAGGTTGGATCAAGAAATTTACAGAAGTTAACAGGTTTATAGTAACTCCTCATG" + }, { + "uri": "file://dog_J_TRG.fasta#TRGJ2-2*01", + "range": { + "from": 0, + "to": 59 + }, + "sequence": "TTACTGGAATAGTTGGAGCAACAAAGTGTTTGGTCCTGGCACAATTCTCAGGGTTACAG" + }, { + "uri": "file://dog_J_TRG.fasta#TRGJ3-1*01", + "range": { + "from": 0, + "to": 66 + }, + "sequence": "ATAACCTCTAACCGGGCTGGATCAAGATATTTACAGAAGGTACTAGGCTTATAGTAACTCCTCCTT" + }, { + "uri": "file://dog_J_TRG.fasta#TRGJ3-2*01", + "range": { + "from": 0, + "to": 59 + }, + "sequence": "CAGCTGGTCTGGCTGGTACAACAAAGTGCTTGGTCCTGGCACAATTCTCAGGGTTACAG" + }, { + "uri": "file://dog_J_TRG.fasta#TRGJ4-1*01", + "range": { + "from": 0, + "to": 66 + }, + "sequence": "ATAACCTCTAACCAGGCTGGGTCAAGATATTTGCAGAAGGTACTAAGCTTATAGTAACTCCTCCTG" + }, { + "uri": "file://dog_J_TRG.fasta#TRGJ4-2*01", + "range": { + "from": 0, + "to": 59 + }, + "sequence": "TTCCTGGAATAGTTGGAGGAACAAAGTGTTTGGTCCCGGCACAACGCTCAGGGTTACAG" + }, { + "uri": "file://dog_J_TRG.fasta#TRGJ5-1*01", + "range": { + "from": 0, + "to": 61 + }, + "sequence": "AAAACTCCGGCTGGATCAAGATTATTTGCAGAAGGAACTAATCTCATAGTAACTCCCCCTG" + }, { + "uri": "file://dog_J_TRG.fasta#TRGJ5-2*01", + "range": { + "from": 0, + "to": 59 + }, + "sequence": "GTATTTGTATAGCAGGTACAACAAAGTATTTGGTCCTGGCACAGTGCTCAGGGTTACAG" + }, { + "uri": "file://dog_J_TRG.fasta#TRGJ6-1*01", + "range": { + "from": 0, + "to": 60 + }, + "sequence": "ATAACTCAGGCTGGATCAAGATATTTGGAGAAGGAACTAAGCTCATAGTAACACCCTCAG" + }, { + "uri": "file://dog_J_TRG.fasta#TRGJ6-2*01", + "range": { + "from": 0, + "to": 61 + }, + "sequence": "AGTGGGTATGGCTGGAACAGCAACAAAGTGATGGGTCCTGACACACAGCTCAGGGTTACAG" + }, { + "uri": "file://dog_J_TRG.fasta#TRGJ7-1*01", + "range": { + "from": 0, + "to": 63 + }, + "sequence": "TCTCTTATTGCATTTCACAGACAGTGAAACTGAGGTTTGGAGAAGGTCAGTTATTTGCCTAAA" + }, { + "uri": "file://dog_J_TRG.fasta#TRGJ7-2*01", + "range": { + "from": 0, + "to": 60 + }, + "sequence": "GGTTGTTTGACTGGAGCAGAATCAAAGTGTTTGGTCCTGGCACAAAGCTCATGGTTACAG" + }, { + "uri": "file://dog_J_TRG.fasta#TRGJ8-1*01", + "range": { + "from": 0, + "to": 60 + }, + "sequence": "ATAACTCAGTCTGGATCAAGATATTTGGACAAGGAACTAAGGTCATAGTAACTCCTCCAG" + }, { + "uri": "file://dog_J_TRG.fasta#TRGJ8-2*01", + "range": { + "from": 0, + "to": 58 + }, + "sequence": "TAGTATGGCTAGAGCAAAATCAAAGCATTTGGTTGTGGCACAAAGCTCAGGGTTACAG" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV1-15*01", + "range": { + "from": 0, + "to": 294 + }, + "sequence": "GAGGTCCAGCTGGTGCAGTCTGGGGCTGAGGTGAAGAAGCCAGGTACATCCGTGAAGGTCTCATGCAAGACATCTGGATACACCTTCACTGACTACTATATGTACTGGGTACGACAGGCTTCAGGAGCAGGGCTTGATTGGATGGGACAGATTGGTCCCTAAGATGGTGCCACAAGGTATGCACAGAAGTTTCAGGGCAGAGTCACCCTGTCAACAGACACATCCACAAGCACAGCCTACATGGAGCTGAGCAGTCTGAGAGCTGAGGACACAGCCATGTACTACTCTGTGAGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV1-30*01", + "range": { + "from": 0, + "to": 294 + }, + "sequence": "GAGGTCCAGCTGGTGCAGTCTGGGGCTGAGGTGAAGAAGCCAGGGGCATCTGTGAAGGTCTCCTGCAAGACATCTGGATACACCTTCATTAACTACTATATGATCTGGGTACGACAGGCTCCAGGAGCAGGGCTTGATTGGATGGGACAGATTGATCCTGAAGATGGTGCCACAAGTTATGCACAGAAGTTCCAGGGCAGAGTCACCCTGACAGCAGACACATCCACAAGCACAGCCTACATGGAGCTGAGCAGTCTGAGAGCTGGGGACATAGCTGTGTACTACTGTGCGAGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-10*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGGTGGAGACTGAGGGAGACCTGGTGAAGCCTGGGGGATCCCTGAGACTTTCCTGTGTGGCCTCTGGATTCACCTTCAGTAGCTACGACATGGACTGGGTCTACCAGGCTCCAGGGAAAGGGTTACAGTGGGTCACATACATTAGCAATGGTGGAAGTAGCACAAGGTATGCAGACGCTGTGAAGGGCCAATTCACCATCTCCAGAGACAACGCCAGGAACACGCTCTATCTGCAGATGAACAGCCTGAGAGACAAGGACATGGCCGTGTATTACTGTGTGAGTGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-13*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGAGCAACTGGTGGAGTTTGGAGGACACATGGTGAATCCTGGGGGTTCCCTGGGTCTCTCCTGTCAGGCCTCTGGATTCACCTTCAGTAGCTATGGCATGAGCTGGGTCCGCCAGGCTCAAAAGAAGGGGCTGCAGTGGGTCGGACATATTAGCTATGATGGAAGTAGCACATACTACACAGACACTGTGAGGGACAGATTCACCATCTCCAGAGACAACACCAAGAACATGCTGTATCTGCAGATGAACAGCCTGAGAGCCGAGGACACAGCCGTGTATTACTGCATGAGGAA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-14*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGATGGTGGAGTCTGGGGGAGACCTGGTGAAGCCTGGGGGATCCCTGAGACTCTCCTGTGTGGCCTCTGGATTCACCTTCAGTAACTACAAAATGTACTGGGTCCACCAGGCTCCAGGGAAAGGGCTGGAGTGGGTCGCAAGGATTTATGAGAGTGGAAGTACCACATACTACGCAGAAGCTGTAAAGGGCCGATTCACCATCTCCAGAGACAACGCCAAGAACATGGTGTATCTGCAGATGAACAGCCTGAGAGCCTAGGACACGGCCGTGTATTACTGTGTGAGTGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-16*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTACAGCTGGTGGAGTCTGGAGGAGACCTGGTGAAGCCTGGGGGGTCCCTGAGACTCTCCTGTGTGGCCTCTGGATTCACCTTTAGTAGTTACTACATGTTTTGGATCCGCCAGGCACCAGGGAAGGGCAATCAGTGGGTCGGATATATTAACAAAGATGGAAGTAGCACATACTACCCAGACGCTGTGAAGGGCCGATTCACCATCTCCAGAGACAACGCCAAGAACACACTGTATCTGCAGATGAACAGCCTGACAGTGGAGGACACAGCCCTTTATTACTGTGCGAGAGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-18*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGGGGAGACCTTGTGAAACCTGAGGGGTCCCTGAGACTCTCCTGTGTGGTCTCTGGCTTCACCTTCAGTAGCTACGACATGAGCTGGGTCCGCCAGGCTCCAGGGAAGGGGCTGCAGTGGGTCGCATACATTAGCAGTGATGGAAGGAGCACAAGTTACACAGACGCTGTGAAGGGCCGATTCACCATCTCCAGAGACAATGCCAAGAACACGCTGTATCTGCAGATGAACAGCCTGAGAACTGAGGACACAGCCGTGTATTACTGTGCGAAGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-19*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGGGGAGACCTGGTGAAGCCTGCGGGGTCCCTGAGACTGTCCTGTGTGGCCTCTGGATTCACCTTCAGTAGCTACAGCATGAGCTGGGTCCGCCAGGCTCCTGAGAAGGGGCTGCAGTTGGTCGCAGGTATTAACAGCGGTGGAAGTAGCACATACTACACAGACGCTGTGAAGGGCCGATTCACCATCTCCAGAGACAACGCCAAGAACACAGTGTATCTGCAGATGAACAGCCTGAGAGCCGAGGACACGGCCATGTATTACTGTGCAAAGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-2*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGGGGAGACCTGGTGAAGCCTGGGGGGTCCCTGAGACTCTCCTGTGTGGCCTCTGGATTCACCTTCAGTAGCAACTACATGAGCTGGATCCGCCAGGCTCCAGGGAAGGGGCTGCAGTGGGTCTCACAAATTAGCAGTGATGGAAGTAGCACAAGCTACGCAGACGCTGTGAAGGGCCGATTCACCATCTCCAGAGACAATGCCAAGAACACGCTGTATCTGCAGATGAACAGCCTGAGAGATGAGGACACGGCAGTGTATTACTGTGCAAGGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-21*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGTTGGAGTCTGGGGGAGACCTGGTGAAGCCTGGGGGGTCCCTGAGACTGTCCTGTGTGGTCTCTGGATTCACCTTCAGTAAGTATGGCATGAGCTGGGTCTGCCAGGCTTTGGGGAAGGGGCTACAGTTGGTCGCAGCTATTAGCTAAGATGGAAGGAGCACATACTACACAGACACTGTGAAGGGCCGATTCACCATCTCCAGAGACAATGCCAAGAACACGCTGTACCTGCAGATGAACAGCTTGAGAGCTGAGGACACGGCCGTGTATTACTGTGAGAGTGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-21-1*01", + "range": { + "from": 0, + "to": 284 + }, + "sequence": "GAGGTGAAGCTAGTGGAGTCTGGGGGAGACCTGGTGAAGCCTGGGGGATCAATTAGACTCTCCTATGTGACCTCTGGATTCACCTTCAGGAGCTACTGGATGAGCTGGGTCAGCCAGGCTCCAGGGAAGGGGCTGCAGTGGGTCATATGGGTTAATACTGGTGGAAGCAGAAAAAGCTATGCAGATGCTGTGAAGGGGTGATTCACCATCTCCAGAGACAATGCCAAGAACACGCTGTATCTGCATATGAACAGCCTGAGAGCCCTGTATTATTATGTGAGTGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-23*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGGGGAGACCTGGAGAAGCCTGGGGGATCCCTGAGACTGTCCTGTGTGGCCTCTGGATTCACCTTCAGTAGCTACGGCATGAGCTGGGTCCGCCAGGCTCCAGGGAAGGGGCTGCAGGGGGTCTCATTGATTAGGTATGATGGAAGTAGCACAAGGTATGCAGACGCTGTGAAGGGCCGATTCACCATCTCCAGAGACAATGCCAAGAACACGCTGTATCTGCAGATGAACAGCCTGAGAGCCGAGGACACAGCCGTGTATTCCTGTGCGAAGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-24*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGGGGAGACCTTGTGAAGCCTGAGGGGTCCCTGAGACTCTCCTGTGTGGCCTCTGGATTCACCTTCAGTAGCTTCTACATGAGCTGGTTCTGCCAGGCTCCAAGGAAGGGGCTACAGTGGGTTGCAGAAATTAGCAGTAGTGGAAGTAGCACAAGCTACGCAGACATTGTGAAGGGCCGATTCACCATCTCCAGAGACAATGCCAAGAACATGCTGTATCTGCAGATGAACAGCCTGAGAGCCGAGGACATGGCCGTATATTATTGTGCAAGGTA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-25*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGGTGGAGCCTGGGGGAGAACTGGTGAAGCCTGGGGCGTCCCTGAGACTCTCCTGTGTGGTCCCTGGATTCACCTTCAGTAGCTACAACATGGGCTGGGCTCACCAGCCTCCAGGGAAGGGGATGCAGTGGGTCGCAGGTTTTAACAGCGGTGGAAGTAGCACAAGCTACACAGATGCTGTGAAGGGTGAATTCACCATCTCCAGAGACAATGTCAAGAACACGCTGTATCTGCAGATGAACAGCCTGAGATCCGAGGACACGGCCGTGTATTACTGTGTGAAGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-26*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGTAGCTGGTGGAGTCTGGGGGAGACCTGGTGAAGCCTGGGGGGTCCCTGAGACTCTCCTGTGTGGGCTCTGGATTCACCTTCAGTAGCTACTGGATGAGCTGGGTCCGCCAGGCTCCAGGGAAGGGGCTACAGTGGGTTGCAGAAATTAGCGGTAGTGGAAGTAGCACAAACTATGCAGACGCTGTGAAGGGCCGATTCATCATCTCCAGAGACAATGCCAAGAACACGCTGTATCTGCAGATGAACAGCCTGAGAGCCGAGGACACGGCCATGTATTACTGTGCAAGGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-3*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGGGGAGACATGGTGAAGCCTGGGGGGTCCCTGAGACTCTCCTGTGTGGCCTCTGGATTTACCTTCAGTAGTTACTACATGTATTGGGCCCGCCAGGCTCCAGGGAAGGGGCTTCAGTGGGTCTCACACATTAACAAAGATGGAAGTAGCACAAGCTATGCAGACGCTGTGAAGGGCCGATTCACCATCTCCAGAGACAACGCAAAGAATACGCTGTATCTGCAGATGAACAGCCTGAGAGCTGAGGACACAGCGGTGTATTACTGTGCAAAGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-31*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGGGGAGAACTGGTGAAGCCTGGGGGGTCCCTGAGACTCTCCTGTGTGGCCTCTGGATTCACCTTCAGTAGCTACTACATGAGCTGGATCCGCCAGGCTCCTGGGAAGGGGCTGCAGTGGGTCGCAGATATTAGTGACAGTGGAGGTAGCACATACTACACTGACGCTGTGAAGGGCCGATTCACCATCTCCAGAGACAACGTCAAGAACTCGCTGTATTTGCAGATGAACAGCCTGAGAGCCGAGGACACGGCCGTGTATTACTGTGCGAAGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-32*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GGGGTGCAGCTGGTGGAGTCTGGGGGAGACCTGGTGAAGCCTGGGGGGTCCCTGACACTCTCCTGTGTGGCCTATGGATTCACCTTCAGTAGCTACAGCATGCAATGGGTCTGTCAGGCTCCAGGGAAGGGGGTGCAGTGGGTCGCATACATTAACAGTGGTGGAAGTAGCACAAGCTCCGCAGATGCTGTGAAGGGTCGATTCATCATCTCCAGAGACAACGTCAAGAACACGCTATATCTGCAGATGAACAGCCTGAGAGCCGAGGACACCGCCGTGTATTACTGTGCGGGTGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-33*01", + "range": { + "from": 0, + "to": 294 + }, + "sequence": "GAGATGCAGCTGGTGGAGGCTGGGGGAGACCTGGTGAAGCTTGGGGGGTCCCTGAGACTCTTCTGTGTGGCCTCTGGATTTACCTTCAGTAGCTATTGGATGAGCTGGGTCGGCCAGGCTCCAGGGAAAGGGTTGCAGTGGGTTGCATACATTAACAGTGGTGGAAGTAGCACATACTATGCAGACGCTGTGAAGGGCCGATTCACCATCTCCAGAGACAATGCCAAGAACACGCTGTATCTGCAGATGAACTGCCTGAGAGCCGAGGACACGGCCGTATATTACTGTGTGGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-34*01", + "range": { + "from": 74, + "to": 115 + }, + "sequence": "CTGAGAGCTGAGGACACGGCCGTGTATTACTGTGCGAAGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-35*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGGGGAGACCTGGTGAAGCCTGTGGGATCCCTGAGACTCTCCTGTGTGGCCTCTGGATTCACCTTCAGTAGCTATGACATGAACTGGGTCCGCCAGGCTCCAGGGAAGGGGCTGCAGTGGGTCGCATACATTAGCAGTGGTGGAAGTAGCACATACTATGCAGATGCTGTGAAGGGCCGGTTCACCATCTCCAGAGACAACGCCAAGAACACGCTGTATCTTCAGATGAACAGCCTGAGAGCCGAGGACACGGCCATGTATTACTGTGCGGGTGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-36*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGGGCAGCTGGCGGAGTCTGGGGGAGACCTGGTGAAGCCTGAGAGGTCCCTGAGACTCGCCCGTGTGGCCTCTGGATTCACCTTCATTTCCTATACCATGAGCTGGGTCCACAAGGCTCCTGGGAAGGGGCTGCCGTGAGTCGCATGAATTTATTCTAGTGGAAGTAACATGAGCTATGCAGACGCTGTGAAGGGCCGATTCACCATCTCCAGAGACAATGCCAAGAACATGCTGTATCTGCAGATGAACAGCCTGAGAGCTGAGGACATGGCCATGTATTACTGTGTGAATGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-37*01", + "range": { + "from": 0, + "to": 293 + }, + "sequence": "GAGGTACAGCTGGTGGAGTCTGGGGAAGATTTGGTGAAGCCTGGAGGGTCCCTGAGACTCTCCTGTGTGGCCTCTGGATTCACCTTCAGTAGCAGTGAAATGAGCTGGGTCCACCAGGCTCCAGGGCAGGGGCTGCAGTGGGTCTCATGGATTAGGTATGATGGAAGTATCTCAAGGTATGCAGACACTGTGAAGGGCCGATTCACCATCTCCAGAGACAATGTCAAGAACACGCTGTATCTGCAGATGAACAGCCTGAGAGCCGAGGACACGGCCATATATTACTGTGCAGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-38*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGGGGAGACCTGGTGAAGCCTGGGGGGACCTTGAGACTGTCCTGTGTGGCCTCTGGATTCACCTTTAGTAGCTATGACATGAGCTGGGTCCGTCAGTCTCCAGGGAAGGGGCTGCAGTGGGTCGCAGTTATTTGGAATGATGGAAGTAGCACATACTACGCAGACGCTGTGAAGGGCCGATTCACCATCTCCAGAGACAACGCCAAGAACACGCTGTATCTGCAGATGAACAGCCTGAGAGCCGAGGACACGGCCGTGTATTACTGTGCGAAGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-39*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTACAGCTGGTGGAATCTGGGGGAGACCTCGTGAAGCCTGGGGGTTCCCTGAGACTCTCCTGTGTGGCCTCGGGATTCACCTTCAGTAGCTACTACATGAGCTGGATCCGCCAGGCTCCTGGGAAGGGGCTGCAGTGGGTCGCAGATATTAGTGATAGTGGAGGTAGCACAGGCTACGCAGACGCTGTGAAGGGCCGGTTCACCATCTCCAGAGAGAACGCCAAGAACAAGCTGTATCTTCAGATGAACAGCCTGAGAGCCGAGGACACAGCCGTGTATTACTGTGCGAAGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-41*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGGGGAGACCTGGTGAAGCCTGGGGGGTCCCTGAGACTCTCCTGTGTAGCCTCTGGATTCACCTTCAGTAACTACGACATGAGCTGGGTCCGCCAGGCTCCTGGGAAGGGGCTGCAGTGGGTCGCAGCTATTAGCTATGATGGAAGTAGCACATACTACACTGACGCTGTGAAGGGCCGATTCACCATCTCCAGAGACAACGCCAGGAACACAGTGTATCTGCAGATGAACAGCCTGAGAGCCGAGGACACGGCTGTGTATTACTGTGCGAAGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-43*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "AAGGTGTAGCTGGTGGAGTCTGGGGGAGACCTGATGAAGCCTGGGGGTTCCCTGAGACTGTCCTGTGTGGCCTCTGGATTCACCTTCAGGAGCTATGGCATGAGCTGGGTCTGCCAGGCTTCAGGGAAGGGGCTGCAGTGGGTCGCAGCTATTAGCTATGATGGAAGGAGCACATACTACACAGACACTGTGAAGGGCCGATTCACCATCTCCAGAGACAATGGCAAGAACACGCTGTACCTGCAGATGAACAGCTTGAGAGCTGAGGACACGGCCGTGTATTACTGTGCGAGTGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-44*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGGGGAGACCTGGTGAAGCCTGGGGGTTCCCTGAGACTCTCATGTGTGACTTCTGGATTCACCTTCAGTAGCTATTGGATGAGCTGTGTCCGCCAGGCTCCAGGGAAGGAGCTGCAGTGGGTCGCGTACATTAACAGTGGTGGAAGTAGCACATGGTACACAGACGCTGTGAAGGGTCGATTCACCATCTCCAGAGACAACGCCAAGAACACGCTGTATCTGCAGATGAACAACCTGAGAGCCGAAGACACGGCCGTGTATTACTGTGCGAGGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-45*01", + "range": { + "from": 0, + "to": 295 + }, + "sequence": "GAAGTACAGCTGCTGGAGTCTGGGGGAGACCGAGTGAAACCTGGGGGGTCCCAGAGACTCTCCTGTGTGGCCTCAAGGTTCACCTTCAGTAGCTACAGCATGCATTGTCTCCGTCAGTCTCCTGGGATGGGGCTACAGTGGGTCACATACATTAGCAGTAATGGAAGCAGCACATACTATGCAGACGCTGTGAAGGGTCGATTCACCATCTCCAGAGACAAAGCCAAGAACATGCTTTATCTACAGATGAACAGCCTGAGAGCTCAGGACATAGCCCTGTATTACTGTGCAGATG" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-46*01", + "range": { + "from": 0, + "to": 293 + }, + "sequence": "GAGGTACAGCTGGTGGAGTCTGGGGAAGATTTGGTGAAGCCTGGAGGGTCCCTGAGACTCTCCTGTGTGGCCTCTGGATTCACCTTCAGTAGCAGTGAAATGAGCTGGGTCCACCAGGCTCCAGGGCAGGGGCTGCAGTGGGTCTCATGGATTAGGTATGATGGAAGTAGCTCAAGGTATGCAGACACTGTGAAGGGCCGATTCACCATCTCCAGAGACAATGCCAAGAACACGCTGTATCTGCAGATGAACAGCCTGAGAGCCGAGGACACGGCCATATATTACTGTGCAGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-47*01", + "range": { + "from": 0, + "to": 293 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGGGGAGACCTGGCGAAGCCTGGGGGGTCCCTGAGACTCTCCTGTGTGGCCTCTGGATTAACCTTCAGTAGCTACAGCATGAGCTGGGTCCGCCAGGCTCCTGGGAAGGGGCTGCAGTGGGTCACAGCTATTAGCTATGATGGAAGTAGCACATACTACACTGACGCTGTGAAGGGCCGATTCACCATCTCCAGAGACAACGCCAGGAACACAGTGTATCTGCAGATGAACAGCCTGAGAGCCGAGGACACAGCTGTGTATTACTGTGTGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-47-1*01", + "range": { + "from": 0, + "to": 293 + }, + "sequence": "GAGGTGCCACTGGTGGAATCTGGGGGAGAGCTGGTGAAGCCTGGGGGGTCCCTGAGACTCTCCTTTGTAGCCTCTGCATTCACTTTCAGTAGTTACTGGATAAGCTGGGTCCGCCAAGCTCCAGGGAAAGGGCTGCACTGAGTCTCAGTAATTAACAAAGATGGAAGTACCACATACCACGCAGATGCTGTGAAGGGCCGATTCACCATCTCCAGAGACAATGCCAAGAACACGCTGTATCTGCAGATGAACAGCCTGAGAGCTGAGGACACGGCTGTGTATTACTGTGCACA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-5*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGGGGAGACCTGGTGAAGCCTGGGGGGTCCCTGAGACTTTCCTGTGTGGCCTCTGGATTCACCTTCAGTAGCTACCACATGAGCTGGGTCCGCCAGGCTCCAGGGAAGGGGCTTCAGTGGGTCGCATACATTAACAGTGGTGGAAGTAGCACAAGCTATGCAGACGCTGTGAAGGGCCGATTCACCATCTCCAGAGACAACGCCAAGAACACGCTGTATCTTCAGATGAACAGCCTGAGAGCCGAGGACACGGCCGTGTATTACTGTGCGAGTGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-50*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGGGGAGACCTGGTGAAGCCTGGGGGGTCCTTGAGACTCTCCTGTGTGGCCTCTGGTTTCACCTTCAGTAGCAACGACATGGACTGGGTCCGCCAGGCTCCAGGGAAGGGGCTGCAGTGGCTCACACGGATTAGCAATGATGGAAGGAGCACAGGCTACGCAGATGCTGTGAAGGGCCGATTCACCATCTCCAGAGACAACGCCAAGAACACGCTGTATCTGCAGATGAACAGCCTGAGAGCTGAGGACACAGCCGTGTATTACTGTGCGAAGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-52*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAAGTGCAGCTGGTGGAGTATGGGGGAGAGCTGGTGAAGCCTGGGGGGTCCCTGAGACTGTCCTGTGTGGCCTCCGGATTCACCTTCAGTATCTACTACATGCACTGGGTCCACCAGGCTCCAGGGAAGGGGCTGCAGTGGTTCGCATGAATTAGGAGTGATGGAAGTAGCACATACTACACTGATGCTGTGAAGGGCCGATTCACCATCTCCAGAGACAATTCCAAGAACACTCTGTATCTGCAGATGACCAGCCTGAGAGCCGAGGACACGGCCCTATATTACTGTGCGATGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-54*01", + "range": { + "from": 0, + "to": 297 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGGGGAGACCTGATGAAGCCTGGGGGGTCCCTGAGACTCTCCTGTGTGGCCTCCGGATTCACTATCAGTAGCAACTACATGAACTGGGTCCGCCAGGCTCCAGGGAAGGGGCTGCAGTGGGTCGGATACATTAGCAGTGATGGAAGTAGCACAAGCTATGCAGACGCTGTGAAGGGCCGATTCACCATCTCCAGAGACAATGCCAAGAACACGCTGTATCTGCAGATGAACAGCCTGAGAGCCGAGGACACGGCCGTGTATTACTGTGTGAAGGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-58*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGGGGAGACCTGGTGAAGCCTGGGGGATCCCTGAGACTCTCTTGTGTGGCCTCCGGATTCACCTTCAGTAGCCATGCCAAGAGCTGGGTCCGCCAGGCTCCAGGGAAGGGGCTGAAGTGGGTAGCAGTTATTAGCAGTAGTGGAAGTAGCACAGGCTCCGCAGACACTGTGAAGGGCCGATTCACCATCTCCAGAGACAATGCCAAGAACACGCTGTATCTGCAGATGAACAGCCTGAGAGCTGAGGACACAGCCGTGTATTACTGTGCGAAGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-6*01", + "range": { + "from": 0, + "to": 293 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGGGGAGACCTGGTGAAGCCTGGGGGGTCCCTGAGACTCTCCTGTGTAGCCTCTGGATTCACCTTCAGTAGCTCCGACATGAGCTGGATCCGCCAGGCTCCAGGAAAGGGGCTTCAGTGGGTCGCATACATTAGCAATGATGGAAGTAGCACAAGCTACGCAGACGCTGTGAAGGGCCGATTCACCATCTCCAGAGACAACGCCAAGAACACGCTCTATCTGCAGATGAACAGCCTCAGAGCCGAGGACACGGCCGTGTATTACTGTGCAGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-60*01", + "range": { + "from": 0, + "to": 284 + }, + "sequence": "GAGGTGAAGCTGGTGGAGTCTGGGGGAGACCTGTTGAAGCCTGGGGGATCAATTAAACTCTCCTATGTGACCTCTGGATTCACCTTCAGGAGCTACTGGATGAGCTGGGTCAGCCAGGCTCCAGGGAAGGGGCTGCAGTGGGTCACATGGGTTAATACTGGTGGAAGCAGCAAAAGCTATGCAGATGCTGTGAAGGGGCAATTCACCATCTCCAGAGACAATGCCAAGAACACGCTGTATCTGCATATGAACAGCCTGATAGCCCTGTATTATTGTGTGAGTGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-61*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGTGGAAACCTGGTGAAGCCTGGGGGTTCCCTGAGACTGTCCTGTGTGGCCTCTGGATTAACCTTCTATAGCTATGCCATTTACTGGGTCCACGAGGCTCCTGGGAAGGGGCTGCAGTGGGTCGCAGCTATTACCACTGATGGAAGTAGCACATACTACACTGACGCTGTGAAGGGCCGATTCACCATCTCCAGAGACAATGCCAAGAACACGCTGTATCTGCAGATGAACAGCCTGAGAGCTGAGGACATGCCCGTGTATTACTGTGCGAGGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-67*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGGGGAGACCTGGTGAAGCCTGGGGGGTCCCTGAGACTGTCCTGTGTGGCCTCTGGATTCACCTTCAGTAGCTACTACATGTACTGGGTCCGCCAGGCTCCAGGGAAGGGGCTTCAGTGGGTCGCACGGATTAGCAGTGATGGAAGTAGCACATACTACGCAGACGCTGTGAAGGGCCGATTCACCATCTCCAGAGACAATGCCAAGAACACGCTGTATCTGCAGATGAACAGCCTGAGAGCCGAGGACACGGCTATGTATTACTGTGCAAAGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-69*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTACAGCTGGTGGAGTCTGGGGGAGACCTGGTGAAGCCTGGGGGATCCCTGAGACTGTCCTGTGTGGCCTCTGGATTCACCTTCAGTAGCTATGCCATGAGCTGGGTCCGCCAGGCTCCAGGGAAGGGGCTGCAGTGGGTCGCATACATTAACAGTGGTGGAAGTAGCACATACTACGCAGATGCTGTGAAGGGCCGGTTCACCATCTCCAGAGACAATGCCAGGAACACACTGTATCTGCAGATGAACAGCCTGAGATCCGAGGACACAGCCGTGTATTACTGTCCGAAGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-7*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGAGCAACTGGTGGAGTTTGGAGGACACATGGTGAATCCTGGGGGTTCCCTGGGTCTCTCCTGTCAGGCCTCTGGATTCACCTTCAGTAGCTATGGCATGAGCTGGGTCCGCCAGGCTCAAAAGAAGGGGCTGCAGTGGGTCGGACATATTAGCTATGATGGAAGTAGTACATACTACGCAGACACTTTGAGGGACAGATTCACCATCTCCAGAGACAACACCAAGAACATGCTGTATCTGCAGATGAACAGCCTGAGAGCCGAGGACACAGCCGTGTATTACTGCATGAGGAA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-70*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGAGGAGACCTTGTGAAGCCTGAGCGGTCCCTGAGACTCTCCTGTGTGGCCTCTGGATTCACCTTCAGTAGCTTCTACATGAGCTGGTTCTGCCAGGCTCCAGGGAAGGGGCTACAGTGTGTTGCAGAAATTAGCAGTAGTGGAAATAGCACAAGCTACGCAGACGCTGTGAAGGGCCGATTCACCATCTCCAGAGACAACGCCAAGAACACGCTGTATCTACGGATGCACAGCCTGAGAGCCGAGGACACGGCTGTATATTACTGTGCAAGGTA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-72*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGCGGAGACCTGGTGAAGCCTGGGGATTCCCTGAGACTGTCCTGTGTGGCCTCTGGATTCACCTTCAGTAGCTATGCCATGAGCTGGGTCCGCCAGGCTCCTAGGAAGGGGCTGCAGTGGGTCGGATACATTAGCAGTGATGGAAGTAGCACATAATACGCAGACGCTGTGAAGGGCCGATTCACCATTTCCAGAGACAATGCCAAGAACACGCTGTATCTGCAGATGAACAGCCTGAGAGCTGAGGATACGGCCCTGTATAACTGTGCAAGGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-75*01", + "range": { + "from": 0, + "to": 293 + }, + "sequence": "GAATTGCAGCTGGTGGAGCTTGGGGGAGATCTGGTGAAGCCAGGGGGGTCCCTGAGACTCTCCTGTGTGGCCTCTGGATTCACCTTCAGTAGCTATGCCATGAGTTGGGTCTGCCAGGCTCCAGGGAAGGGGCTGCAGTGGGTTGCAGCTATTAGCAGTAGTGGAAGTAGCACATACCATGTAGACGCTGTGAAGGGCCGATTCACCATCTCCAGAGACAACGCCAAGAACACAGTGTATCTGCAGATGAACAGCCTGAGAGCCGAGGACACGGCCGTGTATTACTGTGCAGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-76*01", + "range": { + "from": 0, + "to": 293 + }, + "sequence": "GAGGTGCCACTGGTGGAATCTGGGGGAGAGCTGGTGAAGCCTGAGGGGTCCCTGAGATTCTCCTGTGTAGCCTCTGGATTCACTTTCAGTAGTTACTGGATAAGCTGGGTCCGCCAAGCTCCAGGGAAAGGGCTGCACTGGGTCTCAGTAATTAACAAAGATGGAAGTACCACATACCACGCAGATGCTGTGAAGGGCCGATTCACCATCTCCAGAGACAATGCCAAGAACACGCTGTATCTGCAGATGAACAGCCTGAGAGCTGAGGGCACGACTGTGTATTACTGTGCACA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-78*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGGGGAGACCTTGTGAAGCCGGAGGGGTCCCTGAGACTCTCCTGTGTGGCCGCTGGATTCACCTTTAGTAGCTACAGCATGAGCTGGGTCCGCCAGGCTCCCGGGAAGGGGGTGCAGTGGGTCACATAGATTTATGCTAGTGGAAGTAGCACAAGCTACACAGATGCTGTGAAGGGCCGATTCACCATCTCCAGAGACAACGCCAAGAACACAGTGTTTCTGCAGATGAACAGCCTGAGAGCTGAGAACACGGCCATGTATTCCTGTGCAAGGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-8*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGGGGAGACCTGGTGAAGCCTGGGGGGTCCCTGAGACTCTCCTGTGTGGCCTCTGGATTCACCTTCAGTAACTACGAAATGTACTGGGTCCGCCAGGCTCCAGGGAAAGGGCTGGAGTGGGTCGCAAGGATTTATGAGAGTGGAAGTACCACATACTATGCAGAAGCTGTAAAGGGCCGATTCACCATCTCCAGAGACAACGCCAAGAACATGGCGTATCTGCAGATGAACAGCCTGAGAGCCGAGGACACGGCCGTGTATTACTGTGCGAGTGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-80*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGGGGAGATCTGGTGAAGCCTGGGGGATCCCTGAGACTCTCTTGTGTGGCCTCTGGATTCACCTTCAGTAGCTACTACATGGAATGGGTCCGCCAGGCTCCAGGGAAGGGGCTGCAGTGGGTCGCACAGATTAGCAGTGATGGAAGTAGCACATACTACCCAGACGCTGTGAAGGGTCAATTCACCATCTCCAGAGACAATGCCAAGAACACGCTGTATCTGCAGATGAACAGCCTGGGAGCCGAGGACACGGCCGTGTATTACTGTGCAAAGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-81*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGAGGAAACCTGGTGAAGCCTGGGGGGTCCCTGAGACTCTCTTGTGTGGCCTCTGGATTCACCTTCAGTAGCTACTACATGGACTGGGTCCGCCAGGCTCCAGGGAAGAGGCTGCAGTGGGTCGCAGGGATTAGCAGTGATGGAAGTAGCACATACTACCCACAGGCTGTGAAGGGCCGATTCACCATCTCCAGAGACAACGCCAAGAACACGCTCTATCTGCAGATGAACAGCCTGAGAGCCGAGGACTCTGCTGTGTATTACTGTGCGATGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-82*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGAGGAGACCTGGTGAAGTCTGGGGGGTCCCTGAGACTCTCTTGTGTGGCCTCTGGATTCACCTTCAGTAGCTACTACATGCACTGGGTCCGCCAGGCTACAGGGAAGGGGCTGCAGTGGGTCACAAGGATTAGCAATGATGGAAGTAGCACAAGGTACGCAGACGCCATGAAGGGCCAATTTACCATCTCCAGAGACAATTCCAAGAATACGCTGTATCTGCAGATGAACAGCCAGAGAGCCGAGGACATGGCCCTATATTACTGTGCAAGGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-83*01", + "range": { + "from": 0, + "to": 297 + }, + "sequence": "GAGTTGCAGCTGGTAGAGTCTGGGGGAGACCTGGTGAAGCCTGGGGGGTCTCTGAGACTTTCTTGTGTGTCCTCTGGATTCACCTTCAGTAGCTACTGGATGCACTGGGTCCTCCAGGCTCCAGGGAAAGGGCTGGAGTGGGTCGCAATTATTAACAGTGGTGGAGGTAGCATATACTACGCAGACACAGTGAAGGGCCGATTCACCATCTCCAGAGAAAACGCCAAGAACACGCTCTATCTGCAGATGAACAGCCTGAGAGCTGAGGACAGGGCCATGCATTACTGTGCGAAGGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV3-9*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "GAGGTGCAGCTGGTGGAGTCTGGAGGAGACCTGGTGAAGCCTGGGGGGTCCCTGAGACTTTCCTGTGTGGCCTCTGGATTCACCTTCAGTAGCTATGACATGGACTGGGTCCGCCAGGCTCCAGGGAAGGGGCTGCAGTGGCTCTCAGAAATTAGCAGTAGTGGAAGTAGCACATACTACGCAGACGCTGTGAAGGGCCGATTCACCATCTCCAGAGACAACGCCAAGAACACGCTGTATCTGCAGATGAACAGCCTGAGAGCCGAGGACACGGCCGTGTATTACTGTGCAAGGGA" + }, { + "uri": "file://dog_V_IGH.fasta#IGHV4-1*01", + "range": { + "from": 0, + "to": 290 + }, + "sequence": "GAACTCACACTGCAGGAGTCAGGGCCAGGACTGGTGAAGCCCTCACAGACCCTCTCTCTCACCTGTGTTGTGTCCGGAGGCTCCGTCACCAGCAGTTACTACTGGAACTGGATCCGCCAGCGCCCTGGGAGGGGACTGGAATGGATGGGGTACTGGACAGGTAGCACAAACTACAACCCGGCATTCCAGGGACGCATCTCCATCACTGCTGACACGGCCAAGAACCAGTTCTCCCTGCAGCTGAGCTCCATGACCACCGAGGACACGGCCGTGTATTACTGTGCAAGAGA" + }, { + "uri": "file://dog_V_IGK.fasta#IGKV2-10*01", + "range": { + "from": 0, + "to": 302 + }, + "sequence": "GATATTGTCATGACACAGACCCCACTGTCCCTGTCCGTCAGCCCTGGAGAGACTGCCTCCATCTCCTGCAAGGCCAGTCAGAGCCTCCTGCACAGTGATGGAAACACGTATTTGAATTGGTTCCGACAGAAGCCAGGCCAGTCTCCACAGCGTTTGATCTATAAGGTCTCCAACAGAGACCCTGGGGTCCCAGACAGGTTCAGTGGCAGCGGGTCAGGGACAGATTTCACCCTGAGAATCAGCAGAGTGGAGGCTGACGATACTGGAGTTTATTACTGCATGCAAGGTACACAGTTTCCTCG" + }, { + "uri": "file://dog_V_IGK.fasta#IGKV2-11*01", + "range": { + "from": 0, + "to": 302 + }, + "sequence": "GATATCGTCATGACACAGACCCCACTGTCCCTGTCCGTCAGCCCTGGAGAGACTGCCTCCATCTCCTGCAAGGCCAGTCAGAGCCTCCTGCACAGTAACGGGAACACCTATTTGTTTTGGTTCCGACAGAAGCCAGGCCAGTCTCCACAGCGCCTGATCAACTTGGTTTCCAACAGAGACCCTGGGGTCCCACACAGGTTCAGTGGCAGCGGGTCAGGGACAGATTTCACCCTGAGAATCAGCAGAGTGGAGGCTGACGATGCTGGAGTTTATTACTGCGGGCAAGGTATACAAGCTCCTCC" + }, { + "uri": "file://dog_V_IGK.fasta#IGKV2-4*01", + "range": { + "from": 0, + "to": 302 + }, + "sequence": "GATATTGTCATGACACAGACGCCACCGTCCCTGTCTGTCAGCCCTAGAGAGACGGCCTCCATCTCCTGCAAGGCCAGTCAGAGCCTCCTGCACAGTGATGGAAACACCTATTTGGATTGGTACCTGCAAAAGCCAGGCCAGTCTCCACAGCTTCTGATCTACTTGGTTTCCAACCGCTTCACTGGCGTGTCAGACAGGTTCAGTGGCAGCGGGTCAGGGACAGATTTCACCCTGAGAATCAGCAGAGTGGAGGCTAACGATACTGGAGTTTATTACTGCGGGCAAGGTACACAGCTTCCTCC" + }, { + "uri": "file://dog_V_IGK.fasta#IGKV2-5*01", + "range": { + "from": 0, + "to": 302 + }, + "sequence": "GATATTGTCATGACACAGACCCCACTGTCCCTGTCCGTCAGCCCTGGAGAGCCGGCCTCCATCTCCTGCAAGGCCAGTCAGAGCCTCCTGCACAGTAATGGGAACACCTATTTGTATTGGTTCCGACAGAAGCCAGGCCAGTCTCCACAGCGTTTGATCTATAAGGTCTCCAACAGAGACCCTGGGGTCCCAGACAGGTTCAGTGGCAGCGGGTCAGGGACAGATTTCACCCTGAGAATCAGCAGAGTGGAGGCTGATGATGCTGGAGTTTATTACTGCGGGCAAGGTATACAAGATCCTCC" + }, { + "uri": "file://dog_V_IGK.fasta#IGKV2-6*01", + "range": { + "from": 0, + "to": 302 + }, + "sequence": "GATATTGTCATGACACAGACCCCACTGTCCCTGTCTGTCAGCCCTGGAGAGACTGCCTCCATCTCCTGCAAGGCCAGTCAGAGCCTCCTGCACAGTGATGGAAACACGTATTTGAACTGGTTCCGACAGAAGCCAGGCCAGTCTCCACAGCGTTTAATCTATAAGGTCTCCAACAGAGACCCTGGGGTCCCAGACAGGTTCAGTGGCAGCGGGTCAGGGACAGATTTCACCCTGAGAATCAGCAGAGTGGAGGCTGACGATACTGGAGTTTATTACTGCGGGCAAGGTATACAAGATCCTCC" + }, { + "uri": "file://dog_V_IGK.fasta#IGKV2-7*01", + "range": { + "from": 0, + "to": 302 + }, + "sequence": "GATATTGTCATGACACAGAACCCACTGTCCCTGTCCGTCAGCCCTGGAGAGACGGCCTCCATCTCCTGCAAGGCCAGTCAGAGCCTCCTGCACAGTAACGGGAACACCTATTTGAATTGGTTCCGACAGAAGCCAGGCCAGTCTCCACAGGGCCTGATCTATAAGGTCTCCAACAGAGACCCTGGGGTCCCAGACAGGTTCAGTGGCAGCGGGTCAGGGACAGATTTCACCCTGAGAATCAGCAGAGTGGAGGCTGACGATGCTGGAGTTTATTACTGCATGCAAGGTATACAAGCTCCTCC" + }, { + "uri": "file://dog_V_IGK.fasta#IGKV2-8*01", + "range": { + "from": 0, + "to": 302 + }, + "sequence": "GATATTGTCATGACACAGACCCCACCGTCCCTGTCCGTCAGCCCTGGAGAGCCGGCCTCCATCTCCTGCAAGGCCAGTCAGAGCCTCCTGCACAGTAACGGGAACACCTATTTGAATTGGTTCCGACAGAAGCCAGGCCAGTCTCCACAGGGCCTGATCTATAGGGTGTCCAACCGCTCCACTGGCGTGTCAGACAGGTTCAGTGGCAGCGGGTCAGGGACAGATTTCACCCTGAGAATCAGCAGAGTGGAGGCTGACGATGCTGGAGTTTATTACTGCGGGCAAGGTATACAAGATCCTCC" + }, { + "uri": "file://dog_V_IGK.fasta#IGKV2-9*01", + "range": { + "from": 0, + "to": 302 + }, + "sequence": "GATATTGTCATGACACAGACCCCACTGTCCCTGTCTGTCAGCCCTGGAGAGACTGCCTCCATCTCTTGCAAGGCCAGTCAGAGCCTCCTGCACAGTGATGGAAACACGTATTTGAATTGGTTCCGACAGAAGCCAGGCCAGTCTCCACAGCGTTTGATCTATAAGGTCTCCAACAGAGACCCTGGGGTCCCAGACAGGTTCAGTGGCAGCGGGTCAGGGACAGATTTCACCCTGAGAATCAGCAGAGTGGAGGCTGACGATACTGGAGTTTATTACTGCGGGCAAGTTATACAAGATCCTCC" + }, { + "uri": "file://dog_V_IGK.fasta#IGKV2S13*01", + "range": { + "from": 0, + "to": 302 + }, + "sequence": "GATATCGTCATGACGCAGACCCCACTGTCCCTGTCTGTCAGCCCTGGAGAGCCGGCCTCCATCTCCTGCAGGGCCAGTCAGAGCCTCCTGCACAGTAATGGGAACACCTATTTGTATTGGTTCCGACAGAAGCCAGGCCAGTCTCCACAGGGCCTGATCTACTTGGTTTCCAACCGTTTCTCTTGGGTCCCAGACAGGTTCAGTGGCAGCGGGTCAGGGACAGATTTCACCCTGAGAATCAGCAGAGTGGAGGCTGACGATGCTGGAGTTTATTACTGCGGGCAAAATTTACAGTTTCCTTC" + }, { + "uri": "file://dog_V_IGK.fasta#IGKV2S14*01", + "range": { + "from": 0, + "to": 302 + }, + "sequence": "GAGGTTGTGATGATACAGACCCCACTGTCCCTGTCTGTCAGCCCTGGAGAGCCGGCCTCCATCTCCTGCAGGGCCAGTCAGAGTCTCCGGCACAGTAATGGAAACACCTATTTGTATTGGTACCTGCAAAAGCCAGGCCAGTCTCCACAGCTTCTGATCGACTTGGTTTCCAACCATTTCACTGGGGTGTCAGACAGGTTCAGTGGCAGCGGGTCTGGCACAGATTTTACCCTGAGGATCAGCAGGGTGGAGGCTGAGGATGTTGGAGTTTATTACTGCATGCAAAGTACACATGATCCTCC" + }, { + "uri": "file://dog_V_IGK.fasta#IGKV2S16*01", + "range": { + "from": 0, + "to": 302 + }, + "sequence": "GAGGCCGTGATGACGCAGACCCCACTGTCCCTGGCCGTCACCCCTGGAGAGCTGGCCACTATCTCCTGCAGGGCCAGTCAGAGTCTCCTGCGCAGTGATGGAAAATCCTATTTGAATTGGTACCTGCAGAAGCCAGGCCAGACTCCTCGGCCGCTGATTTATGAGGCTTCCAAGCGTTTCTCTGGGGTCTCAGACAGGTTCAGTGGCAGCGGGTCAGGGACAGATTTCACCCTTAAAATCAGCAGGGTGGAGGCTGAGGATGTTGGAGTTTATTACTGCCAGCAAAGTCTACATTTTCCTCC" + }, { + "uri": "file://dog_V_IGK.fasta#IGKV2S18*01", + "range": { + "from": 0, + "to": 302 + }, + "sequence": "GATATCGTCATGACACAGACCCCACTGTCCGTGTCTGTCAGCCCTGGAGAGACGGCCTCCATCTCCTGCAGGGCCAGTCAGAGCCTCCTGCACAGTGATGGAAACACCTATTTGGATTGGTACCTGCAGAAGCCAGGCCAGATTCCAAAGGACCTGATCTATAGGGTGTCCAACTGCTTCACTGGGGTGTCAGACAGGTTCAGTGGCAGCGGGTCAGGGACAGATTTCACCCTGAGAATCAGCAGAGTGGAGGCTGACAACGCTGGAGTTTATTACTGCATGCAAGGTATACAAGATCCTCC" + }, { + "uri": "file://dog_V_IGK.fasta#IGKV2S19*01", + "range": { + "from": 0, + "to": 302 + }, + "sequence": "GATATCGTCATGACACAGACTCCACTGTCCCTGTCTGTCAGCCCTGGAGAGACGGCCTCCATCTCCTGCAGGGCCAATCAGAGCCTCCTGCACAGTAATGGGAACACCTATTTGGATTGGTACATGCAGAAGCCAGGCCAGTCTCCACAGGGCCTGATCTATAGGGTGTCCAACCACTTCACTGGCGTGTCAGACAGGTTCAGTGGCAGCGGGTCAGGGACAGATTTCACCCTGAAGATCAGCAGAGTGGAGGCTGACGATGCTGGAGTTTATTACTGCGGGCAAGGTACACACTCTCCTCC" + }, { + "uri": "file://dog_V_IGK.fasta#IGKV3S1*01", + "range": { + "from": 0, + "to": 286 + }, + "sequence": "GAAATCGTGATGACACAGTCTCCAGCCTCCCTCTCCTTGTCTCAGGAGGAAAAAGTCACCATCACCTGCCGGGCCAGTCAGAGTGTTAGCAGCTACTTAGCCTGGTACCAGCAAAAACCTGGGCAGGCTCCCAAGCTCCTCATCTATGGTACATCCAACAGGGCCACTGGTGTCCCATCCCGGTTCAGTGGCAGTGGGTCTGGGACAGACTTCAGCTTCACCATCAGCAGCCTGGAGCCTGAAGATGTTGCAGTTTATTACTGTCAGCAGTATAATAGCGGATATA" + }, { + "uri": "file://dog_V_IGK.fasta#IGKV4-1*01", + "range": { + "from": 0, + "to": 287 + }, + "sequence": "GACATCACGATGACTCAGTGTCCAGGCTCCCTGGCTGTGTCTCCAGGTCAGCAGGTCACCACGAACTGCAGGGCCAGTCAAAGCGTTAGTGGCTACTTAGCCTGGTACCTGCAGAAACCAGGACAGCGTCCTAAGCTGCTCATCTACTTAGCCTCCAGCTGGGCATCTGGGGTCCCTGCCCGATTCAGCAGCAGTGGATCTGGGACAGATTTCACCCTCACCGTCAACAACCTCGAGGCTGAAGATGTGAGGGATTATTACTGTCAGCAGCATTATAGTTCTCCTCT" + }, { + "uri": "file://dog_V_IGK.fasta#IGKV4S1*01", + "range": { + "from": 0, + "to": 305 + }, + "sequence": "GAAATCGTGATGACCCAGTCTCCAGGCTCTCTGGCTGGGTCTGCAGGAGAGAGCGTCTCCATCAACTGCAAGTCCAGCCAGAGTCTTCTGTACAGCTTCAACCAGAAGAACTACTTAGCCTGGTACCAGCAGAAACCAGGAGAGCGTCCTAAGCTGCTCATCTACTTAGCCTCCAGCTGGGCATCTGGGGTCCCTGCCCGATTCAGCAGCAGTGGATCTGGGACAGATTTCACCCTCACCATCAACAACCTCCAGGCTGAAGATGTGGGGGATTATTACTGTCAGCAGCATTATAGTTCTCCTCC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-100*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCGACCTCAGTGTCGGGGTCCCTTGGCCAGAGGGTCACCATCTCCTGCTCTGGAAGCACGAACAACATCGGTATTGTTGGTGCGAGCTGGTACCAACAGCTCCCAGGAAAGGCCCCTAAACTCCTCGTGGACAGTGATGGGGATCGACCGTCAGGGGTCCCTGACCGGTTTTCCGGCTCCAAGTCTGGCAACTCAGCCACCCTGACCATCACTGGGCTTCAGGCTGAGGACGAGGCTGATTATTACTGCCAGTCCTTTGATACCACGCTTGATGCTCA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-103*01", + "range": { + "from": 0, + "to": 299 + }, + "sequence": "CAGGCTGTGCTGACTCAGCCACCCTCTGTGTCTGCAGCCCTGGGGCAGAGGGTCACCATCTCCTGCACTGGAAGTAACACCAACATCGGCAGTGGTTATGATGTACAATGGTACCAGCAGCTCCCAGGAAAGTCCCCTAAAACTATCATTTATGGTAATAGCAATCGACCCTCGGGGGTCCCGGTTCGATTCTCTGGCTCCAAGTCAGGCAGCACAGCCACCCTGACCATCACTGGGATCCAGGCTGAGGATGAGGCTGATTATTACTGCCAGTCCTATGATGACAACCTCGATGGTCA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-104*01", + "range": { + "from": 0, + "to": 293 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCAGCTTCAGTGTCTGGGTCCCTGGGCCAGAGGATCACCATCTCCTGCACTAAAAGCAGCTCCAACATCGGTAGGTATTATGTGAGCTGACAACAGCTCCCAGGAACAGGCCCCAGAACCGTCATCTATGATAATAATAACTGACCCTCGGGGGTCCCTGATCAATTTTCTGGCTCTAAATCAGGCAGCACAGCCACCCTGACCATCTCTAGGCTCCAGGCTGAGGACGATGCTGATTATTACTGCTCGCCATATGCCAGCAGTCTCAGTGCTGG" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-106*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGTTGACTCAACCGGCCTCAGTGTCTGGGTCCCTGGGCCAGAGGGTCATCATCTCCTGCACTGGAAGCAGCTCCAGCATTGGCAGAGGTTATGTGGGCTGGTACCAACAGCTCCCAGGAACAGGCCCCAGAACCCTCATCTATGGTATTAGTAACCTACCCCCGGGAGTCCCCAATAGATTCTCTGGTTCGAGGTCAGGCAGCACAGCCACCCTGACCATCGCTGAGCTCCAGGCTGAGGACGAGGCTGATTATTACTGCTCATCGTGGGACAGAAGTCTCAGTGCTCC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-111*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCGGCCTCAGTGTCTGGGTCCCTGGGCCTGAGGGTCACCATCTGCTGCACTGGAAGCAGCTCCAACATCAGTAGTTATTATGTGGGCTGGTACCAACCACTCGCGGGAACAGGCCCCAGAACTGTCATCTATGATAATAGTAACCGTCCCTCGGGGGTCCCTGATCAATTCTCTGGCTCCAAGTCAGGCAGCACAGCCACCCTGACCATCTCTCGGCTCCAGGCTGAGGACGAGGCTGATTATTACGGCTCATCATATGACAGCAGTCTCAATGCTGG" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-112*01", + "range": { + "from": 0, + "to": 299 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCAGCCTCAGTGTCTCAGTCCCTGGGTCAGAGGGTCACCATCTCCTGTACTGGAAGCAGCTCCAATGTTGGTTATAACAGTTATGTGAGCTGGTACCAGCAGCTCCCAGGAACAGTCCCCAGAACCATCATCTATTATACCAATACTCGACCCTATGGGGTTCCTGATCGATTCTCTGGCTCCAAATCAGGCAACTCAGCCACCCTGACCATTGCTGGACTCCAGGCTGAGGACGAGGCTGATTATTATTGCTCAACATATGACAGCAGTCTCAGTGGTGC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-114*01", + "range": { + "from": 0, + "to": 293 + }, + "sequence": "CAGGCTTTGCTGACTCAGCCACCCTCAGTGTCTGAGGCCCTGGGACAGAGGGTCACCATCTCCTGCACTGGAAGCAGCACCAACATCGGCAGTGGTTATGATGTACAATGGTACCAGCAGCTCCCAGGAAAGTCCCCTCAAACTATCGTATACGGTAATAGCAATTGACCCTCGGGGGTCCCAGATCAATTCTCTGGCTCCAAGTCTCACAATTCAGCCACCCTGACCATCACTGGGCTCCAGACTGAGGACGAGGCTGATTATTACTGCCAGTCCTCTGATGACAACCTCGA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-115*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCAGCCTCAGTGTCTGGGTCCCTGGGCCAGAGGGTCACCATCTCCTGCACTGGAAGCAGCTCCAACATCGGTAGATATAGTGTAGGCTGATACCAGCAGCTCCCGGGAACAGGCCCCAGAACTGTCATCTATGGTAGTAGTAGCCGACCCTCGGGGGTCCCCGATCGATTCTCTGGCTCCAAGTCAGGCAGCACAGCCACCCTGACCATCTCAGGGCTCCAGGCTGAGGACGAGGCTGATTATTACTGTTCAACATACGACAGCAGTCTCAAAGCTCC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-116*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGCCTGTGCTCACTCAGCCGCCCTCAGTGTCTGGGTTCCTGGGACAGAGGGTCACTATCTCCTGCACTGGAAGCAGCTCCAACATCCTTGGTAATTCTGTGAACTGGTACCAGCAGCTCACAGGAAGAGGCCCCAGAACCGTCATCTATTATGATAACAACCGACCCTCTGGGGTCCCTGATCAATTCTCTGGCTCCAAGTCAGGCAACTCAGCCACCCTGACCATCTCTGGGCTCCAGGCTGAGGACGAGACTGATTATTACTGCTCAACGTGGGACAGCAGGCTCAGAGCTCC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-118*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCGGCCTCAGTGTCTGGGTCCCTGGGCCAGAGGGTCACCATCTCCTGCACTGAAAGCAGCTCCAACATCGGTGGATATTATGTGGGCTGGTACCAACAGCTCCCAGGAACAGGCCCCAGAACCATCATCTATAGTAGTAGTAACCGACCCTCAGGGGTCCCTGATTGATTCTCTGGCTCCAGGTCAGGCAGCACAGCCACCCTGACCATCTCTGGGCTCCAGGCTGAGGACGAGGCTGATTATTACTGCTCTACATGGGACAGCAGTCTCAAAGCTCC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-125*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCGGCCTCAGTGTCCGGGTCCCTGGGCCAGAGGGTCACCATCTCCTGCACTGGAAGCAGCTCCAACATCGGTAGAGGTTATGTGGGCTGGTACCAACAGCTCCCGGGAACAGGCCCCAGAACCCTCATCTATGGTAATAGTAACCGACCCTCAGGGGTCCCCGATCGGTTCTCTGGCTCCAGGTCAGGCAGCACAGCCACCCTGACCATCTCTGGGCTCCAGGCTGAGGATGAGGCTGATTATTACTGCTCATCGTGGGACAGCAGTCTCAGTGCTCT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-129*01", + "range": { + "from": 0, + "to": 299 + }, + "sequence": "CAGTCTGTGCTGACTCAACCAGTCTCAGTGTCTGGGGCCCTGTGCCAGAGGGTCACCATCTCCTGCACTGGAAGCAGCTCCAACATTGGTTATAGCAGCTGTGTGAGCTGATATCAGCAGCTCCCAGGAACAGGCCCCAGAACCATCATCTATAGTATGAATACTCTACCCTCTGGGGTTCCTGATCGATTGTCTGGCTCCAGGTCAGGCAACTCAGCCACCCTAACCATCTCTGGGCTCCAGGCTGAGGACAAGGCTGACTATTACTGCTCAACATATGACAGCAGTCTCAATGCTCA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-130*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGACCCAGCTGGCCTCAGTGTCTGGGTCCCTGGGCCAGAGGGTCACCATCACCTGCACTGGAAGCAGCTCCAACATTGGTAGTGATTATGTGGGCTGGTTCCAACAGCTCCCAGGAACAGGCCCTAGAACCCTCATCTAAGGCAATAGTAACCGACCCTCGGGGGTCCCTGATCAATTCTCTGGCTCCAAGTCTGGCAGTACAGCCACCCTGACCATCTCTGGGCTCCAGGCTGAGGATGATGCTGATTATTACTGCACATCATGGGATAGCAGTCTCAAGGCTCC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-135*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGAATCAGCTGCCTTCAGTGTTAGGATCCCTGGGCCAGAGAATCACCATCTCCTGCTCTGGAAGCACGAATGACATCGGTATGCTTGGTGTGAACTGGTACCAAGAGCTCCCAGGAAAGGCCCCTAAACTCCTCGTAGATGGTACTGGGAATCGACCCTCAGGGGTCCCTGACCGATTTTCTGGCTCCAAATCTGGCAACTCAGGCACTCTGACCATCACTGGGCTCCAGGCTGAGGACGAGGCTGATTATTATTGTCAGTCCACTGATCTCACGCTTGGTGCTCC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-136*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCGGCCTCAGTGTCTGGGTCCCTGGGCCAGAGGGTCACCATCTCCTGCACTGGAAGCAGCTCCAACATCGGTAGAGGTTATGTGGGCTGGTACCAGCAGCTCCCAGGAACAGGCCCCAGAACCCTCATCTATGATAGTAGTAGCCGACCCTCGGGGGTCCCTGATCGATTCTCTGGCTCCAGGTCAGGCAGCACAGCAACCCTGACCATCTCTGGGCTCCAGGCTGAGGACGAGGCTGATTATTACTGCTCAGCATATGACAGCAGTCTCAGTGGTGG" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-138*01", + "range": { + "from": 0, + "to": 299 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCGGCCTCAGTGTCTGGGTCCCTGGGCCAGAGGGTCACCATCTCCTGCACTGGAAGCAGCTCCAATGTTGGTTATGGCAATTATGTGGGCTGGTACCAGCAGCTCCCAGGAACAAGCCCCAGAACCCTCATCTATGATAGTAGTAGCCGACCCTCGGGGGTCCCTGATCGATTCTCTGGCTCCAGGTCAGGCAGCACAGCAACCCTGACCATCTCTGGGCTCCAGGCTGAGGATGAAGCCGATTATTACTGCTCATCCTATGACAGCAGTCTCAGTGGTGG" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-139*01", + "range": { + "from": 0, + "to": 297 + }, + "sequence": "CAGGCTGTGCTGACTCCGCTGCCCTCAGTGTCTGCGGCCCTGGGACAGACGGTCACCATCTCTTGTACTGGAAATAGCACCCAAATCAGCAGTGGTTATGCTGTACAATGGTACCAGCAGCTCCCAGGAAAGTCCCCTGAAACTATCATCTATGGTGATAGCAATCGACCCTCGGGGGTCCCAGATCGATTCTCTGGCTTCAGCTCTGGCAATTCAGCCACACTGGCCATCACTGGGCTCCAGGATGAGGACGAGGCTGATTATTACTGCCAGTCCTTAGATGACAACCTCAATGGT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-141*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCGACCTCAGTGTCGGGGTCCCTTGGCCAGAGGGTCACCATCTCCTGCTCTGGAAGCACGAACAACATCGGTATTGTTGGTGCGAGCTGGTACCAACAGCTCCCAGGAAAGGCCCCTAAACTCCTCGTGTACAGTGTTGGGGATCGACCGTCAGGGGTCCCTGACCGGTTTTCCGGCTCCAACTCTGGCAACTCAGCCACCCTGACCATCACTGGGCTTCAGGCTGAGGACGAGGCTGATTATTACTGCCAGTCCTTTGATACCACGCTTGGTGCTCA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-143*01", + "range": { + "from": 0, + "to": 299 + }, + "sequence": "CAGTCTGTGCTGACTCAACCAGTCTCAGTGTCTGGGGCCCTGTGCCAGAGGGTCACCATCTCCTGCACTGGAAGCAGCTCCAACATTGGTTATAGCAGCTGTGTGAGCTGATATCAGCAGCTCCCAGGAACAGGCCCCAGAACCATCATCTATAGTATGAATACTCTACCCTCTGGGGTTCCTGATCGATTGTCTGGCTCCAGGTCAGGCAACTCAGCCACCCTAACCATCTCTGGGCTCCAGGCTGAGGACAAGGCTGACTATTACTGCTCAACATATGACAGCAGTCTCAATGCTCA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-144*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCTCCCTCAGTGTTCAGGTCCCTGGGCCAGAGGGTCACCATCTCCTGCACTGGAAGCAGCTGCAACGTCGGTAGAGGTTATGTGATCTGGTACCAACAGCTCCTGGGAACACGCCCAAGAACCCTCATATATGGTAGTAGTAACCAACCCTCAGGGGTCCCCAATCGATTCTCTGGCTCCAGGTCAGGCAGCACAGCCACTCTGACAATCTCTGGGTTCCAGGCTGAGGATGAGGCTGATTATTACTGCTCATCCTGGGACAGCAGTCTCAGTGCTCT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-146*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGAATCAGCTGCCTTCAGTGTTAGGATCCCTGGGCCAGAGAATCACCATCTCCTGCTCTGGAAGCACGAATGACATCGGTATGCTTGGTGTGAACTGGTACCAAGAGCTCCCAGGAAAGGCCCCTAAACTCCTCGTAGATGGTACTGGGAATCGACCCTCAGGGGTCCCTGACTGATTTTCTGGCTCCAAATCTGGCAACTCAGGCACTCTGACCATCACTGGGCTCCAGGCTGAGGACGAGGCTGATTATTATTGTCAGTCCACTGATCTCACGCTTGGTGCTCC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-147*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCGGCCTCAGTGTCTGGGTCCCTGGGCCAGAGGGTCACCATCTCCTGCACTGGAAGCAGCTCCAACATCGGTAGAGGTTATGTGGGCTGGTACCAGCAGCTCCCAGGAACAGGCCCCAGAACCCTCATCTATGATAATAGTAACCGACCCTCGGGGGTCCCTGATCGATTCTCTGGCTCCAAGTCAGGCAGCACAGCCACCCTGACCATCTCTGGGCTCCAGGCTGAGGACGAGGCTGATTATTACTGCTCAACATACGACAGCAGTCTCAGTGGTGG" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-149*01", + "range": { + "from": 0, + "to": 299 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCGGCCTCAGTGTCTGGGTCCCTGGGCCAGAGGGTCACCATCTCCTGCACTGGAAGCAGCTCCAATGTTGGTTATGGCAATTATGTGGGCTGGTACCAGCAGCTCCCAGGAACAGGCCCCAGAACCCTCATCTATCGTAGTAGTAGCCGACCCTCGGGGGTCCCTGATCGATTCTCTGGCTCCAGGTCAGGCAGCACAGCAACCCTGACCATCTCTGGGCTCCAGGCTGAGGATGAAGCCGATTATTACTGCTCATCCTATGACAGCAGTCTCAGTGGTGG" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-150*01", + "range": { + "from": 0, + "to": 299 + }, + "sequence": "CAGGCTGTGCTGACTCCGCTGCCCTCAGTGTCTGCGGCCCTGGGACAGACGGTCACCATCTCTTGTACTGGAAATAGCACCCAAATCGGCAGTGGTTATGCTGTACAATGGTACCAGCAGCTCCCAGGAAAGTCCCCTGAAACTATCATCTATGGTGATAGCAATCGACCCTCGGGGGTCCCAGATCGATTCTCTGGCTTCAGCTCTGGCAATTCAGCCACACTGGCCATCACTGGGCTCCAGGATGAGGACGAGGCTGATTATTACTGCCAGTCCTTAGATGACAACCTCGATGGTCA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-151*01", + "range": { + "from": 0, + "to": 299 + }, + "sequence": "CAGTCTGCGCTGACTCAAACGGCCTCCATGTCTGGGTCTCTGGGCCAGAGGGTCACCGTCTCCTGCACTGGAAGCAGTTCCAACGTTGGTTATAGAAGTTATGTGGGCTGGTACCAGCAGCTCCCAGGAACAGGCCCCAGAACCATCATCTATAATACCAATACTCGACCCTCTGGGGTTCCTGATCGATTCTCTGGCTCCATATCAGGCAGCACAGCCACCCTGACTATTGCTGGACTCCAGGCTGAGGACGAGGCTGATTATTACTGCTCATCCTATGACAGCAGTCTCAAAGCTCC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-152*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAATCTGTGCTGATCCAGCCGGCCTCAGTGTCGGGATCCCTGGGCCAGAGAGTCACCATCTCCTGCTCTGGAAGGACAAACAACATCGGTAGGTTTGGTGCGAGCTGGTACCAACAGCTCCCAGGAAAGGCCCCTAAACTCCTCGTGGACAGTGATGGGGATTGACCGTCAGGGGTCCCTGACCGGTTTTCCGGCTCCAGGTCTGGCAGCTCAGCCACCCTGACCATCACTGGGGTCCAGGCTGAGGATGAGGCTGATTATTACTGCCAGTCCTTTGATCCCACGCTTGGTGCTCA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-155*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCTCCCTCAGTGTCCGGGTTCCTGGGCCAGAGGGTCACCATCTCCTGCACTGGAAGCAGCTCCAACATCGGTAGAGGTTATGTGCACTGGTACCAACAGCTCCCAGGAACAGGCCCCAGAACCCTCATCTATGGTATTAGTAACCGACCCTCAGGGGTCCCCGATCGATTCTCTGGCTCCAGGTCAGGCAGCACAGCCACTCTGACAATCTCTGGGCTCCAGGCTGAGGATGAGGCTGATTATTACTGCTCATCCTGGGACAGCAGTCTCAGTGCTCT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-157*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCGGCCTCAGTGTCTGGGTCCCTGGGCCAGAGGGTCACCATCTCCTGCACTGGAAGCAGCTCCAACATCGGTAGAGGTTATGTGGGCTGGTACCAGCAGCTCCCAGGAACAGGCCCCAGAACCCTCATCTATGATAATAGTAACCGACCCTCGGGGGTCCCTGATCGATTCTCTGGCTCCAAGTCAGGCAGCACAGCCACCCTGACCATCTCTGGGCTCCAGGCTGAGGACGAGGCTGATTATTACTGCTCAACATACGACAGCAGTCTCAGTGGTGG" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-158*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGAATCAGCTGCCTTCAGTGTTAGGATCCCTGGGCCAGAGAATCACCATCTCCTGCTCTGGAAGCACGAATGACATCGGTATGCTTGGTGTGAACTGGTACCAAGAGCTCCCAGGAAAGGCCCCTAAACTCCTCGTAGATGGTACTGGGAATCGACCCTCAGGGGTCCCTGACCGATTTTCTGGCTCCAAATCTGGCAACTCAGGCACTCTGACCATCACTGGGCTCCAGGCTGAGGACGAGGCTGATTATTATTGTCAGTCCACTGATCTCACGCTTGGTGCTCC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-159*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCTCCCTCAGTGTTCAGGTCCCTGGGCCAGAGGGTCACCATCTCCTGCACTGGAAGCAGCTGCAACGTCGGTAGAGGTTATGTGATCTGGTACCAACAGCTCCTGGGAACACGCCCAAGAACCCTCATATATGGTAGTAGTAACCAACCCTCAGGGGTCCCCAATCGATTCTCTGGCTCCAGGTCAGGCAGCACAGCCACTCTGACAATCTCTGGGTTCCAGGCTGAGGATGAGGCTGATTATTACTGCTCATCCTGGGACAGCAGTCTCAGTGCTCT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-160*01", + "range": { + "from": 0, + "to": 299 + }, + "sequence": "CAGTCTGTGCTGACTCAACCAGTCTCAGTGTCTGGGGCCCTGTGCCAGAGGGTCACCATCTCCTGCACTGGAAGCAGCTCCAACATTGGTTATAGCAGCTGTGTGAGCTGATATCAGCAGCTCCCAGGAACAGGCCCCAGAACCATCATCTATAGTATGAATACTCTACCCTCTGGGGTTCCTGATCGATTGTCTGGCTCCAGGTCAGGCAACTCAGCCACCCTAACCATCTCTGGGCTCCAGGCTGAGGACAAGGCTGACTATTACTGCTCAACATATGACAGCAGTCTCAATGCTCA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-162*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCGACCTCAGTGTCGGGGTCCCTTGGCCAGAGGGTCACCATCTCCTGCTCTGGAAGCACGAACAACATCGGTATTGTTGGTGCGAGCTGGTACCAACAGCTCCCAGGAAAGGCCCCTAAACTCCTCGTGTACAGTGATGGGGATCGACCGTCAGGGGTCCCTGACCGGTTTTCCGGCTCCAACTCTGGCAACTCAGACACCCTGACCATCACTGGGCTTCAGGCTGAGGACGAGGCTGATTATTACTGCCAGTCCTTTGATACCACGCTTGATGCTCA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-41*01", + "range": { + "from": 0, + "to": 299 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCAGCCTCCGTGTCTGGGTCCCTGGGCCAGAGGGTCACCATTTCCTGCACTGGAAGCAGCTCCAACGTTGGTTATAGCAGTAGTGTGGGCTGGTACCAGCAGTTCCCAGGAACAGGCCCCAGAACCATCATCTATTATGATAGTAGCCGACCCTCGGGGGTCCCCGATCGATTCTCTGGCTCCAAGTCTGGCAGCACAGCCACCCTGACCATCTCTGGGCTCCAGGCTGAGGATGAGGCTGATTATTACTGCTCATCTTGGGACAACAGTCTCAAAGCTCC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-44*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGGCTGTGCTGAATCAGCCGGCCTCAGTGTCTGGGGCCCTGGGCCAGAAGGTCACCATCTCCTGCTCTGGAAGCACAAATGACATTGATATATTTGGTGTGAGCTGGTACCAACAGCTCCCAGGAAAGGCCCCTAAACTCCTCGTGGACAGTGATGGGGATCGACCCTCAGGGGTCCCTGACAGATTTTCTGGCTCCAGCTCTGGCAACTCAGGCACCCTGACCATCACTGGGCTCCAGGCTGAGGACGAGGCTGATTATTACTGTCAGTCTGTTGATTCCACGCTTGGTGCTCA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-45*01", + "range": { + "from": 0, + "to": 294 + }, + "sequence": "CAGTCTGTACTGACTCAATCAGCCTCAGCGTCTGGGTCCTTGGGCCAGAGGGTCTCCGTCTCCTGCTCTAGCAGCACAAACAACATTGGTATTATTGGTGTGAAGTGGTACCAGCAGATCCCAAGAAAGGCCCCTAAACTCCTCATATATGATAATGAGAAGAGACCCTCAGGTGTCCCCAATTGATTCTCTGGCTCCAAGTCTGGCAACTTAGGCACCCTAACCATCAATGGGCTTCAGGCTGAGGGCGAGGCTGATTATTACTGCCAGTCCATGGATTTCAGCCTCGGTGGT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-46*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGACTCAACCAGCCTCAGTGTCCGGGTCTCTGGGCCAGAGGGTCACCATCTCCTGCACTGGAAGCAGCTCCAACATTGGTAGAGATTATGTGGGCTGGTACCAACAGCTCCCGGGAACACGCCCCAGAACCCTCATCTATGGTAATAGTAACCGACCCTCGGGGGTCCCCGATCGATTCTCTGGCTCCAAGTCAGGCAGCACAGCCACCCTGACCATCTCTGGGCTCCAGGCTGAGGACGAGGCTGATTATTACTGCTCTACATGGGACAACAGTCTCACTGTTCC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-48*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTATGCTGACTCAGCCAGCCTCAGTGTCTGGGTCCCTGGGCCAGAAGGTCACCATCTCCTGCACTGGAAGCAGCTCCAACATCGGTGGTAATTATGTGGGCTGGTACCAACAGCTCCCAGGAATAGGCCCTAGAACCGTCATCTATGGTAATAATTACCGACCTTCAGGGGTCCCCGATCGATTCTCTGGCTCCAAGTCAGGCAGTTCAGCCACCCTGACCATCTCTGGGCTCCAGGCTGAGGACGAGGCTGAGTATTACTGCTCATCATGGGATGATAGTCTCAGAGGTCA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-49*01", + "range": { + "from": 0, + "to": 299 + }, + "sequence": "CAGGCTGTGCTGACTCAGCCGCCCTCAGTGTCTGCGGTCCTGGGACAGAGGGTCACCATCTCCTGCACTGGAAGCAGCACCAACATTGGCAGTGGTTATGATGTACAATGGTACCAGCAGCTCCCAGGAAAGTCCCCTAAAACTATCATCTATGGTAATAGCAATCGACCCTCAGGGGTCCCGGATCGCTTCTCTGGCTCCAAGTCAGGCAGCACAGCCTCTCTGACCATCACTGGGCTCCAGGCTGAGGACGAGGCTGATTATTACTGCCAGTCCTCTGATGACAACCTCGATGATCA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-50*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCGGCCTCAGTGTCCGGGTCTCTGGGCCAGAGAGTCACCATCTCCTGCACTGGAAGCAGCTCCAACATCGATAGAAAATATGTTGGCTGGTACCAACAGCTCCCGGGAACAGGCCCCAGAACCGTCATCTATGATAATAGTAACCGACCCTCGGGGGTCCCTGATCGATTCTCTGGCTCCAAGTCAGGCAGCACAGCCACCCTGACCATCTCTGGGCTCCAGGCTGAGGACGAGGCTGATTATTACTGCTCAACATACGACAGCAGTCTCAGTAGTGG" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-52*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCGGCCTCAGTGTCTGGGTCCCTGGGCCAGAGGGTCACCATCTCCTGCACTGGAAGCAGCTCCAACATCAGTAGATATAATGTGAACTGGTACCAACAGCTCCTGGGAACAGGCCCCAGAACCCTCATCTATGGTAGTAGTAACCGACCCTCGGGGGTCCCCGATTGATTCTCTGGCTCCAAGTCAGGCAGCCCAGCTACCCTGACCATCTCTGGGCTCCAGGCTGAGGATGAGGCTGATTATTACTGCTCAACATACGACAGGGGTCTCAGTGCTCG" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-54*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGCCTGTGCTGACTCAGCCGCCCTCAGGGTCTGGGGGCCTGGGCCAGAGGTTCAGCATCTCCTGTTCTGGAAGCACAAACAACATCAGTGATTATTATGTGAACTGGTACTAACAGCTCCCAGGGACAGCCCCTAAAACCATTATCTATTTGGATGATACCAGACCCCCTGGGGTCCCGGATTGATTCTCTGTCTCCAAGTCTAGCAGCTCAGCTACCCTGACCATCTCTGGGCTCCAGGCTGAGGATGAAGCTGATTATTACTGCTCATCCTGGGGTGATAGTCTCAATGCTCC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-55*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCGGCCTCAGTGTCTGGGTCCCTGGGCCAGAGGATCACCATCTCCTGCACTGGAAGCAGCTCCAACATTGGAGGTAATAATGTGGGTTGGTACCAGCAGCTCCCAGGAAGAGGCCCCAGAACTGTCATCTATAGTACAAATAGTCGACCCTCGGGGGTGCCCGATCGATTCTCTGGCTCCAAGTCTGGCAGCACAGCCACCCTGACCATCTCTGGGCTCCAGGCTGAGGATGAGGCTGATTATTACTGCTCAACGTGGGATGATAGTCTCAGTGCTCC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-56*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CGGTCTGTGCTGACTCAGCCGCCCTCAGTGTCGGGATCTGTGGGCCAGAGAATCACCATCTCCCGCTCTGGAAGCACAAACAGCATTGGTATACTTGGTGTGAACTGGTACCAAGAGCTCCCAGGAAAGGCCCCTAAACTCCTCGTAGATGGTACTGGGAATAGACCCTCAGGGGTCCCTGACCGATTTTCTGGCTCCAAATCTGGCAACTCAGGCACTCTGACCATCACTGGGCTTCAGCCTGAGGACGAGGCTGATTATTATTGTCAGTCCATTGAACCCATGCTTGGTGCTCC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-57*01", + "range": { + "from": 0, + "to": 299 + }, + "sequence": "CAGGCTGTGCTGACTCCGCTGCCCTCAGTGTCTGCGGCCCTGGGACAGACGGTCACCATCTCTTGTACTGGAAATAGCACCCAAATCAGCAGTGGTTATGCTGTACAATGGTACCAGCAGCTCCCAGGAAAGTCCCCTGAAACTATCATCTATGGTGATAGCAATCGACCCTCGGGGGTCCCAGATCGATTCTCTGGCTTCAGCTCTGGCAATTCAGCCACACTGGCCATCACTGGGCTCCAGGATGAGGACGAGGCTGATTATTACTGCCAGTCCTTAGATGACAACCTCAATGGTCA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-58*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCGGCCTCAGTGTCTGGGTCCCTGGGCCAGAGGGTCACCATCTCCTGCACTGGAAGCAGCTCCAACATCGGTAGATATAGTGTTGGCTGGTTCCAGCAGCTCCCGGGAAAAGGCCCCAGAACCGTCATCTATAGTAGTAGTAACCGACCCTCAGGGGTCCCTGATCGATTCTCTGGCTCCAAGTCAGGCAGCACAGCCACCCTGACCATCTCTGGGCTCCAGGCTGAGGACGAGGCTGATTATTACTGCTCAACATACGACAGCAGTCTCAGTAGTAG" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-66*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCGCCCTCAGTGTCAGGATCTGTGGGCCAGAGAATCACCATCTCCTGCTCTGGAAGCACAAACAGCATTGGTATACTTGGTGTGAACTGGTACCAACTGCTCTCAGGAAAGGCCCCTAAACTCCTCGTAGATGGTACTGGAAATCGACCCTCAGGGGTCCCTGACCGATTTTCTGGCTCCAAATCTGGCAACTCAGGCACTCTGACCATCACTGGGCTTCAGCCTGAGGACGAGGCTGATTATTATTGTCAGTCCATTGAACCCATGCTTGGTGCTCC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-67*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTCCTGACTCAGCCGGCCTCAGTGTCTGGGGTTCTGGGCCAGAGGGTCACCATCTCCTGCACTGGAAGCAGCTCCAACATTGGTGGAAATTATGTGAGCTGGCACCAGCAGGTCCCAGAAACAGGCCCCAGAAACATCATCTATGCTGATAACTACCGAGCCTCGGGGGTCCCTGATCGATTCTCTGGCTCCAAGTCAGGCAGCACAGCCACCCTGACCATCTCTGTGCTCCAGGCTGAGGATGAGGCTGATTATTACTGCTCAGTGGGGGATGATAGTCTCAAAGCACC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-69*01", + "range": { + "from": 0, + "to": 292 + }, + "sequence": "CAGTCTGTGCTAACTCAGCCACCCTCAGTGTCGGGGTCGCTGGGCCAGAGGGTCACCATCTCCTGCTCTGGAAGCACAAACAACATCAGTATTGTTGGTGCGAGCTGGTACCAACAGCTCCCAGGAAAGGCCCCTAAACTCCTCGTGGACAGTGATGGGGATCGACCGTCAGGGGTCCCTGACCGATTTTCTGGCTCTAAGTCTGGCAAATCAGCCACCCTGACCATCACTGGGCTTCAGGCTGAGGACGAGGCTGATTATTACTGTATATTGGTCCCACGCTTTGTGCTCA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-70*01", + "range": { + "from": 0, + "to": 299 + }, + "sequence": "CAGTCTGTGCTGACTCAACCGGCCTCCGTGTCTGGGTCCCTGGGCCAGAGAGTCACCATCTCTTGCACTAGAAGCAGCTCGAACGTTGGCTATGGCAATGATGTGGGATGGTACCAGCAGCTCCCAGGAACAGGCCCCAGAACCATCATCTATAATACCAATACTCGACCCTCTGGGGTTCCTGATCGATTCTCTGGCTCCAAATCAGGCAGCACAGCCACCCTGACCATCTCTGGACTCCAGGCTGAGGACGAGGCTGATTATTACTGCTCTTCCTATGACAGCAGTCTCAATGCTCA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-72*01", + "range": { + "from": 0, + "to": 293 + }, + "sequence": "CAGTCTGTGCTAACTCAGCCGGCCTCAGTGTCTGGTTCCCTGGGTCAGAGGGTCACCATCTGCACTGGAAGCAGCTCCAACATTGGTACATATAGTGTAGGCTGGTACCAACAGCTCCCAGGATCAGGCCCCAGAACCATCATCTATGGTAGTAGTAACCGACCGTTGGGGGTCCCTGATCGATTCTCTGGCTCCAGGTCAGGCAGCACAGCCACCCTGACCATCTCTGGGCTCCAGGCTGAGGACGAAGCTGATTATTACTGCTTCACATACGACAGTAGTCTCAAAGCTCC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-73*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGAATCAGCCACCTTCAGTGTCTGGATCCCTGGGCCAGAGAATCACCATCTCCTGCTCTGGAAGCACGAATGACATCGGTATGCTTGGTGTGAACTGGTACCAACAGCTCCCAGGAAATGCCCCTAAACTCCTTGTAGATGGTACTGGGAATCGACCCTCAGGGGTCCCTGACCAATTTTCTGGCTCCAAATCTGGCAATTCAGGCACTCTGACCATCACTGGGCTCCAGGCTGAGGACGAGGCTGATTATTATTGTCAGTCCTATGATCTCACGCTTGGTGCTCC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-75*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCGGCCTCAGTGACTGGGTCCCTGGGCCAGAGGGTCACCATCTCCTGCACTGGAAGCAGCTCCAACATCGGTGGATATAATGTTGGCTGGTTCCAGCAGCTCCCGGGAACAGGCCCCAGAACCGTCATCTATAGTAGTAGTAACCGACCCTCGGGGGTCCCGGATCGATTCTCTGGCTCCAGGTCAGGCAGCACAGCCACCCTGACCATCTCTGGGCTCCAGGCTGAGGACGAGGCTGAGTATTACTGCTCAACATGGGACAGCAGTCTCAAAGCTCC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-78*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGACTCAACCGGCCTCAGTGTCCAGGTCCCTGGGCCAGATAGTCACCATCTCTTGCGCTGGAAGCAGCTCCAACATCCGTACAAAATATGTGGGCTGGTACTAACAGCTCCCGAGAACAGGCCCCAGAACCGTCATCTATGGTAATAGTAACTGACCCTCGGGGGTCCTCGATCAATTCTCTGGCTCCAAGTCAGGCAGCATAGCCACCCTGACCATCTCTGTGCTCCAGGCTGAGGACGAGGCTTATTATTACTGCTCAACATATGACAGCAGTCTCAGTGCTCT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-80*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCGACCTCAGTGTCGTGGTCCCTGGGCCAGAGGGTCACAATCTCATGCTCTAGAAGCACGAATAACATCGGTATTGTCGGGGCGAGCTGGTACCAACAGCTCCCAGGAAAGGCCCCTAAACTCCTCGTGGACAGTGATGGGGATCAACTGTCAGGGGTCCCTGACCGATTTTCTGGCTCCAAGTCTGGCAACTCAGCCAACCTGACCATCACTGGGCTCCAGGCTGAGGACAAGGCTGATTATTACTGCCAGTCCTTTGATCACACGCTTGGTGCTCG" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-82*01", + "range": { + "from": 0, + "to": 293 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCAGCCTCAGTGTCGGGGTCCCTGGGCCAGAGAGTCACCATCTCCTGCTCTGGAAGGACAAACATCGGTAGGTTTGGTGCTAGCTGGTACCAACAGCTCCCAGGAAAGGCCCCTAAACTCCTCGTGGACAGTGATGGGGATCGACCGTCAGGGGTCCCTGACCGATTTTCCGGCTCCAAGTCTGGCAACTCGGCCACTCTGACCATCACTGGTCTCCATGCTGAGGACGAGGCTGATTATTACTGTCTGTCTATTGGTCCCACGCTTGGTGCTCA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-84*01", + "range": { + "from": 0, + "to": 299 + }, + "sequence": "CAGGCTGTGCTGACTCAGCCGGCCTCAGTGTCTGGGTCCCTGGGCCAGAGGGTCACCATCTCCTGCACTGGAAGCAGCTCCAATGTTGGTTATGGCAATTATGTGGGCTGGTACCAGCAGCTCCCAGGAACAGGCCCCAGAACCCTCATCTATGGTAGTAGTTACCGACCCTCGGGGGTCCCTGATCGATTCTCTGGCTCCAGTTCAGGCAGCTCAGCCACACTGACCATCTCTGGGCTCCAGGCTGAGGATGAAGCTGATTATTACTGCTCATCCTATGACAGCAGTCTCAGTGGTGG" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-84-1*01", + "range": { + "from": 0, + "to": 294 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCAGCCTCAGCGTCTGGGTCCTTGGGCCAGAGGGTCACTGTCTCCTGCTCTAGCAGCACAAACAACATCGGTATTATTGGTGTGAAGTGGTACCAGCAGATCCCAGGAAAGGCCCATAAACTCCTCATATATGATAATGAGAAGCGACCCTCAGGTGTCCCCAATCGATTCTCTGGCTCCAAGTCTGGCGACTTAAGCACCCTGACCATCAATGGGCTTCAGGGTGAGGACGAGGCTGATTATTATTGCCAGTCCATGGATTTCAGCCTCGGTGGT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-86*01", + "range": { + "from": 0, + "to": 314 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCAGCCTCAGTGTCTGGGTCCCTGGGCCAGAGGGTCACCATCTCCTGCACTGGAATCCCCAGCAACACAGATTTTGATGGAATAGAATTTGATACTTCTGTGAGCTGGTACCAACAGCTCCCAGAAAAGCCCCCTAAAACCATCATCTATGGTAGTACTCTTTCATTCTCGGGGGTCCCCGATCGATTCTCTGGCTCCAGGTCTGGCAGCACAGCCACCCTGACCATCTCTGGGCTCCAGGCTGAGGACGAGGCTGATTATTACTGCTCATCCTGGGATGATAGTCTCAAATCATA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-87*01", + "range": { + "from": 0, + "to": 299 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCAGCCTCAGTGTCTGGATCCCTGGGCCAAAGGGTCACCATCTCCTGCACTGGAAGCACAAACAACATCGGTGGTGATAATTATGTGCACTGGTACCAACAGCTCCCAGGAAAGGCACCCAGTCTCCTCATCTATGGTGATGATAACAGAGAATCTGGGGTCCCTGAACGATTCTCTGGCTCCAAGTCAGGCAGCTCAGCCACTCTGACCATCACTGGGCTCCAGGCTGAGGACGAGGCTGATTATTATTGCCAGTCCTACGATGACAGCCTCAATACTCA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-89*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTACTGACTCAGCCGGCCTCAGTGTCTGGGTCCCTGGGCCAGAGGGTCACCATCTCCTGCACTGGAAGCAGCTCCAACATCGGTGGATATTATGTGAGCTGGCTCTAGCAGCTCCCGGGAACAGGCCCCAGAACCATCATCTATAGTAGTAGTAACCGACCTTCAGGGGTCCCTGATCGATTCTCTGGCTCCAGGTCAGGCAGCACAGCCACCCTGACCATCTCTGGGCTCCAGGCTGAGGATGAGGCTGATTATTACTGTTCAACATACGACAGCAGTCTCAAAGCTCC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-92*01", + "range": { + "from": 0, + "to": 299 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCGGCCTCGGTGTCTGGGTCCCTGGGCCAGAGGGTCACCATCTCCTGCACTGGAAGCAGCTCCAATGTTGGTTATGGCAATTATGTGGGCTGGTACCAGCAGCTTCCAGGAACAGGCCCCAGAACCATTATCTGTTATACCAATACTCGACCCTCTGGGGTTCCTGATCGATACTCTGGCTCCAAGTCAGGCAGCACAGCCACCCTGACCATCTCTGGGCTCCAGGCTGAAGACGAGACTGATTATTACTGTACTACGTGTGACAGCAGTCTCAATGCTAG" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-94*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCTCCCTCAGTGTCCGGGTTCCTGGGCCAGAGGGTCACCATCTCCTGCACTGGAAGCAGCTCCAACATCGGTAGAGGTTATGTGCACTGGTACCAACAGCTCCCAGGAACAGGCCCCAGAACCCTCATCTATGGTATTAGTAACCGACCCTCAGGGGTCCCCGATCGATTCTCTGGCTCCAGGTCAGGCAGCACAGCCACTCTGACAATCTCTGGGCTCCAGGCTGAGGATGAGGCTGATTATTACTGCTCATCCTGGGACAGCAGTCTCAGTGCTCT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-96*01", + "range": { + "from": 0, + "to": 299 + }, + "sequence": "CAGTCTGCGCTGACTCAAACGGCCTCCATGTCTGGGTCTCTGGGCCAGAGGGTCACCGTCTCCTGCACTGGAAGCAGTTCCAACGTTGGTTATAGAAGTTATGTGGGCTGGTACCAGCAGCTCCCAGGAACAGGCCCCAGAACCATCATCTATAATACCAATACTCGACCCTCTGGGGTTCCTGATCGATTCTCTGGCTCCATATCAGGCAGCACAGCCACCCTGACTATTGCTGGACTCCAGGCTGAGGACGAGGCTGATTATTACTGCTCATCCTATGACAGCAGTCTCAAAGCTCC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-97-1*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGTGCTGACTCAGCCTCCCTCAGTGTTCAGGTCCCTGGGCCAGAGGGTCACTATATCCTGCACTGGAAGCAGCTCCAACGTCGGTAGAGGTTATGTGATCTGGTACCAACAGCTCCTGGGAACACGCCCAAGAACCCTCATATATGGTAGTAGTAACCAACCCTCAGGGGTCCCCAATCAATTCTCTGGCTCCAGGTCAGGCAGCACAGACACTCTGACAATCTCTGGGTTCCAGGCTGAGGATGAGGCTGATTATTACTGCTCATCCTGGGACAGCAGTCTCAGTGCTCT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV1-98*01", + "range": { + "from": 0, + "to": 299 + }, + "sequence": "CAGTCTGTGCTGACTCAACCAGTCTCAGTGTCTGGGGCCCTGTGCCAGAGGGTCACCATCTCCTGCACTGGAAACAGCTCCAACATTGGTTATAGCAGTTGTGTGAGCTGATATCAGCAGCTCCCAGGAACAGGCCCCAGAACCATCATCTATAGTATGAATACTCAACCCTCTGGGGTTCCTGATCGATTCTCTGGCTCCAGGTCAGGCAACTCAGCCACCCTAACCATCTCTGGGCTCCAGGCTGAGGACAAGGCTGACTATTACTGCTCAACATATGACAGCAGTCTCAGTGCTCA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV2-31*01", + "range": { + "from": 0, + "to": 297 + }, + "sequence": "CAGTCTGCCCTGACTCAACCTTCCTCGGTGTCTGGGACTTTGGGCCAGACTGTCACCATCTCCTGTGATGGAAGCAGCAGTAACATTGGCAGTAGTAATTATATCGAATGGTACCAACAGTTCCCAGGCACCTCCCCCAAACTCCTGATTTACTATACCAATAATCGGCCATCAGGGATCCCTGCTCGCTTCTCTGGCTCCAAGTCTGGGAACACGGCCTCCTTGACCATCTCTGGGCTCCAGGCTGAAGATGAGGCTGATTATTACTGCAGCGCATATACTGGTAGTAATACTTTC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV2-32*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGTCTGCCCTGACTCAGCCTCCCTCGATGTCTGGGACACTGGGACAGACCATCATCATTTCCTGTACTGGAAGCGGCAGTGACATTGGGAGGTATAGTTATGTCTCCTGGTACCAAGAGCTCCCAAGCACGTCCCCCACACTCCTGATTTATGGTACCAATAATCGGCCATTAGAGATCCCTGCTCGCTTCTCTGGCTCCAAGTCTGGAAACACAGCCCCCATGACCATCTCTGGGCTTCAGGCTGAAGATGAGGCTAATTATTACTGTTGCTCATATACAACCAGTGGCACACA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-11*01", + "range": { + "from": 0, + "to": 288 + }, + "sequence": "TCCTATGTGCTGTCTCAGCCGCCATCAGCGACTGTGACTCTGAGGCAGACGGCCCGCCTCACCTGTGGGGGAGACAGCATTGGAAGTAAAAGTGTTGAATGGTACCAGCAGAAGCCGGGCCAGCCCCCCGTGCTCATTATCTATGGTGATAGCAGCAGGCCGTCAGGGATCCCTGAGCGATTCTCCGGCGCCAACTCGGGGAACACGGCCACCCTGACCATCAGCGGGGCCCTGGCCGAGGACGAGGCTGACTATTACTGCCAGGTGTGGGACAGCAGTACTAAGGCT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-13*01", + "range": { + "from": 0, + "to": 288 + }, + "sequence": "TCCTATGTACTGACTCAGCTGCCATCAGTGACTGTGAACCTGGGACAGACCACCAGCATCACCTGTGGTGGAGACAGCATTGGAGGGAGAACTGTTTACTGGTACCAGCAGAAGCCTGGCCAGCGCCCCCTGCTGATTATCTATAATGATAGCAATTGGCCCTCAGAGATCCCTGCCTGATTCTCTGGCTCCAACTCAGGGAACAGGGCCTCCCTAACCATCATTGGGGCCTGGGCCTAAGATGAGTCTGAGTATTACGGAGAGGTGTGGGACAGCAGTGCTAAGGCT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-14*01", + "range": { + "from": 0, + "to": 288 + }, + "sequence": "TCCTATGTGCTGACACAGCTGCCATCCATGAGTGTGACCCTGAGGCAGACGGCCCGCATCACCTGTGAGGGAGACAGCATTGGAAGTAAAAGAGTTTACTGGTACCAGCAGAAGCTGGGCCAGGTCCCTGTACTGATTATCTATGATGATAGCAGCAGGCCGTCAGGGATCCCTGAGCGATTCTCCGGCGCCAACTCGGGGAACACAGCCACCCTGACCATCAGCGGGGCCCTGGCCGAGGACGAGGCTGACTATTACTGCCAGGTGTGGGACAGCAGTACTAAGGCT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-15*01", + "range": { + "from": 0, + "to": 288 + }, + "sequence": "TCCACTGGGTTGAATCAGGCTCCCTCCGTGTTGGTGGCCCTGGGACAGATGGAAACAATCACCTGCTCGAGAGATGTCTTAGGGAAAAGATATGCATATAGGTACCAGCATAAGCCAAGCCAAGCCCCTGTGCTCCTAATCAATAAAAATAATGAGCAGGATTCTGGGATCCCTGACCGGTTCTCTGGCTCCAACTCGGGCAACACGGCCACCCTGACCATCAGTGGGGCCCGGGCTGAGGACGAGGCTGAGTATTACTGCCAGTCCTATGACAGCAGTGGAAATGTT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-19*01", + "range": { + "from": 0, + "to": 288 + }, + "sequence": "TCCCCTGGGCTGAATCAGCCTCCCTCCGTGTTGGTGGCCCTGGGACAGATGGCAACAAACACCTGCTCCGGAGATGTCTTAGGGAAAAGATATGCATATTGGTACCAGCATAAGCCAAGCCAAGCCCCTGTGCTCCTAATCAATAAAAATAATGAGCTGGGTTCTGGGATCCCTGACCGATTCTCTGGCTCCAACTCGGGCAACACGGCCACCCTGACCATCAGTGGGGCCCGGGCCGAGGACGAGGCTGACTATTACTGCCAGTCCTATGACAGCAGTGGAAATGCT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-2*01", + "range": { + "from": 0, + "to": 288 + }, + "sequence": "TCCTATGTGCTGACTCAGTCACCCTCAGTGTCAGTGACCCTGGGACAGACGGCCAGCATCACCTGTAGGGGAAACAGCATTGGAAGGAAAGATGTTCATTGGTACCAGCAGAAGCCGGGCCAAGCCCCCCTGCTGATTATCTATAATGATAACAGCCAGCCCTCAGGGATCCCTGAGCGATTCTCTGGGACCAACTCAGGGAGCACGGCCACCCTGACCATCAGTGAGGCCCAAACCAACGATGAGGCTGACTATTACTGCCAGGTGTGGGAAAGTAGCGCTGATGCT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-21*01", + "range": { + "from": 0, + "to": 288 + }, + "sequence": "TCCTATGAGCTGACTCAGCCACCATCCGTGAATGTGACCCTGAGGGAGACGGCCCACATCACCTGTGGGGGAGACAGCATTGGAAGTAAATATGTTCAATGGATCCAGCAGAATCCAGGCCAGGCCCCCGTGGTGATTATCTATAAAGATAGCAACAGGCCGACAGGGATCCCTGAGCGATTCTCTGGCGCCAACTCAGGGAACACGGCTACCCTGACCATCAGTGGGGCCCTGGCCGAAGACGAGGCTGACTATTACTGCCAGGTGGGGGACAGTGGTACTAAGGCT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-23*01", + "range": { + "from": 0, + "to": 288 + }, + "sequence": "TCCTATGTACTGACTCAGCTGCCATCAGTGACTGTGAACCTGGGACAGACCACCAGCATCACCTGTGGTGGAGACAGCATTGGAGGGAGAACTGTTTACTGGTACCAGCAGAAGCCTGGCCAGCGCCCCCTGCTGATTATCTATAATGATAGCAATTGGCCCTCAGAGATCCCTGCCTGATTCTCTGGCTCCAACTCAGGGAACAGGGCCTCCCTAACCATCATTGGGGCCTGGGCCTAAGACGAGTCTGAGTATTACGGAGAGGTGTGGGACAGCAGTGCTAAGGCT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-24*01", + "range": { + "from": 0, + "to": 288 + }, + "sequence": "TCCTATGTGCTGACACAGCTGCCATCCGTGAGTGTGACCCTGAGGCAGACGGCCCGCATCACCTGTGGGGGAGACAGCATTGGAAGTAAAAATGTTTACTGGTACCAGCAGAAGCTGGGCCAGGCCCCTGTACTGATTATCTATGATGATAGCAGCAGGCCGTCAGGGATCCCTGAGCGATTCTCCGGCGCCAACTCGGGGAACACGGCCACCCTGACCATCAGCGGGGCCCTGGCCGAGGATGAGGCTGACTATTACTGCCAGGTGTGGGACAGCAGTACTAAGCCT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-25*01", + "range": { + "from": 0, + "to": 288 + }, + "sequence": "TCCACTGGGTTGAATCAGGCTTCCTCCGTGTTGGTGGCCCTGGGACAGATGGAAACAATCACCTGCTCGAGAGATGTCTTAGGGAAAAGATATGCATATAGGTACCAGCATAAGCCAAGCCAAGCCCCTGTGCTCCTAATCAATAAAAATAATGAGCAGGATTCTGGGATCCCTGACCGGTTCTCTGGCTCCAACTCGGGCAACACGGCCACCCTGACCATCAGTGGGGCCCGGGCTGAGGACGAGGCTGAGTATTACTGCCAGTCCTATGACAGCAGTGGAAATGTT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-26*01", + "range": { + "from": 0, + "to": 288 + }, + "sequence": "TCCTATGTGCTGACACAGCTGCCATCCGTGAATGTGACCCTGAGGCAGCCGGCCCACATCACCTGTGGGGGAGACAGCATTGGAAGTAAAAGTGTTCACTGGTACCAACAGAAGCTGGGCCAGGCCCCTGTACTGATTATCTATGGTGATAGCAACAGGCCGTCAGGGATCCCTGAGCGATTCTCTGGTGACAACTCGGGGAACACGGCCACCCTGACCATCAGTGGGGCCCTGGCCGAGGACGAGGCTTACTATTACTGCCAGGTGTGGGACAGCAGTGCTCAGGCT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-27*01", + "range": { + "from": 0, + "to": 288 + }, + "sequence": "TCCAGTGTGCTGACTCAGCCTCCTTCAGTATCAGTGTCTCTGGGACAGACAGCAACCATCTCCTGCTCTGGAGAGAGTCTGAGTAAATATTATGCACAATGGTTCCAGCAGAAGGCAGGCCAAGTCCCTGTGTTGGTCATATATAAGGACACTGAGCGGCCCTCTGGGATCCCTGACCGATTCTCCGGCTCCAGTTCAGGGAACACACACACCCTGACCATCAGCGGGGCTCGGGCCGAGGACGAGGCTGACTATTACTGCGAGTCAGAAGTCAGTACTGGTACTGCT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-28*01", + "range": { + "from": 0, + "to": 288 + }, + "sequence": "TCCTATGTGTTGACTCAGCTGCCTTCAGTGTCAGTGAACCTGGGAAAGACAGCCAGCATCACCTGTGAGGGAAATAACATAGGAGATAAATATGCTTATTGGTACCAGCAGAAGCCTGGCCAGGCCCCCGTGCTGATTATTTATGAGGATAGCAAGCGGCCCTCAGGGATCCCTGAGCGATTCTCTGGCTCCAACTCGGGGAACACGGCCACCCTGACCATCAGCGGGGCCAGGGCCGAGGATGAGGCTGACTATTACTGTCAGGTGTGGGACAACAGTGCTAAGGCT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-29*01", + "range": { + "from": 0, + "to": 288 + }, + "sequence": "TCCAGTGTGCTGACTCAGCCTCCCTCGGTGTCAGTGTCCCTGGGACAGACGGCGACCATCACCTGCTCTGGAGAGAGTCTGAGCAGATACTATGCACAATGGTATCAGCAGAAGCCAGGCCAAGCCCCCATGACAGTCATATATGGGGACAGAGAGCGACCCTCAGGGATCCCTGACCGATTCTCCAGCTCCAGTTCAGAGAACACACACACCTTGACAATCAGTGGAGCCCAGGCTGAGGATGAGGCTGAATATTACTGTGAGATATGGGACGCCAGTGCTGATGAT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-3*01", + "range": { + "from": 0, + "to": 288 + }, + "sequence": "TCCTATGTGCTGACACAGCTGCCATCCAAAAATGTGACCCTGAAGCAGCCGGCCCACATCACCTGTGGGGGAGACAACATTGGAAGTAAAAGTGTTCACTGGTACCAGCAGAAGCTGGGCCAGGCCCCTGTACTGATTATCTATTATGATAGCAGCAGGCCGACAGGGATCCCTGAGCGATTCTCCGGCGCCAACTCGGGGAACACGGCCACCCTGACCATCAGCGGGGCCCTGGCCGAGGACGAGGCTGACTATTACTGCCAGGTGTGGGACAGCAGTGCTAAGGCT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-30*01", + "range": { + "from": 0, + "to": 288 + }, + "sequence": "TCCTACGTGGTGACCCAGCCACCCTCAGTGTCAGTGAACCTGGGACAGACGGCCAGCATCACCTGTGGGGGAGACAACATTGCAAGCACATATGTTTCCTGGCAGCAGCAGAAGTCGGGTCAAGCCCCTGTGACGATTATCTATCGTGATAGCAACCGGCCCTCAGGGATCCCTGAGCGATTCTCTGGCTCCAACTCGGGGAACACGGCCACCCTGACCATCAGCAGGGCCCAGGCCGAGGATGAGGCTGACTATTACTGCCAGGTGTGGAAGAGTGGTAATAAGGCT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-4*01", + "range": { + "from": 0, + "to": 288 + }, + "sequence": "TCCACTGGGTTGAATCAGGCTCCCTCCATGTTGGTGGCCCTGGGACAGATGGAAACAATCACCTGCTCCGGAGATATCTTAGGGAAAAGATATGCATATTGGTACCAGCATAAGCCAAGCCAAGCCCCTGTGCTCCTAATCAATAAAAATAATGAGCGGGCTTCTGGGATCCCTCACTGGTTCTCTGGTTCCAACTCGGGCAACATGGCCACCCTGACCATCAGTGGGGCCCGGGCTGAGGACGAGGCTGACTATTACTGCCAGTCCTATGACAGCAGTGGAAATGCT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-7*01", + "range": { + "from": 0, + "to": 288 + }, + "sequence": "TCCTATGTGCTGACTCTGCTGCTATCAGTGACCGTGAACCTGGGACAGACCACCAGCATCACCTGTGGTGGAGACAGCATTGGAGGGAGAACTGTTTACTGGTACCAGCAGAAGCCTGGCCAGCGCCCCCTGCTGATTATCTATAATGATAGCAATTGACCCTCAGGGATCCCTGCCTGATTCTCTGGCTCCAACTCAGGGAACAGGGCCTCCCTAACCATCATTGGGGCCTGGGCCTAAGACGAGTCTGAGTATTACGGAGAGGTGTGGGACAGCAGTGCTAAGGCT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV3-8*01", + "range": { + "from": 0, + "to": 288 + }, + "sequence": "TCCTATGTGCTGACACAGCTGCCATCCGTGAGTGTGACCCTGAGGCAGACGGCCCGCATCACCTGTGGGGGAGACAGCATTGGAAGTAAAAGTGTTTACTGGTACCAGCAGAAGCTGGGCCAGGCCCCTGTACTGATTATCTATAGAGATAGCAACAGGCCGACAGGGATCCCTGAGCGATTCTCTGGCGCCAACTCGGGGAACACGGCCACCCTGACCATCAGCGGGGCCCTGGCCGAGGACGAGGCTGACTATTACTGCCAGGTGTGGGACAGCAGTACTAAGGCT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV4-10*01", + "range": { + "from": 0, + "to": 317 + }, + "sequence": "TTGCCCGTGCTGACCCAGCCTACAAATGCATCTGCCTCCCTGGAAGAGTCGGTCAAGCTGACCTGCACTTTGAGCAGTGAGCACAGCAATTACATTGTTCATTGGTATCAACAACAACCAGGGAAGGCCCCTCGGTATCTGATGTATGTCAGGAGTGATGGAAGCTACAAAAGGGGGGACGGGATCCCCAGTCGCTTCTCAGGCTCCAGCTCTGGGGCTGACCGCTATTTAACCATCTCCAACATCAAGTCTGAAGATGAGGATGACTATTATTACTGTGGTGCAGACTATACAATCAGTGGCCAATACGGTTAAGC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV4-16*01", + "range": { + "from": 0, + "to": 317 + }, + "sequence": "TTGCCCATGCTGACCCAGCCTACAAATGCATCTGCCTCCCTGGAAGAGTCGGTCAAGCTCACATGCACTTTGAGCAGTGAGCACAGCAATTACATTGTTCAATGGTATCAACAACAACCAGGGAAGGCCCCTCGGTATCTGATGCATGTCAGGAGTGATGGAAGCTACAACAGGGGGGACGGGATCCCCAGTCGCTTCTCAGGCTCCAGCTCTGGGGCTGACCGCTATTTAACCATCTCCAACATCAAGTCTGAAGATGAGGATGACTATTATTACAGTGGTGCATACTATACAATCAGTGGCCAATACGGTTAAGC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV4-20*01", + "range": { + "from": 0, + "to": 317 + }, + "sequence": "TTGCCCATGCTGACCGAGCCTACAAATGCATCTGCCTCCCTGGAAGAGTCAGTCAAGCTCACCTGCACTTTGAGCAGTGAGCACAGCAATTACATTGTTCGATGGTATCAACAACAACCAGGGAAGGCCCCTCGGTATCTGATGTATGTCAGGAGTGATGGAAGCTACAACAGGGGGGACGGGATCCCCAGTCGCTTTTCAGGCTCCAGCTCTGGGGCTGACCGCTATTTAACCATCTCCAACATCAAGTCTGAAGATGAGGCTGAGTATTATTACGGTGGTGCAGACTATAAAATCAGTGACCAATATGGTTAAGA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV4-22*01", + "range": { + "from": 0, + "to": 303 + }, + "sequence": "TTGCCCGTGCTGACCCAGCCTCCAAGTGCATCTGCCTGCCTGGAAACCTCGGTCAAGCTCACATGCACTCTGAGCAGTGAGCACAGCAGTTACTATATTTACTGGTATCAACAACAACAACCAGGGAAGGCCCCTCGGTATCTGATGAAGGTTAACAGTGATGGAAGCCACAGCAGGGGGGACGGGATCCCCAGTCGCTTCTCAGGCTCCAGCTCTGGGGCTGACCGCTATTTAACCATCTCCAACATCCAGTCTGAAGATGAGGCAGATTATTACTGTGGTGTACCCGCTGGTAGCAGTAGC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV4-5*01", + "range": { + "from": 0, + "to": 317 + }, + "sequence": "TTGCCCGTGCTGACCCAGCCTACAAATGCATCTGCCTCCCTGGAAGAGTCGGTCAAGCTGACCTGCACTTTGAGCAGTGAGCACAGCAATTACATTGTTCAGTGGTATCAACAACAACCAGGGAAGGCCCCTCGGTATCTGATGTATGTCAGGAGTGATGGAAGCTACAAAAGGGGGGACGGGATCCCCAGTCGCTTCTCAGGCTCCAGCTCTGGGGCTGACCGCTATTTAACCATCTCCAACATCAAGTCTGAAGATGAGGATGACTATTATTACTGTGGTGCAGACTATACAATCAGTGGCCAATACGGTTAAGC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV4-6*01", + "range": { + "from": 0, + "to": 303 + }, + "sequence": "TTGCCCGTGCTGACCCAGCCTCCAAGTGCATCTGCCTCCCTGGAAGCCTCGGTCAAGCTCACATGCACTCTGAGCAGTGAGCACAGCAGTTACTATATTTACTGGTATGAACAACAACAACCAGGGAAGGCCCCTCGGTATCTGATGAGGGTTAACAGTGATGGAAGCCACAGCAGGGGGGACGGGATCCCCAGTCGCTTCTCAGGCTCCAGCTCTGGGGCTGACCGCTATTTAACCATCTCCAACATCCAGTCTGAGGATGAGGCAGATTATTACTGTGGTGCACCCGCTGGTAGCAGTAGC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV5-109*01", + "range": { + "from": 0, + "to": 319 + }, + "sequence": "CAGCTTGTGCTGACCCAGCCGCCCTCCCTCTCTGCATCCCTGGGATCAACAACCAGACTCACCTGCACCCTGAGCAGTGGCTTCAGTGTTGGTGGCTATAGCATATACTGGCACCAGCAGAAGCCAGGGAGCACTCCCTGGTACCTCCTGTACTACTACTCAAGTACAGAGTTGGGACCTGGGGTCCCCAGCTGCTTCTCTGGATCCAAAGACACCTCAGCCAATGTAGGGCTCCTGCTCATCTCAGGGCTGCAGCCTGAGGATGAGACTGACTACTACTGTGCTATAGGTCACGGCAGTGGGAGCAGCTACACTTACC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV5-131*01", + "range": { + "from": 0, + "to": 325 + }, + "sequence": "CAGCCTGTGCTGACCCAGCCACCCTCCCTCTCTGCATCCCTGGGAACAACAGCCAGACTCACCTGCACCCTGAGCAGTGGCTTCAGTGTTGGTGACTATGACATGTACTGGTACCAGCAGAAGCCAGGGAGCCCTCCCCGGGATCTCCTGTACTACTACTCGGACTCATATAAAAACCAGGGCTCTGGGGTCTCCAAAAGCTTCTCTGGATCCAAGGATACCTCAGCCAATGCAGGGCTCCTGCTCATCTCTGGGCTGCAGCCTGAGGACGAGGCTGACTACTACTGTGCTACAGATCATGGCAGTGAGAGCAGCTACTCTTACC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV5-64*01", + "range": { + "from": 0, + "to": 325 + }, + "sequence": "CAGCTTGTGGTGACCCAGCCGCCCTCCCTCTCTGCATCCCTGGGATCATCCGCCAGACTCACCTGCACCCTGAGCAGTGGCTTCAGTGTTGGCAGTTATTCTGTAACTTGGTTCCAGCAGAAGCCAGGGAGCCCTCTCTGGTACCTCCTGTACTACCACTCAGACTCAGATAAGCACCAGGGCTCCAGGGTCCCCAGCCGCTTCTCTGGATCCAAGGACACCTCGGCCAATGCAGGGCTCCTGCTCATCTCTGGGCTGCAGCCTGAGGATGAGGCTGACTACTACTGTGCCTCCGCTCATGGCAGTGGGAGCAACTACCATTACT" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV5-85*01", + "range": { + "from": 0, + "to": 325 + }, + "sequence": "CAGCCTGTGCTGACCCAGCCACCCTCCCTCTCTGCATCCCTGGGATCAACAGCCAGACCCACCTGCACCCTGAGCAGTGGCTTCAGTGTTGGAAGCTACCATATACTCTGGTTCCAGCAGAAGTCAGAGAGCCCTCCCCGGTATCTCCTGAGGTTCTACTCAGATTCTAATGAACACCAGGGTCCCGGGGTCCCCAGCCGCTTCTCTGGATCCAAGGACACCTCAACCTATGCAGGGCTCTTGCTCATCTCTGGGCTGCAGCCTGAGGACGAGGCTGACTACTACTGTGCTACAGACCATGGCAGTGGGAGCAGCTACACTTACC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-102*01", + "range": { + "from": 0, + "to": 300 + }, + "sequence": "CAGATTGTAGTGACCCAGGAACCATCACTGTCTCCAGGAGGGACAGTCCTACTCACTTGTGGCCTCAGCTCTGGGTCAGTCACTACAAGTAACTACTCCAGCTGGTACCAGCAGACCCCAGGGCGGGCTCCTCGCACGATTATCTACAACACTAACAGCCACCCCTCTGGAGTCCCTGATCGCTTCTCTGGATCCATCTCTGGGAACAAAGCGGCGCTCACCATCACAGGAGCCCAGCCTGAGGACGAGGCTGACTACTACTGTGTTACAGAACATGGTAGTGGGAGCAGCTTCACTTAC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-108*01", + "range": { + "from": 0, + "to": 307 + }, + "sequence": "CAGACTGTGGTGACTCAGGAGTCATCAGTCTCAGTGTCTCCAGGAGGGACAGTCACACTCACGTGTGACCTCAGCTCTGGGTCAGTGACTACAAGTAACAACCCCAGCTGGTACCAGCAGACCCAAGGCCGATCTCCTCGCATGCTTATCTATGACACAAGCAGCTGTCCCTCGGAGGTCCCTGATCGCTTCTCTGGATCCATTTCTGGGAACACAGCTGCCCTCACCATCACAGGAGCCCAGCCTGAGGACAAGGCTGACTACTACTGTAGTATGCATGATGTCAGTGGGAGCAGCTACAATTACC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-113*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGACTGTGGTCACCCAGGAGCCATCACTCTCAGTGTCTCCAGGAGGGACAGTCACACTCACATGTGGCCTCAGTTCTGGGTCAGTCACTATAAGTAACTACCCCAGCTGGTCCCAGCAGACCCCAGGGCAGGCTCCTCACACAATAATCTACAGGACAAACAGCTGACCCTCTGGGGTCCCTGATCGCTTCTCTGGATCCATCTCTGGGAACAACGCCGCCCTCAGCATCACAGTCGCCCAGCCTGAGGACGAGGCTGACTATTACTGTTCATTGTATATGGGTAGTAACATTTA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-119*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGACTGTGGTAACCCAGGAGCCATCACTCTCAGTGTCTCCAGGAGGGACAGTCACACTCACTTGTGGCCTCAGCTCTGGGTCAGTCTCTACAGGTAACAAACCTGGCTGGTACCAGCACACCCCAGGCCAGGCTCCTCGCAGGATTATCTATGACACAAGCAGCCGCCCTTCTGGGGTCCCTGATCGCTTCTCTGGATCCATCTCTGAGAACAAAGCTGCCCTCACCATCACAGAAGCCCAGCCTGAGGATGAGGCTGCCTACCACTGTTCGCTGTATATGAGTGGTGGTGCTTA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-121*01", + "range": { + "from": 0, + "to": 307 + }, + "sequence": "CAGACTGTGGTGACCCAGGAGTCATCAGTCTCAGTGTCTCCAGTCGGAACAGTCACACTCACTTGTGGCCTCAGCTCTGGGTCACTGACTACAAGTAACTACACCAGCTGGTACCAGCAGACCCAAGGCCAGTCTCCTCGCATGCTTGTCTATGACACAAGCAGCTGTCCCTCTGAAGTTCCTGATCACTTCTCTGGATCCATTTCTGGGAACAAAGCCGCCCTCACCATCACAGGAGCCCAGCCTGAGGACGAGGCTGACTACTACTGTGGTATGCATGATGTCAGTGGGAGCAGCTAAAATTACC" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-128*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGACTGTGGTAACCCAGGAGCCATCACTCTCAGTGTCTCCAGGAGGGACAGTCACACTCACATGTGGCCTCAGCTCTGGGTCAGTCTCTACAAGTAATTACCCTGGCTGGTACCAGCAGACCCTAGGCCGGGCTCCTCGCACGATTATCTACAGAACAAGCAGCCGCCCCTCTGGGGTCCCTAATCGCTTCTCTGGATCCATCTCTGGGAACAAAGCCGCCCTCACCATCACAGGAGCCCAGCCTGAGGACGAGGCTGACTATTACTGTTCCTTGTATATGGGTAGTTACACTGA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-142*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGACTGTGGTCACCCAGAAGCCATCACTCTCAGTGTCTCCAGGAGGGACAGTCACACTCATATGTGGCCTCAGCTCTGGGTCAGTCTCTACAAGTAATTACCCTGGCTGGTACCAGCAGACCCAAGGCCGGGCTTCTCGCACAATTATCTACAGCACAAGCAGCCGCCCCTCTGGGGTCCCTAATCGCTTCACTGGATCCATCTCTGGGAACAAAGCCGCCCTCACCATCACAGGAGCCCAGCCTGAGGACGAGGCTGACTATTACTGTTCCTTGTATATGGGTAGTTACACTGA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-153*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGACTGTGGTAACCCAGGAGCCATCACTCTCAGTGTCTCCAGGAGGGACAGTCACACTCACATGTGGCCTCAGCTCTGGGTCAGTCTCTACAAGTAATTACCCTGGCTGGTACCAGCAGACCCAAGGCCGGGCTCCTCGCACGATTATCTACAACACAAGCAGCCGCCCCTCTGGGGTCCCTAATCGCTTCTCTGGATCCATCTCTGGAAACAAAGCCGCCCTCACCATCACAGGAGCCCAGCCCGAGGATGAGGCTGACTATTACTGTTCCTTGTATACGGGTAGTTACACTGA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-161*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGACTGTGGTCACCCAGAAGCCATCACTCTCAGTGTCTCCAGGAGGGACAGTCACACTCATATGTGGCCTCAGCTCTGGGTCAGTCTCTACAAGTAATTACCCTGGCTGGTACCAGCAGACCCAAGGCCGGGCTTCTCGCACAATTATCTACAGCACAAGCAGCCGCCCCTCTGGGGTCCCTAATCGCTTCCCTGGATCCATCTCTGGGAACAAAGCCGCCCTCATCATCACAGGAGCCCAGCCTGAGGACGAGGCTGACTATTACTGTTCCTTGTATATGGGTAGTTACACTGA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-36*01", + "range": { + "from": 0, + "to": 299 + }, + "sequence": "CAGACTGTGGTGACCCAGGAGCCATCACTCTCAGTGTCTCTGGGAGGGACAGTCACCCTCACATGTGGCCTCAGCTCCGGGTCAGTCTCTACAAGTAACTACCCCAACTGGTCCCAGCAGACCCCAGGGCAGGCTCCTCGCACGATTATCTACAACACAAACAGCCGCCCCTCTGGGGTCCCTAATCGCTTCACTGGATCCATCTCTGGGAACAAAGCCGCCCTCACCATCACAGGAGCCCAGCCTGAGGACGAGGCTGACTACTACTGTGCTCTGGGATTAAGTAGTAGTAGTAGTTA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-39*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGACTGTGGTAACCCAGGAGCCATCACTCTCAGTGTCTCCAGGAGGGACAGTCACACTCACATGTGGCCTCAGCTCTGGGTCAGTCTCTACAAGTAACCACCCTAGCTGGTACCAGCAGACCCAAGGGAAGGCTCCTCGCATGCTTATCTACAACACAAACAACCGCCCCTCTGGGATCCCTAATTGCTTCTCTGGATCCATCTCTGGGAACAAAGCCTCCCTCACCATCACAGGAGCCCAGCCTGAGGACGAGACTGACTATTACTGTTTATTGTATATGGGTAGTAACATTTA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-43*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGACTGTGGTAACCCAGGAACCATCACTCTCAGTGTCTCCATGAGGGACAGTCACACTCACATGTGGCCTCAGCTCTGGGTCAGTCTCTACAAGTAACTACCCCAACTGGTACCAGCAGACCCAAGGCCGGGCTCCTCACAGGGTTATCTACAACACAAACAACCGCCCCTCTGGGGTCCCTGATCGCTTCTCTGGATCCATCTCTGGGAACAAAGCCGCCCTCACCATCACAGCTGCCCAGCCTGAGGACGAGGCTGACTATTACTGTTCATTGTATATGGGTAGTAACATTTG" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-93*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGACTGTGGTAACCCAGGAGCCATCACTCTCAGTGTCTCCAGGAGGGACAGTCACACTCACATGTGGCCTCAGCTCTGGGTCAGTCTCTACAAGTAATTACCCTGGCTGGTACCAGCAGACCCAAGGCCGGGCTCCTCGCACGATTATCTACAACACAAGCAGCCGCCCCTCTGGGGTCCCTAATCGCTTCTCTGGATCCATCTCTGGAAACAAAGCCGCCCTCACCATCACAGGAGCCCAGCCCGAGGATGAGGCTGACTATTACTGTTCCTTGTATACGGGTAGTTACACTGA" + }, { + "uri": "file://dog_V_IGL.fasta#Canlupfam_IGLV8-99*01", + "range": { + "from": 0, + "to": 296 + }, + "sequence": "CAGACTGTGGTCACCCAGAAGCCATCACTCTCAGTGTCTCCAGGAGGGACAGTCACACTCATATGTGGCTTCAGCTCTGGGTCAGTCTCTACAAGTAATTACCCTGGCTGGTACCAGCAGACCCAAGGCCGGGCTTCTCGCACAATTATCTACAGCACAAGCAGCCGCCCCTCTGGGGTCCCTAATCGCTTCCCTGGATCCATCTCTGGGAACAAAGCCGCCCTCACCATCACAGGAGCCCAGCCTGAGGACGAGGCTGACTATTACTGTTCCTTGTATATGGGTAGTTACACTGA" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV10*01", + "range": { + "from": 0, + "to": 280 + }, + "sequence": "AAAAACCAAGTGGAACAGAATCCTCCATCTCTGATCACCCTGGAGGGGAAGAACTACACAATTCAATGCAACTATACAGTGAACCCCTTTGGCAGCTTAAGGTGGTACAAGCACAGCACGGGGACAGGTCCTGCTTCTCTGATAGGCATGACTTACAGTGACAGCAAAAAGTCATATGGAAGCTACACAGTGACTCTGGATGCAAACTCCAAGCAGAGCTCCTTGCACATCACAGCTGCCCAGCTTAGTGATTCAGCCTCCTACATCTGTGTGGTGAGCG" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV12*01", + "range": { + "from": 0, + "to": 278 + }, + "sequence": "CAGAAGGGAGTGGAGCAGAGTCCTGGATCTCTGAGCACTCCGGAGGGAACCACTGCCTCTCTCAACTGCAATTATAGTGACAGTGCTTCCCAGTACTTCATGTGGTACAGACAGTATTCTGGGAAGGGCCCTGAGTTGCTGATGAACATATACTCCAAAGGTGAAAAAGAAGAAGGAAGATTTGCAGTGCAGGTCGATAGAGCAAAACAGTATGTCTCCTTGCTCATCAGAGATTCCCAGCCCAGTGACTCAGCCACCTACCTCTGTGCAGTGAGCCG" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV17*01", + "range": { + "from": 0, + "to": 277 + }, + "sequence": "AACAGCCAGCAAGAACAGAATCATCAGGTCCTGAGCATCCAGGAGGGTGAAAATGCCACCATGAACTGCAGTTATAAGACTTCTATTGACAACTTACAGTGGTATAGACAAGACTCAGTCAGAGTCTTTGTCCAATTGATTTTAGTATGTTCAAACCAGAGAGAGAAACACAGTAGAAGACTACAAGTCACACTGAACACCAGCATCAAAAGCAGTTCTTTGTCTATCGTGGCTTCCCAGGCTGCAGACACTGCTACTTATATCTGTGCTATGGATA" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV19*01", + "range": { + "from": 0, + "to": 285 + }, + "sequence": "TCTCAGAAGGTAACTCAAGCCCAGCCTTCCATTTCTACTCTGGAGAAGGAGGCTGTGACCCTGGATTGTGTATATGAAGTTAGTGGTTATAGTTATTATTTATTCTGGTACAAACATCCACCAAGTGTGGAAATTATTTTCCTCATTTGTCAGGACTCATACTGGGAGCAGATTGCAACAGAGGGTCGGTATTCTCTGATCTTTCAGAAATCAGACCATTCCAGCAGCCTCACCATCACAGCATTACAGCCTGCAGACTCTGCAGTGTGTTTCTGTGCTCTGAGA" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV21*01", + "range": { + "from": 0, + "to": 279 + }, + "sequence": "AAACAGGAGGTGAAGCAGAGTCCTGAAGCTCTGAGCGTCCGTGAGCGGGACAGCGTGGTTCTCAGCTGCAATTTCACAGATAGTGCCATTTATTCCCTTCAGTGGTTTAGGCAGGACCCAGGGAAAGGGCTCACACTCCTGTTGTTAATGCAGTCAAATCAAAAAGAACAAACAAGTGGAAGAATTAAAGTCTGGTTGGATATGTCATCAAGACAGAGCACTTTATACATTGCAGCTTCTCAGCCCAGCGACTCGGCCACCTATCTCTGTGCAGTAAGG" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV22*01", + "range": { + "from": 0, + "to": 271 + }, + "sequence": "GGCATGGAGGTGGAGCAGAGGCCCCTCGTTGAGAGTGTGCTGGAGGGAGCCAGTCCCACGCTGCAGTGCAATTTTTCTACCTCTGCGACCAGCGTGCAGTGGTTTCGCCAAGATCCTGGAGGCCACCTCGTCCACCTGTTTTACATTCCTTCGGGGACAAAGCACAATGGAAGATTAAACTCTACGACAGTCACTAAAGAGCGCCGCAGCTCATTGTACATTTTTTCTTCCCAGACCACAGACTCAGCCATTTACTTCTGTGCGGTGAAGC" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV23*01", + "range": { + "from": 0, + "to": 280 + }, + "sequence": "CAGCAGCAGGTGATACAGGGCCCTCAATCTCTGGCAGTCTGGGAAAGAGAGATTTCTACTCTGAACTGTACTTATGAGAACAGTGCTTTTGACTACTTTCTATGGTACTGGCAATACCCTAGTAAAGGCCTTGAATGCTTGCTAACCATGCTCTTGGTTGAGAGTACAAAGGAAAATGGAAGATTCACAGTTTCCCTCAATGTAAGTGCCAAAAAATTCCTATTGCACATCAGAGCTTCCCAGCCTGGAGACTCAACCACCTACCTCTGTGCAACAAGAG" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV24*01", + "range": { + "from": 0, + "to": 277 + }, + "sequence": "GTATTGAATGTGGAACAGAATCCTCCATGGCTTCATGTTCAGGAAGGAGAGAATACCAATTTCACCTGCCATTTTCCTTCCAGCAATTTCTATTCTTTACAGTGGTACAGACAGGAACCTGCAAAAAGTCTCAAATTCCTATTTACAATTGCTCTAAATGGGGATGAAAAGGAAGAAGGACGAGTAAGAGCCACTCTTAATACTCGGGAAGGCTATAGCTCCCTGTACATCAAAAGATCCCAGCCTGAAGATGCAGCTGTATACCTCTGTGCCTCCA" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV25*01", + "range": { + "from": 0, + "to": 269 + }, + "sequence": "GGACAAGAGATAAAGCAAAATCCTCAATTCCTGCTTTTACAAGAAGTAGAGGATTTCACCATTTATTGCAATTCCTCAAGTACTTTAAATGGCTTACAGTGGTATAAGCAGAGGCCTGGAGATGGTCCTGTTCTGATCAAATTATTTAAGGGTGGAGAAGTGAAGCAGAAAAGACGGACAGCTCAGCTTGGAGAGACTAGAAAGGACAGTTCCCTGCACAAGGCGGCCTCCCAGACTGCAGATGCAGGAACCTATTTCTGCGCAGGGAG" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV26*01", + "range": { + "from": 0, + "to": 277 + }, + "sequence": "GAAACTAAGACCACACAGCCAAATGTAATGGAGAGTACTGAAGAAGAACCTGTACACCTGCCCTGTAACCACTCCACAATCAGTGGAAATGAGTACATATATTGGTATCGACAGATTCCCCAACAGAGTCCAGAGTACATGATTCATGGTCTAAAGGACAATGTGACTAATGGAATGGCCACTCTGATCATCGCAGTAGATAGAAAGTCCAGTACCTTGATTCTGCCCTGGATTACCCTCAGAGATGCTGCTGTGTACTATTGCATCGTGAAAGAGG" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV27*01", + "range": { + "from": 0, + "to": 274 + }, + "sequence": "ACCCAGCTGCTGCAGCAGAGTCCTCGCTCTCTAAGCATCCAGGAGGGAGAAAATTTTACTACGTACTGCAACTTCACGAGCACTTTCCCCAGCTTTCAGTGGTACAGACAGAAGCCTGGGGCAGGTCCAGTCCACTTGATGACATTATCTAAGCGAGGAGAAGTGAAGAAGCAGAAGAGTCTAACAGCTCTGTTTGGTGAGGCAAGAAAAGACAGCTCCCTGCTTATCATTGCAGCCCAGCCTGGAGATGCTGCCATCTACTTCTGCGCAGGGT" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV28*01", + "range": { + "from": 0, + "to": 271 + }, + "sequence": "CAGATGAAGGTGGAACAGAGTCCTGGTGTCCTGATCCTCCAAGAGGGGAGAAATGCCTCTATGATGTGTAATTACTCTATTGCCGTTACCAGTGTGCAGTGGTTCCAACAAAACCATGAAGGACACCTCACGTCCTTGTTGTACATCGTTTCTGGAATGAAGCAAAAAGGAAGACTAAAATTCACAGTTGATACCAAGGAGCGTAACAGCCATCTGTATATCACAGACTCCCAGCCTGGGGACTCAGTCACTTATTTCTGTGCTGTGGAGG" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV29*01", + "range": { + "from": 0, + "to": 280 + }, + "sequence": "GACCAGCAGCAGATCAAACAAACTCCATCCTTGAATGTGCAAGAAGGAAAGATTTCTATTCTGAACTGTGACTACAATAGTAATATATTTGATTACTTCCCATGGTACAAAAAATATCCTGCTAAAAGTCCCAAATTCCTAATATCAATACGTTCAGTTGTGGATAAAAATGAAGATGGAAGATTCACAGTCTTCCTCAACAAAAGTGTCAAACGCCTTTCTCTGCACATCAAAGATTCACAGCCCAGAGACTCAGCCCTGTACCTCTGCGGAGCAAGTG" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV34*01", + "range": { + "from": 0, + "to": 272 + }, + "sequence": "AGCCAAGAACTGGAGCAGAGTCCACAGTCTCTGATCATCCAAGAAGGAGAAAATTTCACCATAAACTGCCATTCATCAAAGCCTGTGTATGCCCTACACTGGTACAGGCAAAAAAATAGTGAAGGTCTAATATCCTTGATGATATTATGGGAGAATGGGGAAAAGAGTCATGAAAAAACCACTGCCACGTTAAATGAGAAGAAGCAGCAAAGTTCCCTGCATATCACAGCCTCCCAGTCCAGCGACTCAGGCATCTACTTCTGTGCAGCAGA" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV35*01", + "range": { + "from": 0, + "to": 275 + }, + "sequence": "GGCCAGCAGCTGAATCAGAATCCTAGGTCTGTGTCCATTCAGGAAGGAGAGGATGTCTCCATGAGCTGCAATTCCTCGAGTACACTCAACACTTTTCAATGGTTCAAGCAGGATCCTGTGGAGGGCCTTGTCCTCGTGATAGCCTTATATAAGGCTGGGGAATTGACCCGAGATGGAAAACTGACTGCTCAGTTCAGTGGAACAAGAACGGACAGCTTCTTGACCATTTCAGCCTCTGAGACTGAACATTCAGGCACCTACTTCTGCAGTGGCAA" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV37*01", + "range": { + "from": 0, + "to": 281 + }, + "sequence": "CAACCACTAATGGAATAGAGCCCTCTCTACCTGAAAGTCCAAGAAGGCAAGAGCTTCACAATGAACTGCAGTTACAGAGGCAACACTTCAGCTTTCTTCCAATGGTTTAGGCAGGGTCCTGGGGAAAGCCTCCACGTTTTGATGCAAATGCAATCAAACAAGAAAGAGAAGATCAGTGGAAGATTCACAGCCAGTCTTAACAGGGAGGATCAGTACTTTTCCCTGCACATGGAAGATTCTCACCTCCATGACTCAACCACATTCCTGTGTGCAGCGAACAG" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV38-1*01", + "range": { + "from": 0, + "to": 286 + }, + "sequence": "GCCCAGAAAGTCACTCAGACTCAACCAGACATATCTGTGCGGGACACAGAGACTGTGACCCTGAACTGCATGTATGACACTAGTGACAGTAATTATTTGTTCTGGTACAAACAGCCTCCCAGCAGGGAGATGATTCTCATTATTCGCCAAGAAGCTTATAGACAAGAGAATGCAACAAATAATCGCTTCTCTGTGAACTTCCAAAAAGGAAATAAATCCTTCAGTCTCAGGATCTCAGACTCTCGGCTGGAGGATGCTGCAATGTATTTCTGTGCTCTCATGGAGG" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV38-2*01", + "range": { + "from": 0, + "to": 289 + }, + "sequence": "GCCCAGAAGGTCACTCAGACTCAACCAGAAATGTTTGTTCGGGAGACAGATACTGTGATCCTGCACTGCACGTATGACACTAGCGACAGTGATTATTATTTGTTCTGGTACAAACAGCCTCCCAGCAGGGAGATGATTCTCATTATTCGCCAAGAAGTTTATAGACAAGATAATGCAACAAATAATCGCTTCTCTGTGAACTTCCAGAAAGGAAATAAATCCTTCAGTCTCAAGATCTCAGACTCTCGGCTGGAGGATGCTGCAATGTATTTCTGTGTTCTTGCAGGAG" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV40*01", + "range": { + "from": 0, + "to": 267 + }, + "sequence": "GGCAATTCAGTCAAACAGACAGACCAAATTACTGTCTTGGAAGGAACATCTGTGACTATGAGCTGCATATACACATTCACACAGAACCCTACTCTTTTCTGGTATGTCCAATACCCCAACAAAGCTCTGCAACTTCTTCATAAAGAGACGATGGAAAACAGCAAGAACTTTGGAGCCAGAAATATTAAAGACAAAAACTCCCCTATTACGAAGTCGTCAGTCCAGGTATCAAATTCGGCCATGTACTACTGTCTTCTGAGAGATACA" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV43-1*01", + "range": { + "from": 0, + "to": 274 + }, + "sequence": "GGGATGAAGGTGGAGCAGAGCCCTTCAGCCCTGAGCCTGCAGGAGGGAGCCAGCTCTATCCTCAAGTGCAATTACTCTAGCGCAGTGAACAGTGTGCAGTGGTTCCGACAGAATCCCGGGGGCGGCAGGCTCACCAGGCTGTTTTACATAGCTTCAGGGATGAAGCAGAGCGGGAGGCTAAACTGCACACTGAATGCTAAGGAACGGTTCAGCACCCTCCACGTCGCGGCCTCTCAGCTGGAAGACTCAGCCACCTACCTGTGTGCGGTGGAGG" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV43-2*01", + "range": { + "from": 0, + "to": 274 + }, + "sequence": "GGGATGAAGGTGGAGCAGAGCCCTTCAGCCCTGAGCCTGCAGGAGGGAGCCAGCTGTACTCTGAAGTGCAATTATTCTAGTACTCCGTACAATGTGCAGTGGTTCCGACAGAATCCCAGGGGCGGCAGCCTTACCAGGCTGTTTTACATAGCTTCAGGGATGATGCAGAGCGGAAGGCTAAACTGCACACTGAATGCTAAGGAACGGTTCAGCACCCTCCACGTCGCGGCCTCTCAGCTGGAAGACTCAGCCACCTACCTGTGTGCGGTGGAGG" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV43-3*01", + "range": { + "from": 0, + "to": 274 + }, + "sequence": "GGGATGAAGGTGGAGCAGAGCCCTTCAGCCCTGAGCCTGCAGGAGGGAGCCAGCTCTATCCTCAAGTGCAATTACTCTAGCGCAGTGAACAGTGTGCAGTGGTTCCGACAGAATCCCGGGGGCGGCAGGCTCACCAGGCTGTTTTACATAGCTTCAGGGATGAAGCAGAGCGGAAGGCTAAACTGCACACTGAATGCTAAGGAACGGTTCAGCACCCTCCACGTCGCGGCCTCTCAGCTGGAAGACTCAGCCACCTACCTGTGTGCGGTGGAGG" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV43-4*01", + "range": { + "from": 0, + "to": 274 + }, + "sequence": "GGGATGAAGGTGGAGCAGAGCCCTTCAGCCCTGAGCCTGCAGGAGGGAGCCAGCTCTATCCTCAAGTGCAATTACTCTAGCGCAGTGAACAGTGTGCAGTGGTTCCGACAGAATCCCGGGGGCGGCAGGCTCACCAGGCTGTTTTACATAGCTTCAGGGATGAAGCAGAGCGGAAGGCTAAACTGCACACTGAATGCTAAGGAACGGTTCAGCACCCTCCACGTCGCGGCCTCTCAGCTGGAAGACTCAGCCACCTACCTGTGTGCGGTGGAGG" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV43-5*01", + "range": { + "from": 0, + "to": 274 + }, + "sequence": "GGGATGAAGGTGGAGCAGAGCCCTTCAGCCCTGAGCCTGCAGGAGGGAGCCAGCTCTATTCTCAAGTGCAATTACTCTAGCGCAGTGAGAAGTGTGCAGTGGTTCCGACAGAATCCCAGGGGCGGCAGCCTCACCAGGCTGTTTTACATAGCTTCAGGGATGAAGCAGAGCGGAAGGCTAAACTGCACACTGAATGCTAAGGAACTGTTCAGCACCCTCCACGTCGCGGCCTCTCAGCTGGAAGACTCAGCCACCTACCTGTGTGCGGTGGAGG" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV43-6*01", + "range": { + "from": 0, + "to": 274 + }, + "sequence": "GGGATGAAGGTGGAGCAGAGCCCTTCAGCCCTGAGCCTGCAGGAGGGAGCCAGCTCTATCCTCAAGTGCAATTACTCTAGCGCAGTGAACAGTGTGCAGTGGTTCCGACAGAATCCCGAGGGCGGCAGCCTCACCAGGCTGTTTTACATAGCTTCAGGGATGAAGCAGAGCGGGAGGCTAAACTGCACACTGAATGCTAAGGAACTGTTCAGCACCCTCCACGTTGCGGCCTCTCAGCTGGAAGACTCAGCCACCTACTTGTGTGCGGTGGAGG" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV8-1*01", + "range": { + "from": 0, + "to": 284 + }, + "sequence": "GCCCAGTCAGTAACCCAACCGGATGCCTATGTTACTGTCTCGGAAGAAGCCCCTCTGGAGCTGAGGTGCAGCTACTCATCTTCTATTCAGCCGTATCTCTACTGGTTCGTGCAGTACCCCAACCAAGGCCCCCAGCTTCTCCTCCAGTACACATTAAAGGGAGCACTGGTTAAAGGCATCAAAGGTTTTGAGGCTGAATTTAAGAGGAATGAAACCTCCTTCCACCTGAGGAAACCCTCAGCCCATGGGAGTGACACAGCCAAGTACTTCTGTGCTCTTAGTGA" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV8-2*01", + "range": { + "from": 0, + "to": 278 + }, + "sequence": "GCCCAATCTGTGACCCAGCCTGATGCCCACATCACTGTCCCTGAAGAATCCCCTCTGGAGCTGAGATGCAACTATTCCTATGGTGCAACTCCATATCTCTACTGGTACGTGCAGTACCCCAACCAAGGCCTCAAGCTTCTCCTCAGGTACTTTTCAGGGGATACCGTGGTTCAAGGCATCAAAGGTTTCAAGGCTGAATTTAGTAAAACCTCCTTCCACCTGAAGAAACCCTCAGCCCATTGGAGCGACTCAGCCAAGTACTTCTGTGCTGCGAGTGA" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV9-1*01", + "range": { + "from": 0, + "to": 281 + }, + "sequence": "AGAGACTTAGTGACCCAGAAGGACGGCCAGGTGACCCTTTCAGAAGAGGCTTTCCTGACTGTGAACTGTAATTATTCAGCATCAGGGTACCTTGCCCTTTTCTGGTATGTCCAGTATCTGAGAGAAGGTCCACAACTCCTCCTGAAAGCATCAAGAGACAAGGAGAAGGGAAGTAACAAAGGTCTCGAAGCCACCAATGACAGATCATCCAGATCCTTCCACTTGAAGAAAAGCTCAGTGCAAATGTCAGACTCAGCTGTGTACTACTGTGCCCTGAGAGA" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV9-10*01", + "range": { + "from": 0, + "to": 281 + }, + "sequence": "GGAGACTCAGTGAAACAGATGGAAGGCCAAGTGACCCTCTCAGAAGAGGCTTCGTTGACTATAAATTGTACTTTCTCAACATCAGTGACACCCACTCTCTTCTGGTATGTCCAGTATCTGGGAGAAGGTCCACAGATCCTCCTGAAAGCATTAAGAGACAAGGAAAAGGGAAGCAACAAAGGATTTGAAGCAACCTTGGACAGTTCATCCAAATCCTTCCACTTGAAGAAAGGCTCAGTGCAAATGTCAGACTCAGCTGTGTACTACTGTGTCATGAGTGA" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV9-11*01", + "range": { + "from": 0, + "to": 281 + }, + "sequence": "GGAGACTCAGTGAACCAGACGGAAGGCCAAGTGATCCTCTCAGAAGAGGCCTTCCTGACTATAAACTGTACCTACTCGACAACATGGCCCCCCACTCTTTTCTGGTATGTCCAGTATCTGGGAGAAGGTCCACAGCTCCTCCTGAAAGCAGTGACAGACAAGGAAAAGGGAAGCAACAAAGGTTTTGAAGCCACCTTGGACAAAACATCCAGATCCTTCCACTTGAAGAAAGGCTCGGTGCAACTGTCAGACTCAGCTGTGTACTACTGTGCCTTGAGTGA" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV9-12*01", + "range": { + "from": 0, + "to": 281 + }, + "sequence": "GGAGACTCAGTGAACCAGACGGAAGGCCAAGTGACCCTCTCAGAAGAGGCCTTCCTAACTATAAACTGTACCTACTCGACAACAAGATCCCCCACTCTTTTCTGGTATGTCCAGTATCTGGGAGAAGGTCCAAAGCTCCTCCTGAAAGCATTAAGAGACAAGGAAAAAGGAAGCAACAAAGGTTTTGAAGCCACCTTGGACAAAACATCCAGATCCTTCCACTTGAAGAAAGGCTCGGTGCAACTGTCAGACTCAGCTGTGTACTACTGTGCCCTGAGTGA" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV9-13*01", + "range": { + "from": 0, + "to": 281 + }, + "sequence": "GGATACTCAGTGAAACAGATGGAAGGCCAAGTGACCCTCTCAGAAGAGGCTTCGTTGACTATAAACTGTACTTTCTCAACATCAACGACACCCACTCTCTTCTGGTATGTCCAGTATCTGGGAGAAGGTCCACAGATCCTCCTGAAAGCATTAAGAGACAAGGAAAAGGGAAGCAACAAAGGATTTGAAGCAACCTTGGACAGTTCATCCAAATCCTTCCACTTGAAGAAAGGTTCAGTGAAAATGTCAGACTCAGCTGTGTACTACTGTGCCCTGAGTGA" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV9-3*01", + "range": { + "from": 0, + "to": 281 + }, + "sequence": "GGAGACTCAGTGAACCAGACGGAAGGCCAAGTGACCCTCTCAGAAGAGGCCTTCCTGACTATGAACTGTACTTTCTCAACATCATGGACACCCAGTCTATTCTGGTATGTCCAGTATCTGGGAGAAGGTCCAAAGCTTCTCCTGAAAGCCTTGACAAACAAGGCAAAGGGAAGCAACAAAGGTTTTGAAGCCACCTTGGACAGTTCATCCAAATCCTTCCACTTGAAGAAAGGCTCAGTGGAAATGTCAGACTCAGCTGTGTACTACTGTGCCATGAGTGA" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV9-4*01", + "range": { + "from": 0, + "to": 281 + }, + "sequence": "GGAGACTCAGTGAACCAGACGGAAGGCCAAGTGATCCTCTCAGAAGAGGCCTTCCTGACTATAAACTGTACCTACTCGACAACATGGCTCCCCACTCTTTTCTGGTATGTCCAGTATCTGGGAGAAGGTCCACAGCTCTTCCTGAAAGCTGTGACAGACAAGGAAAAGGGAAGCAACAAAGGTTTTGAAGCCACCTTGGACAAAACATCCAGATCCTTCCACTTGAAGAAAGGCTCGGTGCAACTGTCAGACTCAGCTGTGTACTACTGTGCCCTGAGTGA" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV9-5*01", + "range": { + "from": 0, + "to": 281 + }, + "sequence": "GGAGACTCAGTGAAACAGATGGAAGGCCAAGTGACCCTCTCAGAAGAGGCTTCGTTGACTATAAACTGTACTTTCTCAACATCAACGACACCCACCCTCTTCTGGTATGTCCAGTATCTGGGAGAAGGTCCACAGATCCTCCTGAAAGCATTAAGAGACAAGGAAAAGGGAAGCAACAAAGGATTTGAAGCAACCTTGGACAGTTCATCCAAATCCTTCCACTTGAAGAAAGGCTCAGTGCAAATGTCAGACTCAGCTGTGTACTACTGTGCCCTGAGTGA" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV9-6*01", + "range": { + "from": 0, + "to": 281 + }, + "sequence": "GGAGACTCAGTGAACCAGACGGAAGGCCAAGTGACCCTCTCAGAAGAGGCCTTCCTGACTATGAACTGTACCTACTCAGCAACAGGATCCCTCATTCTATTCTGGTATGTCCAGTATCTGGGAGAAGGGCCACAGCTCCTCCTGAAAGCATTAAGAGACAAGGAAAAAGGAAGCAGCAAAGGTTTTGAAGCCACCTTGGACAAAACATCCAGATCCTTCCACTTGAAGAAAGGCTCGGTGCAACTGTCAGACTCAGCTGTGTACTACTGTGCCCTGAGAGA" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV9-7*01", + "range": { + "from": 0, + "to": 281 + }, + "sequence": "GGAGACTCAGTGAAACAGATGGAAGGCCAAGTGACCCTCTCAGAAGAGGCTTCGTTGACTATAAACTGTACTTTCTCAACATCAACGACACCCACTCTCTTCTGGTATGTCCAGTATCTGGGAGAAGGTCCACAGATCCTCCTGAAAGCATTAAGAGACAAGGAAAAGGGAAGCAACAAAGGATTTGAAGCAACCTTGGACAGTTCATCCAAATCCTTCCACTTGAAGAAAGGCTCAGTGGAAATGTCAGACTCAGCTGTGTACTACTGTGCCCTGAGTGA" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV9-8*01", + "range": { + "from": 0, + "to": 281 + }, + "sequence": "GGAGACTCAGTGAACCAGACGGAAGGCCAAGTGACCCTCTCAGAAGAGGCCTTCCTGACTATGAACTGTACTTTCTCAACATCATGGTCACCCAGTCTATTCTGGTATGTCCAGTATCTGGGAGAAGGTCCAAAGCTTCTCCTGAAAGCCTTGACAAACAAGGCAAAGGGAAGCAACAAAGGTTTTGAAGCCACCTTGGACAGTTCATCCAAATCCTTCCACTTGAAGAAAGGCTCAGTGGAAATGTCAGACTCAGCTGTGTACTACTGTGCCATGAGTGA" + }, { + "uri": "file://dog_V_TRA.fasta#TRAV9-9*01", + "range": { + "from": 0, + "to": 281 + }, + "sequence": "GGAGACTCAGTGAACCAGACGGAAGGCCAAGTGATCCTCTCAGAAGAGGCTTTCCTATCTATAAACTGTACCTACTCAACAACAGGATCCCCCACTCTATTCTGGTATGTCCAGTATCTGGGAGAAGGTCCACAGCTCCTCCTGAAAGCATTAAGAGACAAGGAAAAGGGAAGCAGCAAAGGTTTTGAAGCCACCTTGGACAAAACATCCAGATCCTTCCACTTGAAGAAAGGCTCGGTGCAACTGTCAGACTCAGCTGTGTACTACTGTGCCCTGAGTGA" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV1*01", + "range": { + "from": 0, + "to": 287 + }, + "sequence": "GCAAGCCTGGTGGAGCAAAGGCCCCGCTGGGTCCTGGTAGCTCGTGGGCAGGCTGAAACCCTGCACTGCATCCTGAGAGATTCCCAGTACCCTTGGATGAGCTGGTACCAACAGGATCTCCAGGGGCAACTGCAGGTGCTGGCCACTCTGCGGAGCCCCGGGGACGAGGAGCTCGTATCCCGTCCTGGAGCAGATTACCGGGTCACGAGGGTCAACAGCACGGAGCTGAGGCTGCACGTGGCCAATGTGACCCAGAGCAGAACCCTGTACTGCACCTGCAGTAAAGA" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV10*01", + "range": { + "from": 0, + "to": 287 + }, + "sequence": "GATGCTGGAATTATCCAGAGCCCAAGATACAAGGTCACAGGGACAGGAAAGAGGGTGACTCTGAGATGTCACCAGACAGACAACTATGACTATATGTACTGGTATCGACATGACCTGGGACATGGGCCGAGGCTGATCTATTATTCAAATGGTATTAACAGCACTGAAAAAGGAGACCTCTCCAATGGATACACAGTCTCTAGATCAAACAAGATGGATTTCCCCCTCCTACTGGACTCTGTTACCTCCTCCCAGACATCTGTGTACTTCTGTGCCGACAGTTACTC" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV12-1*01", + "range": { + "from": 0, + "to": 290 + }, + "sequence": "AATACTGAAGTCATCCAGACACCCAGGCGCAAGGTGACAACGATGGGACAAGAAGTAACTCTGGGATGAGAGCTTACTTCTGGCCATTATACCCTTTTCTGGTACAGACAGACCTCAGGGAAGGAACCAAAGCTGCTGATTTACTTCAGCAATAAAGCTCTTATGGATGACTCGGGGATGCCCAAGGAACGGTTCTCAGCAGAGATGCCTGATGATTCATTCTCCATCCTGAAGATCCAGCCAACAGAACCCAGGGACTCAGCCACGTACCTATGTGGCAGCAGGGTAGA" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV12-2*01", + "range": { + "from": 0, + "to": 290 + }, + "sequence": "GACGCTGGAGTCATCCAGATACCCATGCACAAGGTGACAACGATGGGACAAGGAGTAACTCTGGGATGTGAGCCTATTTCTGGTCATGTGGTCCTCTTCTGGCACAGACAGACTTCAGGGCAGGGATGGAAGCTGCTGATTTACTTCAACAATCAATCTCCTGTGGATTACTCGGGGATGCCCAAGGAACGGTTCTCAGCAGAGATGCCTGACAAATTATTCTCCATCCTGAAGATCCAGCCAATAGAACCTGGGGACTCAGCCACGTACCTGTGTGCCAGCAGTGTAGA" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV15*01", + "range": { + "from": 0, + "to": 287 + }, + "sequence": "GGTGCCATGGTCATCCAGAGCCCAAGATACCAGGTTACTAAGGTGGGAAAACCAGTGACTCTGAACTGTTCTCAGAACCTGAATCATGATACCATGTACTGGTACCAACAGAAGCTGAGACAAGCACCAAAGCTGCTGCTCTACTACTATGATACAGAGCTTACCAAAGAAACAGACACCTCTGATAACTTCCAACCCAGCCGGCTCAGCAATTCTCTCTGTTCTCTTAGCATCCGCTCACCAGGCTTGGGGGACTCAGCAGTGTACCTCTGTGCCAGCAGTAAAGA" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV16*01", + "range": { + "from": 0, + "to": 290 + }, + "sequence": "AATGCAAAAGTCATGCAGACTCCAGGACATCTGGTCAAAGGGAAAGGACAAAAAGCAAAAATGGAATGTGTCCCAATAAAAGGACATAGTTATGTTTTCTGGTATCAGCAGATCCCAGCAAAAGAGTTCAAGTTCTTGATTTCTTTCCAGGATAACGCTGTCTTTGATAAAACAGGGATGCCCACGCAGAGATTTTTAGCCTTCTGTCCAAAAAACTCACCCTGTAGCCTAGAGATCGAGCGTACAGAGCTGCAGGATTCAGCCGTGTATTTTTGTGCCAGCAGTGAATC" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV18*01", + "range": { + "from": 0, + "to": 290 + }, + "sequence": "AATGCTGGCGTCACCCAGAATCCGAGACACCTGGTGAGAAGGACAGGACAAGAGGCAATACTGACATGCAGCCCCGAGAAAGGACACAGTTATTTTTATTGGTATCAGCAGTTCCTGGGGGAAGGTCTGAAATTCATGATTTACCTCCAGAAAGAAACTATCTTAGACCAGTCAGGAATGCCAAAGAAACGCTTTTCTACTGAATTTTCCGAAGACGGACTTAGCATCTTAAAGATCCAGCCGGCAGAGTTGGGAGACTCTGCAGTGTACTTCTGTGCCAGCACCGAAGC" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV19*01", + "range": { + "from": 0, + "to": 287 + }, + "sequence": "AGTGGTGGAATCACTCAGACCCCCAAATATTTGTTCAGAGAGGAAGGACGAGGTGTGACTCTGGAATGTGAACAGGATTTTAATCATGACTCTATGTACTGGTACCGACAAGACCCAGGGCAAGGGCTGAGACTGATCTACTACTCGCTGGTAGAAAATGATGCTCAGAAAGGAGACATACCTGAAGGCTACAGTGCCTCTCGGATGAAGAAGGCATTCTTCTCTCTCACCATGACATCGGTGCAAAAGAACTAGACAGCTCTATATCTCTGTGCCAGTGGTAGAGA" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV20*01", + "range": { + "from": 0, + "to": 293 + }, + "sequence": "GGTGCCCTCGTCTTCCAGGCGCCCAGCACAATGATCTGTAAGAGCGGAGCCACCGTGCAGATCCAGTGTCAAACAGTGGACCTTCAAGCCACAACCGTGTTTTGGTATCGCCAGCTCCCGAAGCAGGGCCTTACCCTTATGGTGACCTCTAACGTGGGCAACAGTGCTACACACGAGCAGGGGTTCCCTGCAGCCAAGTTCCCTGTTAACCACCCAAACCTCACGTTTTCCTCCCTGATGGTGACGAGTTCAGGTCCTGGAGACAGCGGCCTCTACTTCTGTGGTGCTAGTCA" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV21*01", + "range": { + "from": 0, + "to": 290 + }, + "sequence": "GACACTGAGGTCACCCAGAGTCCCGGACACCTGGTCAAAGGGAAGGAACAGGGAGCAAAGTTGCATTGTGTCCCTGTAAAAGGACATAATTGTGTGTATTGGTATCACCAAAAGCTGGGAGAAGAGTTCAAGTTTCTGGCTTACTTACAGAATGAAGAGATTGTTGATAAGGGAGAAATGTTCAATCGCTGATTTTCAGCTACATGCCCCAAGAACTTGCCCTGCAGCCTAGAAATCAACTCCACGGAGCCAGGGGACTGGGCCCTGTATTTCTGTGCCAGCAGCCAATG" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV22*01", + "range": { + "from": 0, + "to": 283 + }, + "sequence": "TATGCTGAAATCTGCCAGAGGCCAGCATTCCTGCTCACTAAGGCTGGACAGGAGTCCCTGGAGTGCAAACAGAACTTGAAATACCATGCCATGTACTGGTACCGGCAGGACCCAGGACAAGGTCTGCGGCTGATTTACCTCTCAACTTTTGAAAAAGATGTTCAGAGAGGACATATAACTGAAGGCTACAATGCTTCCCGAGAGGAGAAGGGGCTGTTTCCTCTCACTGTGAGGTTGGTGCACACCAACCAGACAGGTGCCTACCTCTGTTCTGGTAGCGCTC" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV24*01", + "range": { + "from": 0, + "to": 288 + }, + "sequence": "GATGCTGGCATTACCCAGACCCCCCGGAATGGGATTATAAAGAAAGGAAAGAACATTTCACTGGAGTGTTCTCAGATTAAGAGTCATAACTATATGTACTGGTATCGACAAGATCCGGGAATGGGGCTACGGCTGATCTCCTACTCCTTTGGTGTCAATTATATTAATGAAGGAGAGGTCTCCAATGGGTACAGTGCTTCTCGAACGGACCTGGAGAAATTCTCCCTGTCTGTAGGGACTGCCATTCCCAACCAGACAGCTCTCTACTTCTGTGCCAGCAGCGATTCA" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV25*01", + "range": { + "from": 0, + "to": 287 + }, + "sequence": "GATGGTAGTGTCTCCCAGACCCCAAGACACTGTATTTCAGGGACAGGAAAGAAGATTACCCTGGAATGTTCTCAAACTATGGGCTATGACAACATGTACTGGTATCGACAAGACCCAGGAAAGGCACTACAGCTGCTCCATTATTCATATGGTGTTAATACCACAGAGAAAGCAGAGCTCTCCTCTGGATCAACTGTCTCCAGATTAAGGAAAGAGCTTTTCTCCCTAACCCTAGAGTCTACCAGCCTCTCACAGACATCCCTGTACCTCTGTGCCAGCAGTGAATA" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV26*01", + "range": { + "from": 0, + "to": 286 + }, + "sequence": "GATGCTCTGGTCAATCAGTTCCCAAGACATAGGATCTTGGGGACAGGAAAGAAATTAACCCTACAGTGTTTGCAGGATATGAATCATGTTTCAATGTTCTGGTATCGCCAAGACCCAGGATTTGGGCTACAGCTGATCTACTACTCAACTGGTACTGACAACTTTGAAAAAGGAGATGCCCCTGAGGGGTATGATGTCTCTCGAAATGAGCTGAAATCTTTTCCCCTGACCCTGGTCTCTGCCAGCACCAACCAGACATCTGTGTACCTCTGTGCCAGCAGTTAGC" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV27*01", + "range": { + "from": 0, + "to": 293 + }, + "sequence": "GAAGCCGGAGTGACCCAGACCCTGAGATACCTCATCACAGGGACCAGGAGGCAGTTGACACTGCTTTGTTCTCAGGATATGAACCGCGACGCTATGTACTGGTACGGACAAGACCCAGGGCTGGGTCTAAAGCTGATCTACTCTTCAAGGAACGTTCAGTTTACTGAAAAGGGAGATGTCCCTGATGGGTACTGGGTCTCTCGGAAAGAGAAGAGAAATTCCCCCCTGGCCCCGACCCTGGAGTCGGCAGGCACCAACCACACCTCGCTGCACCTCTGCGCCAGCGGTTTATC" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV28*01", + "range": { + "from": 0, + "to": 286 + }, + "sequence": "AACGCACAAGTGACTCAAACCCCGAGACAACTCATCAAAAAAGTGGGAGCGAAAGTTTTGTTGAAATGTTCACAGAATATGGACCATGAAAGAATGTTCTGGTATCGACAAGACCCAGGTCTGGGGTTGCGGCTGCTCTACTGGTCCTATAATATTGACAGTGTTGAGACAGGAGACATCCCTTATGGGTACAGTGTCTCGAGGAAGAAGAAGGATGCCTTCCCCTTGATTCTGGAGTCTGCTCGCATCAACCAGACATCTGTGTACTTCTGCGCCAGTAGTTAGC" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV29*01", + "range": { + "from": 0, + "to": 290 + }, + "sequence": "GGAGCTCTTGTCTCTCAAAAGCCGCGCAGGGACATCTGTCAACGTGGGACCTCCATTACCATCCACTGTGAGGTCGATACCCAAGTCACCTTGATGTTCTGGTACCGTCAGCTCCCAGGACAGAGCTTGATACTGATTGCAACCGCAAACCAGGGTGCAGAGGCCACCTACGAAAGTGGATTTACCAGGGAGAAGTTTCCCATCAGCCGCCGAACCCTAATGTTCTCCACTCTGACTGTGAGCAACCTGAGCCTCGAAGACACCAGCTCTTACTTCTGCAGCGCTAGAGA" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV3-1*01", + "range": { + "from": 0, + "to": 287 + }, + "sequence": "GACACAACAGTTTCCCAGACTCCAAGATACCTCATCGCGCACGTGGGATCGAAGAAGTTACTAAAATGTGAGCAAAATCTGGGCCATAATGCTATGTACTGGTATAAGCAAGACCTCAAGCAACTGCTGAAGATCATGTTTATCTACTTTAATCAGGGACTCAATCTAAATGAATCAGTTCCAGGTCGTTTCTCACCTGAGACATCTGACAAAGCTCATTTAAACCTTCATGTCGACTCCCTGGAGACAGGTGACTCTGCTGTGTATTTCTGTGCCAGCAGCCTAGA" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV3-2*01", + "range": { + "from": 0, + "to": 287 + }, + "sequence": "GACACAACAGTTTCCCAGACTCCAAGATACCTCATCGCGCACGTGGGATCGAAGAAGTTACTAAAATGTGAGCAAAATCTGGGCCATAATGCTATGTACTGGTATAAGCAAGACCTCAAGCAACTGCTGAACGTTATGTTTATCTACAACAATAAGGAACTCATTCTAAATGAATCAGTTCCAGGTCGTTTCTCACCTGAGACATCTGACAAATCTCATTTAAACCTTCATGTCGACTCCCTGGAGACAGGTGACTCTGCTGTGTATTTCTGTGCCAGCAGCCAAGA" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV3-3*01", + "range": { + "from": 0, + "to": 287 + }, + "sequence": "GACCAGTCAGTCCCTGTGACATCCTACAAGCTTCTCACACCTCAGTCTTCTCTTGCTTTTTAGCAATGTGAACAAAATCTGGGCCATGATGCTATGTACTGGTATTAGCAAGACCTCAAGCAACTGCTGAACGTTATGTTTATCTACAACAATAAGGAACTCATTCTAAATGAATCAGTTTCAGGTCGTTTCTCACCTGAGACATCTGACAAAGCTCATTTAAACCTTCATGTCAACTCTCTGGAGACAGGAGACTCTGCTGTGTATTTCTGTGCCAGCAGCCAAGA" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV30*01", + "range": { + "from": 0, + "to": 284 + }, + "sequence": "GCTCAGACTATCCACCAAAGGCCGCTTGCCAGGGTGCAGCTTGTGGGCAGCCTGCTCTCCCTGGAATGTACCGTGCAGGGGGCATCGAGCCCTTATCTCTACTGGTACCGGCAGTCCCTGGGAGGTGCGCCCCAGCTACTCTTCTCCTCATTAAGTGTTACCCAGATAGTCCCTGAGACACCGCACAACTTCACAGCCTCCAGGCCCCAGAACGGCCAGTTCATCCTGAGTTCTAAGAAGCTCCTTCTCAGTGACTCTGGCTTCTACCTCTGCGCCTGGAGTCT" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV4-1*01", + "range": { + "from": 0, + "to": 287 + }, + "sequence": "GACACTGGAATTACCCAGACCCCCAAACACCTGGTAACAGGGCCTGGGAGGAGAGTGACCCTCAACTGTGAACAACATCTGGGACATGATGCTGTGTACTGGTATCAGCAGAGGGCTCAGAAGCCACCGAAGCTCATGTTTGCCTACAGATATAAGGAACTCATTGAGAATGAGCCTGCCTCCGGTCGCTTCTCACCTGAGTGCCCAGACAGCTCCAGGCTCTACCTTCACGTGGACGCCCTGGAGCCTGATGACTCTGCCTTGTATCTCTGTGCTAGCAGCAGGGA" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV4-2*01", + "range": { + "from": 0, + "to": 287 + }, + "sequence": "GACACTGGAATTGCCCAGACCCCCAAACACCTGGTAACAGGGCCTGGGAGGAGAGTGACCCTCAACTGTGAACAACATCTGGGACATGATGAGGTGTACTGGTATCAGCAGAGCGGTCAGAAGCCACCGAAGCTCAAGTTTGCCTACAGATATAAGGAACTCATTGAGAATGAGACTGCCTCCGGTCGCTTCTCACCTGAGTGCCCAGACAGCTCCAGGCTCTACCTTCACGTGGACGCCCTGGAGCCCAATGACTCTGCCTTGTATCTCTGTGCTAGCAGCAGGGA" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV4-3*01", + "range": { + "from": 0, + "to": 287 + }, + "sequence": "GACACTGGAATTACCCAGACCCCCAAACACCTGGTAACAGGGCCTGGGAGGAGAGTGACCCTCAACTGTGAACAACATCTGGGACATGATGCTGTGTACTGGTATCAGCAGAGCGGTCAGAAGCCACCGAAGCTTATGTTTGCCTACAGATATAAGGAACTCATTGAGAATGAGACTGCCTCCGGTCGCTTCTCACCTGAGTGCCCAGACAGCTCCAGGCTCTACCTTCACGTGGACGCCCTGGAGCCTGATGACTCTGCCTTGTATCTCTGTGCTAGCAGCAGGGA" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV4-4*01", + "range": { + "from": 0, + "to": 286 + }, + "sequence": "GACACTGGAATTACCCAGACCCCCAAACACCTGTTAACAGAGACAGGGAGGAGAGTGACCCTCAAGTGTGAACAACATCTGGGACATAATGCTGTGTACTGGTATCAACAGAGCACTCAGAAGCCACCAAAGCTCATGTTTGCCTACAGTTACAAGGTACTCGTTGAAAATGAGACTGCTTCTGGTCGCTTCTCACCTGAGTGCCCAGACAGCTCCAGGTTCTACCTTCACATGGATGCCCTGGAGCCCAATGACTCTGCCTTGTATCTCTGTGCTAGCAAAGAAA" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV5-2*01", + "range": { + "from": 0, + "to": 286 + }, + "sequence": "GAGTCTGAGGTCATCCAAACTCCAAGACACATGATCAAAGCAAGAGGACAGACAGTGACCCTGAGATGTTCCCTTATCTCTGGACACCTATCTGTGTACTGGTACCAACAGGCCCTGGGCCAGGGTCCCCGGTTTCTCATTCAGTATTACAATAGGGAAGAGAGAGACAAAGGAGACATCCCGGCAAGATTCTCAGTGCAGCAGTTCAGTAACTACAGCTCCCAGCTGGAGATGAACTCCCTGGAGCCAGGAGACTCAGCCCTATATCTCTGTGCCAGCAGCTTGG" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV5-4*01", + "range": { + "from": 0, + "to": 286 + }, + "sequence": "GGTGCTGGAGTCATCCAGACCCCCAGACACCTAATCAAAGGCAGCGGGGGGAAGGCTCTTCTGGAATGCCATCTCGTCTCTGGACACAACACTGTGCGCTGGTACAAGCAGGCCCCGGGACAGGGCCCCCAGTTCCTCTTTGAGTATTATAGACAGGAGCAGAGAGACAAAGGAGACGTCTCCGCACGATTCTCAGCACAGCAGTTCAGTGACGCCAGCTCCCAGCTGGAGATGAACCCCCTGGAGCTGGGAGACTCAGCCCTGTATCTCTGTGCCAGCAGCTTGG" + }, { + "uri": "file://dog_V_TRB.fasta#TRBV7*01", + "range": { + "from": 0, + "to": 290 + }, + "sequence": "GAACCCGGAGTCTCCCAATCCCCCAGGCACAGGGTCACGAAGAGGGGCCAGAATGTATCTTTCACCTGTGATCCCATTTCTGGGCATGTTGTCCTTTACTGGTACCGACAGACCATGGGGCAGGGCCCGGAGTTTCTGGTTTACTTCCAAAACGAAGAGGCAATGGACGAGTCAGGGCTGGACAAGACTCGGTTCTCTGCCAAGAGGCCCAAGGGGACCAGATCCACTCTGCAGATCCAGCGTGCAGAGCAGAGGGACTCGGCTGTGTACCTCTGTGCCAGCAGTCTCAC" + }, { + "uri": "file://dog_V_TRD.fasta#TRDV2*01", + "range": { + "from": 0, + "to": 290 + }, + "sequence": "GCTGAGAAAGTCATCCAAACTCAAAATACAGTAACAAAGCAGGAAGGAGAAGCTGCCACCTTTAACTGCAATTATGAAACCACCTGGAGTGATTACTATACTTCGTATCGGTATAAACAGCCTCCTGGCGGAGAGATGATTTTCCTCATATACCAGGATGAAAACAAGCCAAAGGAAAAACAGGGTCGCCTCTCTATAAATTTTCAAAAGGCAGCAAAATCCATCATCCTCACCATCTCTTCCTTACAACTGACAGACTCTGCAACCTATTTCTGTGCTCTTGGTAAGCC" + }, { + "uri": "file://dog_V_TRD.fasta#TRDV3*01", + "range": { + "from": 0, + "to": 294 + }, + "sequence": "TGTGAGCAAGTGATCCAGAGCTCCCTGGAACAGATGGTGGCAAGTGGCAGTAAGGTGACACTGCTCTGCACTTACGACACTTCATATTCAAACCCAGATTTGTACTGGTACCGAATAAGGTCAGATCATTCCTTTCAGTTTATCCTTTACAGGGATAACACTCAATCCAGAAATGCAGATTTTACTCAGGGTCGATTTTCTGTACTACATAATCCGTCCCAGAAAACCTTCCACTTGGTGATCTCCCAGGTGGAGCCTGAAGACAGTGCCACTTACTACTGTGCCATAGATACC" + }, { + "uri": "file://dog_V_TRD.fasta#TRDV4*01", + "range": { + "from": 0, + "to": 288 + }, + "sequence": "GATATCATTTTGGAACCAGAAACCAAAACCCTGACTGTTCTAGTTGGAGAGCCTGCCACCTTCCGCTGCAACGTAACAGGAGGGGACTTGAAGAACTATCAGGTGAGCTGGTATAGGAAGAATGAAGATAACTCCCTGACTTTAATATACAGACAGAGCAACAATACCAATGACAATTTAAGGAGTAATTTCAAAGGAAATACCGATGCTTCAAAGAGTCAATGTATACTCGACATTGAAAAAGCAACAACAGCAAACGCCGGTACCTATTACTGCGCAGCAGATATC" + }, { + "uri": "file://dog_V_TRD.fasta#TRDV5*01", + "range": { + "from": 0, + "to": 289 + }, + "sequence": "TCAGAGACGGTCCGTCAATCTCAGAATAAAGTGTATAAACAAGAGGGAGAATCTGTGACCTTGGACTGCAGCTTCACCCTCAGCTTTAACTACTATGTGATGAACTGGTATCAACAGCCTTTCGGTGAGAGGATGACTGAAGTCATGAGTATTTATTCTGATAGCACCAGCTCTTCGAAAGGACGATATTCTGTGTCATTTCAGAAAGGAAACAAAATTCTAACGCTCACCATCACAGGCTTAACGCTTACAGACTCAGGCGTCTATTTCTGTGCTGTTGGAGAAGTTC" + }, { + "uri": "file://dog_V_TRG.fasta#TRGV2-1*01", + "range": { + "from": 0, + "to": 300 + }, + "sequence": "GCGGGGCTCAAGCTGGAGCAGACTCCTGTGGTCGTGGGGCGCTTGGGCACCTTGGCCACCCTGCCGTGCACAGTGGATACCTCGGTGAGCTACATCCACTGGTACTTCCACAAGGAGGATACAGCCCCCAAGAGGCTCCTCTACCTAGACATGTCCAGGTCGTATGTGCAGAGGGATATATTCGTGAAAGCAGACAAAGTCAACGCCAAGAAAGGCAGGAACAGTTACAGCTGTAACCTGTTGTTGCAGAAACTGGAGAAGAATGATGAGGGCGTGTACTACTGCGCTGCCTGGGAAGCG" + }, { + "uri": "file://dog_V_TRG.fasta#TRGV2-2*01", + "range": { + "from": 0, + "to": 300 + }, + "sequence": "GCAGGGCTCAAGCTTGAGCAGACTCCTGTGGTCGTGGGGCGCTTGGGCACCTCGGCCACCCTGCTGTGCACAGTGGATAGCTCAGTCTACTACATCCACTGGTACTTCCACAAGAAGTTTGCAGCCCCCAAGAGGCTCCTCTATCTAGACATGTCCAGCTCAAGTGTGCAGAGGGATACATCTGTGAAAGCACACAAAGTCAATGCCAAGAAAGGCAAAGACAGTTACAGCTGTAACCTGTTGGTGCAGAAGCTGGAGAAGAGCGACGAAGGCGTGTACTACTGCGCTGCCTGGGAAGAG" + }, { + "uri": "file://dog_V_TRG.fasta#TRGV2-3*01", + "range": { + "from": 0, + "to": 300 + }, + "sequence": "GCAGGGCTCAACCTGGAGCAGAATCCTGTGGCTGTGGAGCGCTTGGACACTTTGGCCATCCTAAAATGTCAAGTGGATACCTGGGTCTGGTATATCAACTGGTACTTCCATCAGGAGGGTACAGCCCCCAAGTGGCTCCTCTACCTAGACATGTCCACCGGGAAAGTGCAGAGGGATGCATTTGTGAATGCAGACAAAGTCAGTGCCCAAAAGGGCAAGGACAGTTACAGCTGCGCCTTGTTGGTACAGAGGCTGAGGAAGAGCGACGAGGGCGTGTACTACTGTGCTGCCTGGGAACCG" + }, { + "uri": "file://dog_V_TRG.fasta#TRGV2-4*01", + "range": { + "from": 0, + "to": 300 + }, + "sequence": "GCAGGACTCAAGCTTGAGCAGACTCCTGTGGTCGTGGTGCACTTGGGCACCTCGGCCACCCTACCGTGCACAGTGGATAGCTCAGTCAACTACATCCACTGGTACTTCCACAGGGAGGGTATTGCCCCTAAGAGACTCCTTTACATACACACATACAGCTCATCTGTGCAGAGGGATGCATCTGTGAACATGGGCAAAGTCAATGCCAAGATGGACAAGAACAATTACAGCTGTAAACTGTTGGTGCAGAAGCTGGAGAAGAGCGACGAAGGCGTGTACTACTGCGCTGCCTGGGAAGAG" + }, { + "uri": "file://dog_V_TRG.fasta#TRGV4-1*01", + "range": { + "from": 0, + "to": 300 + }, + "sequence": "GTGGCATTCAGCCTGGAGCAGCCATCCGTGGTTGTGGTGCGCATGGACACCTTGGCCATTATGCCTTGCAAAGCCAGCGCCAAGATCAGCCATATCCACTGGTACCATCACCAGGAGCATACGGCCCCCCAGAGGATCCTCAGACTGGAAGTGTCTGGATCTTCTGTGAACAAGGATTCAGTCCTGAAAGCAGATAAAATCATTGCCATAAAAGATAAAGATGTCACCAGCTATAGTCTGTTGGTGCTGAAGCTGAAGAAGAACGATGAAGCTGTGTACTACTGTGCTACCTGGGAAGTG" + }, { + "uri": "file://dog_V_TRG.fasta#TRGV5-2*01", + "range": { + "from": 0, + "to": 307 + }, + "sequence": "GACACTTTAATAACCCAACTCATGCCATCCGTTATCAAGAAGCAAGGCAACACAGCATTTTTAGAATGCCAAATAAAAACAGGTGCCTTTAAGAAGAATGTATATATACACTGGTACCGACAGAAGCCAGACCAGCCTCTACAACGAATTCTGTATATTTCCTCGAATGAAAATGTGGTCCACGAACAAGGTGTTAGTGAGGAAAGATATGAAGCCAGGAAATGGAAGCAGGATTTGCCTGCGAGTCTCAGGATACACCGAGTTAATGAGGCAGATGCTGGGCTGTATTACTGTGCCTGCTGGGATA" + }, { + "uri": "file://dog_V_TRG.fasta#TRGV6-1*01", + "range": { + "from": 0, + "to": 300 + }, + "sequence": "GCGGAGACCAGGCTGGAGCAGCCTGCTGTAGTCGTGGCACGTGAGAAAAGCTCAGCCTCCCTGCTGTGCACAGCCAATGTCAAGGCCAGCTATATCCACTGGTACCGCTACCAGGAAGGAAAAGGGCTCCAGAGGCTCCTCCACCTGGCCATGTTCCAATCAAATGTGCAGTGGGATTCGGTCCTGGAAGCAGACAAAGTCACCGCCATAGAGACCAAGGATGGCTACAGCTGTACCCTGTTGGTGCTGAAGCTGGAGAAGAGCGATGAGGGCATGTACTACTGTGCTGCCTGGAGAGCA" + }, { + "uri": "file://dog_V_TRG.fasta#TRGV7-2*01", + "range": { + "from": 0, + "to": 305 + }, + "sequence": "CAGGTGAAGTTAGAACAACATGAAATATCAGTTTCCAGAGCAAGAGATAAGAGTGCCCACATATCATGCAAGGTAGTCACTGAGGATTTTAACAGTGAAGTTATACACTGGTACCGACACAAACCAGATCAGGAGATAGAACATCTAATAATGGTCCAAACAAGCTCTACTCAAGCTTCTTTAGATGGGAGGAAAAACAAACTTGAGGCAAGTAAAAATGCCGTTACTTCCACATCAACTTTGAGTATAAATTTCTTACAAGAAGAAGACGAAGCCATGTACTACTGTGCCTGCTGGAAAGGATC" + }, { + "uri": "file://dog_V_TRG.fasta#TRGV7-3*01", + "range": { + "from": 0, + "to": 309 + }, + "sequence": "CAGGTGAGGTTGGAACAACCTCAAATATCAATTTCTGGAACAAAATATAAGAGCATTAACATATTATGCAAGTTGCATGCTCAAAACTTTAACACTAAAGTGATACACTGGTACCGACAGAAACCAGAGCAGGACATAGAACATCTAACATGGGTCCAAACAAGCGCTAGCAAAGTCCCATCTTTAGATGGGAGGAAAAACAAACTTGAGGCAAGTAAAAATGCTCTTACTTCCACTTCAACTTTGAAAATAAATTTCTTACAAGAAGAAGATGAGGCCATGTACTACTGTTCCTGCTGGAGTAGGATC" + } ] +} ] diff --git a/lib/galaxy/datatypes/test/pbmc3k_tiny.h5ad b/lib/galaxy/datatypes/test/pbmc3k_tiny.h5ad new file mode 100644 index 00000000000..44d8d853a87 Binary files /dev/null and b/lib/galaxy/datatypes/test/pbmc3k_tiny.h5ad differ diff --git a/lib/galaxy/datatypes/test/some.wiff.tar b/lib/galaxy/datatypes/test/some.wiff.tar new file mode 100644 index 00000000000..1a17ec84a28 Binary files /dev/null and b/lib/galaxy/datatypes/test/some.wiff.tar differ diff --git a/lib/galaxy/datatypes/test/test.cel b/lib/galaxy/datatypes/test/test.cel deleted file mode 100644 index b43e1e39807..00000000000 Binary files a/lib/galaxy/datatypes/test/test.cel and /dev/null differ diff --git a/lib/galaxy/datatypes/test/test.gal b/lib/galaxy/datatypes/test/test.gal new file mode 100644 index 00000000000..74dea58354a --- /dev/null +++ b/lib/galaxy/datatypes/test/test.gal @@ -0,0 +1,105 @@ +ATF 1 +20 5 +"Type=GenePix ArrayList V1.0" +"BlockCount=2" +"BlockType=0" +"URL=http://genome-www.stanford.edu/cgi-bin/dbrun/SacchDB?find+Locus+%22[ID]%22" +"Block1= 500, 500, 100, 24, 180, 21, 180" +"Block2= 4996, 500, 100, 24, 180, 21, 180" +Block Column Row Name ID +1 1 1 GENOMIC 1X +1 2 1 3XSSC +1 3 1 GENOMIC 0.5X +1 4 1 3XSSC +1 5 1 GENOMIC 0.25X +1 6 1 3XSSC +1 7 1 EMPTY +1 8 1 EMPTY +1 9 1 EMPTY +1 10 1 EMPTY +1 11 1 EMPTY +1 12 1 EMPTY +1 13 1 EMPTY +1 14 1 EMPTY +1 15 1 EMPTY +1 16 1 EMPTY +1 17 1 EMPTY +1 18 1 EMPTY +1 19 1 EMPTY +1 20 1 EMPTY +1 21 1 EMPTY +1 22 1 EMPTY +1 23 1 EMPTY +1 24 1 EMPTY +1 1 2 TFC3 YAL001C +1 2 2 EFB1 YAL003W +1 3 2 SSA1 YAL005C +1 4 2 FUN14 YAL008W +1 5 2 MDM10 YAL010C +1 6 2 CYS3 YAL012W +1 7 2 YAR062W +1 8 2 YAR068W +1 9 2 YAR070C +1 10 2 YAR073W +1 11 2 ECM15 YBL001C +1 12 2 HTA2 YBL003C +1 13 2 RPL17A YBL087C +1 14 2 YBL089W +1 15 2 MAP2 YBL091C +1 16 2 ROX3 YBL093C +1 17 2 YBL095W +1 18 2 BRN1 YBL097W +1 19 2 YBR066C +1 20 2 BAP2 YBR068C +1 21 2 YBR070C +1 22 2 HSP26 YBR072W +1 23 2 YBR074W +1 24 2 ECM8 YBR076W +2 1 1 GENOMIC 1X +2 2 1 3XSSC +2 3 1 GENOMIC 0.5X +2 4 1 3XSSC +2 5 1 GENOMIC 0.25X +2 6 1 3XSSC +2 7 1 EMPTY +2 8 1 EMPTY +2 9 1 EMPTY +2 10 1 EMPTY +2 11 1 EMPTY +2 12 1 EMPTY +2 13 1 EMPTY +2 14 1 EMPTY +2 15 1 EMPTY +2 16 1 EMPTY +2 17 1 EMPTY +2 18 1 EMPTY +2 19 1 EMPTY +2 20 1 EMPTY +2 21 1 EMPTY +2 22 1 EMPTY +2 23 1 EMPTY +2 24 1 EMPTY +2 1 2 YAL014C +2 2 2 TPD3 YAL016W +2 3 2 YAL018C +2 4 2 ATS1 YAL020C +2 5 2 FUN26 YAL022C +2 6 2 LTE1 YAL024C +2 7 2 PDR3 YBL005W +2 8 2 YBL005W-B +2 9 2 SLA1 YBL007C +2 10 2 YBL009W +2 11 2 SCT1 YBL011W +2 12 2 YBL013W +2 13 2 ATP1 YBL099W +2 14 2 ECM21 YBL101C +2 15 2 YBL101W-B +2 16 2 RTG3 YBL103C +2 17 2 PKC1 YBL105C +2 18 2 YBL107C +2 19 2 ECM33 YBR078W +2 20 2 SEC18 YBR080C +2 21 2 UBC4 YBR082C +2 22 2 RPL19B YBR084C-A +2 23 2 AAC3 YBR085W +2 24 2 RFC5 YBR087W diff --git a/lib/galaxy/datatypes/test/test.gpr b/lib/galaxy/datatypes/test/test.gpr new file mode 100644 index 00000000000..05d9c082b2b --- /dev/null +++ b/lib/galaxy/datatypes/test/test.gpr @@ -0,0 +1,35 @@ +ATF 1.0 +26 43 +"Type=GenePix Results 1.4" +"DateTime=2005/09/06 22:23:23" +"Settings=" +"GalFile=V:\proposed_gal_file_gene.gal" +"Scanner=GenePix 4000B [92881]" +"Comment=" +"PixelSize=10" +"ImageName=635 532" +"FileName=B:\050906_Raka_RNA_Pst_Buffer_24h_1&2\2005-09-06_buffer_24h_360_0460.tif B:\050906_Raka_RNA_Pst_Buffer_24h_1&2\2005-09-06_buffer_24h_360_0460.tif" +"PMTVolts=460 360" +"ScanPower=100 100" +"FocusPosition=0" +"NormalizationFactor:RatioOfMedians=3.53528" +"NormalizationFactor:RatioOfMeans=3.59947" +"NormalizationFactor:MedianOfRatios=3.62904" +"NormalizationFactor:MeanOfRatios=3.7456" +"NormalizationFactor:RegressionRatio=3.45048" +"JpegImage=V:\050906raka_rna_1st\2005-09-06_buffer_24h_360_0460.jpg" +"RatioFormulation=W2/W1 (532/635)" +"Barcode=" +"ImageOrigin=1480, 7160" +"JpegOrigin=1650, 7250" +"Creator=GenePix Pro 6.0.1.06" +"Temperature=25.53" +"LaserPower=3.29 3.74" +"Supplier=" +"Block" "Column" "Row" "Name" "ID" "X" "Y" "Dia." "F635 Median" "F635 Mean" "F635 SD" "B635 Median" "B635 Mean" "B635 SD" "% > B635+1SD" "% > B635+2SD" "F635 % Sat." "F532 Median" "F532 Mean" "F532 SD" "B532 Median" "B532 Mean" "B532 SD" "% > B532+1SD" "% > B532+2SD" "F532 % Sat." "Ratio of Medians" "Ratio of Means" "Median of Ratios" "Mean of Ratios" "Ratios SD" "Rgn Ratio" "Rgn R²" "F Pixels" "B Pixels" "Sum of Medians" "Sum of Means" "Log Ratio" "F635 Median - B635" "F532 Median - B532" "F635 Mean - B635" "F532 Mean - B532" "Flags" +1 1 1 "At3g01120.1" "1D1" 1940 7570 150 6808 7101 3866 32 32 4 100 100 0 67 73 38 32 33 4 93 91 0 0.005 0.006 0.005 0.006 2.030 0.005 0.335 180 850 6811 7110 -7.597 6776 35 7069 41 0 +1 2 1 "At4g36690.2+" "1D3" 2140 7570 150 2796 3472 2461 32 32 4 100 100 0 370 347 127 32 33 4 94 92 0 0.122 0.092 0.108 0.108 1.804 0.067 0.768 184 616 3102 3755 -3.032 2764 338 3440 315 0 +1 3 1 "At3g46230.1" "1D5" 2350 7580 150 3412 3947 2469 32 32 4 100 100 0 66 67 18 33 33 5 91 89 0 0.010 0.009 0.009 0.010 1.674 0.008 0.695 189 577 3413 3949 -6.678 3380 33 3915 34 0 +1 4 1 "At1g03850.1+" "1D7" 2540 7580 140 7962 8149 4046 32 32 4 100 100 0 70 70 21 33 33 4 93 92 0 0.005 0.005 0.005 0.005 1.638 0.004 0.693 180 546 7967 8154 -7.744 7930 37 8117 37 0 +1 5 1 "At2g47470.2+" "1D9" 2740 7580 150 4067 4622 2726 32 32 4 100 100 0 101 107 51 33 33 4 92 91 0 0.017 0.016 0.016 0.017 1.519 0.014 0.599 184 595 4103 4664 -5.891 4035 68 4590 74 0 +1 6 1 "At4g37000.1" "1D11" 2940 7580 150 6916 6974 3544 32 32 4 100 100 0 182 172 58 33 33 3 94 92 0 0.022 0.020 0.021 0.021 1.452 0.017 0.821 183 572 7033 7081 -5.530 6884 149 6942 139 0 diff --git a/lib/galaxy/datatypes/text.py b/lib/galaxy/datatypes/text.py index c2e71444cf1..fab7ef0469c 100644 --- a/lib/galaxy/datatypes/text.py +++ b/lib/galaxy/datatypes/text.py @@ -106,6 +106,30 @@ class Json(Text): return "JSON file (%s)" % (nice_size(dataset.get_size())) +class ExpressionJson(Json): + """ Represents the non-data input or output to a tool or workflow. + """ + file_ext = "json" + MetadataElement(name="json_type", default=None, desc="JavaScript or JSON type of expression", readonly=True, visible=True, no_value=None) + + def set_meta(self, dataset, **kwd): + """ + """ + json_type = "null" + with open(dataset.file_name) as f: + obj = json.load(f) + if isinstance(obj, int): + json_type = "int" + elif isinstance(obj, float): + json_type = "float" + elif isinstance(obj, list): + json_type = "list" + elif isinstance(obj, dict): + json_type = "object" + + dataset.metadata.json_type = json_type + + @build_sniff_from_prefix class Ipynb(Json): file_ext = "ipynb" @@ -256,9 +280,10 @@ class Biom1(Json): if b_name == "columns" and metadata_value: keep_columns = set() for column in metadata_value: - for k, v in column['metadata'].items(): - if v is not None: - keep_columns.add(k) + if column['metadata'] is not None: + for k, v in column['metadata'].items(): + if v is not None: + keep_columns.add(k) final_list = sorted(list(keep_columns)) dataset.metadata.table_column_metadata_headers = final_list if b_name in b_transform: @@ -269,6 +294,75 @@ class Biom1(Json): pass +@build_sniff_from_prefix +class ImgtJson(Json): + file_ext = "imgt.json" + MetadataElement(name="taxon_names", default=[], desc="taxonID: names", readonly=True, visible=True, no_value=[]) + """ + https://github.com/repseqio/library-imgt/releases + Data coming from IMGT server may be used for academic research only, + provided that it is referred to IMGT®, and cited as: + "IMGT®, the international ImMunoGeneTics information system® + http://www.imgt.org (founder and director: Marie-Paule Lefranc, Montpellier, France)." + """ + + def set_peek(self, dataset, is_multi_byte=False): + super(ImgtJson, self).set_peek(dataset) + if not dataset.dataset.purged: + dataset.blurb = "IMGT Library" + + def sniff_prefix(self, file_prefix): + """ + Determines whether the file is in json format with imgt elements + + >>> from galaxy.datatypes.sniff import get_test_fname + >>> fname = get_test_fname( '1.json' ) + >>> ImgtJson().sniff( fname ) + False + >>> fname = get_test_fname( 'imgt.json' ) + >>> ImgtJson().sniff( fname ) + True + """ + is_imgt = False + if self._looks_like_json(file_prefix): + is_imgt = self._looks_like_imgt(file_prefix) + return is_imgt + + def _looks_like_imgt(self, file_prefix, load_size=5000): + """ + @param filepath: [str] The path to the evaluated file. + @param load_size: [int] The size of the file block load in RAM (in + bytes). + """ + is_imgt = False + try: + with open(file_prefix.filename, "r") as fh: + segment_str = fh.read(load_size) + if segment_str.strip().startswith('['): + if '"taxonId"' in segment_str and '"anchorPoints"' in segment_str: + is_imgt = True + except Exception: + pass + return is_imgt + + def set_meta(self, dataset, **kwd): + """ + Store metadata information from the imgt file. + """ + if dataset.has_data(): + with open(dataset.file_name) as fh: + try: + json_dict = json.load(fh) + tax_names = [] + for i, entry in enumerate(json_dict): + if 'taxonId' in entry: + names = "%d: %s" % (entry['taxonId'], ','.join(entry['speciesNames'])) + tax_names.append(names) + dataset.metadata.taxon_names = tax_names + except Exception: + return + + @build_sniff_from_prefix class Obo(Text): """ diff --git a/lib/galaxy/datatypes/util/gff_util.py b/lib/galaxy/datatypes/util/gff_util.py index c130441754c..1d17a0c2b32 100644 --- a/lib/galaxy/datatypes/util/gff_util.py +++ b/lib/galaxy/datatypes/util/gff_util.py @@ -33,15 +33,17 @@ class GFFInterval(GenomicInterval): # Handle feature, score column. self.feature_col = feature_col - if self.feature_col >= self.nfields: + if self.nfields <= self.feature_col: raise MissingFieldError("No field for feature_col (%d)" % feature_col) self.feature = self.fields[self.feature_col] self.score_col = score_col - if self.score_col >= self.nfields: + if self.nfields <= self.score_col: raise MissingFieldError("No field for score_col (%d)" % score_col) self.score = self.fields[self.score_col] # GFF attributes. + if self.nfields < 9: + raise MissingFieldError("No field for attribute column (8)") self.attributes = parse_gff_attributes(fields[8]) def copy(self): diff --git a/lib/galaxy/dependencies/__init__.py b/lib/galaxy/dependencies/__init__.py index 953aac5bf7d..06162cf7b96 100644 --- a/lib/galaxy/dependencies/__init__.py +++ b/lib/galaxy/dependencies/__init__.py @@ -8,6 +8,7 @@ from os.path import dirname, join from xml.etree import ElementTree import pkg_resources +import yaml from galaxy.containers import parse_containers_config from galaxy.util import asbool @@ -27,6 +28,7 @@ class ConditionalDependencies(object): self.conditional_reqs = [] self.container_interface_types = [] self.job_rule_modules = [] + self.error_report_modules = [] self.parse_configs() self.get_conditional_requirements() @@ -75,6 +77,17 @@ class ConditionalDependencies(object): containers_conf = parse_containers_config(containers_conf_yml) self.container_interface_types = [c.get('type', None) for c in containers_conf.values()] + # Parse error report config + error_report_yml = self.config.get( + "error_report_file", + join(dirname(self.config_file), 'error_report.yml')) + try: + with open(error_report_yml, "r") as f: + error_reporters = yaml.safe_load(f) + self.error_report_modules = [er.get('type', None) for er in error_reporters] + except (OSError, IOError): + pass + def get_conditional_requirements(self): crfile = join(dirname(__file__), 'conditional-requirements.txt') for req in pkg_resources.parse_requirements(open(crfile).readlines()): @@ -90,7 +103,7 @@ class ConditionalDependencies(object): def check_psycopg2_binary(self): return self.config["database_connection"].startswith("postgres") - def check_mysql_python(self): + def check_mysqlclient(self): return self.config["database_connection"].startswith("mysql") def check_drmaa(self): @@ -104,6 +117,9 @@ class ConditionalDependencies(object): def check_pbs_python(self): return "galaxy.jobs.runners.pbs:PBSJobRunner" in self.job_runners + def check_pykube(self): + return "galaxy.jobs.runners.kubernetes:KubernetesJobRunner" in self.job_runners + def check_chronos_python(self): return "galaxy.jobs.runners.chronos:ChronosJobRunner" in self.job_runners @@ -130,9 +146,6 @@ class ConditionalDependencies(object): def check_azure_storage(self): return 'azure_blob' in self.object_stores - def check_cloudbridge(self): - return 'cloud' in self.object_stores - def check_kamaki(self): return 'pithos' in self.object_stores @@ -146,6 +159,15 @@ class ConditionalDependencies(object): ('docker' in self.container_interface_types or 'docker_swarm' in self.container_interface_types)) + def check_python_gitlab(self): + return 'gitlab' in self.error_report_modules + + def check_pygithub(self): + return 'github' in self.error_report_modules + + def check_influxdb(self): + return 'influxdb' in self.error_report_modules + def optional(config_file=None): if not config_file: diff --git a/lib/galaxy/dependencies/conditional-requirements.txt b/lib/galaxy/dependencies/conditional-requirements.txt index 92765c5d867..95cb0e11bb3 100644 --- a/lib/galaxy/dependencies/conditional-requirements.txt +++ b/lib/galaxy/dependencies/conditional-requirements.txt @@ -1,8 +1,7 @@ # These dependencies are only required when certain config options are set psycopg2-binary==2.7.4 weberror==0.10.3 -python-openid -mysql-python +mysqlclient fluent-logger raven pbs_python @@ -13,13 +12,20 @@ azure-storage==0.32.0 # PyRods not in PyPI python-ldap==2.4.44 python-pam -cloudbridge==1.0.1 galaxycloudrunner # Chronos client chronos-python==0.38.0 +# Kubernetes job runner +pykube==0.15.0 + # Synnefo / Pithos+ object store client kamaki watchdog + +# Error reporters optional modules +python-gitlab +pygithub +influxdb \ No newline at end of file diff --git a/lib/galaxy/dependencies/pipfiles/default/Pipfile b/lib/galaxy/dependencies/pipfiles/default/Pipfile index a96bdf7bf0b..6741c925615 100644 --- a/lib/galaxy/dependencies/pipfiles/default/Pipfile +++ b/lib/galaxy/dependencies/pipfiles/default/Pipfile @@ -17,9 +17,11 @@ pygithub3 = {version = "*", markers = "python_version < '3'"} pytest = "*" pytest-html = "*" pytest-pythonpath = "*" +pytest-postgresql = "*" recommonmark = "*" selenium = "*" -Sphinx = "<1.7" # Docs do not build with sphinx 1.7 for some reason, can't find issue. +Sphinx = "*" +sphinx_markdown_tables = "*" sphinx_rtd_theme = "*" testfixtures = "*" twill = {version = "==0.9.1", markers = "python_version < '3'"} @@ -39,6 +41,7 @@ uWSGI = "*" pysam = "==0.15.2" bdbag = "==1.4.1" # 1.5.0 requires Python >=2.7.9 bleach = "*" +cwltool = "==1.0.20180721142728" "bz2file" = {version = "*", markers = "python_version < '3.3'"} ipaddress = {version = "*", markers = "python_version < '3.3'"} isa-rwval = "*" @@ -72,7 +75,7 @@ pyparsing = "*" "Fabric3" = "*" paramiko = "*" python-genomespaceclient = "<2.0" -social_auth_core = {version = "==1.5.0", extras = ['openidconnect']} +social_auth_core = {version = "==3.1.0", extras = ['openidconnect']} cloudauthz = "<=0.2.0" gxformat2 = "*" diff --git a/lib/galaxy/dependencies/pipfiles/default/pinned-dev-requirements.txt b/lib/galaxy/dependencies/pipfiles/default/pinned-dev-requirements.txt index 61a8641ceff..6b2a5d41437 100644 --- a/lib/galaxy/dependencies/pipfiles/default/pinned-dev-requirements.txt +++ b/lib/galaxy/dependencies/pipfiles/default/pinned-dev-requirements.txt @@ -2,10 +2,10 @@ --extra-index-url https://pypi.python.org/simple alabaster==0.7.12 argh==0.26.2 -atomicwrites==1.2.1 -attrs==18.2.0 +atomicwrites==1.3.0 +attrs==19.1.0 babel==2.6.0 -certifi==2018.11.29 +certifi==2019.3.9 chardet==3.0.4 commonmark==0.8.1 docutils==0.14 @@ -15,36 +15,41 @@ gunicorn==19.9.0 idna==2.8 imagesize==1.1.0 jinja2==2.10 -lxml==4.3.0 -markupsafe==1.1.0 +lxml==4.3.2 +markdown==2.6.11 +markupsafe==1.1.1 mock==2.0.0 more-itertools==5.0.0 nose==1.3.7 nosehtml==0.4.5 -pathlib2==2.3.3 ; python_version < '3.6' +packaging==19.0 +pathlib2==2.3.2 ; python_version < '3' pathtools==0.1.2 -pbr==5.1.1 -pluggy==0.8.1 -py==1.7.0 +pbr==5.1.3 +pluggy==0.9.0 +py==1.8.0 pygithub3==0.5.1 ; python_version < '3' pygments==2.3.1 -pytest-html==1.19.0 +pyparsing==2.3.1 +pytest-html==1.20.0 pytest-metadata==1.8.0 pytest-pythonpath==0.7.3 -pytest==4.1.0 +pytest-postgresql==1.4.0 +pytest==4.3.1 pytz==2018.9 -pyyaml==3.13 +pyyaml==5.1 recommonmark==0.5.0 requests==2.21.0 -scandir==1.9.0 ; python_version < '3.5' +scandir==1.10.0 ; python_version < '3.5' selenium==3.141.0 six==1.11.0 snowballstemmer==1.2.1 -sphinx-rtd-theme==0.4.2 -sphinx==1.6.7 +sphinx-markdown-tables==0.0.9 +sphinx-rtd-theme==0.4.3 +sphinx==1.8.4 sphinxcontrib-websupport==1.1.0 -testfixtures==6.4.3 +testfixtures==6.6.1 twill==0.9.1 ; python_version < '3' typing==3.6.6 ; python_version < '3.5' -urllib3==1.24.1 +urllib3==1.24.1 ; python_version == '2.7' watchdog==0.9.0 diff --git a/lib/galaxy/dependencies/pipfiles/default/pinned-requirements.txt b/lib/galaxy/dependencies/pipfiles/default/pinned-requirements.txt index b91ba098a00..1789b1067ca 100644 --- a/lib/galaxy/dependencies/pipfiles/default/pinned-requirements.txt +++ b/lib/galaxy/dependencies/pipfiles/default/pinned-requirements.txt @@ -1,9 +1,10 @@ -i https://wheels.galaxyproject.org/simple --extra-index-url https://pypi.python.org/simple -adal==1.2.0 -amqp==2.3.2 +adal==1.2.1 +amqp==2.4.2 appdirs==1.4.3 asn1crypto==0.24.0 +avro==1.8.1 ; python_version < '3' azure-common==1.1.14 azure-cosmosdb-nspkg==2.0.2 azure-cosmosdb-table==1.0.4 @@ -19,145 +20,170 @@ azure-storage-common==1.4.0 azure-storage-nspkg==3.1.0 babel==2.6.0 bagit==1.6.4 -bcrypt==3.1.5 +bcrypt==3.1.6 bdbag==1.4.1 -beaker==1.10.0 +beaker==1.10.1 bioblend==0.12.0 bleach==3.1.0 -boltons==18.0.1 -boto3==1.7.84 +boltons==19.1.0 +boto3==1.9.114 boto==2.49.0 -botocore==1.10.84 -bunch==1.0.1 +botocore==1.12.115 bx-python==0.8.2 bz2file==0.98 ; python_version < '3.3' -cachetools==3.0.0 -certifi==2018.11.29 -cffi==1.11.5 +cachecontrol==0.11.7 +cachetools==3.1.0 +certifi==2019.3.9 +cffi==1.12.2 chardet==3.0.4 -cheetah3==3.1.0 -cliff==2.14.0 +cheetah3==3.2.0 +cliff==2.14.1 cloudauthz==0.2.0 -cloudbridge==1.0.2 +cloudbridge==2.0.0 cmd2==0.8.9 contextlib2==0.5.5 ; python_version < '3.5' -cryptography==2.4.2 -debtcollector==1.20.0 -decorator==4.3.0 +cryptography==2.6.1 +cwltool==1.0.20180721142728 +debtcollector==1.21.0 +decorator==4.3.2 +deprecated==1.2.5 deprecation==2.0.6 dictobj==0.4 docopt==0.6.2 docutils==0.14 dogpile.cache==0.7.1 +ecdsa==0.13 enum34==1.1.6 ; python_version < '3.4' fabric3==1.14.post1 funcsigs==1.0.2 ; python_version < '3.3' -functools32==3.2.3.post2 ; python_version == '2.7' +functools32==3.2.3.post2 ; python_version < '3.2' future==0.17.1 futures==3.2.0 ; python_version == '2.6' or python_version == '2.7' galaxy-sequence-utils==1.1.3 -gxformat2==0.8.0 +google-api-python-client==1.7.8 +google-auth-httplib2==0.0.3 +google-auth==1.6.3 +gxformat2==0.8.2 h5py==2.9.0 +httplib2==0.12.1 idna==2.8 ipaddress==1.0.22 ; python_version < '3.3' isa-rwval==0.10.7 iso8601==0.1.12 isodate==0.6.0 -jmespath==0.9.3 +jmespath==0.9.4 jsonpatch==1.23 jsonpointer==2.0 jsonschema==2.6.0 -keystoneauth1==3.11.2 -kombu==4.2.2.post1 +keystoneauth1==3.13.1 +kombu==4.4.0 +lockfile==0.12.2 +lxml==4.3.2 mako==1.0.7 -markupsafe==1.1.0 +markupsafe==1.1.1 mercurial==3.7.3 ; python_version < '3' +mistune==0.8.4 monotonic==1.5 -msgpack==0.6.0 +msgpack==0.6.1 msrest==0.5.5 msrestazure==0.5.0 munch==2.3.2 +mypy-extensions==0.4.1 netaddr==0.7.19 netifaces==0.10.9 networkx==1.11 nodeenv==1.3.3 nose==1.3.7 -numpy==1.15.4 -oauthlib==2.1.0 +numpy==1.16.2 +oauth2client==4.1.3 +oauthlib==3.0.1 openstacksdk==0.17.0 -os-client-config==1.31.2 -os-service-types==1.4.0 -osc-lib==1.12.0 -oslo.config==6.7.0 -oslo.context==2.22.0 -oslo.i18n==3.23.0 -oslo.log==3.42.2 -oslo.serialization==2.28.1 -oslo.utils==3.39.1 -packaging==18.0 +os-client-config==1.32.0 +os-service-types==1.6.0 +osc-lib==1.12.1 +oslo.config==6.8.1 +oslo.context==2.22.1 +oslo.i18n==3.23.1 +oslo.log==3.42.3 +oslo.serialization==2.28.2 +oslo.utils==3.40.3 +packaging==19.0 paramiko==2.4.2 parsley==1.3 -paste==3.0.6 +paste==3.0.8 pastedeploy==2.0.1 -pastescript==3.0.0 -pbr==5.1.1 +pastescript==3.1.0 +pathlib2==2.3.2 ; python_version < '3' +pbr==5.1.3 prettytable==0.7.2 -psutil==5.4.8 +prov==1.5.1 +psutil==5.6.1 pulsar-galaxy-lib==0.8.3 +pyasn1-modules==0.2.4 pyasn1==0.4.5 pycparser==2.19 -pycryptodome==3.7.2 -pycryptodomex==3.7.2 +pycryptodome==3.7.3 +pyeventsystem==0.1.0 pyinotify==0.9.6 ; sys_platform != 'win32' and sys_platform != 'darwin' and sys_platform != 'sunos5' -pyjwkest==1.4.0 pyjwt==1.7.1 pykwalify==1.7.0 pynacl==1.3.0 -pyopenssl==18.0.0 -pyparsing==2.3.0 +pyopenssl==19.0.0 +pyparsing==2.3.1 pyperclip==1.7.0 pysam==0.15.2 pysftp==0.2.9 python-cinderclient==4.0.0 -python-dateutil==2.7.5 -python-genomespaceclient==1.1.0 +python-dateutil==2.8.0 +python-genomespaceclient==1.2.0 python-glanceclient==2.12.0 +python-jose==3.0.1 python-keystoneclient==3.17.0 python-neutronclient==6.9.0 python-novaclient==11.0.0 -python-openid==2.2.5 +python-openid==2.2.5 ; python_version < '3.0' python-swiftclient==3.6.0 pytz==2018.9 -pyyaml==3.13 +pyyaml==5.1 +rdflib-jsonld==0.4.0 +rdflib==4.2.2 repoze.lru==0.7 -requests-oauthlib==1.1.0 -requests-toolbelt==0.8.0 +requests-oauthlib==1.2.0 +requests-toolbelt==0.9.1 requests==2.21.0 requestsexceptions==1.4.0 rfc3986==1.2.0 routes==2.4.1 -s3transfer==0.1.13 +rsa==4.0 +ruamel.ordereddict==0.4.13 ; platform_python_implementation == 'CPython' and python_version <= '2.7' +ruamel.yaml==0.15.89 +s3transfer==0.2.0 +scandir==1.10.0 ; python_version < '3.5' +schema-salad==2.7.20181126142424 +shellescape==3.4.1 simplejson==3.16.0 six==1.11.0 -social-auth-core[openidconnect]==1.5.0 -sqlalchemy-migrate==0.11.0 -sqlalchemy-utils==0.33.10 -sqlalchemy==1.2.16 -sqlparse==0.2.4 -stevedore==1.30.0 +social-auth-core[openidconnect]==3.1.0 +sqlalchemy-migrate==0.12.0 +sqlalchemy-utils==0.33.11 +sqlalchemy==1.2.18 +sqlparse==0.3.0 +stevedore==1.30.1 subprocess32==3.5.3 ; python_version < '3.0' svgwrite==1.2.1 tempita==0.5.2 tenacity==4.12.0 +typing-extensions==3.7.2 typing==3.6.6 ; python_version < '3.5' tzlocal==1.5.1 unicodecsv==0.14.1 ; python_version < '3.0' -urllib3==1.24.1 -uwsgi==2.0.17.1 +uritemplate==3.0.0 +urllib3==1.24.1 ; python_version == '2.7' +uwsgi==2.0.18 vine==1.2.0 warlock==1.3.0 wcwidth==0.1.7 ; sys_platform != 'win32' webencodings==0.5.1 webob==1.8.5 whoosh==2.7.4 -wrapt==1.11.0 +wrapt==1.11.1 diff --git a/lib/galaxy/dependencies/pipfiles/docker/Dockerfile b/lib/galaxy/dependencies/pipfiles/docker/Dockerfile new file mode 100644 index 00000000000..935c393f052 --- /dev/null +++ b/lib/galaxy/dependencies/pipfiles/docker/Dockerfile @@ -0,0 +1,15 @@ +FROM python:2.7 + +ENV DEBIAN_FRONTEND noninteractive +ENV LC_ALL C.UTF-8 +ENV LANG C.UTF-8 + +# Install Pipenv +RUN apt update && apt -y upgrade +RUN pip install pipenv + +ADD docker_script.sh /docker_script.sh + +WORKDIR /working + +CMD bash /docker_script.sh diff --git a/lib/galaxy/dependencies/pipfiles/docker/Makefile b/lib/galaxy/dependencies/pipfiles/docker/Makefile new file mode 100644 index 00000000000..6c6c187294e --- /dev/null +++ b/lib/galaxy/dependencies/pipfiles/docker/Makefile @@ -0,0 +1,2 @@ +all: + docker build -t galaxy/update-python-dependencies . diff --git a/lib/galaxy/dependencies/pipfiles/docker/docker_script.sh b/lib/galaxy/dependencies/pipfiles/docker/docker_script.sh new file mode 100644 index 00000000000..87092e57cae --- /dev/null +++ b/lib/galaxy/dependencies/pipfiles/docker/docker_script.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +pipenv lock -v +pipenv lock -r > pinned-requirements.txt +pipenv lock -r --dev > pinned-dev-requirements.txt diff --git a/lib/galaxy/dependencies/pipfiles/flake8/pinned-requirements.txt b/lib/galaxy/dependencies/pipfiles/flake8/pinned-requirements.txt index ee4270d1559..31cd04aeaf5 100644 --- a/lib/galaxy/dependencies/pipfiles/flake8/pinned-requirements.txt +++ b/lib/galaxy/dependencies/pipfiles/flake8/pinned-requirements.txt @@ -1,8 +1,11 @@ -i https://pypi.python.org/simple -configparser==3.5.0 ; python_version < '3.2' +configparser==3.7.3 ; python_version < '3.2' +entrypoints==0.3 enum34==1.1.6 ; python_version < '3.4' -flake8-import-order==0.18 -flake8==3.6.0 +flake8-import-order==0.18.1 +flake8==3.7.7 +functools32==3.2.3.post2 ; python_version < '3.2' mccabe==0.6.1 -pycodestyle==2.4.0 -pyflakes==2.0.0 +pycodestyle==2.5.0 +pyflakes==2.1.1 +typing==3.6.6 ; python_version < '3.5' diff --git a/lib/galaxy/dependencies/pipfiles/update.sh b/lib/galaxy/dependencies/pipfiles/update.sh index 711e0595388..721a632b0fb 100755 --- a/lib/galaxy/dependencies/pipfiles/update.sh +++ b/lib/galaxy/dependencies/pipfiles/update.sh @@ -1,28 +1,34 @@ #!/bin/sh -commit=0 - usage() { cat << EOF -Usage: ${0##*/} [-c] +Usage: ${0##*/} [-c] [-d] Use pipenv to regenerate locked and hashed versions of Galaxy dependencies. Use -c to automatically commit these changes (be sure you have no staged git changes). +Use -d to rebuild with Pipenv from the galaxy/update-python-dependencies +container. This container can be built by running 'make' from the docker +subdirectory. EOF } -while getopts ":hc" opt; do +commit=0 +docker=0 +while getopts ":hcd" opt; do case "$opt" in + c) + commit=1 + ;; + d) + docker=1 + ;; h) usage exit 0 ;; - c) - commit=1 - ;; - '?') + *) usage >&2 exit 1 ;; @@ -36,28 +42,37 @@ default" export PIPENV_IGNORE_VIRTUALENVS=1 for env in $ENVS; do cd "$THIS_DIRECTORY/$env" - pipenv lock -v + if [ "$docker" -eq 1 ]; then + docker run -v `pwd`:/working -t 'galaxy/update-python-dependencies' + else + pipenv lock -v + pipenv lock -r > pinned-requirements.txt + pipenv lock -r --dev > pinned-dev-requirements.txt + fi + # Strip out hashes and trailing whitespace for unhashed version # of this requirements file, needed for pipenv < 11.1.2 - pipenv lock -r | sed -e 's/--hash[^[:space:]]*//g' -e 's/[[:space:]]*$//' > pinned-requirements.txt - pipenv lock -r --dev | sed -e 's/--hash[^[:space:]]*//g' -e 's/[[:space:]]*$//' > pinned-dev-requirements.txt + sed -i.raw.orig -e 's/--hash[^[:space:]]*//g' -e 's/[[:space:]]*$//' pinned-requirements.txt pinned-dev-requirements.txt # Fix oscillating environment markers sed -i.orig -e "s/^azure-storage-nspkg==\([^ ;]\{1,\}\).*$/azure-storage-nspkg==\1/" \ -e "s/^cffi==\([^ ;]\{1,\}\).*$/cffi==\1/" \ -e "s/^cmd2==\([^ ;]\{1,\}\).*$/cmd2==\1/" \ + -e "s/^configparser==\([^ ;]\{1,\}\).*$/configparser==\1 ; python_version < '3.2'/" \ -e "s/^enum34==\([^ ;]\{1,\}\).*$/enum34==\1 ; python_version < '3.4'/" \ -e "s/^funcsigs==\([^ ;]\{1,\}\).*$/funcsigs==\1 ; python_version < '3.3'/" \ + -e "s/^functools32==\([^ ;]\{1,\}\).*$/functools32==\1 ; python_version < '3.2'/" \ -e "s/^futures==\([^ ;]\{1,\}\).*$/futures==\1 ; python_version == '2.6' or python_version == '2.7'/" \ -e "s/^monotonic==\([^ ;]\{1,\}\).*$/monotonic==\1/" \ + -e "s/^more-itertools==\([^ ;]\{1,\}\).*$/more-itertools==\1/" \ -e "s/^py2-ipaddress==\([^ ;]\{1,\}\).*$/py2-ipaddress==\1 ; python_version < '3'/" \ -e "s/^pyinotify==\([^ ;]\{1,\}\).*$/pyinotify==\1 ; sys_platform != 'win32' and sys_platform != 'darwin' and sys_platform != 'sunos5'/" \ -e "s/^python-dateutil==\([^ ;]\{1,\}\).*$/python-dateutil==\1/" \ -e "s/^subprocess32==\([^ ;]\{1,\}\).*$/subprocess32==\1 ; python_version < '3.0'/" \ + -e "s/^typing==\([^ ;]\{1,\}\).*$/typing==\1 ; python_version < '3.5'/" \ pinned-requirements.txt pinned-dev-requirements.txt done -if [ "$commit" -eq "1" ]; -then +if [ "$commit" -eq 1 ]; then git add -u "$THIS_DIRECTORY" git commit -m "Rev and re-lock Galaxy dependencies" fi diff --git a/lib/galaxy/exceptions/__init__.py b/lib/galaxy/exceptions/__init__.py index be9a1d5dee1..9f47371b36d 100644 --- a/lib/galaxy/exceptions/__init__.py +++ b/lib/galaxy/exceptions/__init__.py @@ -103,6 +103,10 @@ class ToolMissingException(MessageException): status_code = 400 err_code = error_codes.USER_TOOL_MISSING_PROBLEM + def __init__(self, err_msg=None, type="info", tool_id=None, **extra_error_info): + super(ToolMissingException, self).__init__(err_msg, type, **extra_error_info) + self.tool_id = tool_id + class RequestParameterInvalidException(MessageException): status_code = 400 diff --git a/lib/galaxy/exceptions/error_codes.json b/lib/galaxy/exceptions/error_codes.json index 11908515a1d..5b8c2bbce2a 100644 --- a/lib/galaxy/exceptions/error_codes.json +++ b/lib/galaxy/exceptions/error_codes.json @@ -27,7 +27,7 @@ { "name": "USER_OBJECT_ATTRIBUTE_MISSING", "code": 400005, - "message": "Attempted to create object without required attribute." + "message": "Attempted to create or update object without required attribute." }, { "name": "USER_SLUG_DUPLICATE", @@ -108,7 +108,7 @@ "name": "ADMIN_REQUIRED", "code": 403006, "message": "Action requires admin account." - }, + }, { "name": "USER_ACTIVATION_REQUIRED", "code": 403007, diff --git a/lib/galaxy/jobs/__init__.py b/lib/galaxy/jobs/__init__.py index c2c062d2ae9..e3460c38fc8 100644 --- a/lib/galaxy/jobs/__init__.py +++ b/lib/galaxy/jobs/__init__.py @@ -34,8 +34,8 @@ from galaxy.objectstore import ObjectStorePopulator from galaxy.util import safe_makedirs, unicodify from galaxy.util.bunch import Bunch from galaxy.util.expressions import ExpressionContext -from galaxy.util.handlers import ConfiguresHandlers from galaxy.util.xml_macros import load +from galaxy.web.stack.handlers import ConfiguresHandlers from .datasets import (DatasetPath, NullDatasetPathRewriter, OutputsToWorkingDirectoryPathRewriter, TaskPathRewriter) from .output_checker import check_output, DETECTED_JOB_STATE @@ -987,7 +987,7 @@ class JobWrapper(HasResourceParameters): if os.path.exists(path): util.umask_fix_perms(path, self.app.config.umask, 0o666, self.app.config.gid) - def fail(self, message, exception=False, stdout="", stderr="", exit_code=None): + def fail(self, message, exception=False, tool_stdout="", tool_stderr="", exit_code=None, job_stdout=None, job_stderr=None): """ Indicate job failure by setting state and message on all output datasets. @@ -1035,7 +1035,7 @@ class JobWrapper(HasResourceParameters): job.info = message # TODO: Put setting the stdout, stderr, and exit code in one place # (not duplicated with the finish method). - job.set_streams(stdout, stderr) + job.set_streams(tool_stdout, tool_stderr, job_stdout=job_stdout, job_stderr=job_stderr) # Let the exit code be Null if one is not provided: if (exit_code is not None): job.exit_code = exit_code @@ -1213,9 +1213,11 @@ class JobWrapper(HasResourceParameters): def finish( self, - stdout, - stderr, + tool_stdout, + tool_stderr, tool_exit_code=None, + job_stdout=None, + job_stderr=None, check_output_detected_state=None, remote_metadata_directory=None, ): @@ -1230,12 +1232,15 @@ class JobWrapper(HasResourceParameters): self.sa_session.expunge_all() job = self.get_job() + def fail(): + return self.fail(job.info, tool_stdout=tool_stdout, tool_stderr=tool_stderr, exit_code=tool_exit_code, job_stdout=job_stdout, job_stderr=job_stderr) + # TODO: After failing here, consider returning from the function. try: self.reclaim_ownership() except Exception: log.exception('(%s) Failed to change ownership of %s, failing' % (job.id, self.working_directory)) - return self.fail(job.info, stdout=stdout, stderr=stderr, exit_code=tool_exit_code) + return fail() # if the job was deleted, don't finish it if job.state == job.states.DELETED or job.state == job.states.ERROR: @@ -1244,7 +1249,7 @@ class JobWrapper(HasResourceParameters): # was deleted by an administrator (based on old comments), but it # could also mean that a job was broken up into tasks and one of # the tasks failed. So include the stderr, stdout, and exit code: - return self.fail(job.info, stderr=stderr, stdout=stdout, exit_code=tool_exit_code) + return fail() # We collect the stderr from tools that write their stderr to galaxy.json tool_provided_metadata = self.get_tool_provided_job_metadata() @@ -1256,7 +1261,7 @@ class JobWrapper(HasResourceParameters): # We set final_job_state to use for dataset management, but *don't* set # job.state until after dataset discovery to prevent history issues if check_output_detected_state is None: - check_output_detected_state = self.check_tool_output(stdout, stderr, tool_exit_code, job) + check_output_detected_state = self.check_tool_output(tool_stdout, tool_stderr, tool_exit_code=tool_exit_code, job=job, job_stdout=job_stdout, job_stderr=job_stderr) if check_output_detected_state == DETECTED_JOB_STATE.OK and not tool_provided_metadata.has_failed_outputs(): final_job_state = job.states.OK @@ -1291,6 +1296,7 @@ class JobWrapper(HasResourceParameters): return self.fail("Job %s's output dataset(s) could not be read" % job.id) job_context = ExpressionContext(dict(stdout=job.stdout, stderr=job.stderr)) + implicit_collection_jobs = job.implicit_collection_jobs_association for dataset_assoc in job.output_datasets + job.output_library_datasets: context = self.get_dataset_finish_context(job_context, dataset_assoc) # should this also be checking library associations? - can a library item be added from a history before the job has ended? - @@ -1340,7 +1346,9 @@ class JobWrapper(HasResourceParameters): log.warning('Unable to generate primary composite file automatically for %s: %s', dataset.dataset.id, e) if job.states.ERROR == final_job_state: dataset.blurb = "error" - dataset.mark_unhidden() + if not implicit_collection_jobs: + # Only unhide dataset outputs that are not part of a implicit collection + dataset.mark_unhidden() elif not purged: # If the tool was expected to set the extension, attempt to retrieve it if dataset.ext == 'auto': @@ -1351,7 +1359,8 @@ class JobWrapper(HasResourceParameters): # it would be quicker to just copy the metadata from the originating output dataset, # but somewhat trickier (need to recurse up the copied_from tree), for now we'll call set_meta() retry_internally = util.asbool(self.get_destination_configuration("retry_metadata_internally", True)) - if retry_internally and not self.external_output_metadata.external_metadata_set_successfully(dataset, self.sa_session): + metadata_set_successfully = self.external_output_metadata.external_metadata_set_successfully(dataset, dataset_assoc.name, self.sa_session, working_directory=self.working_directory) + if retry_internally and not metadata_set_successfully: # If Galaxy was expected to sniff type and didn't - do so. if dataset.ext == "_sniff_": extension = sniff.handle_uploaded_dataset_file(dataset.dataset.file_name, self.app.datatypes_registry) @@ -1359,8 +1368,7 @@ class JobWrapper(HasResourceParameters): # call datatype.set_meta directly for the initial set_meta call during dataset creation dataset.datatype.set_meta(dataset, overwrite=False) - elif (job.states.ERROR != final_job_state and - not self.external_output_metadata.external_metadata_set_successfully(dataset, self.sa_session)): + elif (job.states.ERROR != final_job_state and not metadata_set_successfully): dataset._state = model.Dataset.states.FAILED_METADATA else: self.external_output_metadata.load_metadata(dataset, dataset_assoc.name, self.sa_session, working_directory=self.working_directory, remote_metadata_directory=remote_metadata_directory) @@ -1406,46 +1414,17 @@ class JobWrapper(HasResourceParameters): # will now be seen by the user. self.sa_session.flush() - # Shrink streams and ensure unicode. - job.set_streams(job.stdout, job.stderr) - # The exit code will be null if there is no exit code to be set. # This is so that we don't assign an exit code, such as 0, that # is either incorrect or has the wrong semantics. if tool_exit_code is not None: job.exit_code = tool_exit_code # custom post process setup - inp_data = dict([(da.name, da.dataset) for da in job.input_datasets]) - out_data = dict([(da.name, da.dataset) for da in job.output_datasets]) - inp_data.update([(da.name, da.dataset) for da in job.input_library_datasets]) - out_data.update([(da.name, da.dataset) for da in job.output_library_datasets]) - - # TODO: eliminate overlap with tools/evaluation.py - out_collections = dict([(obj.name, obj.dataset_collection_instance) for obj in job.output_dataset_collection_instances]) - out_collections.update([(obj.name, obj.dataset_collection) for obj in job.output_dataset_collections]) - - input_ext = 'data' - input_dbkey = '?' - for _, data in inp_data.items(): - # For loop odd, but sort simulating behavior in galaxy.tools.actions - if not data: - continue - input_ext = data.ext - input_dbkey = data.dbkey or '?' - param_dict = self.get_param_dict(job) - # Create generated output children and primary datasets and dynamic outputs. - tool_working_directory = self.tool_working_directory - self.tool.collect_primary_datasets(out_data, self.get_tool_provided_job_metadata(), tool_working_directory, input_ext, input_dbkey) - self.tool.collect_dynamic_outputs( - out_collections, - self.get_tool_provided_job_metadata(), - job_working_directory=tool_working_directory, - inp_data=inp_data, - job=job, - input_dbkey=input_dbkey, - ) + inp_data, out_data, out_collections = job.io_dicts() + self.discover_outputs(job, inp_data, out_data, out_collections) # Certain tools require tasks to be completed after job execution # ( this used to be performed in the "exec_after_process" hook, but hooks are deprecated ). + param_dict = self.get_param_dict(job) self.tool.exec_after_process(self.app, inp_data, out_data, param_dict, job=job) # Call 'exec_after_process' hook self.tool.call_hook('exec_after_process', self.app, inp_data=inp_data, @@ -1489,8 +1468,42 @@ class JobWrapper(HasResourceParameters): delete_files = cleanup_job == 'always' or (job.state == job.states.OK and cleanup_job == 'onsuccess') self.cleanup(delete_files=delete_files) - def check_tool_output(self, stdout, stderr, tool_exit_code, job): - return check_output(self.tool, stdout, stderr, tool_exit_code, job) + def discover_outputs(self, job, inp_data, out_data, out_collections): + input_ext = 'data' + input_dbkey = '?' + for _, data in inp_data.items(): + # For loop odd, but sort simulating behavior in galaxy.tools.actions + if not data: + continue + input_ext = data.ext + input_dbkey = data.dbkey or '?' + + # Create generated output children and primary datasets. + tool_working_directory = self.tool_working_directory + tool_provided_job_metadata = self.get_tool_provided_job_metadata() + self.tool.discover_outputs( + out_data, + out_collections, + tool_provided_job_metadata, + job=job, + tool_working_directory=tool_working_directory, + inp_data=inp_data, + input_ext=input_ext, + input_dbkey=input_dbkey, + ) + + def check_tool_output(self, tool_stdout, tool_stderr, tool_exit_code, job, job_stdout=None, job_stderr=None): + job_id_tag = "" + if job is not None: + job_id_tag = job.get_id_tag() + + state, tool_stdout, tool_stderr, job_messages = check_output(self.tool.stdio_regexes, self.tool.stdio_exit_codes, tool_stdout, tool_stderr, tool_exit_code, job_id_tag) + + # Store the modified stdout and stderr in the job: + if job is not None: + job.set_streams(tool_stdout, tool_stderr, job_messages=job_messages, job_stdout=job_stdout, job_stderr=job_stderr) + + return state def cleanup(self, delete_files=True): # At least one of these tool cleanup actions (job import), is needed @@ -1501,7 +1514,7 @@ class JobWrapper(HasResourceParameters): for fname in self.extra_filenames: os.remove(fname) self.external_output_metadata.cleanup_external_metadata(self.sa_session) - galaxy.tools.imp_exp.JobExportHistoryArchiveWrapper(self.job_id).cleanup_after_job(self.sa_session) + galaxy.tools.imp_exp.JobExportHistoryArchiveWrapper(self.app, self.job_id).cleanup_after_job() galaxy.tools.imp_exp.JobImportHistoryArchiveWrapper(self.app, self.job_id).cleanup_after_job() if delete_files: self.object_store.delete(self.get_job(), base_dir='job_work', entire_dir=True, dir_only=True, obj_dir=True) @@ -1509,7 +1522,10 @@ class JobWrapper(HasResourceParameters): log.exception("Unable to cleanup job %d", self.job_id) def _collect_extra_files(self, dataset, job_working_directory): - temp_file_path = os.path.join(job_working_directory, "dataset_%s_files" % (dataset.id)) + object_store = self.app.object_store + store_by = getattr(object_store, "store_by", "id") + file_name = "dataset_%s_files" % getattr(dataset, store_by) + temp_file_path = os.path.join(job_working_directory, file_name) extra_dir = None try: # This skips creation of directories - object store @@ -1711,11 +1727,7 @@ class JobWrapper(HasResourceParameters): def get_dataset_finish_context(self, job_context, output_dataset_assoc): meta = {} tool_provided_metadata = self.get_tool_provided_job_metadata() - if hasattr(tool_provided_metadata, "get_meta_by_dataset_id"): - meta = tool_provided_metadata.get_meta_by_dataset_id(output_dataset_assoc.dataset.dataset.id) - elif hasattr(tool_provided_metadata, "get_meta_by_name"): - meta = tool_provided_metadata.get_meta_by_name(output_dataset_assoc.name) - + meta = tool_provided_metadata.get_dataset_meta(output_dataset_assoc.name, output_dataset_assoc.dataset.dataset.id) if meta: return ExpressionContext(meta, job_context) return job_context @@ -2030,7 +2042,7 @@ class TaskWrapper(JobWrapper): # Check what the tool returned. If the stdout or stderr matched # regular expressions that indicate errors, then set an error. # The same goes if the tool's exit code was in a given range. - if self.check_tool_output(stdout, stderr, tool_exit_code, task) == DETECTED_JOB_STATE.OK: + if self.check_tool_output(stdout, stderr, tool_exit_code=tool_exit_code, job=task) == DETECTED_JOB_STATE.OK: task.state = task.states.OK else: task.state = task.states.ERROR @@ -2187,7 +2199,10 @@ class SharedComputeEnvironment(SimpleComputeEnvironment): return self.job_wrapper.get_version_string_path() def tool_directory(self): - return os.path.abspath(self.job_wrapper.tool.tool_dir) + tool_dir = self.job_wrapper.tool.tool_dir + if tool_dir is not None: + tool_dir = os.path.abspath(tool_dir) + return tool_dir def home_directory(self): return self.job_wrapper.home_directory() diff --git a/lib/galaxy/jobs/actions/post.py b/lib/galaxy/jobs/actions/post.py index fdfc4cd92dd..d33aba36280 100644 --- a/lib/galaxy/jobs/actions/post.py +++ b/lib/galaxy/jobs/actions/post.py @@ -74,6 +74,11 @@ class ChangeDatatypeAction(DefaultJobAction): for dataset_assoc in job.output_datasets: if action.output_name == '' or dataset_assoc.name == action.output_name: app.datatypes_registry.change_datatype(dataset_assoc.dataset, action.action_arguments['newtype']) + for dataset_collection_assoc in job.output_dataset_collection_instances: + if action.output_name == '' or dataset_collection_assoc.name == action.output_name: + for dataset_instance in dataset_collection_assoc.dataset_collection_instance.dataset_instances: + if dataset_instance: + app.datatypes_registry.change_datatype(dataset_instance, action.action_arguments['newtype']) @classmethod def get_short_str(cls, pja): diff --git a/lib/galaxy/jobs/command_factory.py b/lib/galaxy/jobs/command_factory.py index ebeaa4bc94a..ad99b0e1e56 100644 --- a/lib/galaxy/jobs/command_factory.py +++ b/lib/galaxy/jobs/command_factory.py @@ -30,7 +30,9 @@ def build_command( include_work_dir_outputs=True, create_tool_working_directory=True, remote_command_params={}, - metadata_directory=None, + remote_job_directory=None, + stdout_file=None, + stderr_file=None, ): """ Compose the sequence of commands necessary to execute a job. This will @@ -104,11 +106,13 @@ def build_command( if include_work_dir_outputs: __handle_work_dir_outputs(commands_builder, job_wrapper, runner, remote_command_params) + if stdout_file and stderr_file: + commands_builder.capture_stdout_stderr(stdout_file, stderr_file) commands_builder.capture_return_code() if include_metadata and job_wrapper.requires_setting_metadata: - metadata_directory = metadata_directory or job_wrapper.working_directory - commands_builder.append_command("cd '%s'" % metadata_directory) + working_directory = remote_job_directory or job_wrapper.working_directory + commands_builder.append_command("cd '%s'" % working_directory) __handle_metadata(commands_builder, job_wrapper, runner, remote_command_params) return commands_builder.build() @@ -139,6 +143,7 @@ def __externalize_commands(job_wrapper, shell, commands_builder, remote_command_ commands = local_container_script if 'working_directory' in remote_command_params: commands = "%s %s" % (shell, join(remote_command_params['working_directory'], script_name)) + commands += " > ../tool_stdout 2> ../tool_stderr" log.info("Built script [%s] for tool command [%s]" % (local_container_script, tool_commands)) return commands @@ -228,24 +233,37 @@ class CommandsBuilder(object): # the last thing that happens is an exit with return code. self.return_code_captured = False - def prepend_command(self, command): + def prepend_command(self, command, sep=";"): if command: - self.commands = u"%s; %s" % (command, + self.commands = u"%s%s %s" % (command, + sep, self.commands) return self def prepend_commands(self, commands): return self.prepend_command(u"; ".join(c for c in commands if c)) - def append_command(self, command): + def append_command(self, command, sep=';'): if command: - self.commands = u"%s; %s" % (self.commands, - command) + self.commands = u"%s%s %s" % (self.commands, + sep, + command) return self def append_commands(self, commands): self.append_command(u"; ".join(c for c in commands if c)) + def capture_stdout_stderr(self, stdout_file, stderr_file): + self.prepend_command("""out="${TMPDIR:-/tmp}/out.$$" err="${TMPDIR:-/tmp}/err.$$" +mkfifo "$out" "$err" +trap 'rm "$out" "$err"' EXIT +tee -a stdout.log < "$out" & +tee -a stderr.log < "$err" >&2 &""", + sep="") + self.append_command("> '{stdout_file}' 2> '{stderr_file}'".format(stdout_file=stdout_file, + stderr_file=stderr_file), + sep="") + def capture_return_code(self): if not self.return_code_captured: self.return_code_captured = True diff --git a/lib/galaxy/jobs/dynamic_tool_destination.py b/lib/galaxy/jobs/dynamic_tool_destination.py index 84f0525a87b..709ec15c3c4 100755 --- a/lib/galaxy/jobs/dynamic_tool_destination.py +++ b/lib/galaxy/jobs/dynamic_tool_destination.py @@ -798,7 +798,7 @@ def validate_destination(app, destination, err_message, err_message_contents, valid_destination = False suggestion = None - if destination is 'fail' and err_message is dest_err_tool_rule_dest: # It's a tool rule that is set to fail. It's valid + if destination == 'fail' and err_message is dest_err_tool_rule_dest: # It's a tool rule that is set to fail. It's valid valid_destination = True elif app is None: if destination in destination_list: @@ -1391,7 +1391,7 @@ def map_tool_to_destination( for line in inp_db: if line[0] == ">": records += 1 - elif filesize_rule_present: + if filesize_rule_present: query_file = str(inp_data[da].file_name) file_size += os.path.getsize(query_file) except AttributeError: diff --git a/lib/galaxy/jobs/handler.py b/lib/galaxy/jobs/handler.py index b48d2ca702a..046163fb59b 100644 --- a/lib/galaxy/jobs/handler.py +++ b/lib/galaxy/jobs/handler.py @@ -28,8 +28,8 @@ from galaxy.jobs import ( TaskWrapper ) from galaxy.jobs.mapper import JobNotReadyException -from galaxy.util.handlers import HANDLER_ASSIGNMENT_METHODS from galaxy.util.monitors import Monitors +from galaxy.web.stack.handlers import HANDLER_ASSIGNMENT_METHODS from galaxy.web.stack.message import JobHandlerMessage log = logging.getLogger(__name__) @@ -295,10 +295,8 @@ class JobHandlerQueue(Monitors): .join(model.JobToInputLibraryDatasetAssociation) \ .join(model.LibraryDatasetDatasetAssociation) \ .join(model.Dataset) \ - .filter(and_((model.Job.state == model.Job.states.NEW), - or_((model.LibraryDatasetDatasetAssociation._state != null()), - (model.Dataset.state.in_(model.Dataset.non_ready_states)), - ))).subquery() + .filter(and_(model.Job.state == model.Job.states.NEW, + model.Dataset.state.in_(model.Dataset.non_ready_states))).subquery() if self.app.config.user_activation_on: jobs_to_check = self.sa_session.query(model.Job).enable_eagerloads(False) \ .outerjoin(model.User) \ @@ -361,8 +359,10 @@ class JobHandlerQueue(Monitors): job.text_metrics = copied_from_job.text_metrics job.dependencies = copied_from_job.dependencies job.state = copied_from_job.state - job.stderr = copied_from_job.stderr - job.stdout = copied_from_job.stdout + job.job_stderr = copied_from_job.job_stderr + job.job_stdout = copied_from_job.job_stdout + job.tool_stderr = copied_from_job.tool_stderr + job.tool_stdout = copied_from_job.tool_stdout job.command_line = copied_from_job.command_line job.traceback = copied_from_job.traceback job.tool_version = copied_from_job.tool_version diff --git a/lib/galaxy/jobs/output_checker.py b/lib/galaxy/jobs/output_checker.py index 6fa2e3deab3..a5fb26ba0e5 100644 --- a/lib/galaxy/jobs/output_checker.py +++ b/lib/galaxy/jobs/output_checker.py @@ -1,5 +1,4 @@ import re -import traceback from logging import getLogger from galaxy.tools.parser.error_level import StdioErrorLevel @@ -14,11 +13,28 @@ DETECTED_JOB_STATE = Bunch( GENERIC_ERROR='generic_error', ) - ERROR_PEAK = 2000 -def check_output(tool, stdout, stderr, tool_exit_code, job): +def check_output_regex(job_id_tag, regex, stream, stream_name, job_messages, max_error_level): + """ + check a single regex against a stream + + regex the regex to check + stream the stream to search in + job_messages a list where the descriptions of the detected regexes can be appended + max_error_level the maximum error level that has been detected so far + returns the max of the error_level of the regex and the given max_error_level + """ + regex_match = re.search(regex.match, stream, re.IGNORECASE) + if regex_match: + reason = __regex_err_msg(regex_match, stream_name, regex) + job_messages.append(reason) + return max(max_error_level, regex.error_level) + return max_error_level + + +def check_output(stdio_regexes, stdio_exit_codes, stdout, stderr, tool_exit_code, job_id_tag): """ Check the output of a tool - given the stdout, stderr, and the tool's exit code, return DETECTED_JOB_STATE.OK if the tool exited succesfully or @@ -37,6 +53,14 @@ def check_output(tool, stdout, stderr, tool_exit_code, job): stdout = unicodify(stdout) stderr = unicodify(stderr) + # messages (descriptions of the detected exit_code and regexes) + # to be prepended to the stdout/stderr after all exit code and regex tests + # are done (otherwise added messages are searched again). + # messages are added it the order of detection + + # If job is failed, track why. + job_messages = [] + try: # Check exit codes and match regular expressions against stdout and # stderr if this tool was configured to do so. @@ -44,7 +68,7 @@ def check_output(tool, stdout, stderr, tool_exit_code, job): # then we assume that the tool writer overwrote the default # behavior of just setting an error if there is *anything* on # stderr. - if len(tool.stdio_regexes) > 0 or len(tool.stdio_exit_codes) > 0: + if len(stdio_regexes) > 0 or len(stdio_exit_codes) > 0: # Check the exit code ranges in the order in which # they were specified. Each exit_code is a StdioExitCode # that includes an applicable range. If the exit code was in @@ -52,7 +76,7 @@ def check_output(tool, stdout, stderr, tool_exit_code, job): # If we've reached a fatal error rule, then stop. max_error_level = StdioErrorLevel.NO_ERROR if tool_exit_code is not None: - for stdio_exit_code in tool.stdio_exit_codes: + for stdio_exit_code in stdio_exit_codes: if (tool_exit_code >= stdio_exit_code.range_start and tool_exit_code <= stdio_exit_code.range_end): # Tack on a generic description of the code @@ -61,18 +85,25 @@ def check_output(tool, stdout, stderr, tool_exit_code, job): code_desc = stdio_exit_code.desc if None is code_desc: code_desc = "" - tool_msg = ("%s: Exit code %d (%s)" % ( + desc = "%s: Exit code %d (%s)" % ( StdioErrorLevel.desc(stdio_exit_code.error_level), tool_exit_code, - code_desc)) - log.info("Job %s: %s" % (job.get_id_tag(), tool_msg)) - stderr = tool_msg + "\n" + stderr + code_desc) + reason = { + 'type': 'exit_code', + 'desc': desc, + 'exit_code': tool_exit_code, + 'code_desc': code_desc, + 'error_level': stdio_exit_code.error_level, + } + log.info("Job %s: %s" % (job_id_tag, reason)) + job_messages.append(reason) max_error_level = max(max_error_level, stdio_exit_code.error_level) if max_error_level >= StdioErrorLevel.MAX: break - if max_error_level < StdioErrorLevel.FATAL: + if max_error_level < StdioErrorLevel.FATAL_OOM: # We'll examine every regex. Each regex specifies whether # it is to be run on stdout, stderr, or both. (It is # possible for neither stdout nor stderr to be scanned, @@ -82,38 +113,20 @@ def check_output(tool, stdout, stderr, tool_exit_code, job): # If warning, then we still set the job's state to OK # but include a message. We'll do this if we haven't seen # a fatal error yet - for regex in tool.stdio_regexes: + for regex in stdio_regexes: # If ( this regex should be matched against stdout ) # - Run the regex's match pattern against stdout # - If it matched, then determine the error level. # o If it was fatal, then we're done - break. - # Repeat the stdout stuff for stderr. - # TODO: Collapse this into a single function. - if regex.stdout_match: - regex_match = re.search(regex.match, stdout, - re.IGNORECASE) - if regex_match: - rexmsg = __regex_err_msg(regex_match, regex) - log.info("Job %s: %s" - % (job.get_id_tag(), rexmsg)) - stdout = rexmsg + "\n" + stdout - max_error_level = max(max_error_level, - regex.error_level) - if max_error_level >= StdioErrorLevel.FATAL: - break - if regex.stderr_match: - regex_match = re.search(regex.match, stderr, - re.IGNORECASE) - if regex_match: - rexmsg = __regex_err_msg(regex_match, regex) - log.info("Job %s: %s" - % (job.get_id_tag(), rexmsg)) - stderr = rexmsg + "\n" + stderr - max_error_level = max(max_error_level, - regex.error_level) - if max_error_level >= StdioErrorLevel.FATAL: - break + max_error_level = check_output_regex(job_id_tag, regex, stderr, 'stderr', job_messages, max_error_level) + if max_error_level >= StdioErrorLevel.MAX: + break + + if regex.stdout_match: + max_error_level = check_output_regex(job_id_tag, regex, stdout, 'stdout', job_messages, max_error_level) + if max_error_level >= StdioErrorLevel.MAX: + break # If we encountered a fatal error, then we'll need to set the # job state accordingly. Otherwise the job is ok: @@ -122,8 +135,6 @@ def check_output(tool, stdout, stderr, tool_exit_code, job): elif max_error_level >= StdioErrorLevel.FATAL: log.debug("Tool exit code indicates an error, failing job.") state = DETECTED_JOB_STATE.GENERIC_ERROR - else: - state = DETECTED_JOB_STATE.OK # When there are no regular expressions and no exit codes to check, # default to the previous behavior: when there's anything on stderr @@ -134,47 +145,43 @@ def check_output(tool, stdout, stderr, tool_exit_code, job): # + "checking stderr for success" ) if stderr: state = DETECTED_JOB_STATE.GENERIC_ERROR - else: - state = DETECTED_JOB_STATE.OK if DETECTED_JOB_STATE != DETECTED_JOB_STATE.OK and stderr: if stderr: peak = stderr[0:ERROR_PEAK] log.debug("job failed, standard error is - [%s]" % peak) - - # On any exception, return True. except Exception: - tb = traceback.format_exc() - log.warning("Tool check encountered unexpected exception; " + - "assuming tool was successful: " + tb) - state = DETECTED_JOB_STATE.OK + log.exception("Job state check encountered unexpected exception; assuming execution successful") - # Store the modified stdout and stderr in the job: - if job is not None: - job.set_streams(stdout, stderr) - - return state + return state, stdout, stderr, job_messages -def __regex_err_msg(match, regex): +def __regex_err_msg(match, stream, regex): """ Return a message about the match on tool output using the given ToolStdioRegex regex object. The regex_match is a MatchObject that will contain the string matched on. """ # Get the description for the error level: - err_msg = StdioErrorLevel.desc(regex.error_level) + ": " + desc = StdioErrorLevel.desc(regex.error_level) + ": " + mstart = match.start() + mend = match.end() + if mend - mstart > 256: + match_str = match.string[mstart : mstart + 256] + "..." + else: + match_str = match.string[mstart: mend] + # If there's a description for the regular expression, then use it. # Otherwise, we'll take the first 256 characters of the match. - if None is not regex.desc: - err_msg += regex.desc + if regex.desc is not None: + desc += regex.desc else: - mstart = match.start() - mend = match.end() - err_msg += "Matched on " - # TODO: Move the constant 256 somewhere else besides here. - if mend - mstart > 256: - err_msg += match.string[mstart : mstart + 256] + "..." - else: - err_msg += match.string[mstart: mend] - return err_msg + desc += "Matched on %s" % match_str + return { + "type": "regex", + "stream": stream, + "desc": desc, + "code_desc": regex.desc, + "match": match_str, + "error_level": regex.error_level, + } diff --git a/lib/galaxy/jobs/runners/__init__.py b/lib/galaxy/jobs/runners/__init__.py index 3ddec1e070e..88867243703 100644 --- a/lib/galaxy/jobs/runners/__init__.py +++ b/lib/galaxy/jobs/runners/__init__.py @@ -193,8 +193,13 @@ class BaseJobRunner(object): """ raise NotImplementedError() - def prepare_job(self, job_wrapper, include_metadata=False, include_work_dir_outputs=True, - modify_command_for_container=True): + def prepare_job(self, + job_wrapper, + include_metadata=False, + include_work_dir_outputs=True, + modify_command_for_container=True, + stdout_file=None, + stderr_file=None): """Some sanity checks that all runners' queue_job() methods are likely to want to do """ job_id = job_wrapper.get_id_tag() @@ -220,7 +225,9 @@ class BaseJobRunner(object): job_wrapper, include_metadata=include_metadata, include_work_dir_outputs=include_work_dir_outputs, - modify_command_for_container=modify_command_for_container + modify_command_for_container=modify_command_for_container, + stdout_file=stdout_file, + stderr_file=stderr_file, ) except Exception as e: log.exception("(%s) Failure preparing job" % job_id) @@ -243,8 +250,13 @@ class BaseJobRunner(object): def recover(self, job, job_wrapper): raise NotImplementedError() - def build_command_line(self, job_wrapper, include_metadata=False, include_work_dir_outputs=True, - modify_command_for_container=True): + def build_command_line(self, + job_wrapper, + include_metadata=False, + include_work_dir_outputs=True, + modify_command_for_container=True, + stdout_file=None, + stderr_file=None): container = self._find_container(job_wrapper) if not container and job_wrapper.requires_containerization: raise Exception("Failed to find a container when required, contact Galaxy admin.") @@ -254,7 +266,9 @@ class BaseJobRunner(object): include_metadata=include_metadata, include_work_dir_outputs=include_work_dir_outputs, modify_command_for_container=modify_command_for_container, - container=container + container=container, + stdout_file=stdout_file, + stderr_file=stderr_file, ) def get_work_dir_outputs(self, job_wrapper, job_working_directory=None, tool_working_directory=None): @@ -441,22 +455,61 @@ class BaseJobRunner(object): job_state.job_wrapper.change_state(model.Job.states.QUEUED) self.app.job_manager.job_handler.dispatcher.put(job_state.job_wrapper) - def _finish_or_resubmit_job(self, job_state, stdout, stderr, exit_code): - job = job_state.job_wrapper.get_job() - check_output_detected_state = job_state.job_wrapper.check_tool_output(stdout, stderr, exit_code, job) - # Flush with streams... - self.sa_session.add(job) - self.sa_session.flush() - if check_output_detected_state != DETECTED_JOB_STATE.OK: - job_runner_state = JobState.runner_states.TOOL_DETECT_ERROR - if check_output_detected_state == DETECTED_JOB_STATE.OUT_OF_MEMORY_ERROR: - job_runner_state = JobState.runner_states.MEMORY_LIMIT_REACHED - job_state.runner_state = job_runner_state - self._handle_runner_state('failure', job_state) - # Was resubmitted or something - I think we are done with it. - if job_state.runner_state_handled: - return - job_state.job_wrapper.finish(stdout, stderr, exit_code, check_output_detected_state=check_output_detected_state) + def _job_io_for_db(self, stream): + return shrink_stream_by_size(stream, DATABASE_MAX_STRING_SIZE, join_by="\n..\n", left_larger=True, beginning_on_size_error=True) + + def _finish_or_resubmit_job(self, job_state, job_stdout, job_stderr, job_id=None, external_job_id=None): + job_wrapper = job_state.job_wrapper + try: + job = job_state.job_wrapper.get_job() + exit_code = job_state.read_exit_code() + + tool_stdout_path = os.path.join(job_wrapper.working_directory, "tool_stdout") + tool_stderr_path = os.path.join(job_wrapper.working_directory, "tool_stderr") + # TODO: These might not exist for running jobs at the upgrade to 19.XX, remove that + # assumption in 20.XX. + if os.path.exists(tool_stdout_path): + with open(tool_stdout_path, "rb") as stdout_file: + tool_stdout = self._job_io_for_db(stdout_file) + else: + # Legacy job, were getting a merged output - assume it is mostly tool output. + tool_stdout = job_stdout + job_stdout = None + + if os.path.exists(tool_stderr_path): + with open(tool_stderr_path, "rb") as stdout_file: + tool_stderr = self._job_io_for_db(stdout_file) + else: + # Legacy job, were getting a merged output - assume it is mostly tool output. + tool_stderr = job_stderr + job_stderr = None + + check_output_detected_state = job_wrapper.check_tool_output(tool_stdout, tool_stderr, tool_exit_code=exit_code, job=job, job_stdout=job_stdout, job_stderr=job_stderr) + job_not_ok = check_output_detected_state != DETECTED_JOB_STATE.OK + + # clean up the job files + cleanup_job = job_state.job_wrapper.cleanup_job + if cleanup_job == "always" or (job_not_ok and cleanup_job == "onsuccess"): + job_state.cleanup() + + # Flush with streams... + self.sa_session.add(job) + self.sa_session.flush() + + if job_not_ok: + job_runner_state = JobState.runner_states.TOOL_DETECT_ERROR + if check_output_detected_state == DETECTED_JOB_STATE.OUT_OF_MEMORY_ERROR: + job_runner_state = JobState.runner_states.MEMORY_LIMIT_REACHED + job_state.runner_state = job_runner_state + self._handle_runner_state('failure', job_state) + # Was resubmitted or something - I think we are done with it. + if job_state.runner_state_handled: + return + + job_wrapper.finish(tool_stdout, tool_stderr, exit_code, check_output_detected_state=check_output_detected_state, job_stdout=job_stdout, job_stderr=job_stderr) + except Exception: + log.exception("(%s/%s) Job wrapper finish method failed" % (job_id or '', external_job_id or '')) + job_wrapper.fail("Unable to finish job", exception=True) class JobState(object): @@ -507,6 +560,24 @@ class JobState(object): def default_exit_code_file(files_dir, id_tag): return os.path.join(files_dir, 'galaxy_%s.ec' % id_tag) + def read_exit_code(self): + try: + # This should be an 8-bit exit code, but read ahead anyway: + exit_code_str = open(self.exit_code_file, "r").read(32) + except Exception: + # By default, the exit code is 0, which typically indicates success. + exit_code_str = "0" + + try: + # Decode the exit code. If it's bogus, then just use 0. + exit_code = int(exit_code_str) + except ValueError: + galaxy_id_tag = self.job_wrapper.get_id_tag() + log.warning("(%s) Exit code '%s' invalid. Using 0." % (galaxy_id_tag, exit_code_str)) + exit_code = 0 + + return exit_code + def cleanup(self): for file in [getattr(self, a) for a in self.cleanup_file_attributes if hasattr(self, a)]: try: @@ -675,8 +746,8 @@ class AsynchronousJobRunner(BaseJobRunner, Monitors): while which_try < self.app.config.retry_job_output_collection + 1: try: with open(job_state.output_file, "rb") as stdout_file, open(job_state.error_file, 'rb') as stderr_file: - stdout = shrink_stream_by_size(stdout_file, DATABASE_MAX_STRING_SIZE, join_by="\n..\n", left_larger=True, beginning_on_size_error=True) - stderr = shrink_stream_by_size(stderr_file, DATABASE_MAX_STRING_SIZE, join_by="\n..\n", left_larger=True, beginning_on_size_error=True) + stdout = self._job_io_for_db(stdout_file) + stderr = self._job_io_for_db(stderr_file) break except Exception as e: if which_try == self.app.config.retry_job_output_collection: @@ -694,30 +765,7 @@ class AsynchronousJobRunner(BaseJobRunner, Monitors): self.mark_as_failed(job_state) return - try: - # This should be an 8-bit exit code, but read ahead anyway: - exit_code_str = open(job_state.exit_code_file, "r").read(32) - except Exception: - # By default, the exit code is 0, which typically indicates success. - exit_code_str = "0" - - try: - # Decode the exit code. If it's bogus, then just use 0. - exit_code = int(exit_code_str) - except ValueError: - log.warning("(%s/%s) Exit code '%s' invalid. Using 0." % (galaxy_id_tag, external_job_id, exit_code_str)) - exit_code = 0 - - # clean up the job files - cleanup_job = job_state.job_wrapper.cleanup_job - if cleanup_job == "always" or (not stderr and cleanup_job == "onsuccess"): - job_state.cleanup() - - try: - self._finish_or_resubmit_job(job_state, stdout, stderr, exit_code) - except Exception: - log.exception("(%s/%s) Job wrapper finish method failed" % (galaxy_id_tag, external_job_id)) - job_state.job_wrapper.fail("Unable to finish job", exception=True) + self._finish_or_resubmit_job(job_state, stdout, stderr, job_id=galaxy_id_tag, external_job_id=external_job_id) def mark_as_finished(self, job_state): self.work_queue.put((self.finish_job, job_state)) diff --git a/lib/galaxy/jobs/runners/chronos.py b/lib/galaxy/jobs/runners/chronos.py index e930a5da85f..8d88a4482ff 100644 --- a/lib/galaxy/jobs/runners/chronos.py +++ b/lib/galaxy/jobs/runners/chronos.py @@ -142,7 +142,6 @@ class ChronosJobRunner(AsynchronousJobRunner): job_id=job_name, job_destination=job_destination) self.monitor_queue.put(ajs) - return None @handle_exception_call def stop_job(self, job_wrapper): diff --git a/lib/galaxy/jobs/runners/cli.py b/lib/galaxy/jobs/runners/cli.py index 409c09f2cdf..a0c4fd1abe5 100644 --- a/lib/galaxy/jobs/runners/cli.py +++ b/lib/galaxy/jobs/runners/cli.py @@ -9,8 +9,8 @@ from galaxy import model from galaxy.jobs import JobDestination from galaxy.jobs.runners import ( AsynchronousJobRunner, - AsynchronousJobState -) + AsynchronousJobState, + JobState) from galaxy.util import asbool from .util.cli import CliInterface, split_params @@ -76,6 +76,7 @@ class ShellJobRunner(AsynchronousJobRunner): script = self.get_job_file( job_wrapper, exit_code_path=ajs.exit_code_file, + shell=job_wrapper.shell, **job_file_kwargs ) @@ -176,6 +177,10 @@ class ShellJobRunner(AsynchronousJobRunner): if not state == model.Job.states.OK: # No need to change_state when the state is OK, this will be handled by `self.finish_job` ajs.job_wrapper.change_state(state) + if state == model.Job.states.ERROR: + # Try to find out the reason for exiting + self.__handle_out_of_memory(ajs, external_job_id) + self.work_queue.put((self.mark_as_failed, ajs)) if state == model.Job.states.RUNNING and not ajs.running: ajs.running = True ajs.old_state = state @@ -187,6 +192,16 @@ class ShellJobRunner(AsynchronousJobRunner): # Replace the watch list with the updated version self.watched = new_watched + def __handle_out_of_memory(self, ajs, external_job_id): + shell_params, job_params = self.parse_destination_params(ajs.job_destination.params) + shell, job_interface = self.get_cli_plugins(shell_params, job_params) + cmd_out = shell.execute(job_interface.get_failure_reason(external_job_id)) + if cmd_out is not None: + if job_interface.parse_failure_reason(cmd_out.stdout, external_job_id) \ + == JobState.runner_states.MEMORY_LIMIT_REACHED: + ajs.runner_state = JobState.runner_states.MEMORY_LIMIT_REACHED + ajs.fail_message = "Tool failed due to insufficient memory. Try with more memory." + def __get_job_states(self): job_destinations = {} job_states = {} diff --git a/lib/galaxy/jobs/runners/condor.py b/lib/galaxy/jobs/runners/condor.py index 52b52c62782..ed2d5446089 100644 --- a/lib/galaxy/jobs/runners/condor.py +++ b/lib/galaxy/jobs/runners/condor.py @@ -111,6 +111,7 @@ class CondorJobRunner(AsynchronousJobRunner): job_wrapper, exit_code_path=cjs.exit_code_file, slots_statement=galaxy_slots_statement, + shell=job_wrapper.shell, ) try: self.write_executable_script(executable, script) diff --git a/lib/galaxy/jobs/runners/drmaa.py b/lib/galaxy/jobs/runners/drmaa.py index 2d549f48d9b..1e2878330db 100644 --- a/lib/galaxy/jobs/runners/drmaa.py +++ b/lib/galaxy/jobs/runners/drmaa.py @@ -44,7 +44,7 @@ class DRMAAJobRunner(AsynchronousJobRunner): 'drmaa_library_path': dict(map=str, default=os.environ.get('DRMAA_LIBRARY_PATH', None))} for retry_exception in RETRY_EXCEPTIONS_LOWER: runner_param_specs[retry_exception + '_state'] = dict(map=str, valid=lambda x: x in (model.Job.states.OK, model.Job.states.ERROR), default=model.Job.states.OK) - runner_param_specs[retry_exception + '_retries'] = dict(map=int, valid=lambda x: int >= 0, default=0) + runner_param_specs[retry_exception + '_retries'] = dict(map=int, valid=lambda x: int(x) >= 0, default=0) if 'runner_param_specs' not in kwargs: kwargs['runner_param_specs'] = dict() @@ -152,7 +152,7 @@ class DRMAAJobRunner(AsynchronousJobRunner): jt['nativeSpecification'] = native_spec # fill in the DRM's job run template - script = self.get_job_file(job_wrapper, exit_code_path=ajs.exit_code_file) + script = self.get_job_file(job_wrapper, exit_code_path=ajs.exit_code_file, shell=job_wrapper.shell) try: self.write_executable_script(ajs.job_file, script) except Exception: @@ -254,11 +254,11 @@ class DRMAAJobRunner(AsynchronousJobRunner): """ look at a single watched job, determine its state, and deal with errors that could happen in this process. to be called from check_watched_items() - returns the state or None if exceptions occured + returns the state or None if exceptions occurred in the latter case the job is appended to new_watched if a 1 drmaa.InternalException, 2 drmaa.InvalidJobExceptionnot, or - 3 drmaa.DrmCommunicationException occured + 3 drmaa.DrmCommunicationException occurred (which causes the job to be tested again in the next iteration of check_watched_items) - the job is finished as errored if any other exception occurs - the job is finished OK or errored after the maximum number of retries diff --git a/lib/galaxy/jobs/runners/godocker.py b/lib/galaxy/jobs/runners/godocker.py index 3bc83b807ef..fd00c446585 100644 --- a/lib/galaxy/jobs/runners/godocker.py +++ b/lib/galaxy/jobs/runners/godocker.py @@ -160,7 +160,6 @@ class GodockerJobRunner(AsynchronousJobRunner): # Create an object of AsynchronousJobState and add it to the monitor queue. ajs = AsynchronousJobState(files_dir=job_wrapper.working_directory, job_wrapper=job_wrapper, job_id=job_id, job_destination=job_destination) self.monitor_queue.put(ajs) - return None def check_watched_item(self, job_state): """ Get the job current status from GoDocker diff --git a/lib/galaxy/jobs/runners/kubernetes.py b/lib/galaxy/jobs/runners/kubernetes.py index d55eca35a10..3594e044cb7 100644 --- a/lib/galaxy/jobs/runners/kubernetes.py +++ b/lib/galaxy/jobs/runners/kubernetes.py @@ -3,19 +3,18 @@ Offload jobs to a Kubernetes cluster. """ import logging +import math +import os import re -from os import environ as os_environ from time import sleep - -from six import text_type - from galaxy import model from galaxy.jobs.runners import ( AsynchronousJobRunner, AsynchronousJobState, JobState ) +from galaxy.util.bytesize import ByteSize # pykube imports: try: @@ -46,10 +45,9 @@ class KubernetesJobRunner(AsynchronousJobRunner): # Check if pykube was importable, fail if not assert KubeConfig is not None, K8S_IMPORT_MESSAGE runner_param_specs = dict( - k8s_config_path=dict(map=str, default=os_environ.get('KUBECONFIG', None)), + k8s_config_path=dict(map=str, default=os.environ.get('KUBECONFIG', None)), k8s_use_service_account=dict(map=bool, default=False), - k8s_persistent_volume_claim_name=dict(map=str), - k8s_persistent_volume_claim_mount_path=dict(map=str), + k8s_persistent_volume_claims=dict(map=str), k8s_namespace=dict(map=str, default="default"), k8s_galaxy_instance_id=dict(map=str), k8s_timeout_seconds_job_deletion=dict(map=int, valid=lambda x: int > 0, default=30), @@ -61,7 +59,7 @@ class KubernetesJobRunner(AsynchronousJobRunner): k8s_default_requests_memory=dict(map=str, default=None), k8s_default_limits_cpu=dict(map=str, default=None), k8s_default_limits_memory=dict(map=str, default=None), - k8s_pod_retrials=dict(map=int, valid=lambda x: int > 0, default=3)) + k8s_pod_retrials=dict(map=int, valid=lambda x: int >= 0, default=3)) if 'runner_param_specs' not in kwargs: kwargs['runner_param_specs'] = dict() @@ -70,13 +68,10 @@ class KubernetesJobRunner(AsynchronousJobRunner): """Start the job runner parent object """ super(KubernetesJobRunner, self).__init__(app, nworkers, **kwargs) - # self.cli_interface = CliInterface() - if "k8s_use_service_account" in self.runner_params and self.runner_params["k8s_use_service_account"]: self._pykube_api = HTTPClient(KubeConfig.from_service_account()) else: self._pykube_api = HTTPClient(KubeConfig.from_file(self.runner_params["k8s_config_path"])) - self._galaxy_vol_name = "pvc-galaxy" # TODO this needs to be read from params!! self._galaxy_instance_id = self.__get_galaxy_instance_id() @@ -86,17 +81,39 @@ class KubernetesJobRunner(AsynchronousJobRunner): self._init_monitor_thread() self._init_worker_threads() + self.setup_volumes() + + def setup_volumes(self): + volume_claims = dict(volume.split(":") for volume in self.runner_params['k8s_persistent_volume_claims'].split(',')) + mountable_volumes = [{'name': claim_name, 'persistentVolumeClaim': {'claimName': claim_name}} for claim_name in volume_claims] + self.runner_params['k8s_mountable_volumes'] = mountable_volumes + volume_mounts = [{'name': claim_name, 'mountPath': mount_path} for claim_name, mount_path in volume_claims.items()] + self.runner_params['k8s_volume_mounts'] = volume_mounts def queue_job(self, job_wrapper): """Create job script and submit it to Kubernetes cluster""" # prepare the job # We currently don't need to include_metadata or include_work_dir_outputs, as working directory is the same - # were galaxy will expect results. + # where galaxy will expect results. log.debug("Starting queue_job for job " + job_wrapper.get_id_tag()) - if not self.prepare_job(job_wrapper, include_metadata=False, modify_command_for_container=False): + ajs = AsynchronousJobState(files_dir=job_wrapper.working_directory, + job_wrapper=job_wrapper, + job_destination=job_wrapper.job_destination) + + if not self.prepare_job(job_wrapper, + include_metadata=False, + modify_command_for_container=False, + stdout_file=ajs.output_file, + stderr_file=ajs.error_file): return - job_destination = job_wrapper.job_destination + script = self.get_job_file(job_wrapper, exit_code_path=ajs.exit_code_file, shell=job_wrapper.shell, galaxy_virtual_env=None) + try: + self.write_executable_script(ajs.job_file, script) + except Exception: + job_wrapper.fail("failure preparing job script", exception=True) + log.exception("(%s) failure writing job script" % job_wrapper.get_id_tag()) + return # Construction of the Kubernetes Job object follows: http://kubernetes.io/docs/user-guide/persistent-volumes/ k8s_job_name = self.__produce_unique_k8s_job_name(job_wrapper.get_id_tag()) @@ -110,7 +127,7 @@ class KubernetesJobRunner(AsynchronousJobRunner): "namespace": self.runner_params['k8s_namespace'], "labels": {"app": k8s_job_name} }, - "spec": self.__get_k8s_job_spec(job_wrapper) + "spec": self.__get_k8s_job_spec(ajs) } # Checks if job exists and is trusted, or if it needs re-creation. @@ -140,14 +157,11 @@ class KubernetesJobRunner(AsynchronousJobRunner): job.create() # define job attributes in the AsyncronousJobState for follow-up - ajs = AsynchronousJobState(files_dir=job_wrapper.working_directory, job_wrapper=job_wrapper, - job_id=k8s_job_name, job_destination=job_destination) + ajs.job_id = k8s_job_name + # store runner information for tracking if Galaxy restarts + job_wrapper.set_job_destination(job_wrapper.job_destination, k8s_job_name) self.monitor_queue.put(ajs) - # external_runJob_script can be None, in which case it's not used. - external_runjob_script = None - return external_runjob_script - def __get_pull_policy(self): if "k8s_pull_policy" in self.runner_params: if self.runner_params['k8s_pull_policy'] in ["Always", "IfNotPresent", "Never"]: @@ -202,23 +216,23 @@ class KubernetesJobRunner(AsynchronousJobRunner): instance_id = self._galaxy_instance_id + "-" return "galaxy-" + instance_id + galaxy_internal_job_id - def __get_k8s_job_spec(self, job_wrapper): + def __get_k8s_job_spec(self, ajs): """Creates the k8s Job spec. For a Job spec, the only requirement is to have a .spec.template.""" - k8s_job_spec = {"template": self.__get_k8s_job_spec_template(job_wrapper)} + k8s_job_spec = {"template": self.__get_k8s_job_spec_template(ajs)} return k8s_job_spec - def __get_k8s_job_spec_template(self, job_wrapper): + def __get_k8s_job_spec_template(self, ajs): """The k8s spec template is nothing but a Pod spec, except that it is nested and does not have an apiversion nor kind. In addition to required fields for a Pod, a pod template in a job must specify appropriate labels (see pod selector) and an appropriate restart policy.""" k8s_spec_template = { "metadata": { - "labels": {"app": self.__produce_unique_k8s_job_name(job_wrapper.get_id_tag())} + "labels": {"app": self.__produce_unique_k8s_job_name(ajs.job_wrapper.get_id_tag())} }, "spec": { - "volumes": self.__get_k8s_mountable_volumes(job_wrapper), - "restartPolicy": self.__get_k8s_restart_policy(job_wrapper), - "containers": self.__get_k8s_containers(job_wrapper) + "volumes": self.runner_params['k8s_mountable_volumes'], + "restartPolicy": self.__get_k8s_restart_policy(ajs.job_wrapper), + "containers": self.__get_k8s_containers(ajs) } } # TODO include other relevant elements that people might want to use from @@ -238,55 +252,43 @@ class KubernetesJobRunner(AsynchronousJobRunner): """The default Kubernetes restart policy for Jobs""" return "Never" - def __get_k8s_mountable_volumes(self, job_wrapper): - """Provides the required volumes that the containers in the pod should be able to mount. This should be using - the new persistent volumes and persistent volumes claim objects. This requires that both a PersistentVolume and - a PersistentVolumeClaim are created before starting galaxy (starting a k8s job). - """ - # TODO on this initial version we only support a single volume to be mounted. - k8s_mountable_volume = { - "name": self._galaxy_vol_name, - "persistentVolumeClaim": { - "claimName": self.runner_params['k8s_persistent_volume_claim_name'] - } - } - return [k8s_mountable_volume] - - def __get_k8s_containers(self, job_wrapper): + def __get_k8s_containers(self, ajs): """Fills in all required for setting up the docker containers to be used, including setting a pull policy if this has been set. + $GALAXY_VIRTUAL_ENV is set to None to avoid the galaxy virtualenv inside the tool container. + $GALAXY_LIB is set to None to avoid changing the python path inside the container. + Setting these variables changes the described behaviour in the job file shell script + used to execute the tool inside the container. """ k8s_container = { - "name": self.__get_k8s_container_name(job_wrapper), - "image": self._find_container(job_wrapper).container_id, + "name": self.__get_k8s_container_name(ajs.job_wrapper), + "image": self._find_container(ajs.job_wrapper).container_id, # this form of command overrides the entrypoint and allows multi command # command line execution, separated by ;, which is what Galaxy does # to assemble the command. - # TODO possibly shell needs to be set by job_wrapper - "command": ["/bin/bash", "-c", job_wrapper.runner_command_line], - "workingDir": job_wrapper.working_directory, - "volumeMounts": [{ - "mountPath": self.runner_params['k8s_persistent_volume_claim_mount_path'], - "name": self._galaxy_vol_name - }] + "command": [ajs.job_wrapper.shell], + "args": ["-c", ajs.job_file], + "workingDir": ajs.job_wrapper.working_directory, + "volumeMounts": self.runner_params['k8s_volume_mounts'] } - resources = self.__get_resources(job_wrapper) + resources = self.__get_resources(ajs.job_wrapper) if resources: + envs = [] + if 'limits' in resources: + limits = resources['limits'] + if 'memory' in limits: + envs.append({'name': 'GALAXY_MEMORY_MB', 'value': str(ByteSize(limits['memory']).to_unit('M', as_string=False))}) + if 'cpu' in limits: + envs.append({'name': 'GALAXY_SLOTS', 'value': str(int(math.ceil(float(limits['cpu']))))}) k8s_container['resources'] = resources + k8s_container['env'] = envs if self._default_pull_policy: k8s_container["imagePullPolicy"] = self._default_pull_policy - # if self.__requires_ports(job_wrapper): - # k8s_container['ports'] = self.__get_k8s_containers_ports(job_wrapper) return [k8s_container] - # def __get_k8s_containers_ports(self, job_wrapper): - - # for k,v self.runner_params: - # if k.startswith("container_port_"): - def __get_resources(self, job_wrapper): mem_request = self.__get_memory_request(job_wrapper) cpu_request = self.__get_cpu_request(job_wrapper) @@ -336,7 +338,7 @@ class KubernetesJobRunner(AsynchronousJobRunner): job_destinantion = job_wrapper.job_destination if 'requests_cpu' in job_destinantion.params: - return self.__transform_cpu_value(job_destinantion.params['requests_cpu']) + return job_destinantion.params['requests_cpu'] return None def __get_cpu_limit(self, job_wrapper): @@ -344,40 +346,14 @@ class KubernetesJobRunner(AsynchronousJobRunner): job_destinantion = job_wrapper.job_destination if 'limits_cpu' in job_destinantion.params: - return self.__transform_cpu_value(job_destinantion.params['limits_cpu']) + return job_destinantion.params['limits_cpu'] return None - def __transform_cpu_value(self, cpu_value): - """Transforms cpu value - - If the value is 0 and not a string, then None is returned. - If the value is a float, then it is multiplied by 1000 and expressed as mili cpus. - If the value is an integer, then it is and expressed as CPUs (no unit). - If it is an already formatted string, it is returned as it was. - """ - if not isinstance(cpu_value, str) and float(cpu_value) == 0: - return None - if isinstance(cpu_value, float): - return str(int(cpu_value * 1000)) + "m" - elif isinstance(cpu_value, int): - return str(cpu_value) - return cpu_value - def __transform_memory_value(self, mem_value): - """Transforms memory value - - If the value is 0 and not a string, then None is returned. - If the value has a decimal part, then it is multiplied by 1000 and expressed as Megabytes. - If the value is an integer, then it is truncated and expressed as Gigabytes. - If it is an already formatted string, it is returned as it was. """ - if not isinstance(mem_value, str) and float(mem_value) == 0: - return None - if isinstance(mem_value, float): - return str(int(mem_value * 1000)) + "M" - elif isinstance(mem_value, int): - return str(mem_value) + "G" - return mem_value + Transforms valid kubernetes memory value to bytes + """ + return ByteSize(mem_value).value def __assemble_k8s_container_image_name(self, job_wrapper): """Assembles the container image name as repo/owner/image:tag, where repo, owner and tag are optional""" @@ -435,44 +411,44 @@ class KubernetesJobRunner(AsynchronousJobRunner): # This assumes jobs dependent on a single pod, single container if succeeded > 0: - self.__produce_log_file(job_state) - with open(job_state.error_file, 'w'): - pass job_state.running = False self.mark_as_finished(job_state) return None elif failed > 0 and self.__job_failed_due_to_low_memory(job_state): return self._handle_job_failure(job, job_state, reason="OOM") elif active > 0 and failed <= max_pod_retrials: - job_state.running = True + if not job_state.running: + job_state.running = True + job_state.job_wrapper.change_state(model.Job.states.RUNNING) return job_state elif failed > max_pod_retrials: return self._handle_job_failure(job, job_state) - # We should not get here - log.debug( - "Reaching unexpected point for Kubernetes job name %s where it is not classified as succ., active nor failed.", job.name) - log.debug("k8s full job object:\n%s", job.obj) - return job_state + elif job_state.job_wrapper.get_job().state == model.Job.states.DELETED: + # Job has been deleted via stop_job, cleanup and remove from watched_jobs by returning `None` + if job_state.job_wrapper.cleanup_job in ("always", "onsuccess"): + job_state.job_wrapper.cleanup() + return None + else: + # We really shouldn't reach this point, but we might if the job has been killed by the kubernetes admin + log.info("Kubernetes job '%s' not classified as succ., active or failed. Full Job object: \n%s", job.name, job.obj) elif len(jobs.response['items']) == 0: # there is no job responding to this job_id, it is either lost or something happened. - log.error("No Jobs are available under expected selector app=" + job_state.job_id) + log.error("No Jobs are available under expected selector app=%s", job_state.job_id) with open(job_state.error_file, 'w') as error_file: - error_file.write("No Kubernetes Jobs are available under expected selector app=" + job_state.job_id + "\n") + error_file.write("No Kubernetes Jobs are available under expected selector app=%s\n" % job_state.job_id) self.mark_as_failed(job_state) return job_state else: # there is more than one job associated to the expected unique job id used as selector. - log.error("There is more than one Kubernetes Job associated to job id " + job_state.job_id) - self.__produce_log_file(job_state) + log.error("More than one Kubernetes Job associated to job id '%s'", job_state.job_id) with open(job_state.error_file, 'w') as error_file: - error_file.write("There is more than one Kubernetes Job associated to job id " + job_state.job_id + "\n") + error_file.write("More than one Kubernetes Job associated to job id '%s'\n" % job_state.job_id) self.mark_as_failed(job_state) return job_state def _handle_job_failure(self, job, job_state, reason=None): - self.__produce_log_file(job_state) - with open(job_state.error_file, 'w') as error_file: + with open(job_state.error_file, 'a') as error_file: if reason == "OOM": error_file.write("Job killed after running out of memory. Try with more memory.\n") job_state.fail_message = "Tool failed due to insufficient memory. Try with more memory." @@ -492,7 +468,7 @@ class KubernetesJobRunner(AsynchronousJobRunner): marks the job for resubmission (resubmit logic is part of destinations). """ - pods = Pod.objects(self._pykube_api).filter(selector="app=" + job_state.job_id) + pods = Pod.objects(self._pykube_api).filter(selector="app=%s" % job_state.job_id) pod = Pod(self._pykube_api, pods.response['items'][0]) if pod.obj['status']['phase'] == "Failed" and \ @@ -501,63 +477,6 @@ class KubernetesJobRunner(AsynchronousJobRunner): return False - def fail_job(self, job_state): - """ - Kubernetes runner overrides fail_job (called by mark_as_failed) to rescue the pod's log files which are left as - stdout (pods logs are the natural stdout and stderr of the running processes inside the pods) and are - deleted in the parent implementation as part of the failing the job process. - - :param job_state: - :return: - """ - - # First we rescue the pods logs - try: - with open(job_state.output_file, 'r') as outfile: - stdout_content = outfile.read() - except IOError as e: - stdout_content = "".format(job_state.job_id) - log.error("Failed to get stdout for job %s", job_state.job_id) - log.exception(e) - - if getattr(job_state, 'stop_job', True): - self.stop_job(job_state.job_wrapper) - self._handle_runner_state('failure', job_state) - # Not convinced this is the best way to indicate this state, but - # something necessary - if not job_state.runner_state_handled: - job_state.job_wrapper.fail( - message=getattr(job_state, 'fail_message', 'Job failed'), - stdout=stdout_content, stderr='See stdout for pod\'s stderr.' - ) - if job_state.job_wrapper.cleanup_job == "always": - job_state.cleanup() - - def __produce_log_file(self, job_state): - pod_r = Pod.objects(self._pykube_api).filter(selector="app=" + job_state.job_id) - log_string = "" - for pod_obj in pod_r.response['items']: - try: - pod = Pod(self._pykube_api, pod_obj) - log_string += "\n\n==== Pod " + pod.name + " log start ====\n\n" - log_string += pod.logs(timestamps=True) - log_string += "\n\n==== Pod " + pod.name + " log end ====" - except Exception as detail: - log.info("Could not write log file for pod %s due to HTTPError %s", - pod_obj['metadata']['name'], detail) - if isinstance(log_string, text_type): - log_string = log_string.encode('utf8') - - logs_file_path = job_state.output_file - try: - with open(logs_file_path, mode="w") as logs_file: - logs_file.write(log_string) - except IOError as e: - log.error("Couldn't produce log files for %s", job_state.job_id) - log.exception(e) - - return logs_file_path - def stop_job(self, job_wrapper): """Attempts to delete a dispatched job to the k8s cluster""" job = job_wrapper.get_job() @@ -576,7 +495,6 @@ class KubernetesJobRunner(AsynchronousJobRunner): def recover(self, job, job_wrapper): """Recovers jobs stuck in the queued/running state when Galaxy started""" - # TODO this needs to be implemented to override unimplemented base method job_id = job.get_job_runner_external_id() log.debug("k8s trying to recover job: " + job_id) if job_id is None: diff --git a/lib/galaxy/jobs/runners/local.py b/lib/galaxy/jobs/runners/local.py index ab2e2654f9e..799187b127e 100644 --- a/lib/galaxy/jobs/runners/local.py +++ b/lib/galaxy/jobs/runners/local.py @@ -13,8 +13,6 @@ from time import sleep from galaxy import model from galaxy.util import ( asbool, - DATABASE_MAX_STRING_SIZE, - shrink_stream_by_size ) from ..runners import ( BaseJobRunner, @@ -73,6 +71,7 @@ class LocalJobRunner(BaseJobRunner): 'command': command_line, 'exit_code_path': exit_code_path, 'working_directory': job_wrapper.working_directory, + 'shell': job_wrapper.shell, } job_file_contents = self.get_job_file(job_wrapper, **job_script_props) self.write_executable_script(job_file, job_file_contents) @@ -83,7 +82,6 @@ class LocalJobRunner(BaseJobRunner): return stderr = stdout = '' - exit_code = 0 # command line has been added to the wrapper by prepare_job() command_line, exit_code_path = self.__command_line(job_wrapper) @@ -113,27 +111,20 @@ class LocalJobRunner(BaseJobRunner): if terminated: return - # Reap the process and get the exit code. - exit_code = proc.wait() + proc.wait() finally: with self._proc_lock: self._procs.remove(proc) - try: - exit_code = int(open(exit_code_path, 'r').read()) - except Exception: - log.warning("Failed to read exit code from path %s" % exit_code_path) - pass - if proc.terminated_by_shutdown: self._fail_job_local(job_wrapper, "job terminated by Galaxy shutdown") return stdout_file.seek(0) stderr_file.seek(0) - stdout = shrink_stream_by_size(stdout_file, DATABASE_MAX_STRING_SIZE, join_by="\n..\n", left_larger=True, beginning_on_size_error=True) - stderr = shrink_stream_by_size(stderr_file, DATABASE_MAX_STRING_SIZE, join_by="\n..\n", left_larger=True, beginning_on_size_error=True) + stdout = self._job_io_for_db(stdout_file) + stderr = self._job_io_for_db(stderr_file) stdout_file.close() stderr_file.close() log.debug('execution finished: %s' % command_line) @@ -146,13 +137,9 @@ class LocalJobRunner(BaseJobRunner): job_destination = job_wrapper.job_destination job_state = JobState(job_wrapper, job_destination) + job_state.exit_code_file = JobState.default_exit_code_file(job_wrapper.working_directory, job_id) job_state.stop_job = False - # Finish the job! - try: - self._finish_or_resubmit_job(job_state, stdout, stderr, exit_code) - except Exception: - log.exception("Job wrapper finish method failed") - self._fail_job_local(job_wrapper, "Unable to finish job") + self._finish_or_resubmit_job(job_state, stdout, stderr, job_id=job_id) def stop_job(self, job_wrapper): # if our local job has JobExternalOutputMetadata associated, then our primary job has to have already finished diff --git a/lib/galaxy/jobs/runners/pbs.py b/lib/galaxy/jobs/runners/pbs.py index 46aaa9697f8..ae158a5812c 100644 --- a/lib/galaxy/jobs/runners/pbs.py +++ b/lib/galaxy/jobs/runners/pbs.py @@ -288,7 +288,7 @@ class PBSJobRunner(AsynchronousJobRunner): stage_commands = '' env_setup_commands = [stage_commands] - script = self.get_job_file(job_wrapper, exit_code_path=ecfile, env_setup_commands=env_setup_commands) + script = self.get_job_file(job_wrapper, exit_code_path=ecfile, env_setup_commands=env_setup_commands, shell=job_wrapper.shell) job_file = "%s/%s.sh" % (self.app.config.cluster_files_directory, job_wrapper.job_id) self.write_executable_script(job_file, script) # job was deleted while we were preparing it diff --git a/lib/galaxy/jobs/runners/pulsar.py b/lib/galaxy/jobs/runners/pulsar.py index 027e5036ecc..0e0f5c89b80 100644 --- a/lib/galaxy/jobs/runners/pulsar.py +++ b/lib/galaxy/jobs/runners/pulsar.py @@ -286,12 +286,18 @@ class PulsarJobRunner(AsynchronousJobRunner): unstructured_path_rewrites = compute_environment.unstructured_path_rewrites output_names = compute_environment.output_names() + if self.app.config.metadata_strategy == "legacy": + # Drop this branch in 19.09. + metadata_directory = job_wrapper.working_directory + else: + metadata_directory = os.path.join(job_wrapper.working_directory, "metadata") + client_job_description = ClientJobDescription( command_line=command_line, input_files=self.get_input_files(job_wrapper), client_outputs=self.__client_outputs(client, job_wrapper), working_directory=job_wrapper.tool_working_directory, - metadata_directory=job_wrapper.working_directory, + metadata_directory=metadata_directory, tool=job_wrapper.tool, config_files=job_wrapper.extra_filenames, dependencies_description=dependencies_description, @@ -318,9 +324,9 @@ class PulsarJobRunner(AsynchronousJobRunner): self.monitor_job(pulsar_job_state) def __needed_features(self, client): - return { - 'remote_metadata': PulsarJobRunner.__remote_metadata(client), - } + return { + 'remote_metadata': PulsarJobRunner.__remote_metadata(client), + } def __prepare_job(self, job_wrapper, job_destination): """Build command-line and Pulsar client for this job.""" @@ -363,9 +369,6 @@ class PulsarJobRunner(AsynchronousJobRunner): compute_job_directory=remote_job_directory, ) job_wrapper.disable_commands_in_new_shell() - metadata_directory = None - if remote_metadata: - metadata_directory = remote_job_config['metadata_directory'] # Pulsar handles ``create_tool_working_directory`` and # ``include_work_dir_outputs`` details. @@ -374,10 +377,10 @@ class PulsarJobRunner(AsynchronousJobRunner): job_wrapper=job_wrapper, container=container, include_metadata=remote_metadata, - metadata_directory=metadata_directory, create_tool_working_directory=False, include_work_dir_outputs=False, remote_command_params=remote_command_params, + remote_job_directory=remote_job_directory, ) except UnsupportedPulsarException: log.exception("failure running job %d, unsupported Pulsar target", job_wrapper.job_id) @@ -534,7 +537,7 @@ class PulsarJobRunner(AsynchronousJobRunner): self._handle_runner_state('failure', job_state) if not job_state.runner_state_handled: job_state.job_wrapper.fail(getattr(job_state, "fail_message", message), - stdout=stdout, stderr=stderr, exception=exception) + tool_stdout=stdout, tool_stderr=stderr, exception=exception) def check_pid(self, pid): try: @@ -752,6 +755,8 @@ class PulsarMQJobRunner(PulsarJobRunner): def _monitor(self): # This is a message queue driven runner, don't monitor # just setup required callback. + self._init_noop_monitor() + self.client_manager.ensure_has_status_update_callback(self.__async_update) self.client_manager.ensure_has_ack_consumers() diff --git a/lib/galaxy/jobs/runners/slurm.py b/lib/galaxy/jobs/runners/slurm.py index bc24bb88bcf..333fd55d563 100644 --- a/lib/galaxy/jobs/runners/slurm.py +++ b/lib/galaxy/jobs/runners/slurm.py @@ -30,11 +30,11 @@ SLURM_MEMORY_LIMIT_EXCEEDED_PARTIAL_WARNINGS = [': Exceeded job memory limit at SLURM_MEMORY_LIMIT_SCAN_SIZE = 16 * 1024 * 1024 # 16MB SLURM_UNABLE_TO_ADD_TASK_TO_MEMORY_CG_MSG_RE = re.compile(r"""slurmstepd: error: task/cgroup: unable to add task\[pid=\d+\] to memory cg '\(null\)'$""") SLURM_UNABLE_TO_CREATE_CGROUP_MSG_RE = re.compile(r"""slurmstepd: error: xcgroup_instantiate: unable to create cgroup '[^']+' : No space left on device$""") -SLURM_UNABLE_TO_INSTANCIATE_JOB_MSG_RE = re.compile(r"""slurmstepd: error: jobacct_gather/cgroup: unable to instanciate job \d+ memory cgroup$""") +SLURM_UNABLE_TO_INSTANCIATE_CGROUP_MSG_RE = re.compile(r"""slurmstepd: error: jobacct_gather/cgroup: unable to instanciate (job|user) \d+ memory cgroup$""") SLURM_TOP_WARNING_RES = ( SLURM_UNABLE_TO_ADD_TASK_TO_MEMORY_CG_MSG_RE, SLURM_UNABLE_TO_CREATE_CGROUP_MSG_RE, - SLURM_UNABLE_TO_INSTANCIATE_JOB_MSG_RE + SLURM_UNABLE_TO_INSTANCIATE_CGROUP_MSG_RE ) # These messages are returned to the user diff --git a/lib/galaxy/jobs/runners/util/cli/job/__init__.py b/lib/galaxy/jobs/runners/util/cli/job/__init__.py index c6af843a2aa..a3c8b596a3a 100644 --- a/lib/galaxy/jobs/runners/util/cli/job/__init__.py +++ b/lib/galaxy/jobs/runners/util/cli/job/__init__.py @@ -61,3 +61,15 @@ class BaseJobExec(object): """ Parse the status of output from get_single_status command. """ + + def get_failure_reason(self, job_id): + """ + Return the failure reason for the given job_id. + """ + return None + + def parse_failure_reason(self, reason, job_id): + """ + Parses the failure reason, assigning it against a + """ + return None diff --git a/lib/galaxy/jobs/runners/util/cli/job/lsf.py b/lib/galaxy/jobs/runners/util/cli/job/lsf.py index 7df2e2a738e..cf85dd5f76e 100644 --- a/lib/galaxy/jobs/runners/util/cli/job/lsf.py +++ b/lib/galaxy/jobs/runners/util/cli/job/lsf.py @@ -2,6 +2,7 @@ # non-submit host and using a Slurm cluster. from logging import getLogger +from galaxy.jobs import JobState try: from galaxy.model import Job job_states = Job.states @@ -9,7 +10,6 @@ except ImportError: # Not in Galaxy, map Galaxy job states to Pulsar ones. from pulsar.util import enum job_states = enum(RUNNING='running', OK='complete', QUEUED='queued', ERROR="failed") - from ..job import BaseJobExec log = getLogger(__name__) @@ -18,7 +18,8 @@ argmap = { 'memory': '-M', # There is code in job_script_kwargs relying on this name's setting 'cores': '-n', 'queue': '-q', - 'working_dir': '-cwd' + 'working_dir': '-cwd', + 'project': '-P' } @@ -92,6 +93,18 @@ class LSF(BaseJobExec): return job_states.OK return self._get_job_state(status) + def get_failure_reason(self, job_id): + return "bjobs -l " + job_id + + def parse_failure_reason(self, reason, job_id): + # LSF will produce the following in the job output file: + # TERM_MEMLIMIT: job killed after reaching LSF memory usage limit. + # Exited with exit code 143. + for line in reason.splitlines(): + if "TERM_MEMLIMIT" in line: + return JobState.runner_states.MEMORY_LIMIT_REACHED + return None + def _get_job_state(self, state): # based on: # https://www.ibm.com/support/knowledgecenter/en/SSETD4_9.1.3/lsf_admin/job_state_lsf.html diff --git a/lib/galaxy/jobs/runners/util/cli/shell/local.py b/lib/galaxy/jobs/runners/util/cli/shell/local.py index 078ca06afa9..2e2f86c17f5 100644 --- a/lib/galaxy/jobs/runners/util/cli/shell/local.py +++ b/lib/galaxy/jobs/runners/util/cli/shell/local.py @@ -6,6 +6,8 @@ from subprocess import ( from tempfile import TemporaryFile from time import sleep +import six + from ..shell import BaseShellExec from ....util import ( Bunch, @@ -37,14 +39,17 @@ class LocalShell(BaseShellExec): True >>> exec_result.returncode == TIMEOUT_RETURN_CODE True + >>> shell.execute('echo hi').stdout == "hi\\n" + True """ def __init__(self, **kwds): pass def execute(self, cmd, persist=False, timeout=DEFAULT_TIMEOUT, timeout_check_interval=DEFAULT_TIMEOUT_CHECK_INTERVAL, **kwds): + is_cmd_string = isinstance(cmd, six.string_types) outf = TemporaryFile() - p = Popen(cmd, stdin=None, stdout=outf, stderr=PIPE) + p = Popen(cmd, stdin=None, stdout=outf, stderr=PIPE, shell=is_cmd_string) # poll until timeout for i in range(int(timeout / timeout_check_interval)): diff --git a/lib/galaxy/jobs/runners/util/job_script/CLUSTER_SLOTS_STATEMENT.sh b/lib/galaxy/jobs/runners/util/job_script/CLUSTER_SLOTS_STATEMENT.sh index 624f77c4535..01dccf22c5d 100644 --- a/lib/galaxy/jobs/runners/util/job_script/CLUSTER_SLOTS_STATEMENT.sh +++ b/lib/galaxy/jobs/runners/util/job_script/CLUSTER_SLOTS_STATEMENT.sh @@ -23,6 +23,9 @@ elif [ -f "$PBS_NODEFILE" ]; then GALAXY_SLOTS=`wc -l < $PBS_NODEFILE` elif [ -n "$LSB_DJOB_NUMPROC" ]; then GALAXY_SLOTS="$LSB_DJOB_NUMPROC" +elif [ -n "$GALAXY_SLOTS" ]; then + # kubernetes runner injects GALAXY_SLOTS into environment + GALAXY_SLOTS=$GALAXY_SLOTS else GALAXY_SLOTS="1" unset GALAXY_SLOTS_CONFIGURED diff --git a/lib/galaxy/managers/cloud.py b/lib/galaxy/managers/cloud.py index 4e8e8b7cfea..b43798c219e 100644 --- a/lib/galaxy/managers/cloud.py +++ b/lib/galaxy/managers/cloud.py @@ -18,7 +18,7 @@ from galaxy.managers import sharable from galaxy.util import Params try: - from cloudbridge.cloud.factory import CloudProviderFactory, ProviderList + from cloudbridge.factory import CloudProviderFactory, ProviderList except ImportError: CloudProviderFactory = None ProviderList = None diff --git a/lib/galaxy/managers/cloudauthzs.py b/lib/galaxy/managers/cloudauthzs.py index 095e777b000..3d879c3498d 100644 --- a/lib/galaxy/managers/cloudauthzs.py +++ b/lib/galaxy/managers/cloudauthzs.py @@ -1,12 +1,15 @@ """ -Manager and serializer for cloud authorizations (cloudauthzs). +Manager and (de)serializer for cloud authorizations (cloudauthzs). """ import logging from galaxy import model +from galaxy.exceptions import ( + InternalServerError, + MalformedId +) from galaxy.managers import base -from galaxy.managers import deletable from galaxy.managers import sharable log = logging.getLogger(__name__) @@ -21,7 +24,7 @@ class CloudAuthzManager(sharable.SharableModelManager): super(CloudAuthzManager, self).__init__(app, *args, **kwargs) -class CloudAuthzsSerializer(base.ModelSerializer, deletable.PurgableSerializerMixin): +class CloudAuthzsSerializer(base.ModelSerializer): """ Interface/service object for serializing cloud authorizations (cloudauthzs) into dictionaries. """ @@ -40,12 +43,12 @@ class CloudAuthzsSerializer(base.ModelSerializer, deletable.PurgableSerializerMi 'config', 'authn_id', 'last_update', - 'last_activity' + 'last_activity', + 'create_time' ]) def add_serializers(self): super(CloudAuthzsSerializer, self).add_serializers() - deletable.PurgableSerializerMixin.add_serializers(self) # Arguments of the following lambda functions: # i : an instance of galaxy.model.CloudAuthz. @@ -56,8 +59,65 @@ class CloudAuthzsSerializer(base.ModelSerializer, deletable.PurgableSerializerMi 'model_class' : lambda *a, **c: 'CloudAuthz', 'user_id' : lambda i, k, **c: self.app.security.encode_id(i.user_id), 'provider' : lambda i, k, **c: str(i.provider), - 'config' : lambda i, k, **c: str(i.config), + 'config' : lambda i, k, **c: i.config, 'authn_id' : lambda i, k, **c: self.app.security.encode_id(i.authn_id), 'last_update' : lambda i, k, **c: str(i.last_update), - 'last_activity': lambda i, k, **c: str(i.last_activity) + 'last_activity': lambda i, k, **c: str(i.last_activity), + 'create_time' : lambda i, k, **c: str(i.create_time) }) + + +class CloudAuthzsDeserializer(base.ModelDeserializer): + """ + Service object for validating and deserializing dictionaries that + update/alter cloudauthz configurations. + """ + model_manager_class = CloudAuthzManager + + def add_deserializers(self): + super(CloudAuthzsDeserializer, self).add_deserializers() + self.deserializers.update({ + 'authn_id': self.deserialize_and_validate_authn_id, + 'provider': self.default_deserializer, + 'config': self.default_deserializer, + 'deleted': self.default_deserializer + }) + + def deserialize_and_validate_authn_id(self, item, key, val, **context): + """ + Deserializes an authentication ID (authn_id), and asserts if the + current user can assume that authentication. + + :type item: galaxy.model.CloudAuthz + :param item: an instance of cloudauthz + + :type key: string + :param key: `authn_id` attribute of the cloudauthz object (i.e., the `item` param). + + :type val: string + :param val: the value of `authn_id` attribute of the cloudauthz object (i.e., the `item` param). + + :type context: dict + :param context: a dictionary object containing Galaxy `trans`. + + :rtype: string + :return: decoded authentication ID. + """ + + try: + decoded_authn_id = self.app.security.decode_id(val) + except Exception: + log.debug("cannot decode authz_id `" + str(val) + "`") + raise MalformedId("Invalid `authz_id` {}!".format(val)) + + trans = context.get("trans") + if trans is None: + log.debug("Not found expected `trans` when deserializing CloudAuthz.") + raise InternalServerError + + try: + trans.app.authnz_manager.can_user_assume_authn(trans, decoded_authn_id) + except Exception as e: + raise e + + return decoded_authn_id diff --git a/lib/galaxy/managers/collections.py b/lib/galaxy/managers/collections.py index 4aa717461f6..3e80738527e 100644 --- a/lib/galaxy/managers/collections.py +++ b/lib/galaxy/managers/collections.py @@ -1,10 +1,6 @@ import logging from galaxy import model -from galaxy.dataset_collections import builder -from galaxy.dataset_collections.matching import MatchingCollections -from galaxy.dataset_collections.registry import DatasetCollectionTypesRegistry -from galaxy.dataset_collections.type_description import CollectionTypeDescriptionFactory from galaxy.exceptions import ( ItemAccessibilityException, MessageException, @@ -14,9 +10,13 @@ from galaxy.managers import ( hdas, histories, lddas, - tags ) from galaxy.managers.collections_util import validate_input_element_identifiers +from galaxy.model import tags +from galaxy.model.dataset_collections import builder +from galaxy.model.dataset_collections.matching import MatchingCollections +from galaxy.model.dataset_collections.registry import DATASET_COLLECTION_TYPES_REGISTRY +from galaxy.model.dataset_collections.type_description import COLLECTION_TYPE_DESCRIPTION_FACTORY from galaxy.util import ( odict, validation @@ -36,14 +36,14 @@ class DatasetCollectionManager(object): ELEMENTS_UNINITIALIZED = object() def __init__(self, app): - self.type_registry = DatasetCollectionTypesRegistry(app) - self.collection_type_descriptions = CollectionTypeDescriptionFactory(self.type_registry) + self.type_registry = DATASET_COLLECTION_TYPES_REGISTRY + self.collection_type_descriptions = COLLECTION_TYPE_DESCRIPTION_FACTORY self.model = app.model self.security = app.security self.hda_manager = hdas.HDAManager(app) self.history_manager = histories.HistoryManager(app) - self.tag_manager = tags.GalaxyTagManager(app.model.context) + self.tag_handler = tags.GalaxyTagHandler(app.model.context) self.ldda_manager = lddas.LDDAManager(app) def precreate_dataset_collection_instance(self, trans, parent, name, structure, implicit_inputs=None, implicit_output_name=None, tags=None): @@ -154,7 +154,7 @@ class DatasetCollectionManager(object): # values. if isinstance(tags, list): assert implicit_inputs is None, implicit_inputs - tags = self.tag_manager.add_tags_from_list(trans.user, dataset_collection_instance, tags) + tags = self.tag_handler.add_tags_from_list(trans.user, dataset_collection_instance, tags) else: tags = self._append_tags(dataset_collection_instance, implicit_inputs, tags) return self.__persist(dataset_collection_instance, flush=flush) @@ -239,9 +239,7 @@ class DatasetCollectionManager(object): return dataset_collection def collection_builder_for(self, dataset_collection): - collection_type = dataset_collection.collection_type - collection_type_description = self.collection_type_descriptions.for_collection_type(collection_type) - return builder.BoundCollectionBuilder(dataset_collection, collection_type_description) + return builder.BoundCollectionBuilder(dataset_collection) def delete(self, trans, instance_type, id, recursive=False, purge=False): dataset_collection_instance = self.get_dataset_collection_instance(trans, instance_type, id, check_ownership=True) @@ -284,8 +282,8 @@ class DatasetCollectionManager(object): if copy_elements: copy_kwds["element_destination"] = parent new_hdca = source_hdca.copy(**copy_kwds) - tags_str = self.tag_manager.get_tags_str(source_hdca.tags) - self.tag_manager.apply_item_tags(trans.get_user(), new_hdca, tags_str) + tags_str = self.tag_handler.get_tags_str(source_hdca.tags) + self.tag_handler.apply_item_tags(trans.get_user(), new_hdca, tags_str) parent.add_dataset_collection(new_hdca) trans.sa_session.add(new_hdca) trans.sa_session.flush() @@ -300,7 +298,7 @@ class DatasetCollectionManager(object): changed['annotation'] = new_data['annotation'] if 'tags' in new_data.keys() and trans.get_user(): # set_tags_from_list will flush on its own, no need to add to 'changed' here and incur a second flush. - self.tag_manager.set_tags_from_list(trans.get_user(), dataset_collection_instance, new_data['tags']) + self.tag_handler.set_tags_from_list(trans.get_user(), dataset_collection_instance, new_data['tags']) if changed.keys(): trans.sa_session.flush() @@ -431,11 +429,11 @@ class DatasetCollectionManager(object): element = hda if hide_source_items and self.hda_manager.get_owned(hda.id, user=trans.user, current_history=trans.history): hda.visible = False - self.tag_manager.apply_item_tags(user=trans.user, item=element, tags_str=tag_str) + self.tag_handler.apply_item_tags(user=trans.user, item=element, tags_str=tag_str) elif src_type == 'ldda': element = self.ldda_manager.get(trans, encoded_id, check_accessible=True) element = element.to_history_dataset_association(trans.history, add_to_history=True) - self.tag_manager.apply_item_tags(user=trans.user, item=element, tags_str=tag_str) + self.tag_handler.apply_item_tags(user=trans.user, item=element, tags_str=tag_str) elif src_type == 'hdca': # TODO: Option to copy? Force copy? Copy or allow if not owned? element = self.__get_history_collection_instance(trans, encoded_id).collection diff --git a/lib/galaxy/managers/configuration.py b/lib/galaxy/managers/configuration.py index 04076d4514e..731cc9931e2 100644 --- a/lib/galaxy/managers/configuration.py +++ b/lib/galaxy/managers/configuration.py @@ -50,10 +50,8 @@ class ConfigSerializer(base.ModelSerializer): 'support_url' : _defaults_to(self.app.config.support_url), 'helpsite_url' : _defaults_to(self.app.config.helpsite_url), 'lims_doc_url' : _defaults_to("https://usegalaxy.org/u/rkchak/p/sts"), - 'biostar_url' : _defaults_to(''), - 'biostar_url_redirect' : lambda *a, **c: self.url_for(controller='biostar', action='biostar_redirect', qualified=True), 'default_locale' : _defaults_to(self.app.config.default_locale), - + 'enable_openid' : _defaults_to(False), 'enable_beta_ts_api_install' : _defaults_to(False), 'enable_communication_server' : _defaults_to(False), 'communication_server_port' : _defaults_to(None), @@ -63,6 +61,7 @@ class ConfigSerializer(base.ModelSerializer): 'allow_user_creation' : _defaults_to(False), 'use_remote_user' : _defaults_to(None), 'enable_oidc' : _defaults_to(False), + 'oidc' : _defaults_to(self.app.config.oidc), 'enable_quotas' : _defaults_to(False), 'remote_user_logout_href' : _defaults_to(''), 'datatypes_disable_auto' : _defaults_to(False), @@ -85,6 +84,11 @@ class ConfigSerializer(base.ModelSerializer): 'message_box_visible' : _defaults_to(False), 'message_box_class' : _defaults_to('info'), 'server_startttime' : lambda i, k, **c: server_starttime, + 'mailing_join_addr' : _defaults_to('galaxy-announce-join@bx.psu.edu'), + 'smtp_server' : _defaults_to(None), + 'registration_warning_message' : _defaults_to(None), + 'welcome_url' : _defaults_to(None), + 'show_welcome_with_login' : _defaults_to(True), } diff --git a/lib/galaxy/managers/datasets.py b/lib/galaxy/managers/datasets.py index 93318c38c1a..bbffbd64eae 100644 --- a/lib/galaxy/managers/datasets.py +++ b/lib/galaxy/managers/datasets.py @@ -12,6 +12,7 @@ from galaxy import ( exceptions, model ) +from galaxy.datatypes import sniff from galaxy.managers import ( base, deletable, @@ -19,6 +20,7 @@ from galaxy.managers import ( secured, users ) +from galaxy.util.checkers import check_binary log = logging.getLogger(__name__) @@ -289,6 +291,12 @@ class DatasetAssociationManager(base.ModelManager, """ Stops an dataset_assoc's creating job if all the job's other outputs are deleted. """ + + # Optimize this to skip other checks if this dataset is terminal - we can infer the + # job is already complete. + if dataset_assoc.state in model.Dataset.terminal_states: + return False + if dataset_assoc.parent_id is None and len(dataset_assoc.creating_job_associations) > 0: # Mark associated job for deletion job = dataset_assoc.creating_job_associations[0].job @@ -335,6 +343,42 @@ class DatasetAssociationManager(base.ModelManager, rval["modify_item_roles"] = modify_item_role_list return rval + def __ok_to_edit_metadata(self, trans, dataset_id): + # prevent modifying metadata when dataset is queued or running as input/output + # This code could be more efficient, i.e. by using mappers, but to prevent slowing down loading a History panel, we'll leave the code here for now + for job_to_dataset_association in trans.sa_session.query( + self.app.model.JobToInputDatasetAssociation).filter_by(dataset_id=dataset_id).all() \ + + trans.sa_session.query(self.app.model.JobToOutputDatasetAssociation).filter_by(dataset_id=dataset_id).all(): + if job_to_dataset_association.job.state not in [job_to_dataset_association.job.states.OK, job_to_dataset_association.job.states.ERROR, job_to_dataset_association.job.states.DELETED]: + return False + return True + + def detect_datatype(self, trans, dataset_assoc): + """Sniff and assign the datatype to a given dataset association (ldda or hda)""" + data = trans.sa_session.query(self.model_class).get(dataset_assoc.id) + if data.datatype.allow_datatype_change: + if not self.__ok_to_edit_metadata(trans, data.id): + raise exceptions.ItemAccessibilityException('This dataset is currently being used as input or output. You cannot change datatype until the jobs have completed or you have canceled them.') + else: + path = data.dataset.file_name + is_binary = check_binary(path) + datatype = sniff.guess_ext(path, trans.app.datatypes_registry.sniff_order, is_binary=is_binary) + trans.app.datatypes_registry.change_datatype(data, datatype) + trans.sa_session.flush() + self.set_metadata(trans, dataset_assoc) + else: + raise exceptions.InsufficientPermissionsException('Changing datatype "%s" is not allowed.' % (data.extension)) + + def set_metadata(self, trans, dataset_assoc): + """Trigger a job that detects and sets metadata on a given dataset association (ldda or hda)""" + data = trans.sa_session.query(self.model_class).get(dataset_assoc.id) + if not self.__ok_to_edit_metadata(trans, data.id): + raise exceptions.ItemAccessibilityException('This dataset is currently being used as input or output. You cannot edit metadata until the jobs have completed or you have canceled them.') + else: + trans.app.datatypes_registry.set_external_metadata_tool.tool_action.execute( + trans.app.datatypes_registry.set_external_metadata_tool, trans, incoming={'input1': data}, + overwrite=False) # overwrite is False as per existing behavior + def update_permissions(self, trans, dataset_assoc, **kwd): action = kwd.get('action', 'set_permissions') if action not in ['remove_restrictions', 'make_private', 'set_permissions']: @@ -355,7 +399,7 @@ class DatasetAssociationManager(base.ModelManager, if action == 'remove_restrictions': trans.app.security_agent.make_dataset_public(dataset) if not trans.app.security_agent.dataset_is_public(dataset): - raise exceptions.InternalServerError('An error occured while making dataset public.') + raise exceptions.InternalServerError('An error occurred while making dataset public.') elif action == 'make_private': if not trans.app.security_agent.dataset_is_private_to_user(trans, dataset): private_role = trans.app.security_agent.get_private_user_role(trans.user) @@ -364,7 +408,7 @@ class DatasetAssociationManager(base.ModelManager, trans.sa_session.flush() if not trans.app.security_agent.dataset_is_private_to_user(trans, dataset): # Check again and inform the user if dataset is not private. - raise exceptions.InternalServerError('An error occured and the dataset is NOT private.') + raise exceptions.InternalServerError('An error occurred and the dataset is NOT private.') elif action == 'set_permissions': def to_role_id(encoded_role_id): diff --git a/lib/galaxy/managers/hdas.py b/lib/galaxy/managers/hdas.py index 38c03c86784..1842665ea78 100644 --- a/lib/galaxy/managers/hdas.py +++ b/lib/galaxy/managers/hdas.py @@ -47,6 +47,12 @@ class HDAManager(datasets.DatasetAssociationManager, super(HDAManager, self).__init__(app) self.user_manager = users.UserManager(app) + def get_owned_ids(self, object_ids, history=None): + """Get owned IDs. + """ + filters = [self.model_class.id.in_(object_ids), self.model_class.history_id == history.id] + return self.list(filters=filters) + # .... security and permissions def is_accessible(self, hda, user, **kwargs): """ diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py index b7670902220..4d56a7d02d1 100644 --- a/lib/galaxy/managers/jobs.py +++ b/lib/galaxy/managers/jobs.py @@ -153,7 +153,7 @@ class JobSearch(object): # We need to make sure that the job we are looking for has been run with identical inputs. # Here we deal with 3 requirements: # - the jobs' input dataset (=b) version is 0, meaning the job's input dataset is not yet ready - # - b's update_time is older than the job create time, meaning no changes occured + # - b's update_time is older than the job create time, meaning no changes occurred # - the job has a dataset_version recorded, and that versions' metadata matches c's metadata. or_( and_(or_(a.dataset_version.in_([0, b.version]), @@ -177,13 +177,13 @@ class JobSearch(object): d.value == json.dumps(identifier))) used_ids.append(a.dataset_id) elif t == 'ldda': - a = aliased(model.JobToInputLibraryDatasetAssociation) - conditions.append(and_( - model.Job.id == a.job_id, - a.name == k, - a.ldda_id == v - )) - used_ids.append(a.ldda_id) + a = aliased(model.JobToInputLibraryDatasetAssociation) + conditions.append(and_( + model.Job.id == a.job_id, + a.name == k, + a.ldda_id == v + )) + used_ids.append(a.ldda_id) elif t == 'hdca': a = aliased(model.JobToInputDatasetCollectionAssociation) b = aliased(model.HistoryDatasetCollectionAssociation) diff --git a/lib/galaxy/managers/library_datasets.py b/lib/galaxy/managers/library_datasets.py index 4b740c16f93..46c2c45a06a 100644 --- a/lib/galaxy/managers/library_datasets.py +++ b/lib/galaxy/managers/library_datasets.py @@ -1,7 +1,10 @@ """Manager and Serializer for library datasets.""" import logging -from galaxy import util +from galaxy import ( + model, + util +) from galaxy.exceptions import ( InsufficientPermissionsException, InternalServerError, @@ -10,8 +13,8 @@ from galaxy.exceptions import ( ) from galaxy.managers import ( datasets, - tags ) +from galaxy.model import tags from galaxy.util import validation log = logging.getLogger(__name__) @@ -19,10 +22,11 @@ log = logging.getLogger(__name__) class LibraryDatasetsManager(datasets.DatasetAssociationManager): """Interface/service object for interacting with library datasets.""" + model_class = model.LibraryDatasetDatasetAssociation def __init__(self, app): self.app = app - self.tag_manager = tags.GalaxyTagManager(app.model.context) + self.tag_handler = tags.GalaxyTagHandler(app.model.context) def get(self, trans, decoded_library_dataset_id, check_accessible=True): """ @@ -86,9 +90,11 @@ class LibraryDatasetsManager(datasets.DatasetAssociationManager): ldda.message = new_message changed = True new_file_ext = new_data.get('file_ext', None) - if new_file_ext is not None and new_file_ext != ldda.extension: + if new_file_ext == 'auto': + self.detect_datatype(trans, ldda) + elif new_file_ext is not None and new_file_ext != ldda.extension: ldda.extension = new_file_ext - # TODO trigger set metadata here + self.set_metadata(trans, ldda) changed = True new_genome_build = new_data.get('genome_build', None) if new_genome_build is not None and new_genome_build != ldda.dbkey: @@ -115,7 +121,7 @@ class LibraryDatasetsManager(datasets.DatasetAssociationManager): validated_payload[key] = val if key in ('file_ext'): datatype = self.app.datatypes_registry.get_datatype_by_extension(val) - if datatype is None: + if datatype is None and val not in ("auto",): raise RequestParameterInvalidException('This Galaxy does not recognize the datatype of: %s' % (val)) validated_payload[key] = val if key in ('genome_build'): @@ -219,7 +225,7 @@ class LibraryDatasetsManager(datasets.DatasetAssociationManager): rval['date_uploaded'] = ldda.create_time.strftime("%Y-%m-%d %I:%M %p") rval['can_user_modify'] = trans.user_is_admin or trans.app.security_agent.can_modify_library_item(current_user_roles, ld) rval['is_unrestricted'] = trans.app.security_agent.dataset_is_public(ldda.dataset) - rval['tags'] = self.tag_manager.get_tags_str(ldda.tags) + rval['tags'] = self.tag_handler.get_tags_str(ldda.tags) # Manage dataset permission is always attached to the dataset itself, not the the ld or ldda to maintain consistency rval['can_user_manage'] = trans.user_is_admin or trans.app.security_agent.can_manage_dataset(current_user_roles, ldda.dataset) diff --git a/lib/galaxy/managers/taggable.py b/lib/galaxy/managers/taggable.py index dc4508abefa..794ab41008d 100644 --- a/lib/galaxy/managers/taggable.py +++ b/lib/galaxy/managers/taggable.py @@ -45,7 +45,7 @@ class TaggableManagerMixin(object): #: class of TagAssociation (e.g. HistoryTagAssociation) tag_assoc = None - # TODO: most of this can be done by delegating to the TagManager? + # TODO: most of this can be done by delegating to the GalaxyTagHandler? def get_tags(self, item): """ Return a list of tag strings. @@ -59,7 +59,7 @@ class TaggableManagerMixin(object): return _tags_from_strings(item, self.app.tag_handler, new_tags, user=user) # def tags_by_user( self, user, **kwargs ): - # TODO: here or TagManager + # TODO: here or GalaxyTagHandler # pass diff --git a/lib/galaxy/managers/tools.py b/lib/galaxy/managers/tools.py new file mode 100644 index 00000000000..d9f00a4f15a --- /dev/null +++ b/lib/galaxy/managers/tools.py @@ -0,0 +1,91 @@ +import logging +from uuid import uuid4 + +from galaxy import exceptions +from galaxy import model +from galaxy.exceptions import DuplicatedIdentifierException +from .base import ModelManager + +log = logging.getLogger(__name__) + + +class DynamicToolManager(ModelManager): + """ Manages dynamic tools stored in Galaxy's database. + """ + model_class = model.DynamicTool + + def __init__(self, app): + super(DynamicToolManager, self).__init__(app) + + def get_tool_by_uuid(self, uuid): + dynamic_tool = self._one_or_none( + self.query().filter(self.model_class.uuid == uuid) + ) + return dynamic_tool + + def get_tool_by_tool_id(self, tool_id): + dynamic_tool = self._one_or_none( + self.query().filter(self.model_class.tool_id == tool_id) + ) + return dynamic_tool + + def get_tool_by_id(self, object_id): + dynamic_tool = self._one_or_none( + self.query().filter(self.model_class.id == object_id) + ) + return dynamic_tool + + def create_tool(self, tool_payload, allow_load=False): + if "representation" not in tool_payload: + raise exceptions.ObjectAttributeMissingException( + "A tool 'representation' is required." + ) + + representation = tool_payload["representation"] + if "class" not in representation: + raise exceptions.ObjectAttributeMissingException( + "Current tool representations require 'class'." + ) + + enable_beta_formats = getattr(self.app.config, "enable_beta_tool_formats", False) + if not enable_beta_formats: + raise exceptions.ConfigDoesNotAllowException("Set 'enable_beta_tool_formats' in Galaxy config to create dynamic tools.") + + tool_format = representation["class"] + if tool_format == "GalaxyTool": + uuid = tool_payload.get("uuid", None) + if uuid is None: + uuid = uuid4() + + tool_id = representation.get("id", None) + if tool_id is None: + tool_id = str(uuid) + + tool_version = representation.get("version", None) + value = representation + else: + raise Exception("Unknown tool type encountered.") + # TODO: enforce via DB constraint and catch appropriate + # exception. + existing_tool = self.get_tool_by_uuid(uuid) + if existing_tool is not None and not allow_load: + raise DuplicatedIdentifierException(existing_tool.id) + elif existing_tool: + dynamic_tool = existing_tool + else: + dynamic_tool = self.create( + tool_format=tool_format, + tool_id=tool_id, + tool_version=tool_version, + uuid=uuid, + value=value, + ) + self.app.toolbox.load_dynamic_tool(dynamic_tool) + return dynamic_tool + + def list_tools(self, active=True): + return self.query().filter(self.model_class.active == active) + + def deactivate(self, dynamic_tool): + self.update(dynamic_tool, {"active": False}) + return dynamic_tool diff --git a/lib/galaxy/managers/users.py b/lib/galaxy/managers/users.py index 321ba3478cf..34004f6f85a 100644 --- a/lib/galaxy/managers/users.py +++ b/lib/galaxy/managers/users.py @@ -51,6 +51,36 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin): self.model_class = app.model.User super(UserManager, self).__init__(app) + def register(self, trans, email=None, username=None, password=None, confirm=None, subscribe=False, **kwd): + """ + Register a new user. + """ + if not trans.app.config.allow_user_creation and not trans.user_is_admin: + message = "User registration is disabled. Please contact your local Galaxy administrator for an account." + if trans.app.config.error_email_to is not None: + message += " Contact: %s" % trans.app.config.error_email_to + return None, message + if not email or not username or not password or not confirm: + return None, "Please provide email, username and password." + message = "\n".join([validate_email(trans, email), + validate_password(trans, password, confirm), + validate_publicname(trans, username)]).rstrip() + if message: + return None, message + email = util.restore_text(email) + username = util.restore_text(username) + message, status = trans.app.auth_manager.check_registration_allowed(email, username, password) + if message: + return None, message + if subscribe: + message = self.send_subscription_email(email) + if message: + return None, message + user = self.create(email=email, username=username, password=password) + if self.app.config.user_activation_on: + self.send_activation_email(trans, email, username) + return user, None + def create(self, email=None, username=None, password=None, **kwargs): """ Create a new user. @@ -261,7 +291,7 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin): if not token and not id: return None, "Please provide a token or a user and password." if token: - token_result = trans.sa_session.query(trans.app.model.PasswordResetToken).get(token) + token_result = trans.sa_session.query(self.app.model.PasswordResetToken).get(token) if not token_result or not token_result.expiration_time > datetime.utcnow(): return None, "Invalid or expired password reset token, please request a new one." user = token_result.user @@ -297,10 +327,10 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin): user.set_password_cleartext(password) # Invalidate all other sessions if trans.galaxy_session: - for other_galaxy_session in trans.sa_session.query(trans.app.model.GalaxySession) \ - .filter(and_(trans.app.model.GalaxySession.table.c.user_id == user.id, - trans.app.model.GalaxySession.table.c.is_valid == true(), - trans.app.model.GalaxySession.table.c.id != trans.galaxy_session.id)): + for other_galaxy_session in trans.sa_session.query(self.app.model.GalaxySession) \ + .filter(and_(self.app.model.GalaxySession.table.c.user_id == user.id, + self.app.model.GalaxySession.table.c.is_valid == true(), + self.app.model.GalaxySession.table.c.id != trans.galaxy_session.id)): other_galaxy_session.is_valid = False trans.sa_session.add(other_galaxy_session) trans.sa_session.add(user) @@ -325,14 +355,14 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin): "Your Galaxy Team" % (escape(username), escape(email), datetime.utcnow().strftime("%D"), trans.request.host, activation_link, - trans.app.config.terms_url, - trans.app.config.error_email_to, - trans.app.config.instance_resource_url)) + self.app.config.terms_url, + self.app.config.error_email_to, + self.app.config.instance_resource_url)) to = email - frm = trans.app.config.email_from or 'galaxy-no-reply@' + host + frm = self.app.config.email_from or 'galaxy-no-reply@' + host subject = 'Galaxy Account Activation' try: - util.send_mail(frm, to, subject, body, trans.app.config) + util.send_mail(frm, to, subject, body, self.app.config) return True except Exception: log.debug(body) @@ -343,7 +373,7 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin): """ Check for the activation token. Create new activation token and store it in the database if no token found. """ - user = trans.sa_session.query(trans.app.model.User).filter(trans.app.model.User.table.c.email == email).first() + user = trans.sa_session.query(self.app.model.User).filter(self.app.model.User.table.c.email == email).first() activation_token = user.activation_token if activation_token is None: activation_token = util.hash_util.new_secure_hash(str(random.getrandbits(256))) @@ -372,7 +402,7 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin): frm = trans.app.config.email_from or 'galaxy-no-reply@' + host subject = 'Galaxy Password Reset' try: - util.send_mail(frm, email, subject, body, trans.app.config) + util.send_mail(frm, email, subject, body, self.app.config) trans.sa_session.add(reset_user) trans.sa_session.flush() trans.log_event('User reset password: %s' % email) @@ -383,12 +413,12 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin): return "Failed to produce password reset token. User not found." def get_reset_token(self, trans, email): - reset_user = trans.sa_session.query(trans.app.model.User).filter(trans.app.model.User.table.c.email == email).first() + reset_user = trans.sa_session.query(self.app.model.User).filter(self.app.model.User.table.c.email == email).first() if not reset_user: # Perform a case-insensitive check only if the user wasn't found - reset_user = trans.sa_session.query(trans.app.model.User).filter(func.lower(trans.app.model.User.table.c.email) == func.lower(email)).first() + reset_user = trans.sa_session.query(self.app.model.User).filter(func.lower(self.app.model.User.table.c.email) == func.lower(email)).first() if reset_user: - prt = trans.app.model.PasswordResetToken(reset_user) + prt = self.app.model.PasswordResetToken(reset_user) trans.sa_session.add(prt) trans.sa_session.flush() return reset_user, prt @@ -400,6 +430,20 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin): host = socket.getfqdn() return host + def send_subscription_email(self, email): + if self.app.config.smtp_server is None: + return "Subscribing to the mailing list has failed because mail is not configured for this Galaxy instance. Please contact your local Galaxy administrator." + else: + body = 'Join Mailing list.\n' + to = self.app.config.mailing_join_addr + frm = email + subject = 'Join Mailing List' + try: + util.send_mail(frm, to, subject, body, self.app.config) + except Exception: + log.exception('Subscribing to the mailing list has failed.') + return "Subscribing to the mailing list has failed." + class UserSerializer(base.ModelSerializer, deletable.PurgableSerializerMixin): model_manager_class = UserManager diff --git a/lib/galaxy/managers/workflows.py b/lib/galaxy/managers/workflows.py index 6a3f15e833a..a50c0289973 100644 --- a/lib/galaxy/managers/workflows.py +++ b/lib/galaxy/managers/workflows.py @@ -37,7 +37,6 @@ from galaxy.tools.parameters.basic import ( from galaxy.util.json import safe_loads from galaxy.util.sanitize_html import sanitize_html from galaxy.web import url_for -from galaxy.workflow import modules from galaxy.workflow.modules import ( is_tool_module_type, module_factory, @@ -182,7 +181,7 @@ class WorkflowsManager(object): raise exceptions.RequestParameterInvalidException("Attempting to modify the state of an completed workflow invocation.") step = workflow_invocation_step.workflow_step - module = modules.module_factory.from_workflow_step(trans, step) + module = module_factory.from_workflow_step(trans, step) performed_action = module.do_invocation_step_action(step, action) workflow_invocation_step.action = performed_action trans.sa_session.add(workflow_invocation_step) @@ -271,9 +270,6 @@ class WorkflowContentsManager(UsesAnnotations): workflow_class = as_dict.get("class", None) if workflow_class == "GalaxyWorkflow" or "$graph" in as_dict or "yaml_content" in as_dict: - if not self.app.config.enable_beta_workflow_format: - raise exceptions.ConfigDoesNotAllowException("Format2 workflows not enabled.") - # Format 2 Galaxy workflow. galaxy_interface = Format2ConverterGalaxyInterface() import_options = ImportOptions() @@ -445,8 +441,6 @@ class WorkflowContentsManager(UsesAnnotations): """ def to_format_2(wf_dict, **kwds): - if not trans.app.config.enable_beta_workflow_format: - raise exceptions.ConfigDoesNotAllowException("Format2 workflows not enabled.") return from_galaxy_native(wf_dict, None, **kwds) if version == '': @@ -454,6 +448,13 @@ class WorkflowContentsManager(UsesAnnotations): if version is not None: version = int(version) workflow = stored.get_internal_version(version) + if style == "export": + # Export workflows as GA format through 19.05, in 19.09 this will become format2. + style = "ga" + + if self.app.config.enable_beta_export_format2_default: + style = "format2" + if style == "editor": wf_dict = self._workflow_to_dict_editor(trans, stored, workflow) elif style == "legacy": @@ -468,8 +469,10 @@ class WorkflowContentsManager(UsesAnnotations): elif style == "format2_wrapped_yaml": wf_dict = self._workflow_to_dict_export(trans, stored, workflow=workflow) wf_dict = to_format_2(wf_dict, json_wrapper=True) - else: + elif style == "ga": wf_dict = self._workflow_to_dict_export(trans, stored, workflow=workflow) + else: + raise exceptions.RequestParameterInvalidException('Unknown workflow style [%s]' % style) if version: wf_dict['version'] = version else: @@ -505,9 +508,10 @@ class WorkflowContentsManager(UsesAnnotations): for step in workflow.steps: try: module_injector.inject(step, steps=workflow.steps, exact_tools=False) - except exceptions.ToolMissingException: - if step.tool_id not in missing_tools: - missing_tools.append(step.tool_id) + except exceptions.ToolMissingException as e: + # FIXME: if a subworkflow lacks multiple tools we report only the first missing tool + if e.tool_id not in missing_tools: + missing_tools.append(e.tool_id) continue if step.upgrade_messages: has_upgrade_messages = True @@ -529,7 +533,7 @@ class WorkflowContentsManager(UsesAnnotations): step_model = None if step.type == 'tool': incoming = {} - tool = trans.app.toolbox.get_tool(step.tool_id, tool_version=step.tool_version) + tool = trans.app.toolbox.get_tool(step.tool_id, tool_version=step.tool_version, tool_uuid=step.tool_uuid) params_to_incoming(incoming, tool.inputs, step.state.inputs, trans.app) step_model = tool.to_json(trans, incoming, workflow_building_mode=workflow_building_modes.USE_HISTORY) step_model['post_job_actions'] = [{ @@ -575,12 +579,13 @@ class WorkflowContentsManager(UsesAnnotations): """ return self._resource_mapper_function(trans=trans, stored_workflow=stored, workflow=workflow) - def _workflow_to_dict_editor(self, trans, stored, workflow, tooltip=True): + def _workflow_to_dict_editor(self, trans, stored, workflow, tooltip=True, is_subworkflow=False): # Pack workflow data into a dictionary and return data = {} data['name'] = workflow.name data['steps'] = {} data['upgrade_messages'] = {} + input_step_types = set(workflow.input_step_types) # For each step, rebuild the form and encode the state for step in workflow.steps: # Load from database representation @@ -650,12 +655,13 @@ class WorkflowContentsManager(UsesAnnotations): # workflow outputs outputs = [] for output in step.unique_workflow_outputs: - output_label = output.label - output_name = output.output_name - output_uuid = str(output.uuid) if output.uuid else None - outputs.append({"output_name": output_name, - "uuid": output_uuid, - "label": output_label}) + if output.workflow_step.type not in input_step_types: + output_label = output.label + output_name = output.output_name + output_uuid = str(output.uuid) if output.uuid else None + outputs.append({"output_name": output_name, + "uuid": output_uuid, + "label": output_label}) step_dict['workflow_outputs'] = outputs # Encode input connections as dictionary @@ -678,25 +684,98 @@ class WorkflowContentsManager(UsesAnnotations): step_dict['position'] = step.position # Add to return value data['steps'][step.order_index] = step_dict - data['steps'] = self._resolve_collection_type_source(data['steps']) + if is_subworkflow: + data['steps'] = self._resolve_collection_type(data['steps']) return data - def _resolve_collection_type_source(self, steps): + @staticmethod + def get_step_map_over(current_step, steps): """ - Fill in collection type for step outputs that infer the collection type via collection_type_source. + Given a tool step and its input steps guess that maximum level of mapping over. + All data outputs of a step need to be mapped over to this level. + """ + max_map_over = '' + for input_name, input_connections in current_step['input_connections'].items(): + if isinstance(input_connections, dict): + # if input does not accept multiple inputs + input_connections = [input_connections] + for input_value in input_connections: + current_data_input = None + for current_input in current_step['inputs']: + if current_input['name'] == input_name: + current_data_input = current_input + # we've got one of the tools' input data definitions + break + input_step = steps[input_value['id']] + for input_step_data_output in input_step['outputs']: + if input_step_data_output['name'] == input_value['output_name']: + collection_type = input_step_data_output.get('collection_type') + # This is the defined incoming collection type, in reality there may be additional + # mapping over of the workflows' data input, but this should be taken care of by the workflow editor / + # outer workflow. + if collection_type: + if current_data_input.get('input_type') == 'dataset' and current_data_input.get('multiple'): + # We reduce the innermost collection + if ':' in collection_type: + # more than one layer of nesting and multiple="true" input, + # we consume the innermost collection + collection_type = ":".join(collection_type.rsplit(':')[:-1]) + else: + # We've reduced a list or a pair + collection_type = None + elif current_data_input.get('input_type') == 'dataset_collection': + current_collection_types = current_data_input['collection_types'] + if not current_collection_types: + # Accepts any input dataset collection, no mapping + collection_type = None + elif collection_type in current_collection_types: + # incoming collection type is an exact match, no mapping over + collection_type = None + else: + outer_map_over = collection_type + for accepted_collection_type in current_data_input['collection_types']: + # need to find the lowest level of mapping over, + # for collection_type = 'list:list:list' and accepted_collection_type = ['list:list', 'list'] + # it'd be outer_map_over == 'list' + if collection_type.endswith(accepted_collection_type): + _outer_map_over = collection_type[:-(len(accepted_collection_type) + 1)] + if len(_outer_map_over.split(':')) < len(outer_map_over.split(':')): + outer_map_over = _outer_map_over + collection_type = outer_map_over + # If there is mapping over, we're going to assume it is linked, everything else is (probably) + # too hard to display in the workflow editor. With this assumption we should be able to + # set the maximum mapping over level to the most deeply nested map_over + if collection_type and len(collection_type.split(':')) >= len(max_map_over.split(':')): + max_map_over = collection_type + if max_map_over: + return max_map_over + return None + + def _resolve_collection_type(self, steps): + """ + Fill in collection type for step outputs. + This can either be via collection_type_source and / or "inherited" from the step's input. This information is only needed in the workflow editor. """ for order_index in sorted(steps): step = steps[order_index] - for i, step_data_output in enumerate(step['outputs']): - if step_data_output.get('collection_type_source') and step_data_output['collection_type'] is None: - collection_type_source = step_data_output['collection_type_source'] - for input_connection in step['input_connections'].get(collection_type_source, []): - input_step = steps[input_connection['id']] - for input_step_data_output in input_step['outputs']: - if input_step_data_output['name'] == input_connection['output_name']: - step_data_output['collection_type'] = input_step_data_output.get('collection_type') + if step['type'] == 'tool' and not step.get('errors'): + map_over = self.get_step_map_over(step, steps) + for step_data_output in step['outputs']: + if step_data_output.get('collection_type_source') and step_data_output['collection_type'] is None: + collection_type_source = step_data_output['collection_type_source'] + for input_connection in step['input_connections'].get(collection_type_source, []): + input_step = steps[input_connection['id']] + for input_step_data_output in input_step['outputs']: + if input_step_data_output['name'] == input_connection['output_name']: + step_data_output['collection_type'] = input_step_data_output.get('collection_type') + if map_over: + collection_type = map_over + step_data_output['collection'] = True + if step_data_output.get('collection_type'): + collection_type = "%s:%s" % (map_over, step_data_output['collection_type']) + step_data_output['collection_type'] = collection_type return steps def _workflow_to_dict_export(self, trans, stored=None, workflow=None): @@ -755,6 +834,16 @@ class WorkflowContentsManager(UsesAnnotations): 'changeset_revision': module.tool.changeset_revision, 'tool_shed': module.tool.tool_shed } + + tool_representation = None + dynamic_tool = step.dynamic_tool + if dynamic_tool: + tool_representation = dynamic_tool.value + step_dict['tool_representation'] = tool_representation + if util.is_uuid(step_dict['content_id']): + step_dict['content_id'] = None + step_dict['tool_id'] = None + pja_dict = {} for pja in step.post_job_actions: pja_dict[pja.action_type + pja.output_name] = dict( @@ -1124,7 +1213,7 @@ class WorkflowContentsManager(UsesAnnotations): if not module.label and module.type in ['data_input', 'data_collection_input']: new_state = safe_loads(state) default_label = new_state.get('name') - if str(default_label).lower() not in ['input dataset', 'input dataset collection']: + if default_label and util.unicodify(default_label).lower() not in ['input dataset', 'input dataset collection']: step.label = module.label = default_label diff --git a/lib/galaxy/metadata/__init__.py b/lib/galaxy/metadata/__init__.py index 2c4fcfcda18..5314c6dc3dd 100644 --- a/lib/galaxy/metadata/__init__.py +++ b/lib/galaxy/metadata/__init__.py @@ -17,9 +17,15 @@ from galaxy.util import in_directory log = getLogger(__name__) +SET_METADATA_SCRIPT = 'from galaxy_ext.metadata.set_metadata import set_metadata; set_metadata()' + def get_metadata_compute_strategy(app, job_id): - return JobExternalOutputMetadataWrapper(job_id) + metadata_strategy = app.config.metadata_strategy + if metadata_strategy == "legacy": + return JobExternalOutputMetadataWrapper(job_id) + else: + return PortableDirectoryMetadataGenerator(job_id) @six.add_metaclass(abc.ABCMeta) @@ -52,13 +58,108 @@ class MetadataCollectionStrategy(object): """ @abc.abstractmethod - def external_metadata_set_successfully(self, dataset, sa_session): + def external_metadata_set_successfully(self, dataset, name, sa_session, working_directory): """Return boolean indicating if metadata for specified dataset was written properly.""" @abc.abstractmethod def load_metadata(self, dataset, name, sa_session, working_directory, remote_metadata_directory=None): """Load metadata calculated externally into specified dataset.""" + def _load_metadata_from_path(self, dataset, metadata_output_path, working_directory, remote_metadata_directory): + + def path_rewriter(path): + if not path: + return path + normalized_remote_metadata_directory = remote_metadata_directory and os.path.normpath(remote_metadata_directory) + normalized_path = os.path.normpath(path) + if remote_metadata_directory and normalized_path.startswith(normalized_remote_metadata_directory): + return normalized_path.replace(normalized_remote_metadata_directory, working_directory, 1) + return path + + dataset.metadata.from_JSON_dict(metadata_output_path, path_rewriter=path_rewriter) + + def _metadata_results_from_file(self, dataset, filename_results_code): + with open(filename_results_code, "r") as f: + rval, rstring = json.load(f) + + if not rval: + log.debug('setting metadata externally failed for %s %s: %s' % (dataset.__class__.__name__, dataset.id, rstring)) + return rval + + +class PortableDirectoryMetadataGenerator(MetadataCollectionStrategy): + + def __init__(self, job_id): + self.job_id = job_id + + def setup_external_metadata(self, datasets_dict, sa_session, exec_dir=None, + tmp_dir=None, dataset_files_path=None, + output_fnames=None, config_root=None, + config_file=None, datatypes_config=None, + job_metadata=None, compute_tmp_dir=None, + include_command=True, max_metadata_value_size=0, + kwds=None): + assert job_metadata, "setup_external_metadata must be supplied with job_metadata path" + kwds = kwds or {} + tmp_dir = _init_tmp_dir(tmp_dir) + + metadata_dir = os.path.join(tmp_dir, "metadata") + os.mkdir(metadata_dir) + + def job_relative_path(path): + path_relative = os.path.relpath(path, tmp_dir) + return path_relative + + outputs = {} + + for name, dataset in datasets_dict.items(): + assert name is not None + assert name not in outputs + + key = name + + def _metadata_path(what): + return os.path.join(metadata_dir, "metadata_%s_%s" % (what, key)) + + _initialize_metadata_inputs(dataset, _metadata_path, tmp_dir, kwds) + + outputs[name] = { + "filename_override": _get_filename_override(output_fnames, dataset.file_name) + } + + metadata_params_path = os.path.join(metadata_dir, "params.json") + metadata_params = { + "job_metadata": job_relative_path(job_metadata), + "datatypes_config": datatypes_config, + "max_metadata_value_size": max_metadata_value_size, + "outputs": outputs, + "object_store_store_by": galaxy.model.Dataset.object_store.store_by, + } + with open(metadata_params_path, "w") as f: + json.dump(metadata_params, f) + + if include_command: + # return command required to build + script_path = os.path.join(metadata_dir, "set.py") + with open(script_path, "w") as f: + f.write(SET_METADATA_SCRIPT) + return 'pwd; python "metadata/set.py"' + else: + # return args to galaxy_ext.metadata.set_metadata required to build + return '' + + def load_metadata(self, dataset, name, sa_session, working_directory, remote_metadata_directory=None): + metadata_output_path = os.path.join(working_directory, "metadata", "metadata_out_%s" % name) + self._load_metadata_from_path(dataset, metadata_output_path, working_directory, remote_metadata_directory) + + def external_metadata_set_successfully(self, dataset, name, sa_session, working_directory): + metadata_results_path = os.path.join(working_directory, "metadata", "metadata_results_%s" % name) + try: + return self._metadata_results_from_file(dataset, metadata_results_path) + except Exception: + # if configured we need to try setting metadata internally + return False + class JobExternalOutputMetadataWrapper(MetadataCollectionStrategy): """ @@ -112,10 +213,8 @@ class JobExternalOutputMetadataWrapper(MetadataCollectionStrategy): include_command=True, max_metadata_value_size=0, kwds=None): kwds = kwds or {} - assert tmp_dir is not None - - if not os.path.exists(tmp_dir): - os.makedirs(tmp_dir) + tmp_dir = _init_tmp_dir(tmp_dir) + _assert_datatypes_config(datatypes_config) # path is calculated for Galaxy, may be different on compute - rewrite # for the compute server. @@ -128,18 +227,12 @@ class JobExternalOutputMetadataWrapper(MetadataCollectionStrategy): # fill in metadata_files_dict and return the command with args required to set metadata def __metadata_files_list_to_cmd_line(metadata_files): - def __get_filename_override(): - if output_fnames: - for dataset_path in output_fnames: - if dataset_path.real_path == metadata_files.dataset.file_name: - return dataset_path.false_path or dataset_path.real_path - return "" line = '"%s,%s,%s,%s,%s,%s"' % ( metadata_path_on_compute(metadata_files.filename_in), metadata_path_on_compute(metadata_files.filename_kwds), metadata_path_on_compute(metadata_files.filename_out), metadata_path_on_compute(metadata_files.filename_results_code), - __get_filename_override(), + _get_filename_override(output_fnames, metadata_files.dataset.file_name), metadata_path_on_compute(metadata_files.filename_override_metadata), ) return line @@ -151,8 +244,6 @@ class JobExternalOutputMetadataWrapper(MetadataCollectionStrategy): dataset_files_path = galaxy.model.Dataset.file_path if config_root is None: config_root = os.path.abspath(os.getcwd()) - if datatypes_config is None: - raise Exception('In setup_external_metadata, the received datatypes_config is None.') metadata_files_list = [] for dataset in datasets: key = self._get_dataset_metadata_key(dataset) @@ -169,42 +260,27 @@ class JobExternalOutputMetadataWrapper(MetadataCollectionStrategy): # we will use pathnames relative to the galaxy root, to accommodate instances where the galaxy root # is located differently, i.e. on a cluster node with a different filesystem structure + def _metadata_path(what): + return abspath(tempfile.NamedTemporaryFile(dir=tmp_dir, prefix="metadata_%s_%s_" % (what, key)).name) + + filename_in, filename_out, filename_results_code, filename_kwds, filename_override_metadata = _initialize_metadata_inputs(dataset, _metadata_path, tmp_dir, kwds) + # file to store existing dataset - metadata_files.filename_in = abspath(tempfile.NamedTemporaryFile(dir=tmp_dir, prefix="metadata_in_%s_" % key).name) + metadata_files.filename_in = filename_in - # FIXME: HACK - # sqlalchemy introduced 'expire_on_commit' flag for sessionmaker at version 0.5x - # This may be causing the dataset attribute of the dataset_association object to no-longer be loaded into memory when needed for pickling. - # For now, we'll simply 'touch' dataset_association.dataset to force it back into memory. - dataset.dataset # force dataset_association.dataset to be loaded before pickling - # A better fix could be setting 'expire_on_commit=False' on the session, or modifying where commits occur, or ? - - # Touch also deferred column - dataset._metadata - - cPickle.dump(dataset, open(metadata_files.filename_in, 'wb+')) # file to store metadata results of set_meta() - metadata_files.filename_out = abspath(tempfile.NamedTemporaryFile(dir=tmp_dir, prefix="metadata_out_%s_" % key).name) - open(metadata_files.filename_out, 'wt+') # create the file on disk, so it cannot be reused by tempfile (unlikely, but possible) + metadata_files.filename_out = filename_out + # file to store a 'return code' indicating the results of the set_meta() call # results code is like (True/False - if setting metadata was successful/failed , exception or string of reason of success/failure ) - metadata_files.filename_results_code = abspath(tempfile.NamedTemporaryFile(dir=tmp_dir, prefix="metadata_results_%s_" % key).name) - # create the file on disk, so it cannot be reused by tempfile (unlikely, but possible) - json.dump((False, 'External set_meta() not called'), open(metadata_files.filename_results_code, 'wt+')) + metadata_files.filename_results_code = filename_results_code + # file to store kwds passed to set_meta() - metadata_files.filename_kwds = abspath(tempfile.NamedTemporaryFile(dir=tmp_dir, prefix="metadata_kwds_%s_" % key).name) - json.dump(kwds, open(metadata_files.filename_kwds, 'wt+'), ensure_ascii=True) + metadata_files.filename_kwds = filename_kwds + # existing metadata file parameters need to be overridden with cluster-writable file locations - metadata_files.filename_override_metadata = abspath(tempfile.NamedTemporaryFile(dir=tmp_dir, prefix="metadata_override_%s_" % key).name) - open(metadata_files.filename_override_metadata, 'wt+') # create the file on disk, so it cannot be reused by tempfile (unlikely, but possible) - override_metadata = [] - for meta_key, spec_value in dataset.metadata.spec.items(): - if isinstance(spec_value.param, FileParameter) and dataset.metadata.get(meta_key, None) is not None: - metadata_temp = MetadataTempFile() - metadata_temp.tmp_dir = tmp_dir - shutil.copy(dataset.metadata.get(meta_key, None).file_name, metadata_temp.file_name) - override_metadata.append((meta_key, metadata_temp.to_JSON())) - json.dump(override_metadata, open(metadata_files.filename_override_metadata, 'wt+')) + metadata_files.filename_override_metadata = filename_override_metadata + # add to session and flush sa_session.add(metadata_files) sa_session.flush() @@ -217,20 +293,18 @@ class JobExternalOutputMetadataWrapper(MetadataCollectionStrategy): # return command required to build fd, fp = tempfile.mkstemp(suffix='.py', dir=tmp_dir, prefix="set_metadata_") metadata_script_file = abspath(fp) - os.fdopen(fd, 'w').write('from galaxy_ext.metadata.set_metadata import set_metadata; set_metadata()') + with os.fdopen(fd, 'w') as f: + f.write(SET_METADATA_SCRIPT) return 'python "%s" %s' % (metadata_path_on_compute(metadata_script_file), args) else: # return args to galaxy_ext.metadata.set_metadata required to build return args - def external_metadata_set_successfully(self, dataset, sa_session): + def external_metadata_set_successfully(self, dataset, name, sa_session, working_directory): metadata_files = self._get_output_filenames_by_dataset(dataset, sa_session) if not metadata_files: return False # this file doesn't exist - rval, rstring = json.load(open(metadata_files.filename_results_code)) - if not rval: - log.debug('setting metadata externally failed for %s %s: %s' % (dataset.__class__.__name__, dataset.id, rstring)) - return rval + return self._metadata_results_from_file(dataset, metadata_files.filename_results_code) def cleanup_external_metadata(self, sa_session): log.debug('Cleaning up external metadata files') @@ -262,14 +336,65 @@ class JobExternalOutputMetadataWrapper(MetadataCollectionStrategy): # the metadata that was stored to disk for use via the external process, # and the changes made by the user will be lost, without warning or notice output_filename = self._get_output_filenames_by_dataset(dataset, sa_session).filename_out + self._load_metadata_from_path(dataset, output_filename, working_directory, remote_metadata_directory) - def path_rewriter(path): - if not path: - return path - normalized_remote_metadata_directory = remote_metadata_directory and os.path.normpath(remote_metadata_directory) - normalized_path = os.path.normpath(path) - if remote_metadata_directory and normalized_path.startswith(normalized_remote_metadata_directory): - return normalized_path.replace(normalized_remote_metadata_directory, working_directory, 1) - return path - dataset.metadata.from_JSON_dict(output_filename, path_rewriter=path_rewriter) +def _initialize_metadata_inputs(dataset, path_for_part, tmp_dir, kwds): + filename_in = path_for_part("in") + filename_out = path_for_part("out") + filename_results_code = path_for_part("results") + filename_kwds = path_for_part("kwds") + filename_override_metadata = path_for_part("override") + + _dump_dataset_instance_to(dataset, filename_in) + open(filename_out, 'wt+') # create the file on disk, so it cannot be reused by tempfile (unlikely, but possible) + # create the file on disk, so it cannot be reused by tempfile (unlikely, but possible) + json.dump((False, 'External set_meta() not called'), open(filename_results_code, 'wt+')) + json.dump(kwds, open(filename_kwds, 'wt+'), ensure_ascii=True) + + override_metadata = [] + for meta_key, spec_value in dataset.metadata.spec.items(): + if isinstance(spec_value.param, FileParameter) and dataset.metadata.get(meta_key, None) is not None: + metadata_temp = MetadataTempFile() + metadata_temp.tmp_dir = tmp_dir + shutil.copy(dataset.metadata.get(meta_key, None).file_name, metadata_temp.file_name) + override_metadata.append((meta_key, metadata_temp.to_JSON())) + json.dump(override_metadata, open(filename_override_metadata, 'wt+')) + + return filename_in, filename_out, filename_results_code, filename_kwds, filename_override_metadata + + +def _assert_datatypes_config(datatypes_config): + if datatypes_config is None: + raise Exception('In setup_external_metadata, the received datatypes_config is None.') + + +def _dump_dataset_instance_to(dataset_instance, file_path): + # FIXME: HACK + # sqlalchemy introduced 'expire_on_commit' flag for sessionmaker at version 0.5x + # This may be causing the dataset attribute of the dataset_association object to no-longer be loaded into memory when needed for pickling. + # For now, we'll simply 'touch' dataset_association.dataset to force it back into memory. + dataset_instance.dataset # force dataset_association.dataset to be loaded before pickling + # A better fix could be setting 'expire_on_commit=False' on the session, or modifying where commits occur, or ? + + # Touch also deferred column + dataset_instance._metadata + + cPickle.dump(dataset_instance, open(file_path, 'wb+')) + + +def _get_filename_override(output_fnames, file_name): + if output_fnames: + for dataset_path in output_fnames: + if dataset_path.real_path == file_name: + return dataset_path.false_path or dataset_path.real_path + return "" + + +def _init_tmp_dir(tmp_dir): + assert tmp_dir is not None + + if not os.path.exists(tmp_dir): + os.makedirs(tmp_dir) + + return tmp_dir diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 5f8044ef78d..6edef04fddf 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -19,6 +19,7 @@ from datetime import datetime, timedelta from string import Template from uuid import UUID, uuid4 +from boltons.iterutils import remap from six import string_types from social_core.storage import AssociationMixin, CodeMixin, NonceMixin, PartialMixin, UserMixin from sqlalchemy import ( @@ -43,21 +44,22 @@ from sqlalchemy.schema import UniqueConstraint import galaxy.model.metadata import galaxy.model.orm.now +import galaxy.model.tags import galaxy.security.passwords import galaxy.util -from galaxy.managers import tags -from galaxy.model.item_attrs import UsesAnnotations +from galaxy.model.item_attrs import get_item_annotation_str, UsesAnnotations from galaxy.model.util import pgcalc from galaxy.security import get_permitted_actions from galaxy.util import (directory_hash_id, ready_name_for_url, unicodify, unique_id) from galaxy.util.bunch import Bunch -from galaxy.util.dictifiable import Dictifiable +from galaxy.util.dictifiable import dict_for, Dictifiable +from galaxy.util.form_builder import (AddressField, CheckboxField, HistoryField, + PasswordField, SelectField, TextArea, TextField, WorkflowField, + WorkflowMappingField) from galaxy.util.hash_util import new_secure_hash +from galaxy.util.json import safe_loads from galaxy.util.sanitize_html import sanitize_html -from galaxy.web.form_builder import (AddressField, CheckboxField, HistoryField, - PasswordField, SelectField, TextArea, TextField, WorkflowField, - WorkflowMappingField) log = logging.getLogger(__name__) @@ -148,6 +150,48 @@ class HasTags(object): return [t for t in self.tags if t.user_tname in AUTO_PROPAGATED_TAGS] +class SerializationOptions(object): + + def __init__(self, for_edit, serialize_dataset_objects=None, serialize_files_handler=None): + self.for_edit = for_edit + if serialize_dataset_objects is None: + serialize_dataset_objects = for_edit + self.serialize_dataset_objects = serialize_dataset_objects + self.serialize_files_handler = serialize_files_handler + + def attach_identifier(self, id_encoder, obj, ret_val): + if self.for_edit and obj.id: + ret_val["id"] = obj.id + elif obj.id: + ret_val["encoded_id"] = id_encoder.encode_id(obj.id, kind='model_export') + else: + if not hasattr(obj, "temp_id"): + obj.temp_id = uuid4().hex + ret_val["encoded_id"] = obj.temp_id + + def get_identifier(self, id_encoder, obj): + if self.for_edit and obj.id: + return obj.id + elif obj.id: + return id_encoder.encode_id(obj.id, kind='model_export') + else: + if not hasattr(obj, "temp_id"): + obj.temp_id = uuid4().hex + return obj.temp_id + + def get_identifier_for_id(self, id_encoder, obj_id): + if self.for_edit and obj_id: + return obj_id + elif obj_id: + return id_encoder.encode_id(obj_id, kind='model_export') + else: + raise NotImplementedError() + + def serialize_files(self, dataset, as_dict): + if self.serialize_files_handler is not None: + self.serialize_files_handler.serialize_files(dataset, as_dict) + + class HasName(object): def get_display_name(self): @@ -173,6 +217,12 @@ class UsesCreateAndUpdateTime(object): return (galaxy.model.orm.now.now() - create_time).total_seconds() +class WorkerProcess(UsesCreateAndUpdateTime): + + def __init__(self, server_name): + self.server_name = server_name + + def cached_id(galaxy_model_object): """Get model object id attribute without a firing a database query. @@ -230,17 +280,28 @@ class JobLike(object): # TODO: Make iterable, concatenate with chain return self.text_metrics + self.numeric_metrics - def set_streams(self, stdout, stderr): - stdout = galaxy.util.unicodify(stdout) or u'' - stderr = galaxy.util.unicodify(stderr) or u'' - if (len(stdout) > galaxy.util.DATABASE_MAX_STRING_SIZE): - stdout = galaxy.util.shrink_string_by_size(stdout, galaxy.util.DATABASE_MAX_STRING_SIZE, join_by="\n..\n", left_larger=True, beginning_on_size_error=True) - log.info("stdout for %s %d is greater than %s, only a portion will be logged to database", type(self), self.id, galaxy.util.DATABASE_MAX_STRING_SIZE_PRETTY) - self.stdout = stdout - if (len(stderr) > galaxy.util.DATABASE_MAX_STRING_SIZE): - stderr = galaxy.util.shrink_string_by_size(stderr, galaxy.util.DATABASE_MAX_STRING_SIZE, join_by="\n..\n", left_larger=True, beginning_on_size_error=True) - log.info("stderr for %s %d is greater than %s, only a portion will be logged to database", type(self), self.id, galaxy.util.DATABASE_MAX_STRING_SIZE_PRETTY) - self.stderr = stderr + def set_streams(self, tool_stdout, tool_stderr, job_stdout=None, job_stderr=None, job_messages=None): + def shrink_and_unicodify(what, stream): + stream = galaxy.util.unicodify(stream) or u'' + if (len(stream) > galaxy.util.DATABASE_MAX_STRING_SIZE): + stream = galaxy.util.shrink_string_by_size(tool_stdout, galaxy.util.DATABASE_MAX_STRING_SIZE, join_by="\n..\n", left_larger=True, beginning_on_size_error=True) + log.info("%s for %s %d is greater than %s, only a portion will be logged to database", what, type(self), self.id, galaxy.util.DATABASE_MAX_STRING_SIZE_PRETTY) + return stream + + self.tool_stdout = shrink_and_unicodify('tool_stdout', tool_stdout) + self.tool_stderr = shrink_and_unicodify('tool_stderr', tool_stderr) + if job_stdout is not None: + self.job_stdout = shrink_and_unicodify('job_stdout', job_stdout) + else: + self.job_stdout = None + + if job_stderr is not None: + self.job_stderr = shrink_and_unicodify('job_stderr', job_stderr) + else: + self.job_stderr = None + + if job_messages is not None: + self.job_messages = job_messages def log_str(self): extra = "" @@ -252,6 +313,27 @@ class JobLike(object): return "%s[%s,tool_id=%s]" % (self.__class__.__name__, extra, self.tool_id) + def get_stdout(self): + stdout = self.tool_stdout + if self.job_stdout: + stdout += "\n" + self.job_stdout + return stdout + + def set_stdout(self, stdout): + raise NotImplementedError("Attempt to set stdout, must set tool_stdout or job_stdout") + + def get_stderr(self): + stderr = self.tool_stderr + if self.job_stderr: + stderr += "\n" + self.job_stderr + return stderr + + def set_stderr(self, stderr): + raise NotImplementedError("Attempt to set stdout, must set tool_stderr or job_stderr") + + stdout = property(get_stdout, set_stdout) + stderr = property(get_stderr, set_stderr) + class User(Dictifiable, RepresentById): use_pbkdf2 = True @@ -518,6 +600,24 @@ class PasswordResetToken(object): self.expiration_time = galaxy.model.orm.now.now() + timedelta(hours=24) +class DynamicTool(Dictifiable): + dict_collection_visible_keys = ('id', 'tool_id', 'tool_format', 'tool_version', 'uuid', 'active', 'hidden') + dict_element_visible_keys = ('id', 'tool_id', 'tool_format', 'tool_version', 'uuid', 'active', 'hidden') + + def __init__(self, tool_format=None, tool_id=None, tool_version=None, + uuid=None, active=True, hidden=True, value=None): + self.tool_format = tool_format + self.tool_id = tool_id + self.tool_version = tool_version + self.active = active + self.hidden = hidden + self.value = value + if uuid is None: + self.uuid = uuid4() + else: + self.uuid = UUID(str(uuid)) + + class BaseJobMetric(object): def __init__(self, plugin, metric_name, metric_value): @@ -607,6 +707,7 @@ class Job(JobLike, UsesCreateAndUpdateTime, Dictifiable, RepresentById): self.imported = False self.handler = None self.exit_code = None + self.job_messages = None self._init_metrics() self.state_history.append(JobStateHistory(self)) @@ -620,6 +721,16 @@ class Job(JobLike, UsesCreateAndUpdateTime, Dictifiable, RepresentById): states.DELETED_NEW, ] + def io_dicts(self): + inp_data = dict([(da.name, da.dataset) for da in self.input_datasets]) + out_data = dict([(da.name, da.dataset) for da in self.output_datasets]) + inp_data.update([(da.name, da.dataset) for da in self.input_library_datasets]) + out_data.update([(da.name, da.dataset) for da in self.output_library_datasets]) + + out_collections = dict([(obj.name, obj.dataset_collection_instance) for obj in self.output_dataset_collection_instances]) + out_collections.update([(obj.name, obj.dataset_collection) for obj in self.output_dataset_collections]) + return inp_data, out_data, out_collections + # TODO: Add accessors for members defined in SQL Alchemy for the Job table and # for the mapper defined to the Job table. def get_external_output_metadata(self): @@ -888,9 +999,49 @@ class Job(JobLike, UsesCreateAndUpdateTime, Dictifiable, RepresentById): if flush: object_session(self).flush() + def serialize(self, id_encoder, serialization_options): + job_attrs = dict_for(self) + serialization_options.attach_identifier(id_encoder, self, job_attrs) + job_attrs['tool_id'] = self.tool_id + job_attrs['tool_version'] = self.tool_version + job_attrs['state'] = self.state + job_attrs['info'] = self.info + job_attrs['traceback'] = self.traceback + job_attrs['command_line'] = self.command_line + job_attrs['tool_stderr'] = self.tool_stderr + job_attrs['job_stderr'] = self.job_stderr + job_attrs['tool_stdout'] = self.tool_stdout + job_attrs['job_stdout'] = self.job_stdout + job_attrs['exit_code'] = self.exit_code + job_attrs['create_time'] = self.create_time.isoformat() + job_attrs['update_time'] = self.update_time.isoformat() + + # Get the job's parameters + param_dict = self.raw_param_dict() + params_objects = {} + for key in param_dict: + params_objects[key] = safe_loads(param_dict[key]) + + def remap_objects(p, k, obj): + if isinstance(obj, dict) and "src" in obj and obj["src"] in ["hda", "hdca"]: + new_id = serialization_options.get_identifier_for_id(id_encoder, obj["id"]) + new_obj = obj.copy() + new_obj["id"] = new_id + return (k, new_obj) + return (k, obj) + + params_objects = remap(params_objects, remap_objects) + + params_dict = {} + for name, value in params_objects.items(): + params_dict[name] = value + job_attrs['params'] = params_dict + return job_attrs + def to_dict(self, view='collection', system_details=False): rval = super(Job, self).to_dict(view=view) rval['tool_id'] = self.tool_id + rval['history_id'] = self.history_id if system_details: # System level details that only admins should have. rval['external_id'] = self.job_runner_external_id @@ -980,8 +1131,6 @@ class Task(JobLike, RepresentById): self.task_runner_name = None self.task_runner_external_id = None self.job = job - self.stdout = "" - self.stderr = "" self.exit_code = None self.prepare_input_files_cmd = prepare_files_cmd self._init_metrics() @@ -1027,12 +1176,6 @@ class Task(JobLike, RepresentById): def get_job(self): return self.job - def get_stdout(self): - return self.stdout - - def get_stderr(self): - return self.stderr - def get_prepare_input_files_cmd(self): return self.prepare_input_files_cmd @@ -1109,12 +1252,6 @@ class Task(JobLike, RepresentById): def set_job(self, job): self.job = job - def set_stdout(self, stdout): - self.stdout = stdout - - def set_stderr(self, stderr): - self.stderr = stderr - def set_prepare_input_files_cmd(self, prepare_input_files_cmd): self.prepare_input_files_cmd = prepare_input_files_cmd @@ -1209,6 +1346,15 @@ class ImplicitCollectionJobs(RepresentById): def job_list(self): return [icjja.job for icjja in self.jobs] + def serialize(self, id_encoder, serialization_options): + rval = dict_for( + self, + populated_state=self.populated_state, + jobs=[serialization_options.get_identifier(id_encoder, j_a.job) for j_a in self.jobs] + ) + serialization_options.attach_identifier(id_encoder, self, rval) + return rval + class ImplicitCollectionJobsJobAssociation(RepresentById): @@ -1254,15 +1400,16 @@ class JobExternalOutputMetadata(RepresentById): class JobExportHistoryArchive(RepresentById): def __init__(self, job=None, history=None, dataset=None, compressed=False, - history_attrs_filename=None, datasets_attrs_filename=None, - jobs_attrs_filename=None): + history_attrs_filename=None): self.job = job self.history = history self.dataset = dataset self.compressed = compressed self.history_attrs_filename = history_attrs_filename - self.datasets_attrs_filename = datasets_attrs_filename - self.jobs_attrs_filename = jobs_attrs_filename + + @property + def temp_directory(self): + return os.path.split(self.history_attrs_filename)[0] @property def up_to_date(self): @@ -1559,6 +1706,21 @@ class History(HasTags, Dictifiable, UsesAnnotations, HasName, RepresentById): # This needs to be a list return [hda for hda in self.datasets if not hda.dataset.deleted] + def serialize(self, id_encoder, serialization_options): + + history_attrs = dict_for( + self, + create_time=self.create_time.__str__(), + update_time=self.update_time.__str__(), + name=unicodify(self.name), + hid_counter=self.hid_counter, + genome_build=self.genome_build, + annotation=unicodify(get_item_annotation_str(object_session(self), self.user, self)), + tags=self.make_tag_string_list(), + ) + serialization_options.attach_identifier(id_encoder, self, history_attrs) + return history_attrs + def to_dict(self, view='collection', value_mapper=None): # Get basic value. @@ -1911,8 +2073,12 @@ class DefaultHistoryPermissions(RepresentById): class StorableObject(object): - def __init__(self, id, **kwargs): + def __init__(self, id, uuid): self.id = id + if uuid is None: + self.uuid = uuid4() + else: + self.uuid = UUID(str(uuid)) class Dataset(StorableObject, RepresentById): @@ -1963,7 +2129,7 @@ class Dataset(StorableObject, RepresentById): engine = None def __init__(self, id=None, state=None, external_filename=None, extra_files_path=None, file_size=None, purgable=True, uuid=None): - super(Dataset, self).__init__(id=id) + super(Dataset, self).__init__(id=id, uuid=uuid) self.state = state self.deleted = False self.purged = False @@ -1972,17 +2138,14 @@ class Dataset(StorableObject, RepresentById): self.external_extra_files_path = None self._extra_files_path = extra_files_path self.file_size = file_size - if uuid is None: - self.uuid = uuid4() - else: - self.uuid = UUID(str(uuid)) + self.sources = [] + self.hashes = [] def in_ready_state(self): return self.state in self.ready_states def get_file_name(self): if not self.external_filename: - assert self.id is not None, "ID must be set before filename used (commit the object)" assert self.object_store is not None, "Object Store has not been initialized for dataset %s" % self.id filename = self.object_store.get_filename(self) return filename @@ -2123,6 +2286,47 @@ class Dataset(StorableObject, RepresentById): return True return False + def serialize(self, id_encoder, serialization_options): + # serialize Dataset objects only for jobs that can actually modify these models. + assert serialization_options.serialize_dataset_objects + rval = dict_for( + self, + state=self.state, + deleted=self.deleted, + purged=self.purged, + external_filename=self.external_filename, + _extra_files_path=self._extra_files_path, + file_size=self.file_size, + object_store_id=self.object_store_id, + total_size=self.total_size, + uuid=str(self.uuid or '') or None, + hashes=list(map(lambda h: h.serialize(id_encoder, serialization_options), self.hashes)) + ) + serialization_options.attach_identifier(id_encoder, self, rval) + return rval + + +class DatasetSource(RepresentById): + """ """ + + +class DatasetSourceHash(RepresentById): + """ """ + + +class DatasetHash(RepresentById): + """ """ + def serialize(self, id_encoder, serialization_options): + # serialize Dataset objects only for jobs that can actually modify these models. + rval = dict_for( + self, + hash_function=self.hash_function, + hash_value=self.hash_value, + extra_files_path=self.extra_files_path, + ) + serialization_options.attach_identifier(id_encoder, self, rval) + return rval + def datatype_for_extension(extension, datatypes_registry=None): if datatypes_registry is None: @@ -2580,6 +2784,39 @@ class DatasetInstance(object): return msg + def serialize(self, id_encoder, serialization_options, for_link=False): + if for_link: + rval = dict_for( + self + ) + serialization_options.attach_identifier(id_encoder, self, rval) + return rval + + rval = dict_for( + self, + create_time=self.create_time.__str__(), + update_time=self.update_time.__str__(), + name=unicodify(self.name), + info=unicodify(self.info), + blurb=self.blurb, + peek=self.peek, + extension=self.extension, + metadata=_prepare_metadata_for_serialization(dict(self.metadata.items())), + designation=self.designation, + deleted=self.deleted, + visible=self.visible, + dataset_uuid=(lambda uuid: str(uuid) if uuid else None)(self.dataset.uuid), + ) + + serialization_options.attach_identifier(id_encoder, self, rval) + return rval + + def _handle_serialize_files(self, id_encoder, serialization_options, rval): + if serialization_options.serialize_dataset_objects: + rval["dataset"] = self.dataset.serialize(id_encoder, serialization_options) + else: + serialization_options.serialize_files(self, rval) + class HistoryDatasetAssociation(DatasetInstance, HasTags, Dictifiable, UsesAnnotations, HasName, RepresentById): @@ -2786,6 +3023,31 @@ class HistoryDatasetAssociation(DatasetInstance, HasTags, Dictifiable, UsesAnnot rval += self.get_total_size() return rval + def serialize(self, id_encoder, serialization_options, for_link=False): + if for_link: + rval = dict_for( + self + ) + serialization_options.attach_identifier(id_encoder, self, rval) + return rval + + rval = super(HistoryDatasetAssociation, self).serialize(id_encoder, serialization_options) + rval["hid"] = self.hid + rval["annotation"] = unicodify(getattr(self, 'annotation', '')) + rval["tags"] = self.make_tag_string_list() + if self.history: + rval["history_encoded_id"] = serialization_options.get_identifier(id_encoder, self.history) + + # Handle copied_from_history_dataset_association information... + copied_from_history_dataset_association_chain = [] + src_hda = self + while src_hda.copied_from_history_dataset_association: + src_hda = src_hda.copied_from_history_dataset_association + copied_from_history_dataset_association_chain.append(serialization_options.get_identifier(id_encoder, src_hda)) + rval["copied_from_history_dataset_association_id_chain"] = copied_from_history_dataset_association_chain + self._handle_serialize_files(id_encoder, serialization_options, rval) + return rval + def to_dict(self, view='collection', expose_dataset_path=False): """ Return attributes of this HDA that are exposed using the API. @@ -2903,6 +3165,19 @@ class Library(Dictifiable, HasName, RepresentById): self.synopsis = synopsis self.root_folder = root_folder + def serialize(self, id_encoder, serialization_options): + rval = dict_for( + self, + name=self.name, + description=self.description, + synopsis=self.synopsis, + ) + if self.root_folder: + rval["root_folder"] = self.root_folder.serialize(id_encoder, serialization_options) + + serialization_options.attach_identifier(id_encoder, self, rval) + return rval + def to_dict(self, view='collection', value_mapper=None): """ We prepend an F to folders. @@ -2946,12 +3221,14 @@ class Library(Dictifiable, HasName, RepresentById): class LibraryFolder(Dictifiable, HasName, RepresentById): dict_element_visible_keys = ['id', 'parent_id', 'name', 'description', 'item_count', 'genome_build', 'update_time', 'deleted'] - def __init__(self, name=None, description=None, item_count=0, order_id=None): + def __init__(self, name=None, description=None, item_count=0, order_id=None, genome_build=None): self.name = name or "Unnamed folder" self.description = description self.item_count = item_count self.order_id = order_id - self.genome_build = None + self.genome_build = genome_build + self.folders = [] + self.datasets = [] def add_library_dataset(self, library_dataset, genome_build=None): library_dataset.folder_id = self.id @@ -2970,6 +3247,28 @@ class LibraryFolder(Dictifiable, HasName, RepresentById): # This needs to be a list return [ld for ld in self.datasets if ld.library_dataset_dataset_association and not ld.library_dataset_dataset_association.dataset.deleted] + def serialize(self, id_encoder, serialization_options): + rval = dict_for( + self, + name=self.name, + description=self.description, + genome_build=self.genome_build, + item_count=self.item_count, + order_id=self.order_id, + # update_time=self.update_time, + deleted=self.deleted, + ) + folders = [] + for folder in self.folders: + folders.append(folder.serialize(id_encoder, serialization_options)) + rval["folders"] = folders + datasets = [] + for dataset in self.datasets: + datasets.append(dataset.serialize(id_encoder, serialization_options)) + rval['datasets'] = datasets + serialization_options.attach_identifier(id_encoder, self, rval) + return rval + def to_dict(self, view='collection', value_mapper=None): rval = super(LibraryFolder, self).to_dict(view=view, value_mapper=value_mapper) rval['library_path'] = self.library_path @@ -3040,6 +3339,17 @@ class LibraryDataset(RepresentById): def display_name(self): self.library_dataset_dataset_association.display_name() + def serialize(self, id_encoder, serialization_options): + rval = dict_for( + self, + name=self.name, + info=self.info, + order_id=self.order_id, + ldda=self.library_dataset_dataset_association.serialize(id_encoder, serialization_options, for_link=True), + ) + serialization_options.attach_identifier(id_encoder, self, rval) + return rval + def to_dict(self, view='collection'): # Since this class is a proxy to rather complex attributes we want to # display in other objects, we can't use the simpler method used by @@ -3111,7 +3421,7 @@ class LibraryDatasetDatasetAssociation(DatasetInstance, HasName, RepresentById): copied_from_library_dataset_dataset_association=self, history=target_history) - tag_manager = tags.GalaxyTagManager(sa_session) + tag_manager = galaxy.model.tags.GalaxyTagHandler(sa_session) src_ldda_tags = tag_manager.get_tags_str(self.tags) tag_manager.apply_item_tags(user=self.user, item=hda, tags_str=src_ldda_tags) @@ -3141,7 +3451,7 @@ class LibraryDatasetDatasetAssociation(DatasetInstance, HasName, RepresentById): copied_from_library_dataset_dataset_association=self, folder=target_folder) - tag_manager = tags.GalaxyTagManager(sa_session) + tag_manager = galaxy.model.tags.GalaxyTagHandler(sa_session) src_ldda_tags = tag_manager.get_tags_str(self.tags) tag_manager.apply_item_tags(user=self.user, item=ldda, tags_str=src_ldda_tags) @@ -3167,6 +3477,18 @@ class LibraryDatasetDatasetAssociation(DatasetInstance, HasName, RepresentById): def has_manage_permissions_roles(self, trans): return self.dataset.has_manage_permissions_roles(trans) + def serialize(self, id_encoder, serialization_options, for_link=False): + if for_link: + rval = dict_for( + self + ) + serialization_options.attach_identifier(id_encoder, self, rval) + return rval + + rval = super(LibraryDatasetDatasetAssociation, self).serialize(id_encoder, serialization_options) + self._handle_serialize_files(id_encoder, serialization_options, rval) + return rval + def to_dict(self, view='collection'): # Since this class is a proxy to rather complex attributes we want to # display in other objects, we can't use the simpler method used by @@ -3459,14 +3781,13 @@ class DatasetCollection(Dictifiable, UsesAnnotations, RepresentById): self.populated_state = DatasetCollection.populated_states.FAILED self.populated_state_message = message - def finalize(self): + def finalize(self, collection_type_description): # All jobs have written out their elements - everything should be populated # but might not be - check that second case! (TODO) self.mark_as_populated() - if self.has_subcollections: - # THIS IS WRONG - SHOULD ONLY BE TO THE DEPTH OF THE MAP OVER. + if self.has_subcollections and collection_type_description.has_subcollections(): for element in self.elements: - element.child_collection.finalize() + element.child_collection.finalize(collection_type_description.child_collection_type_description()) @property def dataset_instances(self): @@ -3548,6 +3869,16 @@ class DatasetCollection(Dictifiable, UsesAnnotations, RepresentById): def has_subcollections(self): return ":" in self.collection_type + def serialize(self, id_encoder, serialization_options): + rval = dict_for( + self, + type=self.collection_type, + populated_state=self.populated_state, + elements=list(map(lambda e: e.serialize(id_encoder, serialization_options), self.elements)) + ) + serialization_options.attach_identifier(id_encoder, self, rval) + return rval + class DatasetCollectionInstance(HasName): """ @@ -3687,6 +4018,45 @@ class HistoryDatasetCollectionAssociation(DatasetCollectionInstance, if len(rval) > 0: return rval if multiple else rval[0] + def serialize(self, id_encoder, serialization_options, for_link=False): + if for_link: + rval = dict_for( + self + ) + serialization_options.attach_identifier(id_encoder, self, rval) + return rval + + rval = dict_for( + self, + display_name=self.display_name(), + state=self.state, + hid=self.hid, + collection=self.collection.serialize(id_encoder, serialization_options), + implicit_output_name=self.implicit_output_name, + ) + if self.history: + rval["history_encoded_id"] = serialization_options.get_identifier(id_encoder, self.history) + + implicit_input_collections = [] + for implicit_input_collection in self.implicit_input_collections: + input_hdca = implicit_input_collection.input_dataset_collection + implicit_input_collections.append({ + "name": implicit_input_collection.name, + "input_dataset_collection": serialization_options.get_identifier(id_encoder, input_hdca) + }) + if implicit_input_collections: + rval["implicit_input_collections"] = implicit_input_collections + + # Handle copied_from_history_dataset_association information... + copied_from_history_dataset_collection_association_chain = [] + src_hdca = self + while src_hdca.copied_from_history_dataset_collection_association: + src_hdca = src_hdca.copied_from_history_dataset_collection_association + copied_from_history_dataset_collection_association_chain.append(serialization_options.get_identifier(id_encoder, src_hdca)) + rval["copied_from_history_dataset_collection_association_id_chain"] = copied_from_history_dataset_collection_association_chain + serialization_options.attach_identifier(id_encoder, self, rval) + return rval + def to_dict(self, view='collection'): original_dict_value = super(HistoryDatasetCollectionAssociation, self).to_dict(view=view) dict_value = dict( @@ -3880,6 +4250,21 @@ class DatasetCollectionElement(Dictifiable, RepresentById): ) return new_element + def serialize(self, id_encoder, serialization_options): + rval = dict_for( + self, + element_type=self.element_type, + element_index=self.element_index, + element_identifier=self.element_identifier + ) + serialization_options.attach_identifier(id_encoder, self, rval) + element_obj = self.element_object + if isinstance(element_obj, HistoryDatasetAssociation): + rval["hda"] = element_obj.serialize(id_encoder, serialization_options, for_link=True) + else: + rval["child_collection"] = element_obj.serialize(id_encoder, serialization_options) + return rval + class Event(RepresentById): def __init__(self, message=None, history=None, user=None, galaxy_session=None): @@ -4049,7 +4434,7 @@ class Workflow(Dictifiable, RepresentById): top_level_workflow = self if self.stored_workflow is None: # TODO: enforce this at creation... - assert len(self.parent_workflow_steps) == 1 + assert len(set(w.uuid for w in self.parent_workflow_steps)) == 1 return self.parent_workflow_steps[0].workflow.top_level_workflow return top_level_workflow @@ -4099,6 +4484,7 @@ class WorkflowStep(RepresentById): self.tool_id = None self.tool_inputs = None self.tool_errors = None + self.dynamic_tool = None self.position = None self.inputs = [] self.config = None @@ -4107,6 +4493,10 @@ class WorkflowStep(RepresentById): self.workflow_outputs = [] self._input_connections_by_name = None + @property + def tool_uuid(self): + return self.dynamic_tool and self.dynamic_tool.uuid + def get_input(self, input_name): for step_input in self.inputs: if step_input.name == input_name: @@ -4743,8 +5133,8 @@ class WorkflowInvocationStepOutputDatasetCollectionAssociation(Dictifiable, Repr class MetadataFile(StorableObject, RepresentById): - def __init__(self, dataset=None, name=None): - super(MetadataFile, self).__init__(id=None) + def __init__(self, dataset=None, name=None, uuid=None): + super(MetadataFile, self).__init__(id=None, uuid=uuid) if isinstance(dataset, HistoryDatasetAssociation): self.history_dataset = dataset elif isinstance(dataset, LibraryDatasetDatasetAssociation): @@ -4753,17 +5143,21 @@ class MetadataFile(StorableObject, RepresentById): @property def file_name(self): - assert self.id is not None, "ID must be set before filename used (commit the object)" # Ensure the directory structure and the metadata file object exist try: da = self.history_dataset or self.library_dataset if self.object_store_id is None and da is not None: self.object_store_id = da.dataset.object_store_id - if not da.dataset.object_store.exists(self, extra_dir='_metadata_files', extra_dir_at_root=True, alt_name="metadata_%d.dat" % self.id): - da.dataset.object_store.create(self, extra_dir='_metadata_files', extra_dir_at_root=True, alt_name="metadata_%d.dat" % self.id) - path = da.dataset.object_store.get_filename(self, extra_dir='_metadata_files', extra_dir_at_root=True, alt_name="metadata_%d.dat" % self.id) + object_store = da.dataset.object_store + store_by = object_store.store_by + identifier = getattr(self, store_by) + alt_name = "metadata_%s.dat" % identifier + if not object_store.exists(self, extra_dir='_metadata_files', extra_dir_at_root=True, alt_name=alt_name): + object_store.create(self, extra_dir='_metadata_files', extra_dir_at_root=True, alt_name=alt_name) + path = object_store.get_filename(self, extra_dir='_metadata_files', extra_dir_at_root=True, alt_name=alt_name) return path except AttributeError: + assert self.id is not None, "ID must be set before MetadataFile used without an HDA/LDDA (commit the object)" # In case we're not working with the history_dataset path = os.path.join(Dataset.file_path, '_metadata_files', *directory_hash_id(self.id)) # Create directory if it does not exist @@ -5065,6 +5459,21 @@ class UserAuthnzToken(UserMixin, RepresentById): return instance +class CustosAuthnzToken(RepresentById): + + def __init__(self, user, external_user_id, provider, access_token, id_token, refresh_token, expiration_time, refresh_expiration_time): + self.id = None + # 'user' is a backref to model.User + self.user = user + self.external_user_id = external_user_id + self.provider = provider + self.access_token = access_token + self.id_token = id_token + self.refresh_token = refresh_token + self.expiration_time = expiration_time + self.refresh_expiration_time = refresh_expiration_time + + class CloudAuthz(RepresentById): def __init__(self, user_id, provider, config, authn_id, description=""): self.id = None @@ -5433,3 +5842,13 @@ def copy_list(lst, *args, **kwds): return lst else: return [el.copy(*args, **kwds) for el in lst] + + +def _prepare_metadata_for_serialization(metadata): + """ Prepare metatdata for exporting. """ + for name, value in list(metadata.items()): + # Metadata files are not needed for export because they can be + # regenerated. + if isinstance(value, MetadataFile): + del metadata[name] + return metadata diff --git a/lib/galaxy/dataset_collections/__init__.py b/lib/galaxy/model/dataset_collections/__init__.py similarity index 100% rename from lib/galaxy/dataset_collections/__init__.py rename to lib/galaxy/model/dataset_collections/__init__.py diff --git a/lib/galaxy/dataset_collections/builder.py b/lib/galaxy/model/dataset_collections/builder.py similarity index 71% rename from lib/galaxy/dataset_collections/builder.py rename to lib/galaxy/model/dataset_collections/builder.py index bdb21e75cbf..c24af953f94 100644 --- a/lib/galaxy/dataset_collections/builder.py +++ b/lib/galaxy/model/dataset_collections/builder.py @@ -1,5 +1,6 @@ from galaxy import model from galaxy.util.odict import odict +from .type_description import COLLECTION_TYPE_DESCRIPTION_FACTORY def build_collection(type, dataset_instances): @@ -35,6 +36,28 @@ class CollectionBuilder(object): self._collection_type_description = collection_type_description self._current_elements = odict() + def replace_elements_in_collection(self, template_collection, replacement_dict): + self._current_elements = self._replace_elements_in_collection( + template_collection=template_collection, + replacement_dict=replacement_dict, + ) + + def _replace_elements_in_collection(self, template_collection, replacement_dict): + elements = odict() + for element in template_collection.elements: + if element.is_collection: + collection_builder = CollectionBuilder( + collection_type_description=self._collection_type_description.child_collection_type_description() + ) + collection_builder.replace_elements_in_collection( + template_collection=element.child_collection, + replacement_dict=replacement_dict + ) + elements[element.element_identifier] = collection_builder + else: + elements[element.element_identifier] = replacement_dict.get(element.element_object, element.element_object) + return elements + def get_level(self, identifier): if not self._nested_collection: message_template = "Cannot add nested collection to collection of type [%s]" @@ -78,10 +101,12 @@ class CollectionBuilder(object): class BoundCollectionBuilder(CollectionBuilder): """ More stateful builder that is bound to a particular model object. """ - def __init__(self, dataset_collection, collection_type_description): + def __init__(self, dataset_collection): self.dataset_collection = dataset_collection if dataset_collection.populated: raise Exception("Cannot reset elements of an already populated dataset collection.") + collection_type = dataset_collection.collection_type + collection_type_description = COLLECTION_TYPE_DESCRIPTION_FACTORY.for_collection_type(collection_type) super(BoundCollectionBuilder, self).__init__(collection_type_description) def populate(self): diff --git a/lib/galaxy/dataset_collections/matching.py b/lib/galaxy/model/dataset_collections/matching.py similarity index 100% rename from lib/galaxy/dataset_collections/matching.py rename to lib/galaxy/model/dataset_collections/matching.py diff --git a/lib/galaxy/dataset_collections/registry.py b/lib/galaxy/model/dataset_collections/registry.py similarity index 89% rename from lib/galaxy/dataset_collections/registry.py rename to lib/galaxy/model/dataset_collections/registry.py index a95e47309ee..b10b182447a 100644 --- a/lib/galaxy/dataset_collections/registry.py +++ b/lib/galaxy/model/dataset_collections/registry.py @@ -9,7 +9,7 @@ PLUGIN_CLASSES = [list.ListDatasetCollectionType, paired.PairedDatasetCollection class DatasetCollectionTypesRegistry(object): - def __init__(self, app): + def __init__(self): self.__plugins = dict([(p.collection_type, p()) for p in PLUGIN_CLASSES]) def get(self, plugin_type): @@ -24,3 +24,6 @@ class DatasetCollectionTypesRegistry(object): elements = [e for e in plugin_type_object.prototype_elements()] dataset_collection.elements = elements return dataset_collection + + +DATASET_COLLECTION_TYPES_REGISTRY = DatasetCollectionTypesRegistry() diff --git a/lib/galaxy/dataset_collections/structure.py b/lib/galaxy/model/dataset_collections/structure.py similarity index 100% rename from lib/galaxy/dataset_collections/structure.py rename to lib/galaxy/model/dataset_collections/structure.py diff --git a/lib/galaxy/dataset_collections/subcollections.py b/lib/galaxy/model/dataset_collections/subcollections.py similarity index 100% rename from lib/galaxy/dataset_collections/subcollections.py rename to lib/galaxy/model/dataset_collections/subcollections.py diff --git a/lib/galaxy/dataset_collections/type_description.py b/lib/galaxy/model/dataset_collections/type_description.py similarity index 96% rename from lib/galaxy/dataset_collections/type_description.py rename to lib/galaxy/model/dataset_collections/type_description.py index e002247fab6..1f109d915fd 100644 --- a/lib/galaxy/dataset_collections/type_description.py +++ b/lib/galaxy/model/dataset_collections/type_description.py @@ -1,8 +1,9 @@ +from .registry import DATASET_COLLECTION_TYPES_REGISTRY class CollectionTypeDescriptionFactory(object): - def __init__(self, type_registry): + def __init__(self, type_registry=DATASET_COLLECTION_TYPES_REGISTRY): # taking in type_registry though not using it, because we will someday # I think. self.type_registry = type_registry @@ -136,3 +137,6 @@ def map_over_collection_type(mapped_over_collection_type, target_collection_type target_collection_type = target_collection_type.collection_type return "%s:%s" % (mapped_over_collection_type, target_collection_type) + + +COLLECTION_TYPE_DESCRIPTION_FACTORY = CollectionTypeDescriptionFactory() diff --git a/lib/galaxy/dataset_collections/types/__init__.py b/lib/galaxy/model/dataset_collections/types/__init__.py similarity index 100% rename from lib/galaxy/dataset_collections/types/__init__.py rename to lib/galaxy/model/dataset_collections/types/__init__.py diff --git a/lib/galaxy/dataset_collections/types/list.py b/lib/galaxy/model/dataset_collections/types/list.py similarity index 100% rename from lib/galaxy/dataset_collections/types/list.py rename to lib/galaxy/model/dataset_collections/types/list.py diff --git a/lib/galaxy/dataset_collections/types/paired.py b/lib/galaxy/model/dataset_collections/types/paired.py similarity index 100% rename from lib/galaxy/dataset_collections/types/paired.py rename to lib/galaxy/model/dataset_collections/types/paired.py diff --git a/lib/galaxy/model/item_attrs.py b/lib/galaxy/model/item_attrs.py index d07f2812b4d..bf32ed87a5a 100644 --- a/lib/galaxy/model/item_attrs.py +++ b/lib/galaxy/model/item_attrs.py @@ -8,10 +8,6 @@ import galaxy log = logging.getLogger(__name__) -class RuntimeException(Exception): - pass - - class UsesItemRatings(object): """ Mixin for getting and setting item ratings. @@ -28,7 +24,7 @@ class UsesItemRatings(object): webapp_model = galaxy.model item_rating_assoc_class = self._get_item_rating_assoc_class(item, webapp_model=webapp_model) if not item_rating_assoc_class: - raise RuntimeException("Item does not have ratings: %s" % item.__class__.__name__) + raise Exception("Item does not have ratings: %s" % item.__class__.__name__) item_id_filter = self._get_item_id_filter_str(item, item_rating_assoc_class) ave_rating = db_session.query(func.avg(item_rating_assoc_class.rating)).filter(item_id_filter).scalar() # Convert ave_rating to float; note: if there are no item ratings, ave rating is None. @@ -65,7 +61,7 @@ class UsesItemRatings(object): webapp_model = galaxy.model item_rating_assoc_class = self._get_item_rating_assoc_class(item, webapp_model=webapp_model) if not item_rating_assoc_class: - raise RuntimeException("Item does not have ratings: %s" % item.__class__.__name__) + raise Exception("Item does not have ratings: %s" % item.__class__.__name__) # Query rating table by user and item id. item_id_filter = self._get_item_id_filter_str(item, item_rating_assoc_class) @@ -82,15 +78,7 @@ class UsesItemRatings(object): # Get foreign key in item-rating association table that references item table. if webapp_model is None: webapp_model = galaxy.model - item_fk = None - for fk in item_rating_assoc_class.table.foreign_keys: - if fk.references(item.table): - item_fk = fk - break - - if not item_fk: - raise RuntimeException("Cannot find item id column in item-rating association table: %s, %s" % item_rating_assoc_class.__name__, item_rating_assoc_class.table.name) - + item_fk = get_foreign_key(item_rating_assoc_class, item) # TODO: can we provide a better filter than a raw string? return "%s=%i" % (item_fk.parent.name, item.id) @@ -99,64 +87,16 @@ class UsesAnnotations(object): """ Mixin for getting and setting item annotations. """ def get_item_annotation_str(self, db_session, user, item): - """ Returns a user's annotation string for an item. """ - if hasattr(item, 'annotations'): - # If we already have an annotations object we use it. - annotation_obj = None - for annotation in item.annotations: - if annotation.user == user: - annotation_obj = annotation - break - else: - annotation_obj = self.get_item_annotation_obj(db_session, user, item) - if annotation_obj: - return galaxy.util.unicodify(annotation_obj.annotation) - return None + return get_item_annotation_str(db_session, user, item) def get_item_annotation_obj(self, db_session, user, item): - """ Returns a user's annotation object for an item. """ - # Get annotation association class. - annotation_assoc_class = self._get_annotation_assoc_class(item) - if not annotation_assoc_class: - return None - - # Get annotation association object. - annotation_assoc = db_session.query(annotation_assoc_class).filter_by(user=user) - - # TODO: use filtering like that in _get_item_id_filter_str() - if item.__class__ == galaxy.model.History: - annotation_assoc = annotation_assoc.filter_by(history=item) - elif item.__class__ == galaxy.model.HistoryDatasetAssociation: - annotation_assoc = annotation_assoc.filter_by(hda=item) - elif item.__class__ == galaxy.model.HistoryDatasetCollectionAssociation: - annotation_assoc = annotation_assoc.filter_by(history_dataset_collection=item) - elif item.__class__ == galaxy.model.StoredWorkflow: - annotation_assoc = annotation_assoc.filter_by(stored_workflow=item) - elif item.__class__ == galaxy.model.WorkflowStep: - annotation_assoc = annotation_assoc.filter_by(workflow_step=item) - elif item.__class__ == galaxy.model.Page: - annotation_assoc = annotation_assoc.filter_by(page=item) - elif item.__class__ == galaxy.model.Visualization: - annotation_assoc = annotation_assoc.filter_by(visualization=item) - return annotation_assoc.first() + return get_item_annotation_obj(db_session, user, item) def add_item_annotation(self, db_session, user, item, annotation): - """ Add or update an item's annotation; a user can only have a single annotation for an item. """ - # Get/create annotation association object. - annotation_assoc = self.get_item_annotation_obj(db_session, user, item) - if not annotation_assoc: - annotation_assoc_class = self._get_annotation_assoc_class(item) - if not annotation_assoc_class: - return None - annotation_assoc = annotation_assoc_class() - item.annotations.append(annotation_assoc) - annotation_assoc.user = user - # Set annotation. - annotation_assoc.annotation = annotation - return annotation_assoc + return add_item_annotation(db_session, user, item, annotation) def delete_item_annotation(self, db_session, user, item): - annotation_assoc = self.get_item_annotation_obj(db_session, user, item) + annotation_assoc = get_item_annotation_obj(db_session, user, item) if annotation_assoc: db_session.delete(annotation_assoc) db_session.flush() @@ -170,14 +110,88 @@ class UsesAnnotations(object): return annotation return None - def _get_annotation_assoc_class(self, item): - """ Returns an item's item-annotation association class. """ - class_name = '%sAnnotationAssociation' % item.__class__.__name__ - return getattr(galaxy.model, class_name, None) + +def get_item_annotation_obj(db_session, user, item): + """Returns a user's annotation object for an item.""" + + # Get annotation association class. + annotation_assoc_class = _get_annotation_assoc_class(item) + if not annotation_assoc_class: + return None + + # Get annotation association object. + annotation_assoc = db_session.query(annotation_assoc_class).filter_by(user=user) + + # TODO: use filtering like that in _get_item_id_filter_str() + if item.__class__ == galaxy.model.History: + annotation_assoc = annotation_assoc.filter_by(history=item) + elif item.__class__ == galaxy.model.HistoryDatasetAssociation: + annotation_assoc = annotation_assoc.filter_by(hda=item) + elif item.__class__ == galaxy.model.HistoryDatasetCollectionAssociation: + annotation_assoc = annotation_assoc.filter_by(history_dataset_collection=item) + elif item.__class__ == galaxy.model.StoredWorkflow: + annotation_assoc = annotation_assoc.filter_by(stored_workflow=item) + elif item.__class__ == galaxy.model.WorkflowStep: + annotation_assoc = annotation_assoc.filter_by(workflow_step=item) + elif item.__class__ == galaxy.model.Page: + annotation_assoc = annotation_assoc.filter_by(page=item) + elif item.__class__ == galaxy.model.Visualization: + annotation_assoc = annotation_assoc.filter_by(visualization=item) + return annotation_assoc.first() + + +def get_item_annotation_str(db_session, user, item): + """ Returns a user's annotation string for an item. """ + if hasattr(item, 'annotations'): + # If we already have an annotations object we use it. + annotation_obj = None + for annotation in item.annotations: + if annotation.user == user: + annotation_obj = annotation + break + else: + annotation_obj = get_item_annotation_obj(db_session, user, item) + if annotation_obj: + return galaxy.util.unicodify(annotation_obj.annotation) + return None + + +def add_item_annotation(db_session, user, item, annotation): + """ Add or update an item's annotation; a user can only have a single annotation for an item. """ + # Get/create annotation association object. + annotation_assoc = get_item_annotation_obj(db_session, user, item) + if not annotation_assoc: + annotation_assoc_class = _get_annotation_assoc_class(item) + if not annotation_assoc_class: + return None + annotation_assoc = annotation_assoc_class() + item.annotations.append(annotation_assoc) + annotation_assoc.user = user + # Set annotation. + annotation_assoc.annotation = annotation + return annotation_assoc + + +def _get_annotation_assoc_class(item): + """ Returns an item's item-annotation association class. """ + class_name = '%sAnnotationAssociation' % item.__class__.__name__ + return getattr(galaxy.model, class_name, None) + + +def get_foreign_key(source_class, target_class): + """ Returns foreign key in source class that references target class. """ + target_fk = None + for fk in source_class.table.foreign_keys: + if fk.references(target_class.table): + target_fk = fk + break + if not target_fk: + raise Exception("No foreign key found between objects: %s, %s" % source_class.table, target_class.table) + return target_fk __all__ = ( + 'get_foreign_key', 'UsesAnnotations', 'UsesItemRatings', - 'RuntimeException', ) diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index 63f4daa52d9..54ec5ef59ee 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -40,13 +40,21 @@ from galaxy.model.base import ModelMapping from galaxy.model.custom_types import JSONType, MetadataType, TrimmedString, UUIDType from galaxy.model.orm.engine_factory import build_engine from galaxy.model.orm.now import now -from galaxy.security import GalaxyRBACAgent +from galaxy.model.security import GalaxyRBACAgent log = logging.getLogger(__name__) metadata = MetaData() +model.WorkerProcess.table = Table( + 'worker_process', + metadata, + Column('server_name', Text, primary_key=True), + Column("update_time", DateTime, default=now, onupdate=now), +) + + model.User.table = Table( "galaxy_user", metadata, Column("id", Integer, primary_key=True), @@ -123,6 +131,21 @@ model.UserAuthnzToken.table = Table( Column('lifetime', Integer), Column('assoc_type', VARCHAR(64))) +model.CustosAuthnzToken.table = Table( + "custos_authnz_token", metadata, + Column('id', Integer, primary_key=True), + Column('user_id', Integer, ForeignKey("galaxy_user.id")), + Column('external_user_id', String(64)), + Column('provider', String(255)), + Column('access_token', Text), + Column('id_token', Text), + Column('refresh_token', Text), + Column("expiration_time", DateTime), + Column("refresh_expiration_time", DateTime), + UniqueConstraint("user_id", "external_user_id", "provider"), + UniqueConstraint("external_user_id", "provider"), +) + model.CloudAuthz.table = Table( "cloudauthz", metadata, Column('id', Integer, primary_key=True), @@ -133,7 +156,8 @@ model.CloudAuthz.table = Table( Column('tokens', JSONType), Column('last_update', DateTime), Column('last_activity', DateTime), - Column('description', TEXT)) + Column('description', TEXT), + Column('create_time', DateTime, default=now)) model.PasswordResetToken.table = Table( "password_reset_token", metadata, @@ -141,6 +165,24 @@ model.PasswordResetToken.table = Table( Column("expiration_time", DateTime), Column("user_id", Integer, ForeignKey("galaxy_user.id"), index=True)) + +model.DynamicTool.table = Table( + "dynamic_tool", metadata, + Column("id", Integer, primary_key=True), + Column("uuid", UUIDType()), + Column("create_time", DateTime, default=now), + Column("update_time", DateTime, index=True, default=now, onupdate=now), + Column("tool_id", Unicode(255)), + Column("tool_version", Unicode(255)), + Column("tool_format", Unicode(255)), + Column("tool_path", Unicode(255)), + Column("tool_directory", Unicode(255)), + Column("hidden", Boolean, default=True), + Column("active", Boolean, default=True), + Column("value", JSONType()), +) + + model.History.table = Table( "history", metadata, Column("id", Integer, primary_key=True), @@ -223,6 +265,32 @@ model.Dataset.table = Table( Column('total_size', Numeric(15, 0)), Column('uuid', UUIDType())) +model.DatasetSource.table = Table( + "dataset_source", metadata, + Column("id", Integer, primary_key=True), + Column("dataset_id", Integer, ForeignKey("dataset.id"), index=True), + Column("source_uri", TEXT), + Column("extra_files_path", TEXT), + Column("transform", JSONType) +) + +model.DatasetHash.table = Table( + "dataset_hash", metadata, + Column("id", Integer, primary_key=True), + Column("dataset_id", Integer, ForeignKey("dataset.id"), index=True), + Column("hash_function", TEXT), + Column("hash_value", TEXT), + Column("extra_files_path", TEXT), +) + +model.DatasetSourceHash.table = Table( + "dataset_source_hash", metadata, + Column("id", Integer, primary_key=True), + Column("dataset_source_id", Integer, ForeignKey("dataset_source.id"), index=True), + Column("hash_function", TEXT), + Column("hash_value", TEXT) +) + # hda read access permission given by a user to a specific site (gen. for external display applications) model.HistoryDatasetAssociationDisplayAtAuthorization.table = Table( "history_dataset_association_display_at_authorization", metadata, @@ -522,15 +590,19 @@ model.Job.table = Table( Column("library_folder_id", Integer, ForeignKey("library_folder.id"), index=True), Column("tool_id", String(255)), Column("tool_version", TEXT, default="1.0.0"), + Column("dynamic_tool_id", Integer, ForeignKey("dynamic_tool.id"), index=True, nullable=True), Column("state", String(64), index=True), Column("info", TrimmedString(255)), Column("copied_from_job_id", Integer, nullable=True), Column("command_line", TEXT), Column("dependencies", JSONType, nullable=True), + Column("job_messages", JSONType, nullable=True), Column("param_filename", String(1024)), Column("runner_name", String(255)), - Column("stdout", TEXT), - Column("stderr", TEXT), + Column("job_stdout", TEXT), + Column("job_stderr", TEXT), + Column("tool_stdout", TEXT), + Column("tool_stderr", TEXT), Column("exit_code", Integer, nullable=True), Column("traceback", TEXT), Column("session_id", Integer, ForeignKey("galaxy_session.id"), index=True, nullable=True), @@ -656,9 +728,7 @@ model.JobExportHistoryArchive.table = Table( Column("history_id", Integer, ForeignKey("history.id"), index=True), Column("dataset_id", Integer, ForeignKey("dataset.id"), index=True), Column("compressed", Boolean, index=True, default=False), - Column("history_attrs_filename", TEXT), - Column("datasets_attrs_filename", TEXT), - Column("jobs_attrs_filename", TEXT)) + Column("history_attrs_filename", TEXT)) model.JobImportHistoryArchive.table = Table( "job_import_history_archive", metadata, @@ -723,9 +793,12 @@ model.Task.table = Table( Column("command_line", TEXT), Column("param_filename", String(1024)), Column("runner_name", String(255)), - Column("stdout", TEXT), - Column("stderr", TEXT), + Column("job_stdout", TEXT), # job_stdout makes sense here because it is short for job script standard out. + Column("job_stderr", TEXT), + Column("tool_stdout", TEXT), + Column("tool_stderr", TEXT), Column("exit_code", Integer, nullable=True), + Column("job_messages", JSONType, nullable=True), Column("info", TrimmedString(255)), Column("traceback", TEXT), Column("job_id", Integer, ForeignKey("job.id"), index=True, nullable=False), @@ -887,6 +960,7 @@ model.WorkflowStep.table = Table( Column("update_time", DateTime, default=now, onupdate=now), Column("workflow_id", Integer, ForeignKey("workflow.id"), index=True, nullable=False), Column("subworkflow_id", Integer, ForeignKey("workflow.id"), index=True, nullable=True), + Column("dynamic_tool_id", Integer, ForeignKey("dynamic_tool.id"), index=True, nullable=True), Column("type", String(64)), Column("tool_id", TEXT), Column("tool_version", TEXT), @@ -1064,6 +1138,7 @@ model.MetadataFile.table = Table( Column("create_time", DateTime, default=now), Column("update_time", DateTime, index=True, default=now, onupdate=now), Column("object_store_id", TrimmedString(255), index=True), + Column("uuid", UUIDType(), index=True), Column("deleted", Boolean, index=True, default=False), Column("purged", Boolean, index=True, default=False)) @@ -1433,6 +1508,65 @@ model.APIKeys.table = Table( Column("user_id", Integer, ForeignKey("galaxy_user.id"), index=True), Column("key", TrimmedString(32), index=True, unique=True)) +CleanupEvent_table = Table("cleanup_event", metadata, + Column("id", Integer, primary_key=True), + Column("create_time", DateTime, default=now), + Column("message", TrimmedString(1024))) + +CleanupEventDatasetAssociation_table = Table("cleanup_event_dataset_association", metadata, + Column("id", Integer, primary_key=True), + Column("create_time", DateTime, default=now), + Column("cleanup_event_id", Integer, ForeignKey("cleanup_event.id"), index=True, nullable=True), + Column("dataset_id", Integer, ForeignKey("dataset.id"), index=True)) + +CleanupEventMetadataFileAssociation_table = Table("cleanup_event_metadata_file_association", metadata, + Column("id", Integer, primary_key=True), + Column("create_time", DateTime, default=now), + Column("cleanup_event_id", Integer, ForeignKey("cleanup_event.id"), index=True, nullable=True), + Column("metadata_file_id", Integer, ForeignKey("metadata_file.id"), index=True)) + +CleanupEventHistoryAssociation_table = Table("cleanup_event_history_association", metadata, + Column("id", Integer, primary_key=True), + Column("create_time", DateTime, default=now), + Column("cleanup_event_id", Integer, ForeignKey("cleanup_event.id"), index=True, nullable=True), + Column("history_id", Integer, ForeignKey("history.id"), index=True)) + +CleanupEventHistoryDatasetAssociationAssociation_table = Table("cleanup_event_hda_association", metadata, + Column("id", Integer, primary_key=True), + Column("create_time", DateTime, default=now), + Column("cleanup_event_id", Integer, ForeignKey("cleanup_event.id"), index=True, nullable=True), + Column("hda_id", Integer, ForeignKey("history_dataset_association.id"), index=True)) + +CleanupEventLibraryAssociation_table = Table("cleanup_event_library_association", metadata, + Column("id", Integer, primary_key=True), + Column("create_time", DateTime, default=now), + Column("cleanup_event_id", Integer, ForeignKey("cleanup_event.id"), index=True, nullable=True), + Column("library_id", Integer, ForeignKey("library.id"), index=True)) + +CleanupEventLibraryFolderAssociation_table = Table("cleanup_event_library_folder_association", metadata, + Column("id", Integer, primary_key=True), + Column("create_time", DateTime, default=now), + Column("cleanup_event_id", Integer, ForeignKey("cleanup_event.id"), index=True, nullable=True), + Column("library_folder_id", Integer, ForeignKey("library_folder.id"), index=True)) + +CleanupEventLibraryDatasetAssociation_table = Table("cleanup_event_library_dataset_association", metadata, + Column("id", Integer, primary_key=True), + Column("create_time", DateTime, default=now), + Column("cleanup_event_id", Integer, ForeignKey("cleanup_event.id"), index=True, nullable=True), + Column("library_dataset_id", Integer, ForeignKey("library_dataset.id"), index=True)) + +CleanupEventLibraryDatasetDatasetAssociationAssociation_table = Table("cleanup_event_ldda_association", metadata, + Column("id", Integer, primary_key=True), + Column("create_time", DateTime, default=now), + Column("cleanup_event_id", Integer, ForeignKey("cleanup_event.id"), index=True, nullable=True), + Column("ldda_id", Integer, ForeignKey("library_dataset_dataset_association.id"), index=True)) + +CleanupEventImplicitlyConvertedDatasetAssociationAssociation_table = Table("cleanup_event_icda_association", metadata, + Column("id", Integer, primary_key=True), + Column("create_time", DateTime, default=now), + Column("cleanup_event_id", Integer, ForeignKey("cleanup_event.id"), index=True, nullable=True), + Column("icda_id", Integer, ForeignKey("implicitly_converted_dataset_association.id"), index=True)) + # With the tables defined we can define the mappers and setup the # relationships between the model objects. @@ -1440,6 +1574,8 @@ def simple_mapping(model, **kwds): mapper(model, model.table, properties=kwds) +simple_mapping(model.WorkerProcess) + mapper(model.FormValues, model.FormValues.table, properties=dict( form_definition=relation(model.FormDefinition, primaryjoin=(model.FormValues.table.c.form_definition_id == model.FormDefinition.table.c.id)) @@ -1481,6 +1617,12 @@ mapper(model.UserAuthnzToken, model.UserAuthnzToken.table, properties=dict( backref='social_auth') )) +mapper(model.CustosAuthnzToken, model.CustosAuthnzToken.table, properties=dict( + user=relation(model.User, + primaryjoin=(model.CustosAuthnzToken.table.c.user_id == model.User.table.c.id), + backref='custos_auth') +)) + mapper(model.CloudAuthz, model.CloudAuthz.table, properties=dict( user=relation(model.User, primaryjoin=(model.CloudAuthz.table.c.user_id == model.User.table.c.id), @@ -1492,6 +1634,8 @@ mapper(model.CloudAuthz, model.CloudAuthz.table, properties=dict( mapper(model.ValidationError, model.ValidationError.table) +simple_mapping(model.DynamicTool) + simple_mapping(model.HistoryDatasetAssociation, dataset=relation(model.Dataset, primaryjoin=(model.Dataset.table.c.id == model.HistoryDatasetAssociation.table.c.dataset_id), lazy=False), @@ -1515,9 +1659,6 @@ simple_mapping(model.HistoryDatasetAssociation, implicitly_converted_datasets=relation(model.ImplicitlyConvertedDatasetAssociation, primaryjoin=(model.ImplicitlyConvertedDatasetAssociation.table.c.hda_parent_id == model.HistoryDatasetAssociation.table.c.id)), - implicitly_converted_parent_datasets=relation(model.ImplicitlyConvertedDatasetAssociation, - primaryjoin=(model.ImplicitlyConvertedDatasetAssociation.table.c.hda_id == - model.HistoryDatasetAssociation.table.c.id)), tags=relation(model.HistoryDatasetAssociationTagAssociation, order_by=model.HistoryDatasetAssociationTagAssociation.table.c.id, backref='history_tag_associations'), @@ -1561,6 +1702,18 @@ simple_mapping(model.Dataset, backref='datasets') ) +mapper(model.DatasetHash, model.DatasetHash.table, properties=dict( + dataset=relation(model.Dataset, backref='hashes') +)) + +mapper(model.DatasetSource, model.DatasetSource.table, properties=dict( + dataset=relation(model.Dataset, backref='sources') +)) + +mapper(model.DatasetSourceHash, model.DatasetSourceHash.table, properties=dict( + source=relation(model.DatasetSource, backref='hashes') +)) + mapper(model.HistoryDatasetAssociationHistory, model.HistoryDatasetAssociationHistory.table) mapper(model.HistoryDatasetAssociationDisplayAtAuthorization, model.HistoryDatasetAssociationDisplayAtAuthorization.table, properties=dict( @@ -1586,10 +1739,12 @@ mapper(model.ImplicitlyConvertedDatasetAssociation, model.ImplicitlyConvertedDat model.LibraryDatasetDatasetAssociation.table.c.id)), dataset_ldda=relation(model.LibraryDatasetDatasetAssociation, primaryjoin=(model.ImplicitlyConvertedDatasetAssociation.table.c.ldda_id == - model.LibraryDatasetDatasetAssociation.table.c.id)), + model.LibraryDatasetDatasetAssociation.table.c.id), + backref="implicitly_converted_parent_datasets"), dataset=relation(model.HistoryDatasetAssociation, primaryjoin=(model.ImplicitlyConvertedDatasetAssociation.table.c.hda_id == - model.HistoryDatasetAssociation.table.c.id)) + model.HistoryDatasetAssociation.table.c.id), + backref="implicitly_converted_parent_datasets") )) mapper(model.History, model.History.table, properties=dict( @@ -2063,7 +2218,7 @@ mapper(model.Job, model.Job.table, properties=dict( # user=relation( model.User.mapper ), user=relation(model.User), galaxy_session=relation(model.GalaxySession), - history=relation(model.History), + history=relation(model.History, backref="jobs"), library_folder=relation(model.LibraryFolder, lazy=True), parameters=relation(model.JobParameter, lazy=True), input_datasets=relation(model.JobToInputDatasetAssociation), @@ -2204,8 +2359,11 @@ mapper(model.Workflow, model.Workflow.table, properties=dict( mapper(model.WorkflowStep, model.WorkflowStep.table, properties=dict( subworkflow=relation(model.Workflow, - primaryjoin=((model.Workflow.table.c.id == model.WorkflowStep.table.c.subworkflow_id)), + primaryjoin=(model.Workflow.table.c.id == model.WorkflowStep.table.c.subworkflow_id), backref="parent_workflow_steps"), + dynamic_tool=relation(model.DynamicTool, + primaryjoin=(model.DynamicTool.table.c.id == model.WorkflowStep.table.c.dynamic_tool_id), + backref="workflow_steps"), tags=relation(model.WorkflowStepTagAssociation, order_by=model.WorkflowStepTagAssociation.table.c.id, backref="workflow_steps"), diff --git a/lib/galaxy/model/metadata.py b/lib/galaxy/model/metadata.py index 417c81731c7..bc12efb852e 100644 --- a/lib/galaxy/model/metadata.py +++ b/lib/galaxy/model/metadata.py @@ -17,12 +17,11 @@ from six import string_types from sqlalchemy.orm import object_session import galaxy.model -from galaxy.util import (listify, string_as_bool, +from galaxy.util import (form_builder, listify, string_as_bool, stringify_dictionary_keys) from galaxy.util.json import safe_dumps from galaxy.util.object_wrapper import sanitize_lists_to_string from galaxy.util.odict import odict -from galaxy.web import form_builder log = logging.getLogger(__name__) diff --git a/lib/galaxy/model/migrate/versions/0002_metadata_file_table.py b/lib/galaxy/model/migrate/versions/0002_metadata_file_table.py index 6d40b3a8434..435e4026790 100644 --- a/lib/galaxy/model/migrate/versions/0002_metadata_file_table.py +++ b/lib/galaxy/model/migrate/versions/0002_metadata_file_table.py @@ -1,8 +1,12 @@ +""" +""" import datetime import logging from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, MetaData, Table, TEXT +from galaxy.model.migrate.versions.util import create_table, drop_table + now = datetime.datetime.utcnow log = logging.getLogger(__name__) metadata = MetaData() @@ -21,10 +25,12 @@ MetadataFile_table = Table("metadata_file", metadata, def upgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - MetadataFile_table.create() + + create_table(MetadataFile_table) def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - MetadataFile_table.drop() + + drop_table(MetadataFile_table) diff --git a/lib/galaxy/model/migrate/versions/0003_security_and_libraries.py b/lib/galaxy/model/migrate/versions/0003_security_and_libraries.py index 576ac169b2e..35285f0bb05 100644 --- a/lib/galaxy/model/migrate/versions/0003_security_and_libraries.py +++ b/lib/galaxy/model/migrate/versions/0003_security_and_libraries.py @@ -1,3 +1,5 @@ +""" +""" import datetime import logging import sys @@ -6,8 +8,13 @@ from migrate import ForeignKeyConstraint from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Index, Integer, MetaData, String, Table, TEXT from sqlalchemy.exc import NoSuchTableError -# Need our custom types, but don't import anything else from model from galaxy.model.custom_types import JSONType, MetadataType, TrimmedString +from galaxy.model.migrate.versions.util import ( + add_column, + engine_false, + localtimestamp, + nextval +) now = datetime.datetime.utcnow log = logging.getLogger(__name__) @@ -304,36 +311,10 @@ JobExternalOutputMetadata_table = Table("job_external_output_metadata", metadata Index("ix_jeom_library_dataset_dataset_association_id", JobExternalOutputMetadata_table.c.library_dataset_dataset_association_id) -def engine_false(migrate_engine): - if migrate_engine.name in ['postgres', 'postgresql']: - return "FALSE" - elif migrate_engine.name in ['mysql', 'sqlite']: - return 0 - else: - raise Exception('Unknown database type: %s' % migrate_engine.name) - - def upgrade(migrate_engine): metadata.bind = migrate_engine - # Load existing tables metadata.reflect() - def nextval(table, col='id'): - if migrate_engine.name in ['postgres', 'postgresql']: - return "nextval('%s_%s_seq')" % (table, col) - elif migrate_engine.name in ['mysql', 'sqlite']: - return "null" - else: - raise Exception('Unable to convert data for unknown database type: %s' % migrate_engine.name) - - def localtimestamp(): - if migrate_engine.name in ['mysql', 'postgres', 'postgresql']: - return "LOCALTIMESTAMP" - elif migrate_engine.name == 'sqlite': - return "current_date || ' ' || current_time" - else: - raise Exception('Unable to convert data for unknown database type: %s' % migrate_engine.name) - # Add 2 new columns to the galaxy_user table try: User_table = Table("galaxy_user", metadata, autoload=True) @@ -341,18 +322,10 @@ def upgrade(migrate_engine): User_table = None log.debug("Failed loading table galaxy_user") if User_table is not None: - try: - col = Column('deleted', Boolean, index=True, default=False) - col.create(User_table, index_name='ix_user_deleted') - assert col is User_table.c.deleted - except Exception: - log.exception("Adding column 'deleted' to galaxy_user table failed.") - try: - col = Column('purged', Boolean, index=True, default=False) - col.create(User_table, index_name='ix_user_purged') - assert col is User_table.c.purged - except Exception: - log.exception("Adding column 'purged' to galaxy_user table failed.") + col = Column('deleted', Boolean, index=True, default=False) + add_column(col, User_table, index_name='ix_galaxy_user_deleted') + col = Column('purged', Boolean, index=True, default=False) + add_column(col, User_table, index_name='ix_galaxy_user_purged') # Add 1 new column to the history_dataset_association table try: HistoryDatasetAssociation_table = Table("history_dataset_association", metadata, autoload=True) @@ -470,7 +443,7 @@ def upgrade(migrate_engine): "%s AS deleted " + \ "FROM galaxy_user " + \ "ORDER BY id;" - cmd = cmd % (nextval('role'), localtimestamp(), localtimestamp(), engine_false(migrate_engine)) + cmd = cmd % (nextval(migrate_engine, 'role'), localtimestamp(migrate_engine), localtimestamp(migrate_engine), engine_false(migrate_engine)) migrate_engine.execute(cmd) # Create private roles for each user - pass 2 if migrate_engine.name in ['postgres', 'postgresql', 'sqlite']: @@ -489,7 +462,7 @@ def upgrade(migrate_engine): "FROM galaxy_user, role " + \ "WHERE galaxy_user.email = role.name " + \ "ORDER BY galaxy_user.id;" - cmd = cmd % (nextval('user_role_association'), localtimestamp(), localtimestamp()) + cmd = cmd % (nextval(migrate_engine, 'user_role_association'), localtimestamp(migrate_engine), localtimestamp(migrate_engine)) migrate_engine.execute(cmd) # Create default permissions for each user cmd = \ @@ -501,7 +474,7 @@ def upgrade(migrate_engine): "FROM galaxy_user " + \ "JOIN user_role_association ON user_role_association.user_id = galaxy_user.id " + \ "ORDER BY galaxy_user.id;" - cmd = cmd % nextval('default_user_permissions') + cmd = cmd % nextval(migrate_engine, 'default_user_permissions') migrate_engine.execute(cmd) # Create default history permissions for each active history associated with a user @@ -514,7 +487,7 @@ def upgrade(migrate_engine): "FROM history " + \ "JOIN user_role_association ON user_role_association.user_id = history.user_id " + \ "WHERE history.purged = %s AND history.user_id IS NOT NULL;" - cmd = cmd % (nextval('default_history_permissions'), engine_false(migrate_engine)) + cmd = cmd % (nextval(migrate_engine, 'default_history_permissions'), engine_false(migrate_engine)) migrate_engine.execute(cmd) # Create "manage permissions" dataset_permissions for all activate-able datasets cmd = \ @@ -530,14 +503,14 @@ def upgrade(migrate_engine): "JOIN dataset ON history_dataset_association.dataset_id = dataset.id " + \ "JOIN user_role_association ON user_role_association.user_id = history.user_id " + \ "WHERE dataset.purged = %s AND history.user_id IS NOT NULL;" - cmd = cmd % (nextval('dataset_permissions'), localtimestamp(), localtimestamp(), engine_false(migrate_engine)) + cmd = cmd % (nextval(migrate_engine, 'dataset_permissions'), localtimestamp(migrate_engine), localtimestamp(migrate_engine), engine_false(migrate_engine)) migrate_engine.execute(cmd) def downgrade(migrate_engine): metadata.bind = migrate_engine - # Load existing tables metadata.reflect() + # NOTE: all new data added in the upgrade method is eliminated here via table drops # Drop 1 foreign key constraint from the metadata_file table try: diff --git a/lib/galaxy/model/migrate/versions/0004_indexes_and_defaults.py b/lib/galaxy/model/migrate/versions/0004_indexes_and_defaults.py index 17876bbb4bc..af2daf145ca 100644 --- a/lib/galaxy/model/migrate/versions/0004_indexes_and_defaults.py +++ b/lib/galaxy/model/migrate/versions/0004_indexes_and_defaults.py @@ -1,7 +1,17 @@ +""" +""" import logging import sys -from sqlalchemy import Index, MetaData, Table +from sqlalchemy import ( + MetaData, + Table +) + +from galaxy.model.migrate.versions.util import ( + add_index, + engine_false +) log = logging.getLogger(__name__) log.setLevel(logging.DEBUG) @@ -14,32 +24,13 @@ log.addHandler(handler) metadata = MetaData() -def engine_false(migrate_engine): - if migrate_engine.name in ['postgres', 'postgresql']: - return "FALSE" - elif migrate_engine.name in ['mysql', 'sqlite']: - return 0 - else: - raise Exception('Unknown database type: %s' % migrate_engine.name) - - def upgrade(migrate_engine): metadata.bind = migrate_engine - User_table = Table("galaxy_user", metadata, autoload=True) - HistoryDatasetAssociation_table = Table("history_dataset_association", metadata, autoload=True) - # Load existing tables metadata.reflect() - # Add 2 indexes to the galaxy_user table - i = Index('ix_galaxy_user_deleted', User_table.c.deleted) - try: - i.create() - except Exception: - log.exception("Adding index 'ix_galaxy_user_deleted' to galaxy_user table failed.") - i = Index('ix_galaxy_user_purged', User_table.c.purged) - try: - i.create() - except Exception: - log.exception("Adding index 'ix_galaxy_user_purged' to galaxy_user table failed.") + + User_table = Table("galaxy_user", metadata, autoload=True) + add_index('ix_galaxy_user_deleted', User_table, 'deleted') + add_index('ix_galaxy_user_purged', User_table, 'purged') # Set the default data in the galaxy_user table, but only for null values cmd = "UPDATE galaxy_user SET deleted = %s WHERE deleted is null" % engine_false(migrate_engine) try: @@ -51,14 +42,10 @@ def upgrade(migrate_engine): migrate_engine.execute(cmd) except Exception: log.exception("Setting default data for galaxy_user.purged column failed.") - # Add 1 index to the history_dataset_association table - i = Index('ix_hda_copied_from_library_dataset_dataset_association_id', HistoryDatasetAssociation_table.c.copied_from_library_dataset_dataset_association_id) - try: - i.create() - except Exception: - log.exception("Adding index 'ix_hda_copied_from_library_dataset_dataset_association_id' to history_dataset_association table failed.") + add_index('ix_hda_copied_from_library_dataset_dataset_association_id', + 'history_dataset_association', + 'copied_from_library_dataset_dataset_association_id', metadata) def downgrade(migrate_engine): - metadata.bind = migrate_engine pass diff --git a/lib/galaxy/model/migrate/versions/0008_galaxy_forms.py b/lib/galaxy/model/migrate/versions/0008_galaxy_forms.py index 59d80835b4d..b9118d06d23 100644 --- a/lib/galaxy/model/migrate/versions/0008_galaxy_forms.py +++ b/lib/galaxy/model/migrate/versions/0008_galaxy_forms.py @@ -103,12 +103,11 @@ SampleEvent_table = Table('sample_event', metadata, def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) - # Load existing tables + metadata.bind = migrate_engine metadata.reflect() + # Add all of the new tables above -# metadata.create_all() try: FormDefinitionCurrent_table.create() except Exception: @@ -155,8 +154,8 @@ def upgrade(migrate_engine): def downgrade(migrate_engine): metadata.bind = migrate_engine - # Load existing tables metadata.reflect() + try: FormDefinition_table.drop() except Exception: diff --git a/lib/galaxy/model/migrate/versions/0009_request_table.py b/lib/galaxy/model/migrate/versions/0009_request_table.py index 06b788e9283..35228e5da67 100644 --- a/lib/galaxy/model/migrate/versions/0009_request_table.py +++ b/lib/galaxy/model/migrate/versions/0009_request_table.py @@ -8,9 +8,10 @@ from __future__ import print_function import logging import sys -from sqlalchemy import Boolean, Column, MetaData, Table +from sqlalchemy import Boolean, Column, MetaData from galaxy.model.custom_types import TrimmedString +from galaxy.model.migrate.versions.util import add_column, drop_column log = logging.getLogger(__name__) log.setLevel(logging.DEBUG) @@ -24,31 +25,22 @@ metadata = MetaData() def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) - # Load existing tables - Request_table = Table("request", metadata, autoload=True) - Sample_table = Table("sample", metadata, autoload=True) + metadata.bind = migrate_engine metadata.reflect() - # Add 1 column to the request table - if Request_table is not None: - try: - col = Column('submitted', Boolean, default=False) - col.create(Request_table) - assert col is Request_table.c.submitted - except Exception: - log.exception("Adding column 'submitted' to request table failed.") - # Add 1 column to the sample table - if Sample_table is not None: - try: - col = Column("bar_code", TrimmedString(255), index=True) - col.create(Sample_table, index_name='ix_sample_bar_code') - assert col is Sample_table.c.bar_code - except Exception: - log.exception("Adding column 'bar_code' to sample table failed.") + col = Column('submitted', Boolean, default=False) + add_column(col, 'request', metadata) + + col = Column("bar_code", TrimmedString(255), index=True) + add_column(col, 'sample', metadata, index_name='ix_sample_bar_code') def downgrade(migrate_engine): metadata.bind = migrate_engine - pass + metadata.reflect() + + drop_column('bar_code', 'sample', metadata) + # SQLAlchemy Migrate has a bug when dropping a boolean column in SQLite + if migrate_engine.name != 'sqlite': + drop_column('submitted', 'request', metadata) diff --git a/lib/galaxy/model/migrate/versions/0011_v0010_mysql_index_fix.py b/lib/galaxy/model/migrate/versions/0011_v0010_mysql_index_fix.py index 6f70f50794e..59b522584de 100644 --- a/lib/galaxy/model/migrate/versions/0011_v0010_mysql_index_fix.py +++ b/lib/galaxy/model/migrate/versions/0011_v0010_mysql_index_fix.py @@ -35,8 +35,8 @@ HistoryDatasetAssociationDisplayAtAuthorization_table = Table("history_dataset_a def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine if migrate_engine.name == 'mysql': # Load existing tables metadata.reflect() diff --git a/lib/galaxy/model/migrate/versions/0012_user_address.py b/lib/galaxy/model/migrate/versions/0012_user_address.py index b3919c91d21..dfbfb783c77 100644 --- a/lib/galaxy/model/migrate/versions/0012_user_address.py +++ b/lib/galaxy/model/migrate/versions/0012_user_address.py @@ -13,7 +13,6 @@ import sys from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, MetaData, Table, TEXT from sqlalchemy.exc import NoSuchTableError -# Need our custom types, but don't import anything else from model from galaxy.model.custom_types import TrimmedString now = datetime.datetime.utcnow @@ -45,10 +44,10 @@ UserAddress_table = Table("user_address", metadata, def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) - # Load existing tables + metadata.bind = migrate_engine metadata.reflect() + # Add all of the new tables above try: UserAddress_table.create() @@ -76,8 +75,8 @@ def upgrade(migrate_engine): Request_table = None log.debug("Failed loading table request") if Request_table is not None: + # SQLAlchemy Migrate has a bug when dropping a boolean column in SQLite if migrate_engine.name != 'sqlite': - # DBTODO drop from table doesn't work in sqlite w/ sqlalchemy-migrate .6+ Request_table.c.submitted.drop() col = Column("state", TrimmedString(255), index=True) col.create(Request_table, index_name='ix_request_state') @@ -85,5 +84,4 @@ def upgrade(migrate_engine): def downgrade(migrate_engine): - metadata.bind = migrate_engine pass diff --git a/lib/galaxy/model/migrate/versions/0013_change_lib_item_templates_to_forms.py b/lib/galaxy/model/migrate/versions/0013_change_lib_item_templates_to_forms.py index 3e0733e2afa..0fc99ea6842 100644 --- a/lib/galaxy/model/migrate/versions/0013_change_lib_item_templates_to_forms.py +++ b/lib/galaxy/model/migrate/versions/0013_change_lib_item_templates_to_forms.py @@ -52,8 +52,8 @@ LibraryDatasetDatasetInfoAssociation_table = Table('library_dataset_dataset_info def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine # Load existing tables metadata.reflect() # Drop all of the original library_item_info tables diff --git a/lib/galaxy/model/migrate/versions/0014_pages.py b/lib/galaxy/model/migrate/versions/0014_pages.py index 728c22c7d53..d1a47572de2 100644 --- a/lib/galaxy/model/migrate/versions/0014_pages.py +++ b/lib/galaxy/model/migrate/versions/0014_pages.py @@ -10,6 +10,13 @@ import logging from sqlalchemy import Column, DateTime, ForeignKey, Index, Integer, MetaData, String, Table, TEXT +from galaxy.model.migrate.versions.util import ( + add_column, + create_table, + drop_column, + drop_table +) + now = datetime.datetime.utcnow log = logging.getLogger(__name__) metadata = MetaData() @@ -34,9 +41,10 @@ PageRevision_table = Table("page_revision", metadata, def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() + try: if migrate_engine.name == 'mysql': # Strip slug index prior to creation so we can do it manually. @@ -52,22 +60,16 @@ def upgrade(migrate_engine): i.create() except Exception: log.exception("Could not create page table") - try: - PageRevision_table.create() - except Exception: - log.exception("Could not create page_revision table") + create_table(PageRevision_table) - # Add 1 column to the user table - User_table = Table("galaxy_user", metadata, autoload=True) col = Column('username', String(255), index=True, unique=True, default=False) - col.create(User_table, index_name='ix_user_username', unique_name='username') - assert col is User_table.c.username + add_column(col, 'galaxy_user', metadata, index_name='ix_galaxy_user_username', unique_name='username') def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - Page_table.drop() - PageRevision_table.drop() - User_table = Table("galaxy_user", metadata, autoload=True) - User_table.c.username.drop() + + drop_column('username', 'galaxy_user', metadata) + drop_table(PageRevision_table) + drop_table(Page_table) diff --git a/lib/galaxy/model/migrate/versions/0015_tagging.py b/lib/galaxy/model/migrate/versions/0015_tagging.py index 6a4e6fb7a06..cad62a79000 100644 --- a/lib/galaxy/model/migrate/versions/0015_tagging.py +++ b/lib/galaxy/model/migrate/versions/0015_tagging.py @@ -15,8 +15,8 @@ import logging from migrate import UniqueConstraint from sqlalchemy import Column, ForeignKey, Integer, MetaData, Table -# Need our custom types, but don't import anything else from model from galaxy.model.custom_types import TrimmedString +from galaxy.model.migrate.versions.util import create_table, drop_table log = logging.getLogger(__name__) metadata = MetaData() @@ -52,43 +52,21 @@ HistoryDatasetAssociationTagAssociation_table = Table("history_dataset_associati def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() - try: - Tag_table.create() - except Exception: - log.exception("Creating tag table failed.") - try: - HistoryTagAssociation_table.create() - except Exception: - log.exception("Creating history_tag_association table failed.") - try: - DatasetTagAssociation_table.create() - except Exception: - log.exception("Creating dataset_tag_association table failed.") - try: - HistoryDatasetAssociationTagAssociation_table.create() - except Exception: - log.exception("Creating history_dataset_association_tag_association table failed.") + + create_table(Tag_table) + create_table(HistoryTagAssociation_table) + create_table(DatasetTagAssociation_table) + create_table(HistoryDatasetAssociationTagAssociation_table) def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - try: - Tag_table.drop() - except Exception: - log.exception("Dropping tag table failed.") - try: - HistoryTagAssociation_table.drop() - except Exception: - log.exception("Dropping history_tag_association table failed.") - try: - DatasetTagAssociation_table.drop() - except Exception: - log.exception("Dropping dataset_tag_association table failed.") - try: - HistoryDatasetAssociationTagAssociation_table.drop() - except Exception: - log.exception("Dropping history_dataset_association_tag_association table failed.") + + drop_table(HistoryDatasetAssociationTagAssociation_table) + drop_table(DatasetTagAssociation_table) + drop_table(HistoryTagAssociation_table) + drop_table(Tag_table) diff --git a/lib/galaxy/model/migrate/versions/0021_user_prefs.py b/lib/galaxy/model/migrate/versions/0021_user_prefs.py index 56e16457f0c..02ab0b1e7df 100644 --- a/lib/galaxy/model/migrate/versions/0021_user_prefs.py +++ b/lib/galaxy/model/migrate/versions/0021_user_prefs.py @@ -7,6 +7,8 @@ import logging from sqlalchemy import Column, ForeignKey, Integer, MetaData, Table, Unicode +from galaxy.model.migrate.versions.util import create_table, drop_table + log = logging.getLogger(__name__) metadata = MetaData() @@ -19,19 +21,15 @@ UserPreference_table = Table("user_preference", metadata, def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() - try: - UserPreference_table.create() - except Exception: - log.exception("Creating user_preference table failed.") + + create_table(UserPreference_table) def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - try: - UserPreference_table.drop() - except Exception: - log.exception("Dropping user_preference table failed.") + + drop_table(UserPreference_table) diff --git a/lib/galaxy/model/migrate/versions/0025_user_info.py b/lib/galaxy/model/migrate/versions/0025_user_info.py index 001a3c687c9..70c7c28c9a8 100644 --- a/lib/galaxy/model/migrate/versions/0025_user_info.py +++ b/lib/galaxy/model/migrate/versions/0025_user_info.py @@ -6,9 +6,14 @@ from __future__ import print_function import logging import sys -from migrate import ForeignKeyConstraint -from sqlalchemy import Column, Integer, MetaData, Table -from sqlalchemy.exc import NoSuchTableError +from sqlalchemy import ( + Column, + ForeignKey, + Integer, + MetaData +) + +from galaxy.model.migrate.versions.util import add_column, drop_column log = logging.getLogger(__name__) log.setLevel(logging.DEBUG) @@ -22,40 +27,20 @@ metadata = MetaData() def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) - # Load existing tables + metadata.bind = migrate_engine metadata.reflect() - try: - User_table = Table("galaxy_user", metadata, autoload=True) - except NoSuchTableError: - User_table = None - log.debug("Failed loading table galaxy_user") - if User_table is not None: - try: - col = Column("form_values_id", Integer, index=True) - col.create(User_table, index_name='ix_user_form_values_id') - assert col is User_table.c.form_values_id - except Exception: - log.exception("Adding column 'form_values_id' to galaxy_user table failed.") - try: - FormValues_table = Table("form_values", metadata, autoload=True) - except NoSuchTableError: - FormValues_table = None - log.debug("Failed loading table form_values") - if migrate_engine.name != 'sqlite': - # Add 1 foreign key constraint to the form_values table - if User_table is not None and FormValues_table is not None: - try: - cons = ForeignKeyConstraint([User_table.c.form_values_id], - [FormValues_table.c.id], - name='user_form_values_id_fk') - # Create the constraint - cons.create() - except Exception: - log.exception("Adding foreign key constraint 'user_form_values_id_fk' to table 'galaxy_user' failed.") + + if migrate_engine.name != 'sqlite': + col = Column("form_values_id", Integer, ForeignKey('form_values.id', name='user_form_values_id_fk'), index=True) + else: + # Can't use the ForeignKey in SQLite. + col = Column("form_values_id", Integer, index=True) + add_column(col, 'galaxy_user', metadata, index_name='ix_galaxy_user_form_values_id') def downgrade(migrate_engine): metadata.bind = migrate_engine - pass + metadata.reflect() + + drop_column('form_values_id', 'galaxy_user', metadata) diff --git a/lib/galaxy/model/migrate/versions/0026_cloud_tables.py b/lib/galaxy/model/migrate/versions/0026_cloud_tables.py index 3331a27308c..6e2b3560aa9 100644 --- a/lib/galaxy/model/migrate/versions/0026_cloud_tables.py +++ b/lib/galaxy/model/migrate/versions/0026_cloud_tables.py @@ -8,6 +8,8 @@ import logging from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, MetaData, Table, TEXT +from galaxy.model.migrate.versions.util import create_table, drop_table + now = datetime.datetime.utcnow log = logging.getLogger(__name__) metadata = MetaData() @@ -123,32 +125,27 @@ CloudProvider_table = Table("cloud_provider", metadata, def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) - # Load existing tables + metadata.bind = migrate_engine metadata.reflect() - try: - CloudProvider_table.create() - CloudUserCredentials_table.create() - CloudImage_table.create() - UCI_table.create() - CloudInstance_table.create() - CloudStore_table.create() - CloudSnapshot_table.create() - except Exception: - log.exception("Creating cloud tables failed.") + + create_table(CloudProvider_table) + create_table(CloudUserCredentials_table) + create_table(CloudImage_table) + create_table(UCI_table) + create_table(CloudInstance_table) + create_table(CloudStore_table) + create_table(CloudSnapshot_table) def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - try: - CloudSnapshot_table.drop() - CloudStore_table.drop() - CloudInstance_table.drop() - UCI_table.drop() - CloudImage_table.drop() - CloudUserCredentials_table.drop() - CloudProvider_table.drop() - except Exception: - log.exception("Dropping cloud tables failed.") + + drop_table(CloudSnapshot_table) + drop_table(CloudStore_table) + drop_table(CloudInstance_table) + drop_table(UCI_table) + drop_table(CloudImage_table) + drop_table(CloudUserCredentials_table) + drop_table(CloudProvider_table) diff --git a/lib/galaxy/model/migrate/versions/0027_request_events.py b/lib/galaxy/model/migrate/versions/0027_request_events.py index 4ed755d162c..26cd7b318ae 100644 --- a/lib/galaxy/model/migrate/versions/0027_request_events.py +++ b/lib/galaxy/model/migrate/versions/0027_request_events.py @@ -11,8 +11,8 @@ import sys from sqlalchemy import Column, DateTime, ForeignKey, Integer, MetaData, Table, TEXT from sqlalchemy.exc import NoSuchTableError -# Need our custom types, but don't import anything else from model from galaxy.model.custom_types import TrimmedString +from galaxy.model.migrate.versions.util import localtimestamp, nextval now = datetime.datetime.utcnow log = logging.getLogger(__name__) @@ -34,26 +34,10 @@ RequestEvent_table = Table('request_event', metadata, def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) - - def localtimestamp(): - if migrate_engine.name in ['mysql', 'postgres', 'postgresql']: - return "LOCALTIMESTAMP" - elif migrate_engine.name == 'sqlite': - return "current_date || ' ' || current_time" - else: - raise Exception('Unable to convert data for unknown database type: %s' % migrate_engine.name) - - def nextval(table, col='id'): - if migrate_engine.name in ['postgres', 'postgresql']: - return "nextval('%s_%s_seq')" % (table, col) - elif migrate_engine.name in ['mysql', 'sqlite']: - return "null" - else: - raise Exception('Unable to convert data for unknown database type: %s' % migrate_engine.name) - # Load existing tables + metadata.bind = migrate_engine metadata.reflect() + # Add new request_event table try: RequestEvent_table.create() @@ -70,7 +54,7 @@ def upgrade(migrate_engine): "request.state AS state," + \ "'%s' AS comment " + \ "FROM request;" - cmd = cmd % (nextval('request_event'), localtimestamp(), localtimestamp(), 'Imported from request table') + cmd = cmd % (nextval(migrate_engine, 'request_event'), localtimestamp(migrate_engine), localtimestamp(migrate_engine), 'Imported from request table') migrate_engine.execute(cmd) if migrate_engine.name != 'sqlite': @@ -88,5 +72,4 @@ def upgrade(migrate_engine): def downgrade(migrate_engine): - metadata.bind = migrate_engine pass diff --git a/lib/galaxy/model/migrate/versions/0030_history_slug_column.py b/lib/galaxy/model/migrate/versions/0030_history_slug_column.py index 04564844e18..43beeb8b1ba 100644 --- a/lib/galaxy/model/migrate/versions/0030_history_slug_column.py +++ b/lib/galaxy/model/migrate/versions/0030_history_slug_column.py @@ -7,14 +7,15 @@ import logging from sqlalchemy import Column, Index, MetaData, Table, TEXT +from galaxy.model.migrate.versions.util import drop_column + log = logging.getLogger(__name__) metadata = MetaData() def upgrade(migrate_engine): - metadata.bind = migrate_engine - print(__doc__) + metadata.bind = migrate_engine metadata.reflect() History_table = Table("history", metadata, autoload=True) @@ -36,5 +37,4 @@ def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - History_table = Table("history", metadata, autoload=True) - History_table.c.slug.drop() + drop_column('slug', 'history', metadata) diff --git a/lib/galaxy/model/migrate/versions/0033_published_cols_for_histories_and_workflows.py b/lib/galaxy/model/migrate/versions/0033_published_cols_for_histories_and_workflows.py index 119f9f7b452..e81c4cd5de9 100644 --- a/lib/galaxy/model/migrate/versions/0033_published_cols_for_histories_and_workflows.py +++ b/lib/galaxy/model/migrate/versions/0033_published_cols_for_histories_and_workflows.py @@ -8,24 +8,21 @@ import logging from sqlalchemy import Boolean, Column, Index, MetaData, Table +from galaxy.model.migrate.versions.util import add_column, drop_column + log = logging.getLogger(__name__) metadata = MetaData() def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() # Create published column in history table. History_table = Table("history", metadata, autoload=True) c = Column("published", Boolean, index=True) - try: - c.create(History_table, index_name='ix_history_published') - assert c is History_table.c.published - except Exception: - log.exception("Adding published column to history table failed.") - + add_column(c, History_table, index_name='ix_history_published') if migrate_engine.name != 'sqlite': # Create index for published column in history table. try: @@ -38,12 +35,7 @@ def upgrade(migrate_engine): # Create published column in stored workflows table. StoredWorkflow_table = Table("stored_workflow", metadata, autoload=True) c = Column("published", Boolean, index=True) - try: - c.create(StoredWorkflow_table, index_name='ix_stored_workflow_published') - assert c is StoredWorkflow_table.c.published - except Exception: - log.exception("Adding published column to stored_workflow table failed.") - + add_column(c, StoredWorkflow_table, index_name='ix_stored_workflow_published') if migrate_engine.name != 'sqlite': # Create index for published column in stored workflows table. try: @@ -56,12 +48,7 @@ def upgrade(migrate_engine): # Create importable column in page table. Page_table = Table("page", metadata, autoload=True) c = Column("importable", Boolean, index=True) - try: - c.create(Page_table, index_name='ix_page_importable') - assert c is Page_table.c.importable - except Exception: - log.exception("Adding importable column to page table failed.") - + add_column(c, Page_table, index_name='ix_page_importable') if migrate_engine.name != 'sqlite': # Create index for importable column in page table. try: @@ -76,23 +63,6 @@ def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - # Drop published column from history table. - History_table = Table("history", metadata, autoload=True) - try: - History_table.c.published.drop() - except Exception: - log.exception("Dropping column published from history table failed.") - - # Drop published column from stored_workflow table. - StoredWorkflow_table = Table("stored_workflow", metadata, autoload=True) - try: - StoredWorkflow_table.c.published.drop() - except Exception: - log.exception("Dropping column published from stored_workflow table failed.") - - # Drop importable column from page table. - Page_table = Table("page", metadata, autoload=True) - try: - Page_table.c.importable.drop() - except Exception: - log.exception("Dropping column importable from page table failed.") + drop_column('published', 'history', metadata) + drop_column('published', 'stored_workflow', metadata) + drop_column('importable', 'page', metadata) diff --git a/lib/galaxy/model/migrate/versions/0036_add_deleted_column_to_library_template_assoc_tables.py b/lib/galaxy/model/migrate/versions/0036_add_deleted_column_to_library_template_assoc_tables.py index 736fa417716..e39b53e2fbf 100644 --- a/lib/galaxy/model/migrate/versions/0036_add_deleted_column_to_library_template_assoc_tables.py +++ b/lib/galaxy/model/migrate/versions/0036_add_deleted_column_to_library_template_assoc_tables.py @@ -8,23 +8,17 @@ import logging from sqlalchemy import Boolean, Column, MetaData, Table +from galaxy.model.migrate.versions.util import engine_false + log = logging.getLogger(__name__) metadata = MetaData() -def engine_false(migrate_engine): - if migrate_engine.name in ['postgres', 'postgresql']: - return "FALSE" - elif migrate_engine.name in ['mysql', 'sqlite']: - return 0 - else: - raise Exception('Unknown database type: %s' % migrate_engine.name) - - def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() + try: LibraryInfoAssociation_table = Table("library_info_association", metadata, autoload=True) c = Column("deleted", Boolean, index=True, default=False) @@ -64,5 +58,4 @@ def upgrade(migrate_engine): def downgrade(migrate_engine): - metadata.bind = migrate_engine pass diff --git a/lib/galaxy/model/migrate/versions/0037_samples_library.py b/lib/galaxy/model/migrate/versions/0037_samples_library.py index 6d3f935a4f0..4bebab357c1 100644 --- a/lib/galaxy/model/migrate/versions/0037_samples_library.py +++ b/lib/galaxy/model/migrate/versions/0037_samples_library.py @@ -28,8 +28,8 @@ metadata = MetaData() def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine # Load existing tables metadata.reflect() # retuest_type table diff --git a/lib/galaxy/model/migrate/versions/0038_add_inheritable_column_to_library_template_assoc_tables.py b/lib/galaxy/model/migrate/versions/0038_add_inheritable_column_to_library_template_assoc_tables.py index 6241708a948..621b348e344 100644 --- a/lib/galaxy/model/migrate/versions/0038_add_inheritable_column_to_library_template_assoc_tables.py +++ b/lib/galaxy/model/migrate/versions/0038_add_inheritable_column_to_library_template_assoc_tables.py @@ -12,29 +12,19 @@ import logging from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, MetaData, Table, TEXT from galaxy.model.custom_types import TrimmedString +from galaxy.model.migrate.versions.util import engine_false now = datetime.datetime.utcnow log = logging.getLogger(__name__) metadata = MetaData() -def engine_false(migrate_engine): - if migrate_engine.name in ['postgres', 'postgresql']: - return "FALSE" - elif migrate_engine.name in ['mysql', 'sqlite']: - return 0 - else: - raise Exception('Unknown database type: %s' % migrate_engine.name) - - def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine - # # In case of sqlite, check if the previous migration script deleted the # request table and if so, restore the table. - # if migrate_engine.name == 'sqlite': if not migrate_engine.has_table('request'): # load the tables referenced in foreign keys @@ -83,5 +73,4 @@ def upgrade(migrate_engine): def downgrade(migrate_engine): - metadata.bind = migrate_engine pass diff --git a/lib/galaxy/model/migrate/versions/0040_page_annotations.py b/lib/galaxy/model/migrate/versions/0040_page_annotations.py index 000f772fd57..226fdf2aea2 100644 --- a/lib/galaxy/model/migrate/versions/0040_page_annotations.py +++ b/lib/galaxy/model/migrate/versions/0040_page_annotations.py @@ -18,8 +18,8 @@ PageAnnotationAssociation_table = Table("page_annotation_association", metadata, def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() # Create history_annotation_association table. diff --git a/lib/galaxy/model/migrate/versions/0041_workflow_invocation.py b/lib/galaxy/model/migrate/versions/0041_workflow_invocation.py index 7ded92a29a3..b5490708de5 100644 --- a/lib/galaxy/model/migrate/versions/0041_workflow_invocation.py +++ b/lib/galaxy/model/migrate/versions/0041_workflow_invocation.py @@ -8,6 +8,8 @@ import logging from sqlalchemy import Column, DateTime, ForeignKey, Integer, MetaData, Table +from galaxy.model.migrate.versions.util import create_table, drop_table + logging.basicConfig(level=logging.DEBUG) log = logging.getLogger(__name__) now = datetime.datetime.utcnow @@ -31,15 +33,12 @@ tables = [WorkflowInvocation_table, WorkflowInvocationStep_table] def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() for table in tables: - try: - table.create() - except Exception: - log.warning("Failed to create table '%s', ignoring (might result in wrong schema)" % table.name) + create_table(table) def downgrade(migrate_engine): @@ -47,4 +46,4 @@ def downgrade(migrate_engine): metadata.reflect() for table in reversed(tables): - table.drop() + drop_table(table) diff --git a/lib/galaxy/model/migrate/versions/0043_visualization_sharing_tagging_annotating.py b/lib/galaxy/model/migrate/versions/0043_visualization_sharing_tagging_annotating.py index a6937d0b993..a929fd55f5a 100644 --- a/lib/galaxy/model/migrate/versions/0043_visualization_sharing_tagging_annotating.py +++ b/lib/galaxy/model/migrate/versions/0043_visualization_sharing_tagging_annotating.py @@ -7,6 +7,8 @@ import logging from sqlalchemy import Boolean, Column, ForeignKey, Index, Integer, MetaData, Table, TEXT, Unicode +from galaxy.model.migrate.versions.util import engine_false + log = logging.getLogger(__name__) metadata = MetaData() @@ -37,18 +39,9 @@ VisualizationAnnotationAssociation_table = Table("visualization_annotation_assoc Column("annotation", TEXT, index=False)) -def engine_false(migrate_engine): - if migrate_engine.name in ['postgres', 'postgresql']: - return "FALSE" - elif migrate_engine.name in ['mysql', 'sqlite']: - return 0 - else: - raise Exception('Unknown database type: %s' % migrate_engine.name) - - def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() Visualiation_table = Table("visualization", metadata, autoload=True) diff --git a/lib/galaxy/model/migrate/versions/0047_job_table_user_id_column.py b/lib/galaxy/model/migrate/versions/0047_job_table_user_id_column.py index cb060469018..68b143ed4a4 100644 --- a/lib/galaxy/model/migrate/versions/0047_job_table_user_id_column.py +++ b/lib/galaxy/model/migrate/versions/0047_job_table_user_id_column.py @@ -9,6 +9,8 @@ import sys from sqlalchemy import Column, ForeignKey, Integer, MetaData, Table from sqlalchemy.exc import NoSuchTableError +from galaxy.model.migrate.versions.util import add_column, drop_column + log = logging.getLogger(__name__) log.setLevel(logging.DEBUG) handler = logging.StreamHandler(sys.stdout) @@ -21,30 +23,22 @@ metadata = MetaData() def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() + try: Job_table = Table("job", metadata, autoload=True) except NoSuchTableError: Job_table = None log.debug("Failed loading table job") if Job_table is not None: - if migrate_engine.name != 'sqlite': - try: - col = Column("user_id", Integer, ForeignKey("galaxy_user.id"), index=True, nullable=True) - col.create(Job_table, index_name='ix_job_user_id') - assert col is Job_table.c.user_id - except Exception: - log.exception("Adding column 'user_id' to job table failed.") + col = Column("user_id", Integer, ForeignKey("galaxy_user.id"), index=True, nullable=True) + add_column(col, Job_table, index_name='ix_job_user_id') else: - try: - col = Column("user_id", Integer, nullable=True) - col.create(Job_table) - assert col is Job_table.c.user_id - except Exception: - log.exception("Adding column 'user_id' to job table failed.") + col = Column("user_id", Integer, nullable=True) + add_column(col, Job_table) try: cmd = "SELECT job.id AS galaxy_job_id, " \ + "galaxy_session.user_id AS galaxy_user_id " \ @@ -69,9 +63,5 @@ def upgrade(migrate_engine): def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - try: - Job_table = Table("job", metadata, autoload=True) - col = Job_table.c.user_id - col.drop() - except Exception: - log.exception("Dropping column 'user_id' from job table failed.") + + drop_column('user_id', 'job', metadata) diff --git a/lib/galaxy/model/migrate/versions/0048_dataset_instance_state_column.py b/lib/galaxy/model/migrate/versions/0048_dataset_instance_state_column.py index 73ff14c7d85..009e1c31e7a 100644 --- a/lib/galaxy/model/migrate/versions/0048_dataset_instance_state_column.py +++ b/lib/galaxy/model/migrate/versions/0048_dataset_instance_state_column.py @@ -25,9 +25,10 @@ DATASET_INSTANCE_TABLE_NAMES = ['history_dataset_association', 'library_dataset_ def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() + dataset_instance_tables = [] for table_name in DATASET_INSTANCE_TABLE_NAMES: try: diff --git a/lib/galaxy/model/migrate/versions/0050_drop_cloud_tables.py b/lib/galaxy/model/migrate/versions/0050_drop_cloud_tables.py index 61d1187e893..150f7cfca16 100644 --- a/lib/galaxy/model/migrate/versions/0050_drop_cloud_tables.py +++ b/lib/galaxy/model/migrate/versions/0050_drop_cloud_tables.py @@ -8,6 +8,8 @@ import logging from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, MetaData, Table, TEXT +from galaxy.model.migrate.versions.util import create_table, drop_table + now = datetime.datetime.utcnow log = logging.getLogger(__name__) metadata = MetaData() @@ -123,32 +125,27 @@ CloudProvider_table = Table("cloud_provider", metadata, def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) - # Load existing tables + metadata.bind = migrate_engine metadata.reflect() - try: - CloudSnapshot_table.drop() - CloudStore_table.drop() - CloudInstance_table.drop() - UCI_table.drop() - CloudImage_table.drop() - CloudUserCredentials_table.drop() - CloudProvider_table.drop() - except Exception: - log.exception("Dropping cloud tables failed.") + + drop_table(CloudSnapshot_table) + drop_table(CloudStore_table) + drop_table(CloudInstance_table) + drop_table(UCI_table) + drop_table(CloudImage_table) + drop_table(CloudUserCredentials_table) + drop_table(CloudProvider_table) def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - try: - CloudProvider_table.create() - CloudUserCredentials_table.create() - CloudImage_table.create() - UCI_table.create() - CloudInstance_table.create() - CloudStore_table.create() - CloudSnapshot_table.create() - except Exception: - log.exception("Creating cloud tables failed.") + + create_table(CloudProvider_table) + create_table(CloudUserCredentials_table) + create_table(CloudImage_table) + create_table(UCI_table) + create_table(CloudInstance_table) + create_table(CloudStore_table) + create_table(CloudSnapshot_table) diff --git a/lib/galaxy/model/migrate/versions/0051_imported_col_for_jobs_table.py b/lib/galaxy/model/migrate/versions/0051_imported_col_for_jobs_table.py index ea490d88f70..a0c1e0f235c 100644 --- a/lib/galaxy/model/migrate/versions/0051_imported_col_for_jobs_table.py +++ b/lib/galaxy/model/migrate/versions/0051_imported_col_for_jobs_table.py @@ -7,44 +7,29 @@ import logging from sqlalchemy import Boolean, Column, MetaData, Table +from galaxy.model.migrate.versions.util import add_column, drop_column, engine_false + log = logging.getLogger(__name__) metadata = MetaData() -def engine_false(migrate_engine): - if migrate_engine.name in ['postgres', 'postgresql']: - return "FALSE" - elif migrate_engine.name in ['mysql', 'sqlite']: - return 0 - else: - raise Exception('Unknown database type: %s' % migrate_engine.name) - - def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() # Create and initialize imported column in job table. Jobs_table = Table("job", metadata, autoload=True) c = Column("imported", Boolean, default=False, index=True) + add_column(c, Jobs_table, index_name="ix_job_imported") try: - # Create - c.create(Jobs_table, index_name="ix_job_imported") - assert c is Jobs_table.c.imported - migrate_engine.execute("UPDATE job SET imported=%s" % engine_false(migrate_engine)) except Exception: - log.exception("Adding imported column to job table failed.") + log.exception("Updating column 'imported' of table 'job' failed.") def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - # Drop imported column from job table. - Jobs_table = Table("job", metadata, autoload=True) - try: - Jobs_table.c.imported.drop() - except Exception: - log.exception("Dropping column imported from job table failed.") + drop_column('imported', 'job', metadata) diff --git a/lib/galaxy/model/migrate/versions/0052_sample_dataset_table.py b/lib/galaxy/model/migrate/versions/0052_sample_dataset_table.py index 64253415fd0..f4928697cbd 100644 --- a/lib/galaxy/model/migrate/versions/0052_sample_dataset_table.py +++ b/lib/galaxy/model/migrate/versions/0052_sample_dataset_table.py @@ -12,30 +12,13 @@ from sqlalchemy import Column, DateTime, ForeignKey, Integer, MetaData, Table, T from sqlalchemy.exc import NoSuchTableError from galaxy.model.custom_types import TrimmedString +from galaxy.model.migrate.versions.util import localtimestamp, nextval now = datetime.datetime.utcnow log = logging.getLogger(__name__) metadata = MetaData() -def nextval(migrate_engine, table, col='id'): - if migrate_engine.name in ['postgres', 'postgresql']: - return "nextval('%s_%s_seq')" % (table, col) - elif migrate_engine.name in ['mysql', 'sqlite']: - return "null" - else: - raise Exception('Unable to convert data for unknown database type: %s' % migrate_engine.name) - - -def localtimestamp(migrate_engine): - if migrate_engine.name in ['mysql', 'postgres', 'postgresql']: - return "LOCALTIMESTAMP" - elif migrate_engine.name == 'sqlite': - return "current_date || ' ' || current_time" - else: - raise Exception('Unable to convert data for unknown database type: %s' % migrate_engine.name) - - SampleDataset_table = Table('sample_dataset', metadata, Column("id", Integer, primary_key=True), Column("create_time", DateTime, default=now), @@ -49,9 +32,10 @@ SampleDataset_table = Table('sample_dataset', metadata, def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() + try: SampleDataset_table.create() except Exception: diff --git a/lib/galaxy/model/migrate/versions/0053_item_ratings.py b/lib/galaxy/model/migrate/versions/0053_item_ratings.py index 3b0c21123cd..7da6b654584 100644 --- a/lib/galaxy/model/migrate/versions/0053_item_ratings.py +++ b/lib/galaxy/model/migrate/versions/0053_item_ratings.py @@ -7,6 +7,8 @@ import logging from sqlalchemy import Column, ForeignKey, Index, Integer, MetaData, Table +from galaxy.model.migrate.versions.util import create_table, drop_table + log = logging.getLogger(__name__) metadata = MetaData() @@ -44,15 +46,11 @@ VisualizationRatingAssociation_table = Table("visualization_rating_association", def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() - # Create history_rating_association table. - try: - HistoryRatingAssociation_table.create() - except Exception: - log.exception("Creating history_rating_association table failed.") + create_table(HistoryRatingAssociation_table) # Create history_dataset_association_rating_association table. try: @@ -69,55 +67,17 @@ def upgrade(migrate_engine): else: log.exception("Creating history_dataset_association_rating_association table failed.") - # Create stored_workflow_rating_association table. - try: - StoredWorkflowRatingAssociation_table.create() - except Exception: - log.exception("Creating stored_workflow_rating_association table failed.") - - # Create page_rating_association table. - try: - PageRatingAssociation_table.create() - except Exception: - log.exception("Creating page_rating_association table failed.") - - # Create visualization_rating_association table. - try: - VisualizationRatingAssociation_table.create() - except Exception: - log.exception("Creating visualization_rating_association table failed.") + create_table(StoredWorkflowRatingAssociation_table) + create_table(PageRatingAssociation_table) + create_table(VisualizationRatingAssociation_table) def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - # Drop history_rating_association table. - try: - HistoryRatingAssociation_table.drop() - except Exception: - log.exception("Dropping history_rating_association table failed.") - - # Drop history_dataset_association_rating_association table. - try: - HistoryDatasetAssociationRatingAssociation_table.drop() - except Exception: - log.exception("Dropping history_dataset_association_rating_association table failed.") - - # Drop stored_workflow_rating_association table. - try: - StoredWorkflowRatingAssociation_table.drop() - except Exception: - log.exception("Dropping stored_workflow_rating_association table failed.") - - # Drop page_rating_association table. - try: - PageRatingAssociation_table.drop() - except Exception: - log.exception("Dropping page_rating_association table failed.") - - # Drop visualization_rating_association table. - try: - VisualizationRatingAssociation_table.drop() - except Exception: - log.exception("Dropping visualization_rating_association table failed.") + drop_table(VisualizationRatingAssociation_table) + drop_table(PageRatingAssociation_table) + drop_table(StoredWorkflowRatingAssociation_table) + drop_table(HistoryDatasetAssociationRatingAssociation_table) + drop_table(HistoryRatingAssociation_table) diff --git a/lib/galaxy/model/migrate/versions/0054_visualization_dbkey.py b/lib/galaxy/model/migrate/versions/0054_visualization_dbkey.py index 9c66c8c5904..38f65b48c93 100644 --- a/lib/galaxy/model/migrate/versions/0054_visualization_dbkey.py +++ b/lib/galaxy/model/migrate/versions/0054_visualization_dbkey.py @@ -8,14 +8,15 @@ from json import loads from sqlalchemy import Column, Index, MetaData, Table, TEXT +from galaxy.model.migrate.versions.util import add_column, drop_column + log = logging.getLogger(__name__) metadata = MetaData() def upgrade(migrate_engine): - metadata.bind = migrate_engine - print(__doc__) + metadata.bind = migrate_engine metadata.reflect() Visualization_table = Table("visualization", metadata, autoload=True) @@ -23,16 +24,14 @@ def upgrade(migrate_engine): # Create dbkey columns. x = Column("dbkey", TEXT) + add_column(x, Visualization_table) y = Column("dbkey", TEXT) - x.create(Visualization_table) - y.create(Visualization_revision_table) + add_column(y, Visualization_revision_table) # Manually create indexes for compatability w/ mysql_length. xi = Index("ix_visualization_dbkey", Visualization_table.c.dbkey, mysql_length=200) xi.create() yi = Index("ix_visualization_revision_dbkey", Visualization_revision_table.c.dbkey, mysql_length=200) yi.create() - assert x is Visualization_table.c.dbkey - assert y is Visualization_revision_table.c.dbkey all_viz = migrate_engine.execute("SELECT visualization.id as viz_id, visualization_revision.id as viz_rev_id, visualization_revision.config FROM visualization_revision \ LEFT JOIN visualization ON visualization.id=visualization_revision.visualization_id") @@ -49,8 +48,5 @@ def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - Visualization_table = Table("visualization", metadata, autoload=True) - Visualization_revision_table = Table("visualization_revision", metadata, autoload=True) - - Visualization_table.c.dbkey.drop() - Visualization_revision_table.c.dbkey.drop() + drop_column('dbkey', 'visualization', metadata) + drop_column('dbkey', 'visualization_revision', metadata) diff --git a/lib/galaxy/model/migrate/versions/0055_add_pja_assoc_for_jobs.py b/lib/galaxy/model/migrate/versions/0055_add_pja_assoc_for_jobs.py index afc4f504d76..22f51b9865b 100644 --- a/lib/galaxy/model/migrate/versions/0055_add_pja_assoc_for_jobs.py +++ b/lib/galaxy/model/migrate/versions/0055_add_pja_assoc_for_jobs.py @@ -7,6 +7,8 @@ import logging from sqlalchemy import Column, ForeignKey, Integer, MetaData, Table +from galaxy.model.migrate.versions.util import create_table, drop_table + log = logging.getLogger(__name__) metadata = MetaData() @@ -17,20 +19,15 @@ PostJobActionAssociation_table = Table("post_job_action_association", metadata, def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() - try: - PostJobActionAssociation_table.create() - except Exception: - log.exception("Creating PostJobActionAssociation table failed.") + + create_table(PostJobActionAssociation_table) def downgrade(migrate_engine): metadata.bind = migrate_engine - # Load existing tables metadata.reflect() - try: - PostJobActionAssociation_table.drop() - except Exception: - log.exception("Dropping PostJobActionAssociation table failed.") + + drop_table(PostJobActionAssociation_table) diff --git a/lib/galaxy/model/migrate/versions/0060_history_archive_import.py b/lib/galaxy/model/migrate/versions/0060_history_archive_import.py index 27cae402d9c..9caddb2a303 100644 --- a/lib/galaxy/model/migrate/versions/0060_history_archive_import.py +++ b/lib/galaxy/model/migrate/versions/0060_history_archive_import.py @@ -8,6 +8,8 @@ import logging from sqlalchemy import Boolean, Column, ForeignKey, Integer, MetaData, Table, TEXT +from galaxy.model.migrate.versions.util import engine_false + log = logging.getLogger(__name__) metadata = MetaData() @@ -25,18 +27,9 @@ JobImportHistoryArchive_table = Table("job_import_history_archive", metadata, Column("archive_dir", TEXT)) -def engine_false(migrate_engine): - if migrate_engine.name in ['postgres', 'postgresql']: - return "FALSE" - elif migrate_engine.name in ['mysql', 'sqlite']: - return 0 - else: - raise Exception('Unknown database type: %s' % migrate_engine.name) - - def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() # Add column to history table and initialize. diff --git a/lib/galaxy/model/migrate/versions/0064_add_run_and_sample_run_association_tables.py b/lib/galaxy/model/migrate/versions/0064_add_run_and_sample_run_association_tables.py index c045295cadd..3372e0b1ff2 100644 --- a/lib/galaxy/model/migrate/versions/0064_add_run_and_sample_run_association_tables.py +++ b/lib/galaxy/model/migrate/versions/0064_add_run_and_sample_run_association_tables.py @@ -8,6 +8,8 @@ import logging from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, MetaData, Table +from galaxy.model.migrate.versions.util import create_table, drop_table + now = datetime.datetime.utcnow log = logging.getLogger(__name__) metadata = MetaData() @@ -32,36 +34,19 @@ SampleRunAssociation_table = Table("sample_run_association", metadata, def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() - try: - Run_table.create() - except Exception: - log.exception("Creating Run_table table failed.") - try: - RequestTypeRunAssociation_table.create() - except Exception: - log.exception("Creating RequestTypeRunAssociation table failed.") - try: - SampleRunAssociation_table.create() - except Exception: - log.exception("Creating SampleRunAssociation table failed.") + + create_table(Run_table) + create_table(RequestTypeRunAssociation_table) + create_table(SampleRunAssociation_table) def downgrade(migrate_engine): metadata.bind = migrate_engine - # Load existing tables metadata.reflect() - try: - SampleRunAssociation_table.drop() - except Exception: - log.exception("Dropping SampleRunAssociation table failed.") - try: - RequestTypeRunAssociation_table.drop() - except Exception: - log.exception("Dropping RequestTypeRunAssociation table failed.") - try: - Run_table.drop() - except Exception: - log.exception("Dropping Run_table table failed.") + + drop_table(SampleRunAssociation_table) + drop_table(RequestTypeRunAssociation_table) + drop_table(Run_table) diff --git a/lib/galaxy/model/migrate/versions/0067_populate_sequencer_table.py b/lib/galaxy/model/migrate/versions/0067_populate_sequencer_table.py index 0ec730b2deb..53f3526a0da 100644 --- a/lib/galaxy/model/migrate/versions/0067_populate_sequencer_table.py +++ b/lib/galaxy/model/migrate/versions/0067_populate_sequencer_table.py @@ -13,29 +13,12 @@ from sqlalchemy import Column, ForeignKey, Integer, MetaData, Table from sqlalchemy.exc import NoSuchTableError from galaxy.model.custom_types import JSONType +from galaxy.model.migrate.versions.util import localtimestamp, nextval log = logging.getLogger(__name__) metadata = MetaData() -def nextval(migrate_engine, table, col='id'): - if migrate_engine.name in ['postgres', 'postgresql']: - return "nextval('%s_%s_seq')" % (table, col) - elif migrate_engine.name in ['mysql', 'sqlite']: - return "null" - else: - raise Exception('Unable to convert data for unknown database type: %s' % migrate_engine.name) - - -def localtimestamp(migrate_engine): - if migrate_engine.name in ['mysql', 'postgres', 'postgresql']: - return "LOCALTIMESTAMP" - elif migrate_engine.name == 'sqlite': - return "current_date || ' ' || current_time" - else: - raise Exception('Unable to convert data for unknown database type: %s' % migrate_engine.name) - - def get_latest_id(migrate_engine, table): result = migrate_engine.execute("select id from %s order by id desc" % table) row = result.fetchone() @@ -188,9 +171,10 @@ def update_sequencer_id_in_request_type(migrate_engine, request_type_id, sequenc def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() + try: RequestType_table = Table("request_type", metadata, autoload=True) except NoSuchTableError: @@ -256,6 +240,7 @@ def upgrade(migrate_engine): def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() + try: RequestType_table = Table("request_type", metadata, autoload=True) except NoSuchTableError: diff --git a/lib/galaxy/model/migrate/versions/0068_rename_sequencer_to_external_services.py b/lib/galaxy/model/migrate/versions/0068_rename_sequencer_to_external_services.py index 1514c96691a..e9e93aab9ec 100644 --- a/lib/galaxy/model/migrate/versions/0068_rename_sequencer_to_external_services.py +++ b/lib/galaxy/model/migrate/versions/0068_rename_sequencer_to_external_services.py @@ -15,28 +15,19 @@ from migrate import ForeignKeyConstraint from sqlalchemy import Column, ForeignKey, Integer, MetaData, Table from sqlalchemy.exc import NoSuchTableError -# Need our custom types, but don't import anything else from model from galaxy.model.custom_types import TrimmedString +from galaxy.model.migrate.versions.util import nextval now = datetime.datetime.utcnow log = logging.getLogger(__name__) metadata = MetaData() -def nextval(migrate_engine, table, col='id'): - if migrate_engine.name in ['postgres', 'postgresql']: - return "nextval('%s_%s_seq')" % (table, col) - elif migrate_engine.name in ['mysql', 'sqlite']: - return "null" - else: - raise Exception('Unable to convert data for unknown database type: %s' % migrate_engine.name) - - def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) - # Load existing tables + metadata.bind = migrate_engine metadata.reflect() + # create the column. Call it external_services_id as the table 'sequencer' is # going to be renamed to 'external_service' try: @@ -137,8 +128,8 @@ def upgrade(migrate_engine): def downgrade(migrate_engine): metadata.bind = migrate_engine - # Load existing tables metadata.reflect() + # load sequencer & request_type table try: RequestType_table = Table("request_type", metadata, autoload=True) diff --git a/lib/galaxy/model/migrate/versions/0071_add_history_and_workflow_to_sample.py b/lib/galaxy/model/migrate/versions/0071_add_history_and_workflow_to_sample.py index 8cfc26f1e8a..ef99eb02322 100644 --- a/lib/galaxy/model/migrate/versions/0071_add_history_and_workflow_to_sample.py +++ b/lib/galaxy/model/migrate/versions/0071_add_history_and_workflow_to_sample.py @@ -8,37 +8,29 @@ import logging from sqlalchemy import Column, ForeignKey, Integer, MetaData, Table from galaxy.model.custom_types import JSONType +from galaxy.model.migrate.versions.util import add_column, drop_column log = logging.getLogger(__name__) metadata = MetaData() def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() - try: - Sample_table = Table("sample", metadata, autoload=True) - c1 = Column("workflow", JSONType, nullable=True) - c2 = Column("history_id", Integer, ForeignKey("history.id"), nullable=True) - c1.create(Sample_table) - c2.create(Sample_table) - assert c1 is Sample_table.c.workflow - assert c2 is Sample_table.c.history_id - except Exception: - log.exception("Adding history and workflow columns to sample table failed.") + + Sample_table = Table("sample", metadata, autoload=True) + c1 = Column("workflow", JSONType, nullable=True) + add_column(c1, Sample_table) + + c2 = Column("history_id", Integer, ForeignKey("history.id"), nullable=True) + add_column(c2, Sample_table) def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - try: - Sample_table = Table("sample", metadata, autoload=True) - Sample_table.c.workflow.drop() - except Exception: - log.exception("Dropping workflow column from sample table failed.") - try: - Sample_table = Table("sample", metadata, autoload=True) - Sample_table.c.history_id.drop() - except Exception: - log.exception("Dropping history column from sample table failed.") + + Sample_table = Table("sample", metadata, autoload=True) + drop_column('workflow', Sample_table) + drop_column('history_id', Sample_table) diff --git a/lib/galaxy/model/migrate/versions/0073_add_ldda_to_implicit_conversion_table.py b/lib/galaxy/model/migrate/versions/0073_add_ldda_to_implicit_conversion_table.py index e1b4f77902f..f20f9227fdc 100644 --- a/lib/galaxy/model/migrate/versions/0073_add_ldda_to_implicit_conversion_table.py +++ b/lib/galaxy/model/migrate/versions/0073_add_ldda_to_implicit_conversion_table.py @@ -5,34 +5,37 @@ from __future__ import print_function import logging -from sqlalchemy import Column, ForeignKey, Integer, MetaData, Table +from sqlalchemy import ( + Column, + ForeignKey, + Integer, + MetaData +) + +from galaxy.model.migrate.versions.util import ( + add_column, + drop_column +) log = logging.getLogger(__name__) metadata = MetaData() def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() - try: - Implicitly_converted_table = Table("implicitly_converted_dataset_association", metadata, autoload=True) - if migrate_engine.name != 'sqlite': - c = Column("ldda_parent_id", Integer, ForeignKey("library_dataset_dataset_association.id"), index=True, nullable=True) - else: - # Can't use the ForeignKey in sqlite. - c = Column("ldda_parent_id", Integer, index=True, nullable=True) - c.create(Implicitly_converted_table, index_name="ix_implicitly_converted_dataset_assoc_ldda_parent_id") - assert c is Implicitly_converted_table.c.ldda_parent_id - except Exception: - log.exception("Adding ldda_parent_id column to implicitly_converted_dataset_association table failed.") + + if migrate_engine.name != 'sqlite': + c = Column("ldda_parent_id", Integer, ForeignKey("library_dataset_dataset_association.id"), index=True, nullable=True) + else: + # Can't use the ForeignKey in SQLite. + c = Column("ldda_parent_id", Integer, index=True, nullable=True) + add_column(c, 'implicitly_converted_dataset_association', metadata, index_name='ix_implicitly_converted_dataset_assoc_ldda_parent_id') def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - try: - Implicitly_converted_table = Table("implicitly_converted_dataset_association", metadata, autoload=True) - Implicitly_converted_table.c.ldda_parent_id.drop() - except Exception: - log.exception("Dropping ldda_parent_id column from implicitly_converted_dataset_association table failed.") + + drop_column('ldda_parent_id', 'implicitly_converted_dataset_association', metadata) diff --git a/lib/galaxy/model/migrate/versions/0074_add_purged_column_to_library_dataset_table.py b/lib/galaxy/model/migrate/versions/0074_add_purged_column_to_library_dataset_table.py index 69d8ff056af..4010ef2db5a 100644 --- a/lib/galaxy/model/migrate/versions/0074_add_purged_column_to_library_dataset_table.py +++ b/lib/galaxy/model/migrate/versions/0074_add_purged_column_to_library_dataset_table.py @@ -5,41 +5,21 @@ from __future__ import print_function import logging -from sqlalchemy import Boolean, Column, MetaData, Table +from sqlalchemy import Boolean, Column, MetaData + +from galaxy.model.migrate.versions.util import add_column, drop_column, engine_false, engine_true log = logging.getLogger(__name__) metadata = MetaData() -def engine_false(migrate_engine): - if migrate_engine.name in ['postgres', 'postgresql']: - return "FALSE" - elif migrate_engine.name in ['mysql', 'sqlite']: - return 0 - else: - raise Exception('Unknown database type: %s' % migrate_engine.name) - - -def engine_true(migrate_engine): - if migrate_engine.name in ['postgres', 'postgresql']: - return "TRUE" - elif migrate_engine.name in ['mysql', 'sqlite']: - return 1 - else: - raise Exception('Unknown database type: %s' % migrate_engine.name) - - def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() - try: - LibraryDataset_table = Table("library_dataset", metadata, autoload=True) - c = Column("purged", Boolean, index=True, default=False) - c.create(LibraryDataset_table, index_name='ix_library_dataset_purged') - assert c is LibraryDataset_table.c.purged - except Exception: - log.exception("Adding purged column to library_dataset table failed.") + + c = Column('purged', Boolean, index=True, default=False) + add_column(c, 'library_dataset', metadata, index_name='ix_library_dataset_purged') # Update the purged flag to the default False cmd = "UPDATE library_dataset SET purged = %s;" % engine_false(migrate_engine) try: @@ -63,8 +43,7 @@ def upgrade(migrate_engine): def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - try: - LibraryDataset_table = Table("library_dataset", metadata, autoload=True) - LibraryDataset_table.c.purged.drop() - except Exception: - log.exception("Dropping purged column from library_dataset table failed.") + + # SQLAlchemy Migrate has a bug when dropping a boolean column in SQLite + if migrate_engine.name != 'sqlite': + drop_column('purged', 'library_dataset', metadata) diff --git a/lib/galaxy/model/migrate/versions/0078_add_columns_for_disk_usage_accounting.py b/lib/galaxy/model/migrate/versions/0078_add_columns_for_disk_usage_accounting.py index c0fc4b7b90b..3461fb12053 100644 --- a/lib/galaxy/model/migrate/versions/0078_add_columns_for_disk_usage_accounting.py +++ b/lib/galaxy/model/migrate/versions/0078_add_columns_for_disk_usage_accounting.py @@ -9,72 +9,44 @@ import logging from sqlalchemy import Boolean, Column, MetaData, Numeric, Table +from galaxy.model.migrate.versions.util import add_column, drop_column + log = logging.getLogger(__name__) metadata = MetaData() def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() - try: - Dataset_table = Table("dataset", metadata, autoload=True) - c = Column('total_size', Numeric(15, 0)) - c.create(Dataset_table) - assert c is Dataset_table.c.total_size - except Exception: - log.exception("Adding total_size column to dataset table failed.") + c = Column('total_size', Numeric(15, 0)) + add_column(c, 'dataset', metadata) + HistoryDatasetAssociation_table = Table("history_dataset_association", metadata, autoload=True) + c = Column("purged", Boolean, index=True, default=False) + add_column(c, HistoryDatasetAssociation_table, index_name="ix_history_dataset_association_purged") try: - HistoryDatasetAssociation_table = Table("history_dataset_association", metadata, autoload=True) - c = Column("purged", Boolean, index=True, default=False) - c.create(HistoryDatasetAssociation_table, index_name="ix_history_dataset_association_purged") - assert c is HistoryDatasetAssociation_table.c.purged migrate_engine.execute(HistoryDatasetAssociation_table.update().values(purged=False)) except Exception: - log.exception("Adding purged column to history_dataset_association table failed.") + log.exception("Updating column 'purged' of table 'history_dataset_association' failed.") - try: - User_table = Table("galaxy_user", metadata, autoload=True) - c = Column('disk_usage', Numeric(15, 0), index=True) - c.create(User_table, index_name="ix_galaxy_user_disk_usage") - assert c is User_table.c.disk_usage - except Exception: - log.exception("Adding disk_usage column to galaxy_user table failed.") + c = Column('disk_usage', Numeric(15, 0), index=True) + add_column(c, 'galaxy_user', metadata, index_name="ix_galaxy_user_disk_usage") - try: - GalaxySession_table = Table("galaxy_session", metadata, autoload=True) - c = Column('disk_usage', Numeric(15, 0), index=True) - c.create(GalaxySession_table, index_name="ix_galaxy_session_disk_usage") - assert c is GalaxySession_table.c.disk_usage - except Exception: - log.exception("Adding disk_usage column to galaxy_session table failed.") + c = Column('disk_usage', Numeric(15, 0), index=True) + add_column(c, 'galaxy_session', metadata, index_name="ix_galaxy_session_disk_usage") def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - try: - Dataset_table = Table("dataset", metadata, autoload=True) - Dataset_table.c.total_size.drop() - except Exception: - log.exception("Dropping total_size column from dataset table failed.") - try: - HistoryDatasetAssociation_table = Table("history_dataset_association", metadata, autoload=True) - HistoryDatasetAssociation_table.c.purged.drop() - except Exception: - log.exception("Dropping purged column from history_dataset_association table failed.") + drop_column('disk_usage', 'galaxy_session', metadata) + drop_column('disk_usage', 'galaxy_user', metadata) - try: - User_table = Table("galaxy_user", metadata, autoload=True) - User_table.c.disk_usage.drop() - except Exception: - log.exception("Dropping disk_usage column from galaxy_user table failed.") + # SQLAlchemy Migrate has a bug when dropping a boolean column in SQLite + if migrate_engine.name != 'sqlite': + drop_column('purged', 'history_dataset_association', metadata) - try: - GalaxySession_table = Table("galaxy_session", metadata, autoload=True) - GalaxySession_table.c.disk_usage.drop() - except Exception: - log.exception("Dropping disk_usage column from galaxy_session table failed.") + drop_column('total_size', 'dataset', metadata) diff --git a/lib/galaxy/model/migrate/versions/0082_add_tool_shed_repository_table.py b/lib/galaxy/model/migrate/versions/0082_add_tool_shed_repository_table.py index de7160de87e..b1777ff8e9d 100644 --- a/lib/galaxy/model/migrate/versions/0082_add_tool_shed_repository_table.py +++ b/lib/galaxy/model/migrate/versions/0082_add_tool_shed_repository_table.py @@ -37,8 +37,8 @@ ToolShedRepository_table = Table("tool_shed_repository", metadata, def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() try: ToolShedRepository_table.create() diff --git a/lib/galaxy/model/migrate/versions/0083_add_prepare_files_to_task.py b/lib/galaxy/model/migrate/versions/0083_add_prepare_files_to_task.py index 9c5f5038855..2373f44f280 100644 --- a/lib/galaxy/model/migrate/versions/0083_add_prepare_files_to_task.py +++ b/lib/galaxy/model/migrate/versions/0083_add_prepare_files_to_task.py @@ -7,53 +7,35 @@ import logging from sqlalchemy import Column, MetaData, String, Table, TEXT +from galaxy.model.migrate.versions.util import add_column, drop_column + log = logging.getLogger(__name__) metadata = MetaData() def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() - try: - task_table = Table("task", metadata, autoload=True) - c = Column("prepare_input_files_cmd", TEXT, nullable=True) - c.create(task_table) - assert c is task_table.c.prepare_input_files_cmd - except Exception: - log.exception("Adding prepare_input_files_cmd column to task table failed.") - try: - task_table = Table("task", metadata, autoload=True) - c = Column("working_directory", String(1024), nullable=True) - c.create(task_table) - assert c is task_table.c.working_directory - except Exception: - log.exception("Adding working_directory column to task table failed.") + + task_table = Table("task", metadata, autoload=True) + c = Column("prepare_input_files_cmd", TEXT, nullable=True) + add_column(c, task_table) + + c = Column("working_directory", String(1024), nullable=True) + add_column(c, task_table) # remove the 'part_file' column - nobody used tasks before this, so no data needs to be migrated - try: - task_table.c.part_file.drop() - except Exception: - log.exception("Deleting column 'part_file' from the 'task' table failed.") + drop_column('part_file', task_table) def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - try: - task_table = Table("task", metadata, autoload=True) - task_table.c.prepare_input_files_cmd.drop() - except Exception: - log.exception("Dropping prepare_input_files_cmd column from task table failed.") - try: - task_table = Table("task", metadata, autoload=True) - task_table.c.working_directory.drop() - except Exception: - log.exception("Dropping working_directory column from task table failed.") - try: - task_table = Table("task", metadata, autoload=True) - c = Column("part_file", String(1024), nullable=True) - c.create(task_table) - assert c is task_table.c.part_file - except Exception: - log.exception("Adding part_file column to task table failed.") + + task_table = Table("task", metadata, autoload=True) + c = Column("part_file", String(1024), nullable=True) + add_column(c, task_table) + + drop_column('working_directory', task_table) + drop_column('prepare_input_files_cmd', task_table) diff --git a/lib/galaxy/model/migrate/versions/0084_add_ldda_id_to_implicit_conversion_table.py b/lib/galaxy/model/migrate/versions/0084_add_ldda_id_to_implicit_conversion_table.py index c111aa02f59..c2275ccdcc0 100644 --- a/lib/galaxy/model/migrate/versions/0084_add_ldda_id_to_implicit_conversion_table.py +++ b/lib/galaxy/model/migrate/versions/0084_add_ldda_id_to_implicit_conversion_table.py @@ -5,33 +5,37 @@ from __future__ import print_function import logging -from sqlalchemy import Column, ForeignKey, Integer, MetaData, Table +from sqlalchemy import ( + Column, + ForeignKey, + Integer, + MetaData +) + +from galaxy.model.migrate.versions.util import ( + add_column, + drop_column +) log = logging.getLogger(__name__) metadata = MetaData() def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() - try: - Implicitly_converted_table = Table("implicitly_converted_dataset_association", metadata, autoload=True) - if migrate_engine.name != 'sqlite': - c = Column("ldda_id", Integer, ForeignKey("library_dataset_dataset_association.id"), index=True, nullable=True) - else: - c = Column("ldda_id", Integer, index=True, nullable=True) - c.create(Implicitly_converted_table, index_name="ix_implicitly_converted_ds_assoc_ldda_id") - assert c is Implicitly_converted_table.c.ldda_id - except Exception: - log.exception("Adding ldda_id column to implicitly_converted_dataset_association table failed.") + + if migrate_engine.name != 'sqlite': + c = Column("ldda_id", Integer, ForeignKey("library_dataset_dataset_association.id"), index=True, nullable=True) + else: + # Can't use the ForeignKey in SQLite. + c = Column("ldda_id", Integer, index=True, nullable=True) + add_column(c, 'implicitly_converted_dataset_association', metadata, index_name='ix_implicitly_converted_ds_assoc_ldda_id') def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - try: - Implicitly_converted_table = Table("implicitly_converted_dataset_association", metadata, autoload=True) - Implicitly_converted_table.c.ldda_id.drop() - except Exception: - log.exception("Dropping ldda_id column from implicitly_converted_dataset_association table failed.") + + drop_column('ldda_id', 'implicitly_converted_dataset_association', metadata) diff --git a/lib/galaxy/model/migrate/versions/0086_add_tool_shed_repository_table_columns.py b/lib/galaxy/model/migrate/versions/0086_add_tool_shed_repository_table_columns.py index c77f51a8b93..979de66fcb3 100644 --- a/lib/galaxy/model/migrate/versions/0086_add_tool_shed_repository_table_columns.py +++ b/lib/galaxy/model/migrate/versions/0086_add_tool_shed_repository_table_columns.py @@ -8,8 +8,8 @@ import sys from sqlalchemy import Boolean, Column, MetaData, Table -# Need our custom types, but don't import anything else from model from galaxy.model.custom_types import JSONType +from galaxy.model.migrate.versions.util import add_column, drop_column, engine_false log = logging.getLogger(__name__) log.setLevel(logging.DEBUG) @@ -22,57 +22,35 @@ log.addHandler(handler) metadata = MetaData() -def engine_false(migrate_engine): - if migrate_engine.name in ['postgres', 'postgresql']: - return "FALSE" - elif migrate_engine.name in ['mysql', 'sqlite']: - return 0 - else: - raise Exception('Unknown database type: %s' % migrate_engine.name) - - def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() + ToolShedRepository_table = Table("tool_shed_repository", metadata, autoload=True) c = Column("metadata", JSONType(), nullable=True) - try: - c.create(ToolShedRepository_table) - assert c is ToolShedRepository_table.c.metadata - except Exception: - log.exception("Adding metadata column to the tool_shed_repository table failed.") + add_column(c, ToolShedRepository_table) c = Column("includes_datatypes", Boolean, index=True, default=False) + add_column(c, ToolShedRepository_table, index_name="ix_tool_shed_repository_includes_datatypes") try: - c.create(ToolShedRepository_table, index_name="ix_tool_shed_repository_includes_datatypes") - assert c is ToolShedRepository_table.c.includes_datatypes migrate_engine.execute("UPDATE tool_shed_repository SET includes_datatypes=%s" % engine_false(migrate_engine)) except Exception: - log.exception("Adding includes_datatypes column to the tool_shed_repository table failed.") + log.exception("Updating column 'includes_datatypes' of table 'tool_shed_repository' failed.") c = Column("update_available", Boolean, default=False) + add_column(c, ToolShedRepository_table) try: - c.create(ToolShedRepository_table) - assert c is ToolShedRepository_table.c.update_available migrate_engine.execute("UPDATE tool_shed_repository SET update_available=%s" % engine_false(migrate_engine)) except Exception: - log.exception("Adding update_available column to the tool_shed_repository table failed.") + log.exception("Updating column 'update_available' of table 'tool_shed_repository' failed.") def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() + ToolShedRepository_table = Table("tool_shed_repository", metadata, autoload=True) - try: - ToolShedRepository_table.c.metadata.drop() - except Exception: - log.exception("Dropping column metadata from the tool_shed_repository table failed.") + drop_column('metadata', ToolShedRepository_table) # SQLAlchemy Migrate has a bug when dropping a boolean column in SQLite if migrate_engine.name != 'sqlite': - try: - ToolShedRepository_table.c.includes_datatypes.drop() - except Exception: - log.exception("Dropping column includes_datatypes from the tool_shed_repository table failed.") - try: - ToolShedRepository_table.c.update_available.drop() - except Exception: - log.exception("Dropping column update_available from the tool_shed_repository table failed.") + drop_column('includes_datatypes', ToolShedRepository_table) + drop_column('update_available', ToolShedRepository_table) diff --git a/lib/galaxy/model/migrate/versions/0089_add_object_store_id_columns.py b/lib/galaxy/model/migrate/versions/0089_add_object_store_id_columns.py index b99e12cdc34..7b4d304fa25 100644 --- a/lib/galaxy/model/migrate/versions/0089_add_object_store_id_columns.py +++ b/lib/galaxy/model/migrate/versions/0089_add_object_store_id_columns.py @@ -14,9 +14,10 @@ metadata = MetaData() def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() + for t_name in ('dataset', 'job', 'metadata_file'): t = Table(t_name, metadata, autoload=True) c = Column("object_store_id", TrimmedString(255), index=True) @@ -30,6 +31,7 @@ def upgrade(migrate_engine): def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() + for t_name in ('dataset', 'job', 'metadata_file'): t = Table(t_name, metadata, autoload=True) try: diff --git a/lib/galaxy/model/migrate/versions/0090_add_tool_shed_repository_table_columns.py b/lib/galaxy/model/migrate/versions/0090_add_tool_shed_repository_table_columns.py index 05deb99d8ef..f473ede50ca 100644 --- a/lib/galaxy/model/migrate/versions/0090_add_tool_shed_repository_table_columns.py +++ b/lib/galaxy/model/migrate/versions/0090_add_tool_shed_repository_table_columns.py @@ -8,6 +8,8 @@ import sys from sqlalchemy import Boolean, Column, MetaData, Table +from galaxy.model.migrate.versions.util import drop_column, engine_false + log = logging.getLogger(__name__) log.setLevel(logging.DEBUG) handler = logging.StreamHandler(sys.stdout) @@ -19,19 +21,11 @@ log.addHandler(handler) metadata = MetaData() -def engine_false(migrate_engine): - if migrate_engine.name in ['postgres', 'postgresql']: - return "FALSE" - elif migrate_engine.name in ['mysql', 'sqlite']: - return 0 - else: - raise Exception('Unknown database type: %s' % migrate_engine.name) - - def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() + ToolShedRepository_table = Table("tool_shed_repository", metadata, autoload=True) c = Column("uninstalled", Boolean, default=False) try: @@ -52,14 +46,9 @@ def upgrade(migrate_engine): def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() + ToolShedRepository_table = Table("tool_shed_repository", metadata, autoload=True) # SQLAlchemy Migrate has a bug when dropping a boolean column in SQLite if migrate_engine.name != 'sqlite': - try: - ToolShedRepository_table.c.uninstalled.drop() - except Exception: - log.exception("Dropping column uninstalled from the tool_shed_repository table failed.") - try: - ToolShedRepository_table.c.dist_to_shed.drop() - except Exception: - log.exception("Dropping column dist_to_shed from the tool_shed_repository table failed.") + drop_column('uninstalled', ToolShedRepository_table) + drop_column('dist_to_shed', ToolShedRepository_table) diff --git a/lib/galaxy/model/migrate/versions/0091_add_tool_version_tables.py b/lib/galaxy/model/migrate/versions/0091_add_tool_version_tables.py index 4dcecbaaf89..00b3baa7bc2 100644 --- a/lib/galaxy/model/migrate/versions/0091_add_tool_version_tables.py +++ b/lib/galaxy/model/migrate/versions/0091_add_tool_version_tables.py @@ -10,8 +10,8 @@ from json import loads from sqlalchemy import Column, DateTime, ForeignKey, Integer, MetaData, String, Table, TEXT -# Need our custom types, but don't import anything else from model from galaxy.model.custom_types import _sniffnfix_pg9_hex, TrimmedString +from galaxy.model.migrate.versions.util import localtimestamp, nextval now = datetime.datetime.utcnow log = logging.getLogger(__name__) @@ -25,24 +25,6 @@ log.addHandler(handler) metadata = MetaData() -def nextval(migrate_engine, table, col='id'): - if migrate_engine.name in ['postgres', 'postgresql']: - return "nextval('%s_%s_seq')" % (table, col) - elif migrate_engine.name in ['mysql', 'sqlite']: - return "null" - else: - raise Exception('Unable to convert data for unknown database type: %s' % migrate_engine.name) - - -def localtimestamp(migrate_engine): - if migrate_engine.name in ['mysql', 'postgres', 'postgresql']: - return "LOCALTIMESTAMP" - elif migrate_engine.name == 'sqlite': - return "current_date || ' ' || current_time" - else: - raise Exception('Unable to convert data for unknown database type: %s' % migrate_engine.name) - - ToolVersion_table = Table("tool_version", metadata, Column("id", Integer, primary_key=True), Column("create_time", DateTime, default=now), @@ -57,12 +39,10 @@ ToolVersionAssociation_table = Table("tool_version_association", metadata, def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) - - ToolIdGuidMap_table = Table("tool_id_guid_map", metadata, autoload=True) - + metadata.bind = migrate_engine metadata.reflect() + # Create the tables. try: ToolVersion_table.create() @@ -90,6 +70,7 @@ def upgrade(migrate_engine): count += 1 print("Added %d rows to the new tool_version table." % count) # Drop the tool_id_guid_map table since the 2 new tables render it unnecessary. + ToolIdGuidMap_table = Table("tool_id_guid_map", metadata, autoload=True) try: ToolIdGuidMap_table.drop() except Exception: diff --git a/lib/galaxy/model/migrate/versions/0094_add_job_handler_col.py b/lib/galaxy/model/migrate/versions/0094_add_job_handler_col.py index a93a56c4ec7..52eb9eed12c 100644 --- a/lib/galaxy/model/migrate/versions/0094_add_job_handler_col.py +++ b/lib/galaxy/model/migrate/versions/0094_add_job_handler_col.py @@ -5,10 +5,10 @@ from __future__ import print_function import logging -from sqlalchemy import Column, MetaData, Table +from sqlalchemy import Column, MetaData -# Need our custom types, but don't import anything else from model from galaxy.model.custom_types import TrimmedString +from galaxy.model.migrate.versions.util import add_column, drop_column log = logging.getLogger(__name__) metadata = MetaData() @@ -18,28 +18,15 @@ handler_col = Column("handler", TrimmedString(255), index=True) def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() - # Add column to Job table. - try: - Job_table = Table("job", metadata, autoload=True) - handler_col.create(Job_table, index_name="ix_job_handler") - assert handler_col is Job_table.c.handler - - except Exception: - log.exception("Adding column 'handler' to job table failed.") + add_column(handler_col, 'job', metadata, index_name="ix_job_handler") def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - # Drop column from Job table. - try: - Job_table = Table("job", metadata, autoload=True) - handler_col = Job_table.c.handler - handler_col.drop() - except Exception: - log.exception("Dropping column 'handler' from job table failed.") + drop_column('handler', 'job', metadata) diff --git a/lib/galaxy/model/migrate/versions/0098_genome_index_tool_data_table.py b/lib/galaxy/model/migrate/versions/0098_genome_index_tool_data_table.py index d83d0fa022b..fb5a71fdc57 100644 --- a/lib/galaxy/model/migrate/versions/0098_genome_index_tool_data_table.py +++ b/lib/galaxy/model/migrate/versions/0098_genome_index_tool_data_table.py @@ -9,6 +9,8 @@ import sys from sqlalchemy import Column, DateTime, ForeignKey, Integer, MetaData, String, Table +from galaxy.model.migrate.versions.util import create_table, drop_table + now = datetime.datetime.utcnow log = logging.getLogger(__name__) log.setLevel(logging.DEBUG) @@ -35,20 +37,15 @@ GenomeIndexToolData_table = Table("genome_index_tool_data", metadata, def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) - + metadata.bind = migrate_engine metadata.reflect() - try: - GenomeIndexToolData_table.create() - except Exception: - log.exception("Creating genome_index_tool_data table failed.") + + create_table(GenomeIndexToolData_table) def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - try: - GenomeIndexToolData_table.drop() - except Exception: - log.exception("Dropping genome_index_tool_data table failed.") + + drop_table(GenomeIndexToolData_table) diff --git a/lib/galaxy/model/migrate/versions/0101_drop_installed_changeset_revision_column.py b/lib/galaxy/model/migrate/versions/0101_drop_installed_changeset_revision_column.py index e9fb8feb899..6b1a3d89df9 100644 --- a/lib/galaxy/model/migrate/versions/0101_drop_installed_changeset_revision_column.py +++ b/lib/galaxy/model/migrate/versions/0101_drop_installed_changeset_revision_column.py @@ -9,6 +9,8 @@ import sys from sqlalchemy import MetaData, Table from sqlalchemy.exc import NoSuchTableError +from galaxy.model.migrate.versions.util import drop_column + log = logging.getLogger(__name__) log.setLevel(logging.DEBUG) handler = logging.StreamHandler(sys.stdout) @@ -21,8 +23,8 @@ metadata = MetaData() def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() try: ToolDependency_table = Table("tool_dependency", metadata, autoload=True) @@ -30,11 +32,7 @@ def upgrade(migrate_engine): ToolDependency_table = None log.debug("Failed loading table tool_dependency") if ToolDependency_table is not None: - try: - col = ToolDependency_table.c.installed_changeset_revision - col.drop() - except Exception: - log.exception("Dropping column 'installed_changeset_revision' from tool_dependency table failed.") + drop_column('installed_changeset_revision', ToolDependency_table) def downgrade(migrate_engine): diff --git a/lib/galaxy/model/migrate/versions/0102_add_tool_dependency_status_columns.py b/lib/galaxy/model/migrate/versions/0102_add_tool_dependency_status_columns.py index 43cf8d775b0..118fcf28816 100644 --- a/lib/galaxy/model/migrate/versions/0102_add_tool_dependency_status_columns.py +++ b/lib/galaxy/model/migrate/versions/0102_add_tool_dependency_status_columns.py @@ -8,8 +8,8 @@ import sys from sqlalchemy import Boolean, Column, MetaData, Table, TEXT -# Need our custom types, but don't import anything else from model from galaxy.model.custom_types import TrimmedString +from galaxy.model.migrate.versions.util import add_column, drop_column log = logging.getLogger(__name__) log.setLevel(logging.DEBUG) @@ -23,50 +23,34 @@ metadata = MetaData() def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() + ToolDependency_table = Table("tool_dependency", metadata, autoload=True) if migrate_engine.name == 'sqlite': col = Column("status", TrimmedString(255)) else: col = Column("status", TrimmedString(255), nullable=False) - try: - col.create(ToolDependency_table) - assert col is ToolDependency_table.c.status - except Exception: - log.exception("Adding status column to the tool_dependency table failed.") - col = Column("error_message", TEXT) - try: - col.create(ToolDependency_table) - assert col is ToolDependency_table.c.error_message - except Exception: - log.exception("Adding error_message column to the tool_dependency table failed.") + add_column(col, ToolDependency_table) + col = Column("error_message", TEXT) + add_column(col, ToolDependency_table) + + # SQLAlchemy Migrate has a bug when dropping a boolean column in SQLite + # TODO move to alembic. if migrate_engine.name != 'sqlite': - # This breaks in sqlite due to failure to drop check constraint. - # TODO move to alembic. - try: - ToolDependency_table.c.uninstalled.drop() - except Exception: - log.exception("Dropping uninstalled column from the tool_dependency table failed.") + drop_column('uninstalled', ToolDependency_table) def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() + ToolDependency_table = Table("tool_dependency", metadata, autoload=True) - try: - ToolDependency_table.c.status.drop() - except Exception: - log.exception("Dropping column status from the tool_dependency table failed.") - try: - ToolDependency_table.c.error_message.drop() - except Exception: - log.exception("Dropping column error_message from the tool_dependency table failed.") - col = Column("uninstalled", Boolean, default=False) - try: - col.create(ToolDependency_table) - assert col is ToolDependency_table.c.uninstalled - except Exception: - log.exception("Adding uninstalled column to the tool_dependency table failed.") + if migrate_engine.name != 'sqlite': + col = Column("uninstalled", Boolean, default=False) + add_column(col, ToolDependency_table) + + drop_column('error_message', ToolDependency_table) + drop_column('status', ToolDependency_table) diff --git a/lib/galaxy/model/migrate/versions/0106_add_missing_indexes.py b/lib/galaxy/model/migrate/versions/0106_add_missing_indexes.py index 946be8e433a..56f11fdef07 100644 --- a/lib/galaxy/model/migrate/versions/0106_add_missing_indexes.py +++ b/lib/galaxy/model/migrate/versions/0106_add_missing_indexes.py @@ -6,7 +6,8 @@ from __future__ import print_function import logging from sqlalchemy import Index, MetaData, Table -from sqlalchemy.engine import reflection + +from galaxy.model.migrate.versions.util import add_index log = logging.getLogger(__name__) log.setLevel(logging.DEBUG) @@ -54,18 +55,9 @@ def upgrade(migrate_engine): print(__doc__) metadata.bind = migrate_engine metadata.reflect() - insp = reflection.Inspector.from_engine(migrate_engine) - # Create missing indexes + for ix, table, col in indexes: - try: - log.debug("Creating index '%s' on column '%s' in table '%s'" % (ix, col, table)) - t = Table(table, metadata, autoload=True) - if ix not in [ins_ix.get('name', None) for ins_ix in insp.get_indexes(table)]: - Index(ix, t.c[col]).create() - else: - pass # Index already exists, don't recreate. - except Exception: - log.exception("Unable to create index '%s'.", ix) + add_index(ix, table, col, metadata) def downgrade(migrate_engine): diff --git a/lib/galaxy/model/migrate/versions/0116_drop_update_available_col_add_tool_shed_status_col.py b/lib/galaxy/model/migrate/versions/0116_drop_update_available_col_add_tool_shed_status_col.py index efa6c840388..13788bfdf08 100644 --- a/lib/galaxy/model/migrate/versions/0116_drop_update_available_col_add_tool_shed_status_col.py +++ b/lib/galaxy/model/migrate/versions/0116_drop_update_available_col_add_tool_shed_status_col.py @@ -10,6 +10,7 @@ from sqlalchemy import Boolean, Column, MetaData, Table from sqlalchemy.exc import NoSuchTableError from galaxy.model.custom_types import JSONType +from galaxy.model.migrate.versions.util import add_column, drop_column, engine_false log = logging.getLogger(__name__) log.setLevel(logging.DEBUG) @@ -22,43 +23,28 @@ log.addHandler(handler) metadata = MetaData() -def engine_false(migrate_engine): - if migrate_engine.name in ['postgres', 'postgresql']: - return "FALSE" - elif migrate_engine.name in ['mysql', 'sqlite']: - return 0 - else: - raise Exception('Unknown database type: %s' % migrate_engine.name) - - def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() + try: ToolShedRepository_table = Table("tool_shed_repository", metadata, autoload=True) except NoSuchTableError: ToolShedRepository_table = None log.debug("Failed loading table tool_shed_repository") if ToolShedRepository_table is not None: - # For some unknown reason it is no longer possible to drop a column in a migration script if using the sqlite database. + # SQLAlchemy Migrate has a bug when dropping a boolean column in SQLite if migrate_engine.name != 'sqlite': - try: - col = ToolShedRepository_table.c.update_available - col.drop() - except Exception: - log.exception("Dropping column update_available from the tool_shed_repository table failed.") + drop_column('update_available', ToolShedRepository_table) c = Column("tool_shed_status", JSONType, nullable=True) - try: - c.create(ToolShedRepository_table) - assert c is ToolShedRepository_table.c.tool_shed_status - except Exception: - log.exception("Adding tool_shed_status column to the tool_shed_repository table failed.") + add_column(c, ToolShedRepository_table) def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() + try: ToolShedRepository_table = Table("tool_shed_repository", metadata, autoload=True) except NoSuchTableError: @@ -67,15 +53,10 @@ def downgrade(migrate_engine): if ToolShedRepository_table is not None: # For some unknown reason it is no longer possible to drop a column in a migration script if using the sqlite database. if migrate_engine.name != 'sqlite': - try: - col = ToolShedRepository_table.c.tool_shed_status - col.drop() - except Exception: - log.exception("Dropping column tool_shed_status from the tool_shed_repository table failed.") + drop_column('tool_shed_status', ToolShedRepository_table) c = Column("update_available", Boolean, default=False) + add_column(c, ToolShedRepository_table) try: - c.create(ToolShedRepository_table) - assert c is ToolShedRepository_table.c.update_available migrate_engine.execute("UPDATE tool_shed_repository SET update_available=%s" % engine_false(migrate_engine)) except Exception: - log.exception("Adding column update_available to the tool_shed_repository table failed.") + log.exception("Updating column 'update_available' of table 'tool_shed_repository' failed.") diff --git a/lib/galaxy/model/migrate/versions/0119_job_metrics.py b/lib/galaxy/model/migrate/versions/0119_job_metrics.py index b2242b603fc..7f9590ca11b 100644 --- a/lib/galaxy/model/migrate/versions/0119_job_metrics.py +++ b/lib/galaxy/model/migrate/versions/0119_job_metrics.py @@ -7,6 +7,8 @@ import logging from sqlalchemy import Column, ForeignKey, Integer, MetaData, Numeric, Table, Unicode +from galaxy.model.migrate.versions.util import create_table, drop_table + log = logging.getLogger(__name__) metadata = MetaData() @@ -65,12 +67,12 @@ TABLES = [ def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() for table in TABLES: - __create(table) + create_table(table) def downgrade(migrate_engine): @@ -78,18 +80,4 @@ def downgrade(migrate_engine): metadata.reflect() for table in TABLES: - __drop(table) - - -def __create(table): - try: - table.create() - except Exception: - log.exception("Creating %s table failed.", table.name) - - -def __drop(table): - try: - table.drop() - except Exception: - log.exception("Dropping %s table failed.", table.name) + drop_table(table) diff --git a/lib/galaxy/model/migrate/versions/0120_dataset_collections.py b/lib/galaxy/model/migrate/versions/0120_dataset_collections.py index 0cffb880a33..e7922631e78 100644 --- a/lib/galaxy/model/migrate/versions/0120_dataset_collections.py +++ b/lib/galaxy/model/migrate/versions/0120_dataset_collections.py @@ -9,6 +9,7 @@ import logging from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, MetaData, Table, TEXT, Unicode from galaxy.model.custom_types import TrimmedString +from galaxy.model.migrate.versions.util import create_table, drop_table now = datetime.datetime.utcnow log = logging.getLogger(__name__) @@ -129,12 +130,12 @@ TABLES = [ def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() for table in TABLES: - __create(table) + create_table(table) try: hda_table = Table("history_dataset_association", metadata, autoload=True) @@ -155,18 +156,4 @@ def downgrade(migrate_engine): log.exception("Dropping HDA column failed.") for table in reversed(TABLES): - __drop(table) - - -def __create(table): - try: - table.create() - except Exception: - log.exception("Creating %s table failed.", table.name) - - -def __drop(table): - try: - table.drop() - except Exception: - log.exception("Dropping %s table failed.", table.name) + drop_table(table) diff --git a/lib/galaxy/model/migrate/versions/0122_grow_mysql_blobs.py b/lib/galaxy/model/migrate/versions/0122_grow_mysql_blobs.py index 39842616410..90a81af0de2 100644 --- a/lib/galaxy/model/migrate/versions/0122_grow_mysql_blobs.py +++ b/lib/galaxy/model/migrate/versions/0122_grow_mysql_blobs.py @@ -33,8 +33,8 @@ BLOB_COLUMNS = [ def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() if migrate_engine.name != "mysql": @@ -49,7 +49,4 @@ def upgrade(migrate_engine): def downgrade(migrate_engine): - metadata.bind = migrate_engine - metadata.reflect() - # Ignoring..., changed the datatype so no guarantee these columns weren't - # MEDIUMBLOBs before. + pass diff --git a/lib/galaxy/model/migrate/versions/0123_add_workflow_request_tables.py b/lib/galaxy/model/migrate/versions/0123_add_workflow_request_tables.py index 3a288f02d31..34113a3bd20 100644 --- a/lib/galaxy/model/migrate/versions/0123_add_workflow_request_tables.py +++ b/lib/galaxy/model/migrate/versions/0123_add_workflow_request_tables.py @@ -8,6 +8,7 @@ import logging from sqlalchemy import Column, ForeignKey, Integer, MetaData, String, Table, TEXT, Unicode from galaxy.model.custom_types import JSONType, TrimmedString, UUIDType +from galaxy.model.migrate.versions.util import add_column, create_table, drop_column, drop_table log = logging.getLogger(__name__) metadata = MetaData() @@ -66,7 +67,7 @@ def upgrade(migrate_engine): metadata.reflect() for table in TABLES: - __create(table) + create_table(table) History_column = Column("history_id", Integer, ForeignKey("history.id"), nullable=True) State_column = Column("state", TrimmedString(64)) @@ -75,11 +76,11 @@ def upgrade(migrate_engine): SchedulerId_column = Column("scheduler", TrimmedString(255)) HandlerId_column = Column("handler", TrimmedString(255)) WorkflowUUID_column = Column("uuid", UUIDType, nullable=True) - __add_column(History_column, "workflow_invocation", metadata) - __add_column(State_column, "workflow_invocation", metadata) - __add_column(SchedulerId_column, "workflow_invocation", metadata, index_nane="id_workflow_invocation_scheduler") - __add_column(HandlerId_column, "workflow_invocation", metadata, index_name="id_workflow_invocation_handler") - __add_column(WorkflowUUID_column, "workflow_invocation", metadata) + add_column(History_column, "workflow_invocation", metadata) + add_column(State_column, "workflow_invocation", metadata) + add_column(SchedulerId_column, "workflow_invocation", metadata, index_nane="id_workflow_invocation_scheduler") + add_column(HandlerId_column, "workflow_invocation", metadata, index_name="id_workflow_invocation_handler") + add_column(WorkflowUUID_column, "workflow_invocation", metadata) # All previous invocations have been scheduled... cmd = "UPDATE workflow_invocation SET state = 'scheduled'" @@ -89,7 +90,7 @@ def upgrade(migrate_engine): log.exception("failed to update past workflow invocation states.") WorkflowInvocationStepAction_column = Column("action", JSONType, nullable=True) - __add_column(WorkflowInvocationStepAction_column, "workflow_invocation_step", metadata) + add_column(WorkflowInvocationStepAction_column, "workflow_invocation_step", metadata) def downgrade(migrate_engine): @@ -97,41 +98,11 @@ def downgrade(migrate_engine): metadata.reflect() for table in TABLES: - __drop(table) + drop_table(table) - __drop_column("state", "workflow_invocation", metadata) - __drop_column("scheduler", "workflow_invocation", metadata) - __drop_column("uuid", "workflow_invocation", metadata) - __drop_column("history_id", "workflow_invocation", metadata) - __drop_column("handler", "workflow_invocation", metadata) - __drop_column("action", "workflow_invocation_step", metadata) - - -def __add_column(column, table_name, metadata, **kwds): - try: - table = Table(table_name, metadata, autoload=True) - column.create(table, **kwds) - except Exception: - log.exception("Adding column %s column failed.", column) - - -def __drop_column(column_name, table_name, metadata): - try: - table = Table(table_name, metadata, autoload=True) - getattr(table.c, column_name).drop() - except Exception: - log.exception("Dropping column %s failed.", column_name) - - -def __create(table): - try: - table.create() - except Exception: - log.exception("Creating %s table failed.", table.name) - - -def __drop(table): - try: - table.drop() - except Exception: - log.exception("Dropping %s table failed.", table.name) + drop_column("state", "workflow_invocation", metadata) + drop_column("scheduler", "workflow_invocation", metadata) + drop_column("uuid", "workflow_invocation", metadata) + drop_column("history_id", "workflow_invocation", metadata) + drop_column("handler", "workflow_invocation", metadata) + drop_column("action", "workflow_invocation_step", metadata) diff --git a/lib/galaxy/model/migrate/versions/0124_job_state_history.py b/lib/galaxy/model/migrate/versions/0124_job_state_history.py index d7740c94157..82b98750a07 100644 --- a/lib/galaxy/model/migrate/versions/0124_job_state_history.py +++ b/lib/galaxy/model/migrate/versions/0124_job_state_history.py @@ -9,6 +9,7 @@ import logging from sqlalchemy import Column, DateTime, ForeignKey, Integer, MetaData, String, Table from galaxy.model.custom_types import TrimmedString +from galaxy.model.migrate.versions.util import create_table, drop_table now = datetime.datetime.utcnow log = logging.getLogger(__name__) @@ -24,21 +25,15 @@ JobStateHistory_table = Table("job_state_history", metadata, def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() - try: - JobStateHistory_table.create() - except Exception: - log.exception("Creating %s table failed.", JobStateHistory_table.name) + create_table(JobStateHistory_table) def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - try: - JobStateHistory_table.drop() - except Exception: - log.exception("Dropping %s table failed.", JobStateHistory_table.name) + drop_table(JobStateHistory_table) diff --git a/lib/galaxy/model/migrate/versions/0125_workflow_step_tracking.py b/lib/galaxy/model/migrate/versions/0125_workflow_step_tracking.py index 0726fd756cf..7c38260d644 100644 --- a/lib/galaxy/model/migrate/versions/0125_workflow_step_tracking.py +++ b/lib/galaxy/model/migrate/versions/0125_workflow_step_tracking.py @@ -5,9 +5,10 @@ from __future__ import print_function import logging -from sqlalchemy import Column, MetaData, Table +from sqlalchemy import Column, MetaData from galaxy.model.custom_types import TrimmedString, UUIDType +from galaxy.model.migrate.versions.util import add_column, drop_column log = logging.getLogger(__name__) metadata = MetaData() @@ -20,29 +21,13 @@ def upgrade(migrate_engine): StepLabel_column = Column("label", TrimmedString(255)) StepUUID_column = Column("uuid", UUIDType, nullable=True) - __add_column(StepLabel_column, "workflow_step", metadata) - __add_column(StepUUID_column, "workflow_step", metadata) + add_column(StepLabel_column, "workflow_step", metadata) + add_column(StepUUID_column, "workflow_step", metadata) def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - __drop_column("label", "workflow_step", metadata) - __drop_column("uuid", "workflow_step", metadata) - - -def __add_column(column, table_name, metadata, **kwds): - try: - table = Table(table_name, metadata, autoload=True) - column.create(table, **kwds) - except Exception: - log.exception("Adding column %s failed.", column) - - -def __drop_column(column_name, table_name, metadata): - try: - table = Table(table_name, metadata, autoload=True) - getattr(table.c, column_name).drop() - except Exception: - log.exception("Dropping column %s failed.", column_name) + drop_column("label", "workflow_step", metadata) + drop_column("uuid", "workflow_step", metadata) diff --git a/lib/galaxy/model/migrate/versions/0126_password_reset.py b/lib/galaxy/model/migrate/versions/0126_password_reset.py index f218c05ce03..ba8703ebb9b 100644 --- a/lib/galaxy/model/migrate/versions/0126_password_reset.py +++ b/lib/galaxy/model/migrate/versions/0126_password_reset.py @@ -7,6 +7,8 @@ import logging from sqlalchemy import Column, DateTime, ForeignKey, Integer, MetaData, String, Table +from galaxy.model.migrate.versions.util import create_table, drop_table + log = logging.getLogger(__name__) metadata = MetaData() @@ -17,19 +19,15 @@ PasswordResetToken_table = Table("password_reset_token", metadata, def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() - try: - PasswordResetToken_table.create() - except Exception: - log.exception("Creating %s table failed.", PasswordResetToken_table.name) + + create_table(PasswordResetToken_table) def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - try: - PasswordResetToken_table.drop() - except Exception: - log.exception("Dropping %s table failed.", PasswordResetToken_table.name) + + drop_table(PasswordResetToken_table) diff --git a/lib/galaxy/model/migrate/versions/0127_output_collection_adjustments.py b/lib/galaxy/model/migrate/versions/0127_output_collection_adjustments.py index 771ef05c103..8d16c7de3e2 100644 --- a/lib/galaxy/model/migrate/versions/0127_output_collection_adjustments.py +++ b/lib/galaxy/model/migrate/versions/0127_output_collection_adjustments.py @@ -8,6 +8,7 @@ import logging from sqlalchemy import Column, ForeignKey, Integer, MetaData, Table, TEXT, Unicode from galaxy.model.custom_types import TrimmedString +from galaxy.model.migrate.versions.util import add_column, create_table, drop_column, drop_table log = logging.getLogger(__name__) metadata = MetaData() @@ -27,23 +28,20 @@ TABLES = [ def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() for table in TABLES: - __create(table) + create_table(table) - try: - dataset_collection_table = Table("dataset_collection", metadata, autoload=True) - # need server_default because column in non-null - populated_state_column = Column('populated_state', TrimmedString(64), default='ok', server_default="ok", nullable=False) - populated_state_column.create(dataset_collection_table) + dataset_collection_table = Table("dataset_collection", metadata, autoload=True) + # need server_default because column in non-null + populated_state_column = Column('populated_state', TrimmedString(64), default='ok', server_default="ok", nullable=False) + add_column(populated_state_column, dataset_collection_table) - populated_message_column = Column('populated_state_message', TEXT, nullable=True) - populated_message_column.create(dataset_collection_table) - except Exception: - log.exception("Creating dataset collection populated column failed.") + populated_message_column = Column('populated_state_message', TEXT, nullable=True) + add_column(populated_message_column, dataset_collection_table) def downgrade(migrate_engine): @@ -51,27 +49,8 @@ def downgrade(migrate_engine): metadata.reflect() for table in TABLES: - __drop(table) + drop_table(table) - try: - dataset_collection_table = Table("dataset_collection", metadata, autoload=True) - populated_state_column = dataset_collection_table.c.populated_state - populated_state_column.drop() - populated_message_column = dataset_collection_table.c.populated_state_message - populated_message_column.drop() - except Exception: - log.exception("Dropping dataset collection populated_state/ column failed.") - - -def __create(table): - try: - table.create() - except Exception: - log.exception("Creating %s table failed.", table.name) - - -def __drop(table): - try: - table.drop() - except Exception: - log.exception("Dropping %s table failed.", table.name) + dataset_collection_table = Table("dataset_collection", metadata, autoload=True) + drop_column('populated_state', dataset_collection_table) + drop_column('populated_state_message', dataset_collection_table) diff --git a/lib/galaxy/model/migrate/versions/0128_session_timeout.py b/lib/galaxy/model/migrate/versions/0128_session_timeout.py index 5725f3feacc..9e97b4ebdb6 100644 --- a/lib/galaxy/model/migrate/versions/0128_session_timeout.py +++ b/lib/galaxy/model/migrate/versions/0128_session_timeout.py @@ -5,39 +5,25 @@ from __future__ import print_function import logging -from sqlalchemy import Column, DateTime, MetaData, Table +from sqlalchemy import Column, DateTime, MetaData + +from galaxy.model.migrate.versions.util import add_column, drop_column log = logging.getLogger(__name__) metadata = MetaData() def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() lastaction_column = Column("last_action", DateTime) - __add_column(lastaction_column, "galaxy_session", metadata) + add_column(lastaction_column, "galaxy_session", metadata) def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - __drop_column("last_action", "galaxy_session", metadata) - - -def __add_column(column, table_name, metadata, **kwds): - try: - table = Table(table_name, metadata, autoload=True) - column.create(table, **kwds) - except Exception: - log.exception("Adding column %s failed.", column) - - -def __drop_column(column_name, table_name, metadata): - try: - table = Table(table_name, metadata, autoload=True) - getattr(table.c, column_name).drop() - except Exception: - log.exception("Dropping column %s failed.", column_name) + drop_column("last_action", "galaxy_session", metadata) diff --git a/lib/galaxy/model/migrate/versions/0129_job_external_output_metadata_validity.py b/lib/galaxy/model/migrate/versions/0129_job_external_output_metadata_validity.py index 4c604079b27..bc9736a34a3 100644 --- a/lib/galaxy/model/migrate/versions/0129_job_external_output_metadata_validity.py +++ b/lib/galaxy/model/migrate/versions/0129_job_external_output_metadata_validity.py @@ -5,7 +5,9 @@ from __future__ import print_function import logging -from sqlalchemy import Boolean, Column, MetaData, Table +from sqlalchemy import Boolean, Column, MetaData + +from galaxy.model.migrate.versions.util import add_column, drop_column log = logging.getLogger(__name__) metadata = MetaData() @@ -17,7 +19,7 @@ def upgrade(migrate_engine): metadata.reflect() isvalid_column = Column("is_valid", Boolean, default=True) - __add_column(isvalid_column, "job_external_output_metadata", metadata) + add_column(isvalid_column, "job_external_output_metadata", metadata) def downgrade(migrate_engine): @@ -25,20 +27,4 @@ def downgrade(migrate_engine): metadata.reflect() # SQLAlchemy Migrate has a bug when dropping a boolean column in SQLite if migrate_engine.name != 'sqlite': - __drop_column("is_valid", "job_external_output_metadata", metadata) - - -def __add_column(column, table_name, metadata, **kwds): - try: - table = Table(table_name, metadata, autoload=True) - column.create(table, **kwds) - except Exception: - log.exception("Adding column %s failed.", column) - - -def __drop_column(column_name, table_name, metadata): - try: - table = Table(table_name, metadata, autoload=True) - getattr(table.c, column_name).drop() - except Exception: - log.exception("Dropping column %s failed.", column_name) + drop_column("is_valid", "job_external_output_metadata", metadata) diff --git a/lib/galaxy/model/migrate/versions/0131_subworkflow_and_input_parameter_modules.py b/lib/galaxy/model/migrate/versions/0131_subworkflow_and_input_parameter_modules.py index ec06a3e7aaf..c2c0bcf2575 100644 --- a/lib/galaxy/model/migrate/versions/0131_subworkflow_and_input_parameter_modules.py +++ b/lib/galaxy/model/migrate/versions/0131_subworkflow_and_input_parameter_modules.py @@ -8,6 +8,7 @@ import logging from sqlalchemy import Column, ForeignKey, ForeignKeyConstraint, Index, Integer, MetaData, Table from galaxy.model.custom_types import JSONType, TrimmedString, UUIDType +from galaxy.model.migrate.versions.util import add_column, alter_column, create_table, drop_column, drop_table log = logging.getLogger(__name__) metadata = MetaData() @@ -46,9 +47,10 @@ INDEXES = [ def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() + if migrate_engine.name in ['postgres', 'postgresql']: subworkflow_id_column = Column("subworkflow_id", Integer, ForeignKey("workflow.id"), nullable=True) input_subworkflow_step_id_column = Column("input_subworkflow_step_id", Integer, ForeignKey("workflow_step.id"), nullable=True) @@ -57,72 +59,34 @@ def upgrade(migrate_engine): subworkflow_id_column = Column("subworkflow_id", Integer, nullable=True) input_subworkflow_step_id_column = Column("input_subworkflow_step_id", Integer, nullable=True) parent_workflow_id_column = Column("parent_workflow_id", Integer, nullable=True) - __add_column(subworkflow_id_column, "workflow_step", metadata) - __add_column(input_subworkflow_step_id_column, "workflow_step_connection", metadata) - __add_column(parent_workflow_id_column, "workflow", metadata) + add_column(subworkflow_id_column, "workflow_step", metadata) + add_column(input_subworkflow_step_id_column, "workflow_step_connection", metadata) + add_column(parent_workflow_id_column, "workflow", metadata) workflow_output_label_column = Column("label", TrimmedString(255)) workflow_output_uuid_column = Column("uuid", UUIDType, nullable=True) - __add_column(workflow_output_label_column, "workflow_output", metadata) - __add_column(workflow_output_uuid_column, "workflow_output", metadata) + add_column(workflow_output_label_column, "workflow_output", metadata) + add_column(workflow_output_uuid_column, "workflow_output", metadata) # Make stored_workflow_id nullable, since now workflows can belong to either # a stored workflow or a parent workflow. - __alter_column("workflow", "stored_workflow_id", metadata, nullable=True) + alter_column("stored_workflow_id", "workflow", metadata, nullable=True) for table in TABLES: # Indexes are automatically created when the tables are. - __create(table) + create_table(table) def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - __drop_column("subworkflow_id", "workflow_step", metadata) - __drop_column("parent_workflow_id", "workflow", metadata) + drop_column("subworkflow_id", "workflow_step", metadata) + drop_column("parent_workflow_id", "workflow", metadata) - __drop_column("input_subworkflow_step_id", "workflow_step_connection", metadata) + drop_column("input_subworkflow_step_id", "workflow_step_connection", metadata) - __drop_column("label", "workflow_output", metadata) - __drop_column("uuid", "workflow_output", metadata) + drop_column("label", "workflow_output", metadata) + drop_column("uuid", "workflow_output", metadata) for table in TABLES: - __drop(table) - - -def __alter_column(table_name, column_name, metadata, **kwds): - try: - table = Table(table_name, metadata, autoload=True) - getattr(table.c, column_name).alter(**kwds) - except Exception: - log.exception("Adding column %s failed.", column_name) - - -def __add_column(column, table_name, metadata, **kwds): - try: - table = Table(table_name, metadata, autoload=True) - column.create(table, **kwds) - except Exception: - log.exception("Adding column %s failed.", column) - - -def __drop_column(column_name, table_name, metadata): - try: - table = Table(table_name, metadata, autoload=True) - getattr(table.c, column_name).drop() - except Exception: - log.exception("Dropping column %s failed.", column_name) - - -def __create(table): - try: - table.create() - except Exception: - log.exception("Creating %s table failed.", table.name) - - -def __drop(table): - try: - table.drop() - except Exception: - log.exception("Dropping %s table failed.", table.name) + drop_table(table) diff --git a/lib/galaxy/model/migrate/versions/0134_hda_set_deleted_if_purged.py b/lib/galaxy/model/migrate/versions/0134_hda_set_deleted_if_purged.py index e6360006881..185b0d9fa53 100644 --- a/lib/galaxy/model/migrate/versions/0134_hda_set_deleted_if_purged.py +++ b/lib/galaxy/model/migrate/versions/0134_hda_set_deleted_if_purged.py @@ -6,18 +6,11 @@ from __future__ import print_function import logging +from galaxy.model.migrate.versions.util import engine_true + log = logging.getLogger(__name__) -def engine_true(migrate_engine): - if migrate_engine.name in ['postgres', 'postgresql']: - return "TRUE" - elif migrate_engine.name in ['mysql', 'sqlite']: - return 1 - else: - raise Exception('Unknown database type: %s' % migrate_engine.name) - - def upgrade(migrate_engine): print(__doc__) cmd = 'UPDATE history_dataset_association SET deleted=%s WHERE purged;' % engine_true(migrate_engine) diff --git a/lib/galaxy/model/migrate/versions/0136_collection_and_workflow_state.py b/lib/galaxy/model/migrate/versions/0136_collection_and_workflow_state.py index d2597359041..c5a54d92c1b 100644 --- a/lib/galaxy/model/migrate/versions/0136_collection_and_workflow_state.py +++ b/lib/galaxy/model/migrate/versions/0136_collection_and_workflow_state.py @@ -10,14 +10,13 @@ from collections import OrderedDict from sqlalchemy import Column, ForeignKey, Integer, MetaData, String, Table from galaxy.model.custom_types import TrimmedString - +from galaxy.model.migrate.versions.util import add_column, create_table, drop_column, drop_table now = datetime.datetime.utcnow log = logging.getLogger(__name__) metadata = MetaData() - workflow_invocation_output_dataset_association_table = Table( "workflow_invocation_output_dataset_association", metadata, Column("id", Integer, primary_key=True), @@ -85,13 +84,13 @@ def get_new_tables(): def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) - + metadata.bind = migrate_engine metadata.reflect() + tables = get_new_tables() for table in tables.values(): - __create(table) + create_table(table) # Set default for creation to scheduled, actual mapping has new as default. workflow_invocation_step_state_column = Column("state", TrimmedString(64), default="scheduled") @@ -103,13 +102,13 @@ def upgrade(migrate_engine): job_id_column = Column("job_id", Integer, nullable=True) dataset_collection_element_count_column = Column("element_count", Integer, nullable=True) - __add_column(implicit_collection_jobs_id_column, "history_dataset_collection_association", metadata) - __add_column(job_id_column, "history_dataset_collection_association", metadata) - __add_column(dataset_collection_element_count_column, "dataset_collection", metadata) + add_column(implicit_collection_jobs_id_column, "history_dataset_collection_association", metadata) + add_column(job_id_column, "history_dataset_collection_association", metadata) + add_column(dataset_collection_element_count_column, "dataset_collection", metadata) implicit_collection_jobs_id_column = Column("implicit_collection_jobs_id", Integer, ForeignKey("implicit_collection_jobs.id"), nullable=True) - __add_column(implicit_collection_jobs_id_column, "workflow_invocation_step", metadata) - __add_column(workflow_invocation_step_state_column, "workflow_invocation_step", metadata) + add_column(implicit_collection_jobs_id_column, "workflow_invocation_step", metadata) + add_column(workflow_invocation_step_state_column, "workflow_invocation_step", metadata) cmd = \ "UPDATE dataset_collection SET element_count = " + \ @@ -118,46 +117,16 @@ def upgrade(migrate_engine): migrate_engine.execute(cmd) -def __add_column(column, table_name, metadata, **kwds): - try: - table = Table(table_name, metadata, autoload=True) - column.create(table, **kwds) - except Exception: - log.exception("Adding column %s failed.", column) - - -def __drop_column(column_name, table_name, metadata): - try: - table = Table(table_name, metadata, autoload=True) - getattr(table.c, column_name).drop() - except Exception: - log.exception("Dropping column %s failed.", column_name) - - def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - __drop_column("implicit_collection_jobs_id", "history_dataset_collection_association", metadata) - __drop_column("job_id", "history_dataset_collection_association", metadata) - __drop_column("implicit_collection_jobs_id", "workflow_invocation_step", metadata) - __drop_column("state", "workflow_invocation_step", metadata) - __drop_column("element_count", "dataset_collection", metadata) + drop_column("implicit_collection_jobs_id", "history_dataset_collection_association", metadata) + drop_column("job_id", "history_dataset_collection_association", metadata) + drop_column("implicit_collection_jobs_id", "workflow_invocation_step", metadata) + drop_column("state", "workflow_invocation_step", metadata) + drop_column("element_count", "dataset_collection", metadata) tables = get_new_tables() for table in reversed(list(tables.values())): - __drop(table) - - -def __create(table): - try: - table.create() - except Exception: - log.exception("Creating %s table failed.", table.name) - - -def __drop(table): - try: - table.drop() - except Exception: - log.exception("Dropping %s table failed.", table.name) + drop_table(table) diff --git a/lib/galaxy/model/migrate/versions/0145_add_workflow_step_input.py b/lib/galaxy/model/migrate/versions/0145_add_workflow_step_input.py index bdbc3b207f2..4944fc3ec9c 100644 --- a/lib/galaxy/model/migrate/versions/0145_add_workflow_step_input.py +++ b/lib/galaxy/model/migrate/versions/0145_add_workflow_step_input.py @@ -17,6 +17,7 @@ from sqlalchemy import ( ) from galaxy.model.custom_types import JSONType +from galaxy.model.migrate.versions.util import create_table, drop_table log = logging.getLogger(__name__) metadata = MetaData() @@ -38,8 +39,8 @@ WorkflowStepInput_table = Table( def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() OldWorkflowStepConnection_table = Table("workflow_step_connection", metadata, autoload=True) @@ -60,7 +61,7 @@ def upgrade(migrate_engine): Column("input_subworkflow_step_id", Integer, ForeignKey("workflow_step.id"), index=True), ) for table in (WorkflowStepInput_table, NewWorkflowStepConnection_table): - _create(table) + create_table(table) insert_step_inputs_cmd = \ "INSERT INTO workflow_step_input (workflow_step_id, name) " + \ @@ -72,7 +73,7 @@ def upgrade(migrate_engine): "SELECT wsc.output_step_id, wsi.id, wsc.output_name, wsc.input_subworkflow_step_id " + \ "FROM workflow_step_connection_preupgrade145 AS wsc JOIN workflow_step_input AS wsi ON wsc.input_step_id = wsi.workflow_step_id AND wsc.input_name = wsi.name ORDER BY wsc.id" migrate_engine.execute(insert_step_connections_cmd) - _drop(OldWorkflowStepConnection_table) + drop_table(OldWorkflowStepConnection_table) def downgrade(migrate_engine): @@ -96,7 +97,7 @@ def downgrade(migrate_engine): Column("input_name", TEXT), Column("input_subworkflow_step_id", Integer, ForeignKey("workflow_step.id"), index=True), ) - _create(OldWorkflowStepConnection_table) + create_table(OldWorkflowStepConnection_table) insert_step_connections_cmd = \ "INSERT INTO workflow_step_connection (output_step_id, input_step_id, output_name, input_name, input_subworkflow_step_id) " + \ @@ -105,18 +106,4 @@ def downgrade(migrate_engine): migrate_engine.execute(insert_step_connections_cmd) for table in (NewWorkflowStepConnection_table, WorkflowStepInput_table): - _drop(table) - - -def _create(table): - try: - table.create() - except Exception: - log.exception("Creating %s table failed.", table.name) - - -def _drop(table): - try: - table.drop() - except Exception: - log.exception("Dropping %s table failed.", table.name) + drop_table(table) diff --git a/lib/galaxy/model/migrate/versions/0146_workflow_paths.py b/lib/galaxy/model/migrate/versions/0146_workflow_paths.py index 96f107d59f2..9fcd370b403 100644 --- a/lib/galaxy/model/migrate/versions/0146_workflow_paths.py +++ b/lib/galaxy/model/migrate/versions/0146_workflow_paths.py @@ -8,10 +8,11 @@ import logging from sqlalchemy import ( Column, MetaData, - Table, TEXT, ) +from galaxy.model.migrate.versions.util import add_column, drop_column + log = logging.getLogger(__name__) metadata = MetaData() @@ -19,30 +20,14 @@ from_path_column = Column("from_path", TEXT, nullable=True) def upgrade(migrate_engine): - metadata.bind = migrate_engine print(__doc__) + metadata.bind = migrate_engine metadata.reflect() - _add_column(from_path_column, "stored_workflow", metadata) + add_column(from_path_column, "stored_workflow", metadata) def downgrade(migrate_engine): metadata.bind = migrate_engine - _drop_column("from_path", "stored_workflow", metadata) - - -def _add_column(column, table_name, metadata, **kwds): - try: - table = Table(table_name, metadata, autoload=True) - column.create(table, **kwds) - except Exception: - log.exception("Adding column %s failed.", column) - - -def _drop_column(column_name, table_name, metadata): - try: - table = Table(table_name, metadata, autoload=True) - getattr(table.c, column_name).drop() - except Exception: - log.exception("Dropping column %s failed.", column_name) + drop_column("from_path", "stored_workflow", metadata) diff --git a/lib/galaxy/model/migrate/versions/0147_job_messages.py b/lib/galaxy/model/migrate/versions/0147_job_messages.py new file mode 100644 index 00000000000..b24a45d531b --- /dev/null +++ b/lib/galaxy/model/migrate/versions/0147_job_messages.py @@ -0,0 +1,58 @@ +""" +Add structured failure reason column to jobs table +""" +from __future__ import print_function + +import logging + +from sqlalchemy import Column, MetaData, Table, TEXT + +from galaxy.model.custom_types import JSONType +from galaxy.model.migrate.versions.util import ( + add_column, + alter_column, + drop_column +) + +log = logging.getLogger(__name__) +metadata = MetaData() + + +def upgrade(migrate_engine): + print(__doc__) + metadata.bind = migrate_engine + metadata.reflect() + + jobs_table = Table("job", metadata, autoload=True) + job_messages_column = Column("job_messages", JSONType, nullable=True) + add_column(job_messages_column, jobs_table) + job_job_stdout_column = Column("job_stdout", TEXT, nullable=True) + add_column(job_job_stdout_column, jobs_table) + job_job_stderr_column = Column("job_stderr", TEXT, nullable=True) + add_column(job_job_stderr_column, jobs_table) + + tasks_table = Table("task", metadata, autoload=True) + task_job_messages_column = Column("job_messages", JSONType, nullable=True) + add_column(task_job_messages_column, tasks_table) + task_job_stdout_column = Column("job_stdout", TEXT, nullable=True) + add_column(task_job_stdout_column, tasks_table) + task_job_stderr_column = Column("job_stderr", TEXT, nullable=True) + add_column(task_job_stderr_column, tasks_table) + + for table in [jobs_table, tasks_table]: + alter_column('stdout', table, name='tool_stdout') + alter_column('stderr', table, name='tool_stderr') + + +def downgrade(migrate_engine): + metadata.bind = migrate_engine + metadata.reflect() + + jobs_table = Table("job", metadata, autoload=True) + tasks_table = Table("task", metadata, autoload=True) + for colname in ["job_messages", "job_stdout", "job_stderr"]: + drop_column(colname, jobs_table) + drop_column(colname, tasks_table) + for table in [jobs_table, tasks_table]: + alter_column('tool_stdout', table, name='stdout') + alter_column('tool_stderr', table, name='stderr') diff --git a/lib/galaxy/model/migrate/versions/0148_add_checksum_table.py b/lib/galaxy/model/migrate/versions/0148_add_checksum_table.py new file mode 100644 index 00000000000..ba32b4113b6 --- /dev/null +++ b/lib/galaxy/model/migrate/versions/0148_add_checksum_table.py @@ -0,0 +1,64 @@ +""" +Migration script to add dataset source and hash tables. +""" +from __future__ import print_function + +import logging + +from sqlalchemy import Column, ForeignKey, Integer, MetaData, Table, TEXT + +from galaxy.model.custom_types import JSONType + +log = logging.getLogger(__name__) +log.setLevel(logging.DEBUG) + +metadata = MetaData() + +dataset_source_table = Table( + "dataset_source", metadata, + Column("id", Integer, primary_key=True), + Column("dataset_id", Integer, ForeignKey("dataset.id"), index=True), + Column("source_uri", TEXT), + Column("extra_files_path", TEXT), + Column("transform", JSONType) +) + +dataset_hash_table = Table( + "dataset_hash", metadata, + Column("id", Integer, primary_key=True), + Column("dataset_id", Integer, ForeignKey("dataset.id"), index=True), + Column("hash_function", TEXT), + Column("hash_value", TEXT), + Column("extra_files_path", TEXT), +) + +dataset_source_hash_table = Table( + "dataset_source_hash", metadata, + Column("id", Integer, primary_key=True), + Column("dataset_source_id", Integer, ForeignKey("dataset_source.id"), index=True), + Column("hash_function", TEXT), + Column("hash_value", TEXT) +) + + +def upgrade(migrate_engine): + print(__doc__) + metadata.bind = migrate_engine + metadata.reflect() + try: + dataset_source_table.create() + dataset_hash_table.create() + dataset_source_hash_table.create() + except Exception: + log.exception("Adding dataset source and hash tables failed.") + + +def downgrade(migrate_engine): + metadata.bind = migrate_engine + metadata.reflect() + try: + dataset_source_hash_table.drop() + dataset_hash_table.drop() + dataset_source_table.drop() + except Exception: + log.exception("Dropping dataset source and hash tables failed.") diff --git a/lib/galaxy/model/migrate/versions/0149_dynamic_tools.py b/lib/galaxy/model/migrate/versions/0149_dynamic_tools.py new file mode 100644 index 00000000000..a4834117250 --- /dev/null +++ b/lib/galaxy/model/migrate/versions/0149_dynamic_tools.py @@ -0,0 +1,65 @@ +""" +Migration script to add the dynamic_tool table. +""" +import datetime +import logging + +from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, MetaData, Table, Unicode + +from galaxy.model.custom_types import JSONType, UUIDType +from galaxy.model.migrate.versions.util import add_column, create_table, drop_column, drop_table + +now = datetime.datetime.utcnow +log = logging.getLogger(__name__) +metadata = MetaData() + + +DynamicTool_table = Table( + "dynamic_tool", metadata, + Column("id", Integer, primary_key=True), + Column("uuid", UUIDType()), + Column("create_time", DateTime, default=now), + Column("update_time", DateTime, default=now, onupdate=now), + Column("tool_id", Unicode(255)), + Column("tool_version", Unicode(255)), + Column("tool_format", Unicode(255)), + Column("tool_path", Unicode(255)), + Column("tool_directory", Unicode(255)), + Column("hidden", Boolean), + Column("active", Boolean), + Column("value", JSONType()), +) + +TABLES = [ + DynamicTool_table, +] + + +def upgrade(migrate_engine): + print(__doc__) + metadata.bind = migrate_engine + metadata.reflect() + + for table in TABLES: + create_table(table) + + if migrate_engine.name in ['postgres', 'postgresql']: + workflow_dynamic_tool_id_column = Column("dynamic_tool_id", Integer, ForeignKey("dynamic_tool.id"), nullable=True) + job_workflow_dynamic_tool_id_column = Column("dynamic_tool_id", Integer, ForeignKey("dynamic_tool.id"), nullable=True) + else: + workflow_dynamic_tool_id_column = Column("dynamic_tool_id", Integer, nullable=True) + job_workflow_dynamic_tool_id_column = Column("dynamic_tool_id", Integer, nullable=True) + + add_column(workflow_dynamic_tool_id_column, "workflow_step", metadata) + add_column(job_workflow_dynamic_tool_id_column, "job", metadata) + + +def downgrade(migrate_engine): + metadata.bind = migrate_engine + metadata.reflect() + + drop_column("dynamic_tool_id", "workflow_step", metadata) + drop_column("dynamic_tool_id", "job", metadata) + + for table in TABLES: + drop_table(table) diff --git a/lib/galaxy/model/migrate/versions/0150_add_create_time_field_for_cloudauthz.py b/lib/galaxy/model/migrate/versions/0150_add_create_time_field_for_cloudauthz.py new file mode 100644 index 00000000000..0f29f430ef7 --- /dev/null +++ b/lib/galaxy/model/migrate/versions/0150_add_create_time_field_for_cloudauthz.py @@ -0,0 +1,32 @@ +""" +Adds `create_time` columns to cloudauthz table. +""" + +from __future__ import print_function + +import logging + +from sqlalchemy import Column, DateTime, MetaData, Table + +from galaxy.model.migrate.versions.util import add_column, drop_column + +log = logging.getLogger(__name__) +metadata = MetaData() + + +def upgrade(migrate_engine): + print(__doc__) + metadata.bind = migrate_engine + metadata.reflect() + + cloudauthz_table = Table("cloudauthz", metadata, autoload=True) + create_time_column = Column('create_time', DateTime) + add_column(create_time_column, cloudauthz_table) + + +def downgrade(migrate_engine): + metadata.bind = migrate_engine + metadata.reflect() + + cloudauthz_table = Table("cloudauthz", metadata, autoload=True) + drop_column('create_time', cloudauthz_table) diff --git a/lib/galaxy/model/migrate/versions/0151_add_worker_process.py b/lib/galaxy/model/migrate/versions/0151_add_worker_process.py new file mode 100644 index 00000000000..c16ed1a63ca --- /dev/null +++ b/lib/galaxy/model/migrate/versions/0151_add_worker_process.py @@ -0,0 +1,43 @@ +""" +Add table for worker processes +""" +from __future__ import print_function + +import logging + +from sqlalchemy import ( + Column, + DateTime, + MetaData, + Table, + TEXT, +) + +from galaxy.model.migrate.versions.util import create_table, drop_table +from galaxy.model.orm.now import now + +log = logging.getLogger(__name__) +metadata = MetaData() + + +WorkerProcess_table = Table( + 'worker_process', + metadata, + Column('server_name', TEXT, primary_key=True), + Column("update_time", DateTime, default=now, onupdate=now), +) + + +def upgrade(migrate_engine): + print(__doc__) + metadata.bind = migrate_engine + metadata.reflect() + + create_table(WorkerProcess_table) + + +def downgrade(migrate_engine): + metadata.bind = migrate_engine + metadata.reflect() + + drop_table(WorkerProcess_table) diff --git a/lib/galaxy/model/migrate/versions/0152_add_metadata_file_uuid.py b/lib/galaxy/model/migrate/versions/0152_add_metadata_file_uuid.py new file mode 100644 index 00000000000..f9d26724a37 --- /dev/null +++ b/lib/galaxy/model/migrate/versions/0152_add_metadata_file_uuid.py @@ -0,0 +1,43 @@ +""" +Adds `uuid` column to MetadataFile table. +""" + +from __future__ import print_function + +import logging + +from sqlalchemy import Column, MetaData, Table + +from galaxy.model.custom_types import UUIDType + +log = logging.getLogger(__name__) + + +def upgrade(migrate_engine): + print(__doc__) + metadata = MetaData() + metadata.bind = migrate_engine + metadata.reflect() + + metadata_file_table = Table("metadata_file", metadata, autoload=True) + + try: + uuid_column = Column('uuid', UUIDType()) + uuid_column.create(metadata_file_table) + assert uuid_column is metadata_file_table.c.uuid + except Exception: + log.exception("Adding column 'uuid' to `MetadataFile` table failed.") + + +def downgrade(migrate_engine): + metadata = MetaData() + metadata.bind = migrate_engine + metadata.reflect() + + metadata_file_table = Table("metadata_file", metadata, autoload=True) + + try: + column = metadata_file_table.c.uuid + column.drop() + except Exception: + log.exception("Dropping 'uuid' column from `metadata_file` table failed.") diff --git a/lib/galaxy/model/migrate/versions/0153_add_custos_authnz_token_table.py b/lib/galaxy/model/migrate/versions/0153_add_custos_authnz_token_table.py new file mode 100644 index 00000000000..8c0055f4c24 --- /dev/null +++ b/lib/galaxy/model/migrate/versions/0153_add_custos_authnz_token_table.py @@ -0,0 +1,48 @@ +""" +Migration for adding custos_authnz_token table. +""" +from __future__ import print_function + +import logging + +from sqlalchemy import (Column, DateTime, ForeignKey, Integer, MetaData, + String, Table, Text, UniqueConstraint) + +log = logging.getLogger(__name__) +metadata = MetaData() + +CustosAuthnzToken_table = Table( + "custos_authnz_token", metadata, + Column('id', Integer, primary_key=True), + Column('user_id', Integer, ForeignKey("galaxy_user.id")), + Column('external_user_id', String(64)), + Column('provider', String(255)), + Column('access_token', Text), + Column('id_token', Text), + Column('refresh_token', Text), + Column("expiration_time", DateTime), + Column("refresh_expiration_time", DateTime), + UniqueConstraint("user_id", "external_user_id", "provider"), + UniqueConstraint("external_user_id", "provider"), +) + + +def upgrade(migrate_engine): + print(__doc__) + metadata.bind = migrate_engine + metadata.reflect() + + try: + CustosAuthnzToken_table.create() + except Exception: + log.exception("Failed to create custos_authnz_token table") + + +def downgrade(migrate_engine): + metadata.bind = migrate_engine + metadata.reflect() + + try: + CustosAuthnzToken_table.drop() + except Exception: + log.exception("Failed to drop custos_authnz_token table") diff --git a/lib/galaxy/model/migrate/versions/__init__.py b/lib/galaxy/model/migrate/versions/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/galaxy/model/migrate/versions/util.py b/lib/galaxy/model/migrate/versions/util.py new file mode 100644 index 00000000000..cfc60f788ae --- /dev/null +++ b/lib/galaxy/model/migrate/versions/util.py @@ -0,0 +1,133 @@ +import logging + +from sqlalchemy import ( + Index, + Table +) + +log = logging.getLogger(__name__) + + +def engine_false(migrate_engine): + if migrate_engine.name in ['postgres', 'postgresql']: + return "FALSE" + elif migrate_engine.name in ['mysql', 'sqlite']: + return 0 + else: + raise Exception('Unknown database type: %s' % migrate_engine.name) + + +def engine_true(migrate_engine): + if migrate_engine.name in ['postgres', 'postgresql']: + return "TRUE" + elif migrate_engine.name in ['mysql', 'sqlite']: + return 1 + else: + raise Exception('Unknown database type: %s' % migrate_engine.name) + + +def nextval(migrate_engine, table, col='id'): + if migrate_engine.name in ['postgres', 'postgresql']: + return "nextval('%s_%s_seq')" % (table, col) + elif migrate_engine.name in ['mysql', 'sqlite']: + return "null" + else: + raise Exception('Unable to convert data for unknown database type: %s' % migrate_engine.name) + + +def localtimestamp(migrate_engine): + if migrate_engine.name in ['mysql', 'postgres', 'postgresql']: + return "LOCALTIMESTAMP" + elif migrate_engine.name == 'sqlite': + return "current_date || ' ' || current_time" + else: + raise Exception('Unable to convert data for unknown database type: %s' % migrate_engine.name) + + +def create_table(table): + try: + table.create() + except Exception: + log.exception("Creating table '%s' failed.", table) + + +def drop_table(table): + try: + table.drop() + except Exception: + log.exception("Dropping table '%s' failed.", table) + + +def add_column(column, table, metadata=None, **kwds): + """ + :param table: Table to add the column to + :type table: :class:`Table` or str + + :param metadata: Needed only if ``table`` is a table name + :type metadata: :class:`Metadata` + """ + try: + if not isinstance(table, Table): + assert metadata is not None + table = Table(table, metadata, autoload=True) + column.create(table, **kwds) + assert column is table.c[column.name] + except Exception: + log.exception("Adding column '%s' to table '%s' failed.", column, table) + + +def alter_column(column_name, table, metadata=None, **kwds): + """ + :param table: Table to drop the column from + :type table: :class:`Table` or str + + :param metadata: Needed only if ``table`` is a table name + :type metadata: :class:`Metadata` + """ + try: + if not isinstance(table, Table): + assert metadata is not None + table = Table(table, metadata, autoload=True) + column = table.c[column_name] + column.alter(**kwds) + except Exception: + log.exception("Modifying column '%s' of table '%s' failed.", column_name, table) + + +def drop_column(column_name, table, metadata=None): + """ + :param table: Table to drop the column from + :type table: :class:`Table` or str + + :param metadata: Needed only if ``table`` is a table name + :type metadata: :class:`Metadata` + """ + try: + if not isinstance(table, Table): + assert metadata is not None + table = Table(table, metadata, autoload=True) + column = table.c[column_name] + column.drop() + except Exception: + log.exception("Dropping column '%s' from table '%s' failed.", column_name, table) + + +def add_index(index_name, table, column_name, metadata=None): + """ + :param table: Table to drop the column from + :type table: :class:`Table` or str + + :param metadata: Needed only if ``table`` is a table name + :type metadata: :class:`Metadata` + """ + try: + if not isinstance(table, Table): + assert metadata is not None + table = Table(table, metadata, autoload=True) + if index_name not in [ix.name for ix in table.indexes]: + index = Index(index_name, table.c[column_name]) + index.create() + else: + log.debug("Index '%s' on column '%s' in table '%s' already exists.", index_name, column_name, table) + except Exception: + log.exception("Adding index '%s' on column '%s' to table '%s' failed.", index_name, column_name, table) diff --git a/lib/galaxy/model/orm/scripts.py b/lib/galaxy/model/orm/scripts.py index 746d2b91759..ddd62fe5700 100644 --- a/lib/galaxy/model/orm/scripts.py +++ b/lib/galaxy/model/orm/scripts.py @@ -99,8 +99,9 @@ def get_config(argv, use_argparse=True, cwd=None): 'sqlite:///pg/testdb1' >>> write_ini('galaxy.ini', 'database_file', 'moo.sqlite') >>> config = get_config(['manage_db.py'], cwd=config_dir) - >>> config['db_url'] - 'sqlite:///moo.sqlite?isolation_level=IMMEDIATE' + >>> uri_with_env = os.getenv("GALAXY_TEST_DBURI", "sqlite:///moo.sqlite?isolation_level=IMMEDIATE") + >>> config['db_url'] == uri_with_env + True >>> config['repo'] 'lib/galaxy/model/migrate' >>> rmtree(config_dir) diff --git a/lib/galaxy/model/security.py b/lib/galaxy/model/security.py new file mode 100644 index 00000000000..698942a7d00 --- /dev/null +++ b/lib/galaxy/model/security.py @@ -0,0 +1,1458 @@ +import logging +import socket +from datetime import datetime, timedelta + +from sqlalchemy import and_, false, not_, or_ +from sqlalchemy.orm import eagerload_all + +import galaxy.model +from galaxy.security import Action, get_permitted_actions, RBACAgent +from galaxy.util import listify +from galaxy.util.bunch import Bunch + + +log = logging.getLogger(__name__) + + +class GalaxyRBACAgent(RBACAgent): + def __init__(self, model, permitted_actions=None): + self.model = model + if permitted_actions: + self.permitted_actions = permitted_actions + # List of "library_item" objects and their associated permissions and info template objects + self.library_item_assocs = ( + (self.model.Library, self.model.LibraryPermissions), + (self.model.LibraryFolder, self.model.LibraryFolderPermissions), + (self.model.LibraryDataset, self.model.LibraryDatasetPermissions), + (self.model.LibraryDatasetDatasetAssociation, self.model.LibraryDatasetDatasetAssociationPermissions)) + + @property + def sa_session(self): + """Returns a SQLAlchemy session""" + return self.model.context + + def sort_by_attr(self, seq, attr): + """ + Sort the sequence of objects by object's attribute + Arguments: + seq - the list or any sequence (including immutable one) of objects to sort. + attr - the name of attribute to sort by + """ + # Use the "Schwartzian transform" + # Create the auxiliary list of tuples where every i-th tuple has form + # (seq[i].attr, i, seq[i]) and sort it. The second item of tuple is needed not + # only to provide stable sorting, but mainly to eliminate comparison of objects + # (which can be expensive or prohibited) in case of equal attribute values. + intermed = [(getattr(v, attr), i, v) for i, v in enumerate(seq)] + intermed.sort() + return [_[-1] for _ in intermed] + + def _get_npns_roles(self, trans): + """ + non-private, non-sharing roles + """ + return trans.sa_session.query(trans.app.model.Role) \ + .filter(and_(self.model.Role.table.c.deleted == false(), + self.model.Role.table.c.type != self.model.Role.types.PRIVATE, + self.model.Role.table.c.type != self.model.Role.types.SHARING)) \ + .order_by(self.model.Role.table.c.name) + + def get_all_roles(self, trans, cntrller): + admin_controller = cntrller in ['library_admin'] + roles = set() + if not trans.user: + return self._get_npns_roles(trans) + if admin_controller: + # The library is public and the user is an admin, so all roles are legitimate + for role in trans.sa_session.query(trans.app.model.Role) \ + .filter(self.model.Role.table.c.deleted == false()) \ + .order_by(self.model.Role.table.c.name): + roles.add(role) + else: + # Add the current user's private role + roles.add(self.get_private_user_role(trans.user)) + # Add the current user's sharing roles + for role in self.get_sharing_roles(trans.user): + roles.add(role) + # Add all remaining non-private, non-sharing roles + for role in self._get_npns_roles(trans): + roles.add(role) + return self.sort_by_attr([role for role in roles], 'name') + + def get_roles_for_action(self, item, action): + """ + Return a list containing the roles associated with given action on given item + where item is one of Library, LibraryFolder, LibraryDatasetDatasetAssociation, + LibraryDataset, Dataset. + """ + roles = [] + for item_permission in item.actions: + permission_action = self.get_action(item_permission.action) + if permission_action == action: + roles.append(item_permission.role) + return roles + + def get_valid_roles(self, trans, item, query=None, page=None, page_limit=None, is_library_access=False): + """ + This method retrieves the list of possible roles that user can select + in the item permissions form. Admins can select any role so the + results are paginated in order to save the bandwidth and to speed + things up. + Standard users can select their own private role, any of their + sharing roles and any public role (not private and not sharing). + """ + roles = [] + if query not in [None, '']: + query = query.strip().replace('_', '/_').replace('%', '/%').replace('/', '//') + search_query = query + '%' + else: + search_query = None + # Limit the query only to get the page needed + if page is not None and page_limit is not None: + limit = page * page_limit + else: + limit = None + total_count = None + if isinstance(item, self.model.Library) and self.library_is_public(item): + is_public_item = True + elif isinstance(item, self.model.Dataset) and self.dataset_is_public(item): + is_public_item = True + elif isinstance(item, self.model.LibraryFolder): + is_public_item = True + else: + is_public_item = False + # Admins can always choose from all non-deleted roles + if trans.user_is_admin or trans.app.config.expose_user_email: + if trans.user_is_admin: + db_query = trans.sa_session.query(trans.app.model.Role).filter(self.model.Role.table.c.deleted == false()) + else: + # User is not an admin but the configuration exposes all private roles to all users. + db_query = trans.sa_session.query(trans.app.model.Role) \ + .filter(and_(self.model.Role.table.c.deleted == false(), + self.model.Role.table.c.type == self.model.Role.types.PRIVATE)) + if search_query: + db_query = db_query.filter(self.model.Role.table.c.name.like(search_query, escape='/')) + total_count = db_query.count() + if limit is not None: + # Takes the least number of results from beginning that includes the requested page + roles = db_query.order_by(self.model.Role.table.c.name).limit(limit).all() + page_start = (page * page_limit) - page_limit + page_end = page_start + page_limit + if total_count < page_start + 1: + # Return empty list if there are less results than the requested position + roles = [] + else: + roles = roles[page_start:page_end] + else: + roles = db_query.order_by(self.model.Role.table.c.name) + # Non-admin and public item + elif is_public_item: + # Add the current user's private role + roles.append(self.get_private_user_role(trans.user)) + # Add the current user's sharing roles + for role in self.get_sharing_roles(trans.user): + roles.append(role) + # Add all remaining non-private, non-sharing roles + for role in self._get_npns_roles(trans): + roles.append(role) + # User will see all the roles derived from the access roles on the item + else: + # If item has roles associated with the access permission, we need to start with them. + access_roles = item.get_access_roles(trans) + for role in access_roles: + if self.ok_to_display(trans.user, role): + roles.append(role) + # Each role potentially has users. We need to find all roles that each of those users have. + for ura in role.users: + user = ura.user + for ura2 in user.roles: + if self.ok_to_display(trans.user, ura2.role): + roles.append(ura2.role) + # Each role also potentially has groups which, in turn, have members ( users ). We need to + # find all roles that each group's members have. + for gra in role.groups: + group = gra.group + for uga in group.users: + user = uga.user + for ura in user.roles: + if self.ok_to_display(trans.user, ura.role): + roles.append(ura.role) + + # Omit duplicated roles by converting to set + return_roles = set(roles) + if total_count is None: + total_count = len(return_roles) + return self.sort_by_attr([role for role in return_roles], 'name'), total_count + + def get_legitimate_roles(self, trans, item, cntrller): + """ + Return a sorted list of legitimate roles that can be associated with a permission on + item where item is a Library or a Dataset. The cntrller param is the controller from + which the request is sent. We cannot use trans.user_is_admin because the controller is + what is important since admin users do not necessarily have permission to do things + on items outside of the admin view. + + If cntrller is from the admin side ( e.g., library_admin ): + + - if item is public, all roles, including private roles, are legitimate. + - if item is restricted, legitimate roles are derived from the users and groups associated + with each role that is associated with the access permission ( i.e., DATASET_MANAGE_PERMISSIONS or + LIBRARY_MANAGE ) on item. Legitimate roles will include private roles. + + If cntrller is not from the admin side ( e.g., root, library ): + + - if item is public, all non-private roles, except for the current user's private role, + are legitimate. + - if item is restricted, legitimate roles are derived from the users and groups associated + with each role that is associated with the access permission on item. Private roles, except + for the current user's private role, will be excluded. + """ + admin_controller = cntrller in ['library_admin'] + roles = set() + if (isinstance(item, self.model.Library) and self.library_is_public(item)) or \ + (isinstance(item, self.model.Dataset) and self.dataset_is_public(item)): + return self.get_all_roles(trans, cntrller) + # If item has roles associated with the access permission, we need to start with them. + access_roles = item.get_access_roles(trans) + for role in access_roles: + if admin_controller or self.ok_to_display(trans.user, role): + roles.add(role) + # Each role potentially has users. We need to find all roles that each of those users have. + for ura in role.users: + user = ura.user + for ura2 in user.roles: + if admin_controller or self.ok_to_display(trans.user, ura2.role): + roles.add(ura2.role) + # Each role also potentially has groups which, in turn, have members ( users ). We need to + # find all roles that each group's members have. + for gra in role.groups: + group = gra.group + for uga in group.users: + user = uga.user + for ura in user.roles: + if admin_controller or self.ok_to_display(trans.user, ura.role): + roles.add(ura.role) + return self.sort_by_attr([role for role in roles], 'name') + + def ok_to_display(self, user, role): + """ + Method for checking if: + - a role is private and is the current user's private role + - a role is a sharing role and belongs to the current user + """ + if user: + if role.type == self.model.Role.types.PRIVATE: + return role == self.get_private_user_role(user) + if role.type == self.model.Role.types.SHARING: + return role in self.get_sharing_roles(user) + # If role.type is neither private nor sharing, it's ok to display + return True + return role.type != self.model.Role.types.PRIVATE and role.type != self.model.Role.types.SHARING + + def allow_action(self, roles, action, item): + """ + Method for checking a permission for the current user ( based on roles ) to perform a + specific action on an item, which must be one of: + Dataset, Library, LibraryFolder, LibraryDataset, LibraryDatasetDatasetAssociation + """ + # SM: Note that calling get_item_actions will emit a query. + item_actions = self.get_item_actions(action, item) + + if not item_actions: + return action.model == 'restrict' + ret_val = False + # For DATASET_ACCESS only, user must have ALL associated roles + if action == self.permitted_actions.DATASET_ACCESS: + for item_action in item_actions: + if item_action.role not in roles: + break + else: + ret_val = True + # For remaining actions, user must have any associated role + else: + for item_action in item_actions: + if item_action.role in roles: + ret_val = True + break + return ret_val + + def get_actions_for_items(self, trans, action, permission_items): + # TODO: Rename this; it's a replacement for get_item_actions, but it + # doesn't represent what it's really doing, which is confusing. + # TODO: Make this work for other classes besides lib_datasets. + # That should be as easy as checking the type and writing a query for each; + # we're avoiding using the SQLAlchemy backrefs because they can cause lots + # of queries to be generated. + # + # Originally, get_item_actions did: + # return [ permission for permission in item.actions if permission.action == action.action ] + # The "item" can be just about anything with permissions, and referencing + # item.actions causes the item's permissions to be retrieved. + # This method will retrieve all permissions for all "items" and only + # return the permissions associated with that given action. + # We initialize the permissions list to be empty; we will return an + # empty list by default. + # + # If the dataset id has no corresponding action in its permissions, + # then the returned permissions will not carry an entry for the dataset. + ret_permissions = {} + if (len(permission_items) > 0): + # SM: NB: LibraryDatasets became Datasets for some odd reason. + if (isinstance(permission_items[0], trans.model.LibraryDataset)): + ids = [item.library_dataset_id for item in permission_items] + permissions = trans.sa_session.query(trans.model.LibraryDatasetPermissions) \ + .filter(and_(trans.model.LibraryDatasetPermissions.library_dataset_id.in_(ids), + trans.model.LibraryDatasetPermissions.action == action.action)) \ + .all() + + # Massage the return data. We will return a list of permissions + # for each library dataset. So we initialize the return list to + # have an empty list for each dataset. Then each permission is + # appended to the right lib dataset. + # TODO: Consider eliminating the initialization and just return + # empty values for each library dataset id. + for item in permission_items: + ret_permissions[item.library_dataset_id] = [] + for permission in permissions: + ret_permissions[permission.library_dataset_id].append(permission) + elif (isinstance(permission_items[0], trans.model.Dataset)): + ids = [item.id for item in permission_items] + permissions = trans.sa_session.query(trans.model.DatasetPermissions) \ + .filter(and_(trans.model.DatasetPermissions.dataset_id.in_(ids), + trans.model.DatasetPermissions.action == action.action)) \ + .all() + + # Massage the return data. We will return a list of permissions + # for each library dataset. So we initialize the return list to + # have an empty list for each dataset. Then each permission is + # appended to the right lib dataset. + # TODO: Consider eliminating the initialization and just return + # empty values for each library dataset id. + for item in permission_items: + ret_permissions[item.id] = [] + for permission in permissions: + ret_permissions[permission.dataset_id].append(permission) + + # Test that we get the same response from get_item_actions each item: + test_code = False + if test_code: + try: + log.debug("get_actions_for_items: Test start") + for item in permission_items: + base_result = self.get_item_actions(action, item) + new_result = ret_permissions[item.library_dataset_id] + # For now, just test against LibraryDatasetIds; other classes + # are not tested yet. + if len(base_result) == len(new_result): + common_result = set(base_result).intersection(new_result) + if len(common_result) == len(base_result): + log.debug("Match on permissions for id %d" % + item.library_dataset_id) + # TODO: Fix this failure message: + else: + log.debug("Error: dataset %d; originally: %s; now: %s" + % (item.library_dataset_id, + base_result, new_result)) + else: + log.debug("Error: dataset %d: had %d entries, now %d entries" + % (item.library_dataset_id, len(base_result), + len(new_result))) + log.debug("get_actions_for_items: Test end") + except Exception as e: + log.debug("Exception in test code: %s" % e) + + return ret_permissions + + def allow_action_on_libitems(self, trans, user_roles, action, items): + """ + This should be the equivalent of allow_action defined on multiple items. + It is meant to specifically replace allow_action for multiple + LibraryDatasets, but it could be reproduced or modified for + allow_action's permitted classes - Dataset, Library, LibraryFolder, and + LDDAs. + """ + all_items_actions = self.get_actions_for_items(trans, action, items) + ret_allow_action = {} + + # Change item to lib_dataset or vice-versa. + for item in items: + if item.id in all_items_actions: + item_actions = all_items_actions[item.id] + + if self.permitted_actions.DATASET_ACCESS == action: + ret_allow_action[item.id] = True + for item_action in item_actions: + if item_action.role not in user_roles: + ret_allow_action[item.id] = False + break + + # Else look for just one dataset role to be in the list of + # acceptable user roles: + else: + ret_allow_action[item.id] = False + for item_action in item_actions: + if item_action.role in user_roles: + ret_allow_action[item.id] = True + break + + else: + if 'restrict' == action.model: + ret_allow_action[item.id] = True + else: + ret_allow_action[item.id] = False + + # Test it: the result for each dataset should match the result for + # allow_action: + test_code = False + if test_code: + log.debug("allow_action_for_items: test start") + for item in items: + orig_value = self.allow_action(user_roles, action, item) + if orig_value == ret_allow_action[item.id]: + log.debug("Item %d: success" % item.id) + else: + log.debug("Item %d: fail: original: %s; new: %s" + % (item.id, orig_value, ret_allow_action[item.id])) + log.debug("allow_action_for_items: test end") + return ret_allow_action + + # DELETEME: SM: DO NOT TOUCH! This actually works. + def dataset_access_mapping(self, trans, user_roles, datasets): + ''' + For the given list of datasets, return a mapping of the datasets' ids + to whether they can be accessed by the user or not. The datasets input + is expected to be a simple list of Dataset objects. + ''' + datasets_public_map = self.datasets_are_public(trans, datasets) + datasets_allow_action_map = self.allow_action_on_libitems(trans, user_roles, self.permitted_actions.DATASET_ACCESS, datasets) + can_access = {} + for dataset in datasets: + can_access[dataset.id] = datasets_public_map[dataset.id] or datasets_allow_action_map[dataset.id] + return can_access + + def dataset_permission_map_for_access(self, trans, user_roles, libitems): + ''' + For a given list of library items (e.g., Datasets), return a map of the + datasets' ids to whether they can have permission to use that action + (e.g., "access" or "modify") on the dataset. The libitems input is + expected to be a simple list of library items, such as Datasets or + LibraryDatasets. + NB: This is currently only usable for Datasets; it was intended to + be used for any library item. + ''' + # Map the library items to whether they are publicly accessible or not. + # Then determine what actions are allowed on the item (in case it's not + # public). Finally, the item is accessible if it's publicly available + # or the right permissions are enabled. + # TODO: This only works for Datasets; other code is using X_is_public, + # so this will have to be rewritten to support other items. + libitems_public_map = self.datasets_are_public(trans, libitems) + libitems_allow_action_map = self.allow_action_on_libitems( + trans, user_roles, self.permitted_actions.DATASET_ACCESS, libitems + ) + can_access = {} + for libitem in libitems: + can_access[libitem.id] = libitems_public_map[libitem.id] or libitems_allow_action_map[libitem.id] + return can_access + + def item_permission_map_for_modify(self, trans, user_roles, libitems): + return self.allow_action_on_libitems( + trans, user_roles, self.permitted_actions.LIBRARY_MODIFY, libitems + ) + + def item_permission_map_for_manage(self, trans, user_roles, libitems): + return self.allow_action_on_libitems( + trans, user_roles, self.permitted_actions.LIBRARY_MANAGE, libitems + ) + + def item_permission_map_for_add(self, trans, user_roles, libitems): + return self.allow_action_on_libitems( + trans, user_roles, self.permitted_actions.LIBRARY_ADD, libitems + ) + + def can_access_dataset(self, user_roles, dataset): + # SM: dataset_is_public will access dataset.actions, which is a + # backref that causes a query to be made to DatasetPermissions + retval = self.dataset_is_public(dataset) or self.allow_action(user_roles, self.permitted_actions.DATASET_ACCESS, dataset) + return retval + + def can_access_datasets(self, user_roles, action_tuples): + user_role_ids = [galaxy.model.cached_id(r) for r in user_roles] + + # For DATASET_ACCESS, user must have ALL associated roles + for action, user_role_id in action_tuples: + if action == self.permitted_actions.DATASET_ACCESS.action: + if user_role_id not in user_role_ids: + return False + + return True + + def can_manage_dataset(self, roles, dataset): + return self.allow_action(roles, self.permitted_actions.DATASET_MANAGE_PERMISSIONS, dataset) + + def can_access_library(self, roles, library): + return self.library_is_public(library) or self.allow_action(roles, self.permitted_actions.LIBRARY_ACCESS, library) + + def get_accessible_libraries(self, trans, user): + """Return all data libraries that the received user can access""" + accessible_libraries = [] + current_user_role_ids = [role.id for role in user.all_roles()] + library_access_action = self.permitted_actions.LIBRARY_ACCESS.action + restricted_library_ids = [lp.library_id for lp in trans.sa_session.query(trans.model.LibraryPermissions) + .filter(trans.model.LibraryPermissions.table.c.action == library_access_action).distinct()] + accessible_restricted_library_ids = [lp.library_id for lp in trans.sa_session.query(trans.model.LibraryPermissions) + .filter(and_( + trans.model.LibraryPermissions.table.c.action == library_access_action, + trans.model.LibraryPermissions.table.c.role_id.in_(current_user_role_ids)))] + # Filter to get libraries accessible by the current user. Get both + # public libraries and restricted libraries accessible by the current user. + for library in trans.sa_session.query(trans.model.Library) \ + .filter(and_(trans.model.Library.table.c.deleted == false(), + (or_(not_(trans.model.Library.table.c.id.in_(restricted_library_ids)), + trans.model.Library.table.c.id.in_(accessible_restricted_library_ids))))) \ + .order_by(trans.app.model.Library.name): + accessible_libraries.append(library) + return accessible_libraries + + def has_accessible_folders(self, trans, folder, user, roles, search_downward=True): + if self.has_accessible_library_datasets(trans, folder, user, roles, search_downward=search_downward) or \ + self.can_add_library_item(roles, folder) or \ + self.can_modify_library_item(roles, folder) or \ + self.can_manage_library_item(roles, folder): + return True + if search_downward: + for folder in folder.active_folders: + return self.has_accessible_folders(trans, folder, user, roles, search_downward=search_downward) + return False + + def has_accessible_library_datasets(self, trans, folder, user, roles, search_downward=True): + for library_dataset in trans.sa_session.query(trans.model.LibraryDataset) \ + .filter(and_(trans.model.LibraryDataset.table.c.deleted == false(), + trans.app.model.LibraryDataset.table.c.folder_id == folder.id)): + if self.can_access_library_item(roles, library_dataset, user): + return True + if search_downward: + return self.__active_folders_have_accessible_library_datasets(trans, folder, user, roles) + return False + + def __active_folders_have_accessible_library_datasets(self, trans, folder, user, roles): + for active_folder in folder.active_folders: + if self.has_accessible_library_datasets(trans, active_folder, user, roles): + return True + return False + + def can_access_library_item(self, roles, item, user): + if type(item) == self.model.Library: + return self.can_access_library(roles, item) + elif type(item) == self.model.LibraryFolder: + return self.can_access_library(roles, item.parent_library) and self.check_folder_contents(user, roles, item)[0] + elif type(item) == self.model.LibraryDataset: + return self.can_access_library(roles, item.folder.parent_library) and self.can_access_dataset(roles, item.library_dataset_dataset_association.dataset) + elif type(item) == self.model.LibraryDatasetDatasetAssociation: + return self.can_access_library(roles, item.library_dataset.folder.parent_library) and self.can_access_dataset(roles, item.dataset) + elif type(item) == self.model.LibraryDatasetCollectionAssociation: + return self.can_access_library(roles, item.folder.parent_library) + else: + log.warning('Unknown library item type: %s' % type(item)) + return False + + def can_add_library_item(self, roles, item): + return self.allow_action(roles, self.permitted_actions.LIBRARY_ADD, item) + + def can_modify_library_item(self, roles, item): + return self.allow_action(roles, self.permitted_actions.LIBRARY_MODIFY, item) + + def can_manage_library_item(self, roles, item): + return self.allow_action(roles, self.permitted_actions.LIBRARY_MANAGE, item) + + def get_item_actions(self, action, item): + # item must be one of: Dataset, Library, LibraryFolder, LibraryDataset, LibraryDatasetDatasetAssociation + # SM: Accessing item.actions emits a query to Library_Dataset_Permissions + # if the item is a LibraryDataset: + # TODO: Pass in the item's actions - the item isn't needed + return [permission for permission in item.actions if permission.action == action.action] + + def guess_derived_permissions_for_datasets(self, datasets=[]): + """Returns a dict of { action : [ role, role, ... ] } for the output dataset based upon provided datasets""" + perms = {} + for dataset in datasets: + if not isinstance(dataset, self.model.Dataset): + dataset = dataset.dataset + these_perms = {} + # initialize blank perms + for action in self.get_actions(): + these_perms[action] = [] + # collect this dataset's perms + these_perms = self.get_permissions(dataset) + # join or intersect this dataset's permissions with others + for action, roles in these_perms.items(): + if action not in perms.keys(): + perms[action] = roles + else: + if action.model == 'grant': + # intersect existing roles with new roles + perms[action] = [_ for _ in roles if _ in perms[action]] + elif action.model == 'restrict': + # join existing roles with new roles + perms[action].extend([_ for _ in roles if _ not in perms[action]]) + return perms + + def guess_derived_permissions(self, all_input_permissions): + """Returns a dict of { action : [ role_id, role_id, ... ] } for the output dataset based upon input dataset permissions. + + all_input_permissions should be of the form {action_name: set(role_ids)} + """ + perms = {} + for action_name, role_ids in all_input_permissions.items(): + if not role_ids: + continue + action = self.get_action(action_name) + if action not in perms.keys(): + perms[action] = list(role_ids) + else: + if action.model == 'grant': + # intersect existing roles with new roles + perms[action] = [_ for _ in role_ids if _ in perms[action]] + elif action.model == 'restrict': + # join existing roles with new roles + perms[action].extend([_ for _ in role_ids if _ not in perms[action]]) + return perms + + def associate_components(self, **kwd): + if 'user' in kwd: + if 'group' in kwd: + return self.associate_user_group(kwd['user'], kwd['group']) + elif 'role' in kwd: + return self.associate_user_role(kwd['user'], kwd['role']) + elif 'role' in kwd: + if 'group' in kwd: + return self.associate_group_role(kwd['group'], kwd['role']) + if 'action' in kwd: + if 'dataset' in kwd and 'role' in kwd: + return self.associate_action_dataset_role(kwd['action'], kwd['dataset'], kwd['role']) + raise Exception('No valid method of associating provided components: %s' % kwd) + + def associate_user_group(self, user, group): + assoc = self.model.UserGroupAssociation(user, group) + self.sa_session.add(assoc) + self.sa_session.flush() + return assoc + + def associate_user_role(self, user, role): + assoc = self.model.UserRoleAssociation(user, role) + self.sa_session.add(assoc) + self.sa_session.flush() + return assoc + + def associate_group_role(self, group, role): + assoc = self.model.GroupRoleAssociation(group, role) + self.sa_session.add(assoc) + self.sa_session.flush() + return assoc + + def associate_action_dataset_role(self, action, dataset, role): + assoc = self.model.DatasetPermissions(action, dataset, role) + self.sa_session.add(assoc) + self.sa_session.flush() + return assoc + + def create_private_user_role(self, user): + # Create private role + role = self.model.Role(name=user.email, description='Private Role for ' + user.email, type=self.model.Role.types.PRIVATE) + self.sa_session.add(role) + self.sa_session.flush() + # Add user to role + self.associate_components(role=role, user=user) + return role + + def get_private_user_role(self, user, auto_create=False): + role = self.sa_session.query(self.model.Role) \ + .filter(and_(self.model.UserRoleAssociation.table.c.user_id == user.id, + self.model.Role.table.c.id == self.model.UserRoleAssociation.table.c.role_id, + self.model.Role.table.c.type == self.model.Role.types.PRIVATE)) \ + .first() + if not role: + if auto_create: + return self.create_private_user_role(user) + else: + return None + return role + + def get_role(self, name, type=None): + type = type or self.model.Role.types.SYSTEM + # will raise exception if not found + return self.sa_session.query(self.model.Role) \ + .filter(and_(self.model.Role.table.c.name == name, + self.model.Role.table.c.type == type)) \ + .one() + + def create_role(self, name, description, in_users, in_groups, create_group_for_role=False, type=None): + type = type or self.model.Role.types.SYSTEM + role = self.model.Role(name=name, description=description, type=type) + self.sa_session.add(role) + # Create the UserRoleAssociations + for user in [self.sa_session.query(self.model.User).get(x) for x in in_users]: + self.associate_user_role(user, role) + # Create the GroupRoleAssociations + for group in [self.sa_session.query(self.model.Group).get(x) for x in in_groups]: + self.associate_group_role(group, role) + if create_group_for_role: + # Create the group + group = self.model.Group(name=name) + self.sa_session.add(group) + # Associate the group with the role + self.associate_group_role(group, role) + num_in_groups = len(in_groups) + 1 + else: + num_in_groups = len(in_groups) + self.sa_session.flush() + return role, num_in_groups + + def get_sharing_roles(self, user): + return self.sa_session.query(self.model.Role) \ + .filter(and_((self.model.Role.table.c.name).like("Sharing role for: %" + user.email + "%"), + self.model.Role.table.c.type == self.model.Role.types.SHARING)) + + def user_set_default_permissions(self, user, permissions={}, history=False, dataset=False, bypass_manage_permission=False, default_access_private=False): + # bypass_manage_permission is used to change permissions of datasets in a userless history when logging in + flush_needed = False + if user is None: + return None + if not permissions: + # default permissions + permissions = {self.permitted_actions.DATASET_MANAGE_PERMISSIONS: [self.get_private_user_role(user, auto_create=True)]} + # new_user_dataset_access_role_default_private is set as True in config file + if default_access_private: + permissions[self.permitted_actions.DATASET_ACCESS] = next(iter(permissions.values())) + # Delete all of the current default permissions for the user + for dup in user.default_permissions: + self.sa_session.delete(dup) + flush_needed = True + # Add the new default permissions for the user + for action, roles in permissions.items(): + if isinstance(action, Action): + action = action.action + for dup in [self.model.DefaultUserPermissions(user, action, role) for role in roles]: + self.sa_session.add(dup) + flush_needed = True + if flush_needed: + self.sa_session.flush() + if history: + for history in user.active_histories: + self.history_set_default_permissions(history, permissions=permissions, dataset=dataset, bypass_manage_permission=bypass_manage_permission) + + def user_get_default_permissions(self, user): + permissions = {} + for dup in user.default_permissions: + action = self.get_action(dup.action) + if action in permissions: + permissions[action].append(dup.role) + else: + permissions[action] = [dup.role] + return permissions + + def history_set_default_permissions(self, history, permissions={}, dataset=False, bypass_manage_permission=False): + # bypass_manage_permission is used to change permissions of datasets in a user-less history when logging in + flush_needed = False + user = history.user + if not user: + # default permissions on a user-less history are None + return None + if not permissions: + permissions = self.user_get_default_permissions(user) + # Delete all of the current default permission for the history + for dhp in history.default_permissions: + self.sa_session.delete(dhp) + flush_needed = True + # Add the new default permissions for the history + for action, roles in permissions.items(): + if isinstance(action, Action): + action = action.action + for dhp in [self.model.DefaultHistoryPermissions(history, action, role) for role in roles]: + self.sa_session.add(dhp) + flush_needed = True + if flush_needed: + self.sa_session.flush() + if dataset: + # Only deal with datasets that are not purged + for hda in history.activatable_datasets: + dataset = hda.dataset + if dataset.library_associations: + # Don't change permissions on a dataset associated with a library + continue + if [assoc for assoc in dataset.history_associations if assoc.history not in user.histories]: + # Don't change permissions on a dataset associated with a history not owned by the user + continue + if bypass_manage_permission or self.can_manage_dataset(user.all_roles(), dataset): + self.set_all_dataset_permissions(dataset, permissions) + + def history_get_default_permissions(self, history): + permissions = {} + for dhp in history.default_permissions: + action = self.get_action(dhp.action) + if action in permissions: + permissions[action].append(dhp.role) + else: + permissions[action] = [dhp.role] + return permissions + + def set_all_dataset_permissions(self, dataset, permissions={}, new=False, flush=True): + """ + Set new full permissions on a dataset, eliminating all current permissions. + Permission looks like: { Action : [ Role, Role ] } + """ + # Make sure that DATASET_MANAGE_PERMISSIONS is associated with at least 1 role + has_dataset_manage_permissions = False + for action, roles in permissions.items(): + if isinstance(action, Action): + if action == self.permitted_actions.DATASET_MANAGE_PERMISSIONS and roles: + has_dataset_manage_permissions = True + break + elif action == self.permitted_actions.DATASET_MANAGE_PERMISSIONS.action and roles: + has_dataset_manage_permissions = True + break + if not has_dataset_manage_permissions: + return "At least 1 role must be associated with manage permissions on this dataset." + flush_needed = False + # Delete all of the current permissions on the dataset + if not new: + for dp in dataset.actions: + self.sa_session.delete(dp) + flush_needed = True + # Add the new permissions on the dataset + for action, roles in permissions.items(): + if isinstance(action, Action): + action = action.action + for role in roles: + if hasattr(role, "id"): + role_id = role.id + else: + role_id = role + dp = self.model.DatasetPermissions(action, dataset, role_id=role_id) + self.sa_session.add(dp) + flush_needed = True + if flush_needed and flush: + self.sa_session.flush() + return "" + + def set_dataset_permission(self, dataset, permission={}): + """ + Set a specific permission on a dataset, leaving all other current permissions on the dataset alone. + Permission looks like: { Action.action : [ Role, Role ] } + """ + flush_needed = False + for action, roles in permission.items(): + if isinstance(action, Action): + action = action.action + # Delete the current specific permission on the dataset if one exists + for dp in dataset.actions: + if dp.action == action: + self.sa_session.delete(dp) + flush_needed = True + # Add the new specific permission on the dataset + for dp in [self.model.DatasetPermissions(action, dataset, role) for role in roles]: + self.sa_session.add(dp) + flush_needed = True + if flush_needed: + self.sa_session.flush() + + def get_permissions(self, item): + """ + Return a dictionary containing the actions and associated roles on item + where item is one of Library, LibraryFolder, LibraryDatasetDatasetAssociation, + LibraryDataset, Dataset. The dictionary looks like: { Action : [ Role, Role ] }. + """ + permissions = {} + for item_permission in item.actions: + action = self.get_action(item_permission.action) + if action in permissions: + permissions[action].append(item_permission.role) + else: + permissions[action] = [item_permission.role] + return permissions + + def copy_dataset_permissions(self, src, dst): + if not isinstance(src, self.model.Dataset): + src = src.dataset + if not isinstance(dst, self.model.Dataset): + dst = dst.dataset + self.set_all_dataset_permissions(dst, self.get_permissions(src)) + + def privately_share_dataset(self, dataset, users=[]): + intersect = None + for user in users: + roles = [ura.role for ura in user.roles if ura.role.type == self.model.Role.types.SHARING] + if intersect is None: + intersect = roles + else: + new_intersect = [] + for role in roles: + if role in intersect: + new_intersect.append(role) + intersect = new_intersect + sharing_role = None + if intersect: + for role in intersect: + if not [_ for _ in [ura.user for ura in role.users] if _ not in users]: + # only use a role if it contains ONLY the users we're sharing with + sharing_role = role + break + if sharing_role is None: + sharing_role = self.model.Role(name="Sharing role for: " + ", ".join([u.email for u in users]), + type=self.model.Role.types.SHARING) + self.sa_session.add(sharing_role) + self.sa_session.flush() + for user in users: + self.associate_components(user=user, role=sharing_role) + self.set_dataset_permission(dataset, {self.permitted_actions.DATASET_ACCESS: [sharing_role]}) + + def set_all_library_permissions(self, trans, library_item, permissions={}): + # Set new permissions on library_item, eliminating all current permissions + flush_needed = False + for role_assoc in library_item.actions: + self.sa_session.delete(role_assoc) + flush_needed = True + # Add the new permissions on library_item + for item_class, permission_class in self.library_item_assocs: + if isinstance(library_item, item_class): + for action, roles in list(permissions.items()): + if isinstance(action, Action): + action = action.action + for role_assoc in [permission_class(action, library_item, role) for role in roles]: + self.sa_session.add(role_assoc) + flush_needed = True + if isinstance(library_item, self.model.LibraryDatasetDatasetAssociation): + # Permission setting related to DATASET_MANAGE_PERMISSIONS was broken for a period of time, + # so it is possible that some Datasets have no roles associated with the DATASET_MANAGE_PERMISSIONS + # permission. In this case, we'll reset this permission to the library_item user's private role. + if not library_item.dataset.has_manage_permissions_roles(trans): + # Well this looks like a bug, this should be looked at. + # Default permissions above is single hash that keeps getting reeditted here + # because permission is being defined instead of permissions. -John + permissions[self.permitted_actions.DATASET_MANAGE_PERMISSIONS] = [trans.app.security_agent.get_private_user_role(library_item.user)] + self.set_dataset_permission(library_item.dataset, permissions) + if action == self.permitted_actions.LIBRARY_MANAGE.action and roles: + # Handle the special case when we are setting the LIBRARY_MANAGE_PERMISSION on a + # library_dataset_dataset_association since the roles need to be applied to the + # DATASET_MANAGE_PERMISSIONS permission on the associated dataset. + permissions = {} + permissions[self.permitted_actions.DATASET_MANAGE_PERMISSIONS] = roles + self.set_dataset_permission(library_item.dataset, permissions) + if flush_needed: + self.sa_session.flush() + + def set_library_item_permission(self, library_item, permission={}): + """ + Set a specific permission on a library item, leaving all other current permissions on the item alone. + Permission looks like: { Action.action : [ Role, Role ] } + """ + flush_needed = False + for action, roles in permission.items(): + if isinstance(action, Action): + action = action.action + # Delete the current specific permission on the library item if one exists + for item_permission in library_item.actions: + if item_permission.action == action: + self.sa_session.delete(item_permission) + flush_needed = True + # Add the new specific permission on the library item + if isinstance(library_item, self.model.LibraryDataset): + for item_permission in [self.model.LibraryDatasetPermissions(action, library_item, role) for role in roles]: + self.sa_session.add(item_permission) + flush_needed = True + elif isinstance(library_item, self.model.LibraryPermissions): + for item_permission in [self.model.LibraryPermissions(action, library_item, role) for role in roles]: + self.sa_session.add(item_permission) + flush_needed = True + if flush_needed: + self.sa_session.flush() + + def library_is_public(self, library, contents=False): + if contents: + # Check all contained folders and datasets to find any that are not public + if not self.folder_is_public(library.root_folder): + return False + # A library is considered public if there are no "access" actions associated with it. + return self.permitted_actions.LIBRARY_ACCESS.action not in [a.action for a in library.actions] + + def library_is_unrestricted(self, library): + # A library is considered unrestricted if there are no "access" actions associated with it. + return self.permitted_actions.LIBRARY_ACCESS.action not in [a.action for a in library.actions] + + def make_library_public(self, library, contents=False): + flush_needed = False + if contents: + # Make all contained folders (include deleted folders, but not purged folders), public + self.make_folder_public(library.root_folder) + # A library is considered public if there are no LIBRARY_ACCESS actions associated with it. + for lp in library.actions: + if lp.action == self.permitted_actions.LIBRARY_ACCESS.action: + self.sa_session.delete(lp) + flush_needed = True + if flush_needed: + self.sa_session.flush() + + def folder_is_public(self, folder): + for sub_folder in folder.folders: + if not self.folder_is_public(sub_folder): + return False + for library_dataset in folder.datasets: + ldda = library_dataset.library_dataset_dataset_association + if ldda and ldda.dataset and not self.dataset_is_public(ldda.dataset): + return False + return True + + def folder_is_unrestricted(self, folder): + # TODO implement folder restrictions + # for now all folders are _visible_ but the restricted datasets within are not visible + return True + + def make_folder_public(self, folder): + # Make all of the contents (include deleted contents, but not purged contents) of folder public + for sub_folder in folder.folders: + if not sub_folder.purged: + self.make_folder_public(sub_folder) + for library_dataset in folder.datasets: + dataset = library_dataset.library_dataset_dataset_association.dataset + if not dataset.purged and not self.dataset_is_public(dataset): + self.make_dataset_public(dataset) + + def dataset_is_public(self, dataset): + """ + A dataset is considered public if there are no "access" actions + associated with it. Any other actions ( 'manage permissions', + 'edit metadata' ) are irrelevant. Accessing dataset.actions + will cause a query to be emitted. + """ + return self.permitted_actions.DATASET_ACCESS.action not in [a.action for a in dataset.actions] + + def dataset_is_unrestricted(self, trans, dataset): + """ + Different implementation of the method above with signature: + def dataset_is_public( self, dataset ) + """ + return len(dataset.library_dataset_dataset_association.get_access_roles(trans)) == 0 + + def dataset_is_private_to_user(self, trans, dataset): + """ + If the Dataset object has exactly one access role and that is + the current user's private role then we consider the dataset private. + """ + private_role = self.get_private_user_role(trans.user) + access_roles = dataset.get_access_roles(trans) + + if len(access_roles) != 1: + return False + else: + if access_roles[0].id == private_role.id: + return True + else: + return False + + def datasets_are_public(self, trans, datasets): + ''' + Given a transaction object and a list of Datasets, return + a mapping from Dataset ids to whether the Dataset is public + or not. All Dataset ids should be returned in the mapping's keys. + ''' + # We go the other way around from dataset_is_public: we start with + # all datasets being marked as public. If there is an access action + # associated with the dataset, then we mark it as nonpublic: + datasets_public = {} + dataset_ids = [dataset.id for dataset in datasets] + for dataset_id in dataset_ids: + datasets_public[dataset_id] = True + + # Now get all datasets which have DATASET_ACCESS actions: + access_data_perms = trans.sa_session.query(trans.app.model.DatasetPermissions) \ + .filter(and_(trans.app.model.DatasetPermissions.dataset_id.in_(dataset_ids), + trans.app.model.DatasetPermissions.action == self.permitted_actions.DATASET_ACCESS.action)) \ + .all() + # Every dataset returned has "access" privileges associated with it, + # so it's not public. + for permission in access_data_perms: + datasets_public[permission.dataset_id] = False + return datasets_public + + def make_dataset_public(self, dataset): + # A dataset is considered public if there are no "access" actions associated with it. Any + # other actions ( 'manage permissions', 'edit metadata' ) are irrelevant. + flush_needed = False + for dp in dataset.actions: + if dp.action == self.permitted_actions.DATASET_ACCESS.action: + self.sa_session.delete(dp) + flush_needed = True + if flush_needed: + self.sa_session.flush() + + def derive_roles_from_access(self, trans, item_id, cntrller, library=False, **kwd): + # Check the access permission on a dataset. If library is true, item_id refers to a library. If library + # is False, item_id refers to a dataset ( item_id must currently be decoded before being sent ). The + # cntrller param is the calling controller, which needs to be passed to get_legitimate_roles(). + msg = '' + permissions = {} + # accessible will be True only if at least 1 user has every role in DATASET_ACCESS_in + accessible = False + # legitimate will be True only if all roles in DATASET_ACCESS_in are in the set of roles returned from + # get_legitimate_roles() + # legitimate = False # TODO: not used + # private_role_found will be true only if more than 1 role is being associated with the DATASET_ACCESS + # permission on item, and at least 1 of the roles is private. + private_role_found = False + error = False + for k, v in get_permitted_actions(filter='DATASET').items(): + # Change for removing the prefix '_in' from the roles select box + in_roles = [self.sa_session.query(self.model.Role).get(x) for x in listify(kwd[k])] + if not in_roles: + in_roles = [self.sa_session.query(self.model.Role).get(x) for x in listify(kwd.get(k + '_in', []))] + if v == self.permitted_actions.DATASET_ACCESS and in_roles: + if library: + item = self.sa_session.query(self.model.Library).get(item_id) + else: + item = self.sa_session.query(self.model.Dataset).get(item_id) + if (library and not self.library_is_public(item)) or (not library and not self.dataset_is_public(item)): + # Ensure that roles being associated with DATASET_ACCESS are a subset of the legitimate roles + # derived from the roles associated with the access permission on item if it's not public. This + # will keep illegitimate roles from being associated with the DATASET_ACCESS permission on the + # dataset (i.e., in the case where item is .a library, if Role1 is associated with LIBRARY_ACCESS, + # then only those users that have Role1 should be associated with DATASET_ACCESS. + legitimate_roles = self.get_legitimate_roles(trans, item, cntrller) + illegitimate_roles = [] + for role in in_roles: + if role not in legitimate_roles: + illegitimate_roles.append(role) + if illegitimate_roles: + # This condition should never occur since illegitimate roles are filtered out of the set of + # roles displayed on the forms, but just in case there is a bug somewhere that incorrectly + # filters, we'll display this message. + error = True + msg += "The following roles are not associated with users that have the 'access' permission on this " + msg += "item, so they were incorrectly displayed: " + for role in illegitimate_roles: + msg += "%s, " % role.name + msg = msg.rstrip(", ") + new_in_roles = [] + for role in in_roles: + if role in legitimate_roles: + new_in_roles.append(role) + in_roles = new_in_roles + if len(in_roles) > 1: + # At least 1 user must have every role associated with the access + # permission on this dataset, or the dataset is not accessible. + # Since we have more than 1 role, none of them can be private. + for role in in_roles: + if role.type == self.model.Role.types.PRIVATE: + private_role_found = True + break + if len(in_roles) == 1: + accessible = True + else: + # At least 1 user must have every role associated with the access + # permission on this dataset, or the dataset is not accessible. + in_roles_set = set() + for role in in_roles: + in_roles_set.add(role) + users_set = set() + for role in in_roles: + for ura in role.users: + users_set.add(ura.user) + for gra in role.groups: + group = gra.group + for uga in group.users: + users_set.add(uga.user) + # Make sure that at least 1 user has every role being associated with the dataset. + for user in users_set: + user_roles_set = set() + for ura in user.roles: + user_roles_set.add(ura.role) + if in_roles_set.issubset(user_roles_set): + accessible = True + break + if private_role_found or not accessible: + error = True + # Don't set the permissions for DATASET_ACCESS if inaccessible or multiple roles with + # at least 1 private, but set all other permissions. + permissions[self.get_action(v.action)] = [] + msg = "At least 1 user must have every role associated with accessing datasets. " + if private_role_found: + msg += "Since you are associating more than 1 role, no private roles are allowed." + if not accessible: + msg += "The roles you attempted to associate for access would make the datasets in-accessible by everyone." + else: + permissions[self.get_action(v.action)] = in_roles + else: + permissions[self.get_action(v.action)] = in_roles + return permissions, in_roles, error, msg + + def copy_library_permissions(self, trans, source_library_item, target_library_item, user=None): + # Copy all relevant permissions from source. + permissions = {} + for role_assoc in source_library_item.actions: + if role_assoc.action != self.permitted_actions.LIBRARY_ACCESS.action: + # LIBRARY_ACCESS is a special permission that is set only at the library level. + if role_assoc.action in permissions: + permissions[role_assoc.action].append(role_assoc.role) + else: + permissions[role_assoc.action] = [role_assoc.role] + self.set_all_library_permissions(trans, target_library_item, permissions) + if user: + item_class = None + for item_class, permission_class in self.library_item_assocs: + if isinstance(target_library_item, item_class): + break + if item_class: + # Make sure user's private role is included + private_role = self.model.security_agent.get_private_user_role(user) + for name, action in self.permitted_actions.items(): + if not permission_class.filter_by(role_id=private_role.id, action=action.action).first(): + lp = permission_class(action.action, target_library_item, private_role) + self.sa_session.add(lp) + self.sa_session.flush() + else: + raise Exception('Invalid class (%s) specified for target_library_item (%s)' % + (target_library_item.__class__, target_library_item.__class__.__name__)) + + def get_permitted_libraries(self, trans, user, actions): + """ + This method is historical (it is not currently used), but may be useful again at some + point. It returns a dictionary whose keys are library objects and whose values are a + comma-separated string of folder ids. This method works with the show_library_item() + method below, and it returns libraries for which the received user has permission to + perform the received actions. Here is an example call to this method to return all + libraries for which the received user has LIBRARY_ADD permission:: + + libraries = trans.app.security_agent.get_permitted_libraries( trans, user, + [ trans.app.security_agent.permitted_actions.LIBRARY_ADD ] ) + """ + all_libraries = trans.sa_session.query(trans.app.model.Library) \ + .filter(trans.app.model.Library.table.c.deleted == false()) \ + .order_by(trans.app.model.Library.name) + roles = user.all_roles() + actions_to_check = actions + # The libraries dictionary looks like: { library : '1,2' }, library : '3' } + # Its keys are the libraries that should be displayed for the current user and whose values are a + # string of comma-separated folder ids, of the associated folders the should NOT be displayed. + # The folders that should not be displayed may not be a complete list, but it is ultimately passed + # to the calling method to keep from re-checking the same folders when the library / folder + # select lists are rendered. + libraries = {} + for library in all_libraries: + can_show, hidden_folder_ids = self.show_library_item(self, roles, library, actions_to_check) + if can_show: + libraries[library] = hidden_folder_ids + return libraries + + def show_library_item(self, user, roles, library_item, actions_to_check, hidden_folder_ids=''): + """ + This method must be sent an instance of Library() or LibraryFolder(). Recursive execution produces a + comma-separated string of folder ids whose folders do NOT meet the criteria for showing. Along with + the string, True is returned if the current user has permission to perform any 1 of actions_to_check + on library_item. Otherwise, cycle through all sub-folders in library_item until one is found that meets + this criteria, if it exists. This method does not necessarily scan the entire library as it returns + when it finds the first library_item that allows user to perform any one action in actions_to_check. + """ + for action in actions_to_check: + if self.allow_action(roles, action, library_item): + return True, hidden_folder_ids + if isinstance(library_item, self.model.Library): + return self.show_library_item(user, roles, library_item.root_folder, actions_to_check, hidden_folder_ids='') + if isinstance(library_item, self.model.LibraryFolder): + for folder in library_item.active_folders: + can_show, hidden_folder_ids = self.show_library_item(user, roles, folder, actions_to_check, hidden_folder_ids=hidden_folder_ids) + if can_show: + return True, hidden_folder_ids + if hidden_folder_ids: + hidden_folder_ids = '%s,%d' % (hidden_folder_ids, folder.id) + else: + hidden_folder_ids = '%d' % folder.id + return False, hidden_folder_ids + + def get_showable_folders(self, user, roles, library_item, actions_to_check, hidden_folder_ids=[], showable_folders=[]): + """ + This method must be sent an instance of Library(), all the folders of which are scanned to determine if + user is allowed to perform any action in actions_to_check. The param hidden_folder_ids, if passed, should + contain a list of folder IDs which was generated when the library was previously scanned + using the same actions_to_check. A list of showable folders is generated. This method scans the entire library. + """ + if isinstance(library_item, self.model.Library): + return self.get_showable_folders(user, roles, library_item.root_folder, actions_to_check, showable_folders=[]) + if isinstance(library_item, self.model.LibraryFolder): + if library_item.id not in hidden_folder_ids: + for action in actions_to_check: + if self.allow_action(roles, action, library_item): + showable_folders.append(library_item) + break + for folder in library_item.active_folders: + self.get_showable_folders(user, roles, folder, actions_to_check, showable_folders=showable_folders) + return showable_folders + + def set_entity_user_associations(self, users=[], roles=[], groups=[], delete_existing_assocs=True): + for user in users: + if delete_existing_assocs: + flush_needed = False + for a in user.non_private_roles + user.groups: + self.sa_session.delete(a) + flush_needed = True + if flush_needed: + self.sa_session.flush() + self.sa_session.refresh(user) + for role in roles: + # Make sure we are not creating an additional association with a PRIVATE role + if role not in [x.role for x in user.roles]: + self.associate_components(user=user, role=role) + for group in groups: + self.associate_components(user=user, group=group) + + def set_entity_group_associations(self, groups=[], users=[], roles=[], delete_existing_assocs=True): + for group in groups: + if delete_existing_assocs: + flush_needed = False + for a in group.roles + group.users: + self.sa_session.delete(a) + flush_needed = True + if flush_needed: + self.sa_session.flush() + for role in roles: + self.associate_components(group=group, role=role) + for user in users: + self.associate_components(group=group, user=user) + + def set_entity_role_associations(self, roles=[], users=[], groups=[], delete_existing_assocs=True): + for role in roles: + if delete_existing_assocs: + flush_needed = False + for a in role.users + role.groups: + self.sa_session.delete(a) + flush_needed = True + if flush_needed: + self.sa_session.flush() + for user in users: + self.associate_components(user=user, role=role) + for group in groups: + self.associate_components(group=group, role=role) + + def get_component_associations(self, **kwd): + assert len(kwd) == 2, 'You must specify exactly 2 Galaxy security components to check for associations.' + if 'dataset' in kwd: + if 'action' in kwd: + return self.sa_session.query(self.model.DatasetPermissions).filter_by(action=kwd['action'].action, dataset_id=kwd['dataset'].id).first() + elif 'user' in kwd: + if 'group' in kwd: + return self.sa_session.query(self.model.UserGroupAssociation).filter_by(group_id=kwd['group'].id, user_id=kwd['user'].id).first() + elif 'role' in kwd: + return self.sa_session.query(self.model.UserRoleAssociation).filter_by(role_id=kwd['role'].id, user_id=kwd['user'].id).first() + elif 'group' in kwd: + if 'role' in kwd: + return self.sa_session.query(self.model.GroupRoleAssociation).filter_by(role_id=kwd['role'].id, group_id=kwd['group'].id).first() + raise Exception('No valid method of associating provided components: %s' % kwd) + + def check_folder_contents(self, user, roles, folder, hidden_folder_ids=''): + """ + This method must always be sent an instance of LibraryFolder(). Recursive execution produces a + comma-separated string of folder ids whose folders do NOT meet the criteria for showing. Along + with the string, True is returned if the current user has permission to access folder. Otherwise, + cycle through all sub-folders in folder until one is found that meets this criteria, if it exists. + This method does not necessarily scan the entire library as it returns when it finds the first + folder that is accessible to user. + """ + # If a folder is writeable, it's accessable and we need not go further + if self.can_add_library_item(roles, folder): + return True, '' + action = self.permitted_actions.DATASET_ACCESS + + lddas = self.sa_session.query(self.model.LibraryDatasetDatasetAssociation) \ + .join("library_dataset") \ + .filter(self.model.LibraryDataset.folder == folder) \ + .join("dataset") \ + .options(eagerload_all("dataset.actions")) \ + .all() + + for ldda in lddas: + ldda_access_permissions = self.get_item_actions(action, ldda.dataset) + if not ldda_access_permissions: + # Dataset is public + return True, hidden_folder_ids + for ldda_access_permission in ldda_access_permissions: + if ldda_access_permission.role in roles: + # The current user has access permission on the dataset + return True, hidden_folder_ids + for sub_folder in folder.active_folders: + can_access, hidden_folder_ids = self.check_folder_contents(user, roles, sub_folder, hidden_folder_ids=hidden_folder_ids) + if can_access: + return True, hidden_folder_ids + if hidden_folder_ids: + hidden_folder_ids = '%s,%d' % (hidden_folder_ids, sub_folder.id) + else: + hidden_folder_ids = '%d' % sub_folder.id + return False, hidden_folder_ids + + +class HostAgent(RBACAgent): + """ + A simple security agent which allows access to datasets based on host. + This exists so that externals sites such as UCSC can gain access to + datasets which have permissions which would normally prevent such access. + """ + # TODO: Make sites user configurable + sites = Bunch( + ucsc_main=('hgw1.cse.ucsc.edu', 'hgw2.cse.ucsc.edu', 'hgw3.cse.ucsc.edu', 'hgw4.cse.ucsc.edu', + 'hgw5.cse.ucsc.edu', 'hgw6.cse.ucsc.edu', 'hgw7.cse.ucsc.edu', 'hgw8.cse.ucsc.edu'), + ucsc_test=('hgwdev.cse.ucsc.edu', ), + ucsc_archaea=('lowepub.cse.ucsc.edu', ) + ) + + def __init__(self, model, permitted_actions=None): + self.model = model + if permitted_actions: + self.permitted_actions = permitted_actions + + @property + def sa_session(self): + """Returns a SQLAlchemy session""" + return self.model.context + + def allow_action(self, addr, action, **kwd): + if 'dataset' in kwd and action == self.permitted_actions.DATASET_ACCESS: + hda = kwd['dataset'] + if action == self.permitted_actions.DATASET_ACCESS and action.action not in [dp.action for dp in hda.dataset.actions]: + log.debug('Allowing access to public dataset with hda: %i.' % hda.id) + return True # dataset has no roles associated with the access permission, thus is already public + hdadaa = self.sa_session.query(self.model.HistoryDatasetAssociationDisplayAtAuthorization) \ + .filter_by(history_dataset_association_id=hda.id).first() + if not hdadaa: + log.debug('Denying access to private dataset with hda: %i. No hdadaa record for this dataset.' % hda.id) + return False # no auth + # We could just look up the reverse of addr, but then we'd also + # have to verify it with the forward address and special case any + # IPs (instead of hosts) in the server list. + # + # This would be improved by caching, but that's what the OS's name + # service cache daemon is for (you ARE running nscd, right?). + for server in HostAgent.sites.get(hdadaa.site, []): + # We're going to search in order, but if the remote site is load + # balancing their connections (as UCSC does), this is okay. + try: + if socket.gethostbyname(server) == addr: + break # remote host is in the server list + except (socket.error, socket.gaierror): + pass # can't resolve, try next + else: + log.debug('Denying access to private dataset with hda: %i. Remote addr is not a valid server for site: %s.' % (hda.id, hdadaa.site)) + return False # remote addr is not in the server list + if (datetime.utcnow() - hdadaa.update_time) > timedelta(seconds=60): + log.debug('Denying access to private dataset with hda: %i. Authorization was granted, but has expired.' % hda.id) + return False # not authz'd in the last 60 seconds + log.debug('Allowing access to private dataset with hda: %i. Remote server is: %s.' % (hda.id, server)) + return True + else: + raise Exception('The dataset access permission is the only valid permission in the host security agent.') + + def set_dataset_permissions(self, hda, user, site): + hdadaa = self.sa_session.query(self.model.HistoryDatasetAssociationDisplayAtAuthorization) \ + .filter_by(history_dataset_association_id=hda.id).first() + if hdadaa: + hdadaa.update_time = datetime.utcnow() + else: + hdadaa = self.model.HistoryDatasetAssociationDisplayAtAuthorization(hda=hda, user=user, site=site) + self.sa_session.add(hdadaa) + self.sa_session.flush() diff --git a/lib/galaxy/model/store/__init__.py b/lib/galaxy/model/store/__init__.py new file mode 100644 index 00000000000..14451ff74de --- /dev/null +++ b/lib/galaxy/model/store/__init__.py @@ -0,0 +1,1321 @@ +import abc +import contextlib +import datetime +import os +import shutil +import tarfile +import tempfile +from json import dump, dumps, load + +import six +from bdbag import bdbag_api as bdb +from boltons.iterutils import remap +from sqlalchemy.orm import eagerload_all +from sqlalchemy.sql import expression + +from galaxy.exceptions import MalformedContents, ObjectNotFound +from galaxy.security.idencoding import IdEncodingHelper +from galaxy.util import FILENAME_VALID_CHARS +from galaxy.util import in_directory +from galaxy.util.bunch import Bunch +from galaxy.version import VERSION_MAJOR +from ..item_attrs import add_item_annotation, get_item_annotation_str +from ... import model + +ATTRS_FILENAME_HISTORY = 'history_attrs.txt' +ATTRS_FILENAME_DATASETS = 'datasets_attrs.txt' +ATTRS_FILENAME_JOBS = 'jobs_attrs.txt' +ATTRS_FILENAME_IMPLICIT_COLLECTION_JOBS = 'implicit_collection_jobs_attrs.txt' +ATTRS_FILENAME_COLLECTIONS = 'collections_attrs.txt' +ATTRS_FILENAME_EXPORT = 'export_attrs.txt' +ATTRS_FILENAME_LIBRARIES = 'libraries_attrs.txt' + + +class ImportOptions(object): + + def __init__(self, allow_edit=False, allow_library_creation=False, allow_dataset_object_edit=None): + self.allow_edit = allow_edit + self.allow_library_creation = allow_library_creation + if allow_dataset_object_edit is None: + allow_dataset_object_edit = allow_edit + self.allow_dataset_object_edit = allow_dataset_object_edit + + +class SessionlessContext(object): + + def __init__(self): + self.objects = [] + + def flush(self): + pass + + def add(self, obj): + self.objects.append(obj) + + def query(self, model_class): + + def find(obj_id): + for obj in self.objects: + if isinstance(obj, model_class) and obj.id == obj_id: + return obj + return None + + return Bunch(find=find) + + +@six.add_metaclass(abc.ABCMeta) +class ModelImportStore(object): + + def __init__(self, import_options=None, app=None, user=None, object_store=None): + if object_store is None: + if app is not None: + object_store = app.object_store + self.object_store = object_store + self.app = app + if app is not None: + self.sa_session = app.model.context.current + self.sessionless = False + else: + self.sa_session = SessionlessContext() + self.sessionless = True + self.user = user + self.import_options = import_options or ImportOptions() + + @abc.abstractmethod + def defines_new_history(self): + """Does this store define a new history to create.""" + + @abc.abstractmethod + def new_history_properties(self): + """Dict of history properties if defines_new_history() is truthy.""" + + @abc.abstractmethod + def datasets_properties(self): + """Return a list of HDA properties.""" + + def library_properties(self): + """Return a list of library properties.""" + return [] + + @abc.abstractmethod + def collections_properties(self): + """Return a list of HDCA properties.""" + + @abc.abstractmethod + def jobs_properties(self): + """Return a list of jobs properties.""" + + @abc.abstractproperty + def object_key(self): + """Key used to connect objects in metadata. + + Legacy exports used 'hid' but associated objects may not be from the same history + and a history may contain multiple objects with the same 'hid'. + """ + + @abc.abstractproperty + def trust_hid(self, obj_attrs): + """Trust HID when importing objects into a new History.""" + + @contextlib.contextmanager + def target_history(self, default_history=None): + new_history = None + + if self.defines_new_history(): + history_properties = self.new_history_properties() + history_name = history_properties.get('name') + if history_name: + history_name = 'imported from archive: %s' % history_name + else: + history_name = 'unnamed imported history' + + # Create history. + new_history = model.History(name=history_name, + user=self.user) + new_history.importing = True + hid_counter = history_properties.get('hid_counter') + genome_build = history_properties.get('genome_build') + + # TODO: This seems like it shouldn't be imported, try to test and verify we can calculate this + # and get away without it. -John + if hid_counter: + new_history.hid_counter = hid_counter + if genome_build: + new_history.genome_build = genome_build + + self._session_add(new_history) + self._flush() + + if self.user: + add_item_annotation(self.sa_session, self.user, new_history, history_properties.get('annotation')) + + history = new_history + else: + history = default_history + + yield history + + if new_history is not None: + # Done importing. + new_history.importing = False + self._flush() + + def perform_import(self, history=None, new_history=False, job=None): + object_import_tracker = ObjectImportTracker() + + datasets_attrs = self.datasets_properties() + collections_attrs = self.collections_properties() + + self._import_datasets(object_import_tracker, datasets_attrs, history, new_history, job) + self._import_dataset_copied_associations(object_import_tracker, datasets_attrs) + self._import_libraries(object_import_tracker) + self._import_collection_instances(object_import_tracker, collections_attrs, history, new_history) + self._import_collection_implicit_input_associations(object_import_tracker, collections_attrs) + self._import_collection_copied_associations(object_import_tracker, collections_attrs) + self._reassign_hids(object_import_tracker, history) + self._import_jobs(object_import_tracker, history) + self._import_implicit_collection_jobs(object_import_tracker) + self._flush() + + def _import_datasets(self, object_import_tracker, datasets_attrs, history, new_history, job): + object_key = self.object_key + + for dataset_attrs in datasets_attrs: + def handle_dataset_object_edit(dataset_instance): + if "dataset" in dataset_attrs: + assert self.import_options.allow_dataset_object_edit + dataset_attributes = [ + "state", + "deleted", + "purged", + "external_filename", + "_extra_files_path", + "file_size", + "object_store_id", + "total_size", + "uuid" + ] + + for attribute in dataset_attributes: + if attribute in dataset_attrs["dataset"]: + setattr(dataset_instance.dataset, attribute, dataset_attrs["dataset"][attribute]) + if "hashes" in dataset_attrs["dataset"]: + for hash_attrs in dataset_attrs["dataset"]["hashes"]: + hash_obj = model.DatasetHash() + hash_obj.hash_value = hash_attrs["hash_value"] + hash_obj.hash_function = hash_attrs["hash_function"] + hash_obj.extra_files_path = hash_attrs["extra_files_path"] + dataset_instance.dataset.hashes.append(hash_obj) + + if 'id' in dataset_attrs["dataset"] and self.import_options.allow_edit: + dataset_instance.dataset.id = dataset_attrs["dataset"]['id'] + + if 'id' in dataset_attrs and self.import_options.allow_edit and not self.sessionless: + hda = self.sa_session.query(model.HistoryDatasetAssociation).get(dataset_attrs["id"]) + attributes = [ + "name", + "extension", + "info", + "blurb", + "peek", + "designation", + "visible", + "metadata", + ] + for attribute in attributes: + if attribute in dataset_attrs: + setattr(hda, attribute, dataset_attrs[attribute]) + + handle_dataset_object_edit(hda) + self._flush() + else: + metadata = dataset_attrs['metadata'] + + model_class = dataset_attrs.get("model_class", "HistoryDatasetAssociation") + if model_class == "HistoryDatasetAssociation": + # Create dataset and HDA. + dataset_instance = model.HistoryDatasetAssociation(name=dataset_attrs['name'], + extension=dataset_attrs['extension'], + info=dataset_attrs['info'], + blurb=dataset_attrs['blurb'], + peek=dataset_attrs['peek'], + designation=dataset_attrs['designation'], + visible=dataset_attrs['visible'], + deleted=dataset_attrs.get('deleted', False), + dbkey=metadata['dbkey'], + metadata=metadata, + history=history, + create_dataset=True, + flush=False, + sa_session=self.sa_session) + if 'id' in dataset_attrs and self.import_options.allow_edit: + dataset_instance.id = dataset_attrs['id'] + elif model_class == "LibraryDatasetDatasetAssociation": + # Create dataset and HDA. + dataset_instance = model.LibraryDatasetDatasetAssociation(name=dataset_attrs['name'], + extension=dataset_attrs['extension'], + info=dataset_attrs['info'], + blurb=dataset_attrs['blurb'], + peek=dataset_attrs['peek'], + designation=dataset_attrs['designation'], + visible=dataset_attrs['visible'], + deleted=dataset_attrs.get('deleted', False), + dbkey=metadata['dbkey'], + metadata=metadata, + create_dataset=True, + sa_session=self.sa_session) + else: + raise Exception("Unknown dataset instance type encountered") + + # Older style... + if 'uuid' in dataset_attrs: + dataset_instance.dataset.uuid = dataset_attrs["uuid"] + if 'dataset_uuid' in dataset_attrs: + dataset_instance.dataset.uuid = dataset_attrs["dataset_uuid"] + + self._session_add(dataset_instance) + self._flush() + + if model_class == "HistoryDatasetAssociation": + # don't use add_history to manage HID handling across full import to try to preserve + # HID structure. + dataset_instance.history = history + if new_history and self.trust_hid(dataset_attrs): + dataset_instance.hid = dataset_attrs['hid'] + else: + object_import_tracker.requires_hid.append(dataset_instance) + + self._flush() + if 'dataset' in dataset_attrs: + handle_dataset_object_edit(dataset_instance) + else: + file_name = dataset_attrs.get('file_name') + if file_name: + # Do security check and move/copy dataset data. + archive_path = os.path.abspath(os.path.join(self.archive_dir, file_name)) + if os.path.islink(archive_path): + raise MalformedContents("Invalid dataset path: %s" % archive_path) + + temp_dataset_file_name = \ + os.path.realpath(archive_path) + + if not in_directory(temp_dataset_file_name, self.archive_dir): + raise MalformedContents("Invalid dataset path: %s" % temp_dataset_file_name) + + if not file_name or not os.path.exists(temp_dataset_file_name): + dataset_instance.state = dataset_instance.states.DISCARDED + dataset_instance.deleted = True + dataset_instance.purged = True + dataset_instance.dataset.deleted = True + dataset_instance.dataset.purged = True + else: + dataset_instance.state = dataset_instance.states.OK + self.object_store.update_from_file(dataset_instance.dataset, file_name=temp_dataset_file_name, create=True) + + # Import additional files if present. Histories exported previously might not have this attribute set. + dataset_extra_files_path = dataset_attrs.get('extra_files_path', None) + if dataset_extra_files_path: + try: + file_list = os.listdir(os.path.join(self.archive_dir, dataset_extra_files_path)) + except OSError: + file_list = [] + + if file_list: + for extra_file in file_list: + self.object_store.update_from_file( + dataset_instance.dataset, extra_dir='dataset_%s_files' % dataset_instance.dataset.id, + alt_name=extra_file, file_name=os.path.join(self.archive_dir, dataset_extra_files_path, extra_file), + create=True) + dataset_instance.dataset.set_total_size() # update the filesize record in the database + + if dataset_instance.deleted: + dataset_instance.dataset.deleted = True + + if model_class == "HistoryDatasetAssociation" and self.user: + add_item_annotation(self.sa_session, self.user, dataset_instance, dataset_attrs['annotation']) + # TODO: Set tags. + + if self.app: + self.app.datatypes_registry.set_external_metadata_tool.regenerate_imported_metadata_if_needed( + dataset_instance, history, job + ) + + if model_class == "HistoryDatasetAssociation": + if object_key in dataset_attrs: + object_import_tracker.hdas_by_key[dataset_attrs[object_key]] = dataset_instance + else: + assert 'id' in dataset_attrs + object_import_tracker.hdas_by_id[dataset_attrs['id']] = dataset_instance + else: + object_import_tracker.lddas_by_key[dataset_attrs[object_key]] = dataset_instance + + def _import_libraries(self, object_import_tracker): + object_key = self.object_key + + libraries_attrs = self.library_properties() + for library_attrs in libraries_attrs: + assert self.import_options.allow_library_creation + name = library_attrs['name'] + description = library_attrs['description'] + synopsis = library_attrs['synopsis'] + library = model.Library(name=name, description=description, synopsis=synopsis) + self._session_add(library) + + def import_folder(folder_attrs): + name = folder_attrs['name'] + description = folder_attrs['description'] + genome_build = folder_attrs['genome_build'] + deleted = folder_attrs['deleted'] + library_folder = model.LibraryFolder( + name=name, + description=description, + genome_build=genome_build + ) + library_folder.deleted = deleted + + self._session_add(library_folder) + self._flush() + + for sub_folder_attrs in folder_attrs.get("folders", []): + sub_folder = import_folder(sub_folder_attrs) + library_folder.add_folder(sub_folder) + + for ld_attrs in folder_attrs.get("datasets", []): + ld = model.LibraryDataset( + folder=library_folder, + name=ld_attrs['name'], + info=ld_attrs['info'], + order_id=ld_attrs['order_id'] + ) + if 'ldda' in ld_attrs: + ldda = object_import_tracker.lddas_by_key[ld_attrs["ldda"][object_key]] + ld.library_dataset_dataset_association = ldda + self._session_add(ld) + + self._flush() + + return library_folder + + if 'root_folder' in library_attrs: + library.root_folder = import_folder(library_attrs['root_folder']) + + object_import_tracker.libraries_by_key[library_attrs[object_key]] = library + + def _import_collection_instances(self, object_import_tracker, collections_attrs, history, new_history): + object_key = self.object_key + + def import_collection(collection_attrs): + def materialize_elements(dc): + if "elements" not in collection_attrs: + return + + elements_attrs = collection_attrs['elements'] + for element_attrs in elements_attrs: + dce = model.DatasetCollectionElement(collection=dc, + element=model.DatasetCollectionElement.UNINITIALIZED_ELEMENT, + element_index=element_attrs['element_index'], + element_identifier=element_attrs['element_identifier']) + if 'hda' in element_attrs: + hda_attrs = element_attrs['hda'] + hda_key = hda_attrs[object_key] + hda = object_import_tracker.hdas_by_key[hda_key] + dce.hda = hda + elif 'child_collection' in element_attrs: + dce.child_collection = import_collection(element_attrs['child_collection']) + else: + raise Exception("Unknown collection element type encountered.") + + self._session_add(dce) + + if "id" in collection_attrs and self.import_options.allow_edit and not self.sessionless: + dc = self.sa_session.query(model.DatasetCollection).get(collection_attrs["id"]) + attributes = [ + "collection_type", + "populated_state", + "element_count", + ] + for attribute in attributes: + if attribute in collection_attrs: + setattr(dc, attribute, collection_attrs[attribute]) + materialize_elements(dc) + else: + # create collection + dc = model.DatasetCollection(collection_type=collection_attrs['type']) + dc.populated_state = collection_attrs["populated_state"] + # TODO: element_count... + materialize_elements(dc) + + self._session_add(dc) + return dc + + for collection_attrs in collections_attrs: + dc = import_collection(collection_attrs["collection"]) + if 'id' in collection_attrs and self.import_options.allow_edit and not self.sessionless: + hdca = self.sa_session.query(model.HistoryDatasetCollectionAssociation).get(collection_attrs["id"]) + # TODO: edit attributes... + else: + hdca = model.HistoryDatasetCollectionAssociation(collection=dc, + visible=True, + name=collection_attrs['display_name'], + implicit_output_name=collection_attrs.get("implicit_output_name")) + if 'id' in collection_attrs and self.import_options.allow_edit: + hdca.id = collection_attrs['id'] + + hdca.history = history + if new_history and self.trust_hid(collection_attrs): + hdca.hid = collection_attrs['hid'] + else: + object_import_tracker.requires_hid.append(hdca) + + self._session_add(hdca) + if object_key in collection_attrs: + object_import_tracker.hdcas_by_key[collection_attrs[object_key]] = hdca + else: + assert 'id' in collection_attrs + object_import_tracker.hdcas_by_id[collection_attrs['id']] = hdca + + def _import_collection_implicit_input_associations(self, object_import_tracker, collections_attrs): + object_key = self.object_key + + for collection_attrs in collections_attrs: + if "id" in collection_attrs: + # Existing object, not a new one, this property is immutable via model stores currently. + continue + + hdca = object_import_tracker.hdcas_by_key[collection_attrs[object_key]] + if "implicit_input_collections" in collection_attrs: + implicit_input_collections = collection_attrs["implicit_input_collections"] + for implicit_input_collection in implicit_input_collections: + name = implicit_input_collection["name"] + input_collection_identifier = implicit_input_collection["input_dataset_collection"] + if input_collection_identifier in object_import_tracker.hdcas_by_key: + input_dataset_collection = object_import_tracker.hdcas_by_key[input_collection_identifier] + hdca.add_implicit_input_collection(name, input_dataset_collection) + + def _import_dataset_copied_associations(self, object_import_tracker, datasets_attrs): + object_key = self.object_key + + # Re-establish copied_from_history_dataset_association relationships so history extraction + # has a greater chance of working in this history, for reproducibility. + for dataset_attrs in datasets_attrs: + if "id" in dataset_attrs: + # Existing object, not a new one, this property is not immutable via model stores currently. + continue + + dataset_key = dataset_attrs[object_key] + if dataset_key not in object_import_tracker.hdas_by_key: + continue + + hda = object_import_tracker.hdas_by_key[dataset_key] + copied_from_chain = dataset_attrs.get("copied_from_history_dataset_association_id_chain", []) + copied_from_object_key = _copied_from_object_key(copied_from_chain, object_import_tracker.hdas_by_key) + if not copied_from_object_key: + continue + + # Re-establish the chain if we can. + if copied_from_object_key in object_import_tracker.hdas_by_key: + hda.copied_from_history_dataset_association = object_import_tracker.hdas_by_key[copied_from_object_key] + else: + # We're at the end of the chain and this HDA was copied from an HDA + # outside the history. So when we find this job and are looking for inputs/outputs + # attach to this node... unless we've already encountered another dataset + # copied from that jobs output... in that case we are going to cheat and + # say this dataset was copied from that one. It wasn't in the original Galaxy + # instance but I think it is find to pretend in order to create a DAG here. + hda_copied_from_sinks = object_import_tracker.hda_copied_from_sinks + if copied_from_object_key in hda_copied_from_sinks: + hda.copied_from_history_dataset_association = object_import_tracker.hdas_by_key[hda_copied_from_sinks[copied_from_object_key]] + else: + hda_copied_from_sinks[copied_from_object_key] = dataset_key + + def _import_collection_copied_associations(self, object_import_tracker, collections_attrs): + object_key = self.object_key + + # Re-establish copied_from_history_dataset_collection_association relationships so history extraction + # has a greater chance of working in this history, for reproducibility. Very similar to HDA code above + # see comments there. + for collection_attrs in collections_attrs: + if "id" in collection_attrs: + # Existing object, not a new one, this property is immutable via model stores currently. + continue + + dataset_collection_key = collection_attrs[object_key] + if dataset_collection_key not in object_import_tracker.hdcas_by_key: + continue + + hdca = object_import_tracker.hdcas_by_key[dataset_collection_key] + copied_from_chain = collection_attrs.get("copied_from_history_dataset_collection_association_id_chain", []) + copied_from_object_key = _copied_from_object_key(copied_from_chain, object_import_tracker.hdcas_by_key) + if not copied_from_object_key: + continue + + # Re-establish the chain if we can, again see comments for hdas above for this to make more + # sense. + hdca_copied_from_sinks = object_import_tracker.hdca_copied_from_sinks + if copied_from_object_key in object_import_tracker.hdcas_by_key: + hdca.copied_from_history_dataset_collection_association = object_import_tracker.hdcas_by_key[copied_from_object_key] + else: + if copied_from_object_key in hdca_copied_from_sinks: + hdca.copied_from_history_dataset_association = object_import_tracker.hdcas_by_key[hdca_copied_from_sinks[copied_from_object_key]] + else: + hdca_copied_from_sinks[copied_from_object_key] = dataset_collection_key + + def _reassign_hids(self, object_import_tracker, history): + # assign HIDs for newly created objects that didn't match original history + requires_hid = object_import_tracker.requires_hid + requires_hid_len = len(requires_hid) + if requires_hid_len > 0 and not self.sessionless: + base = history._next_hid(n=requires_hid_len) + for i, obj in enumerate(requires_hid): + obj.hid = base + i + self._flush() + + def _import_jobs(self, object_import_tracker, history): + object_key = self.object_key + # + # Create jobs. + # + jobs_attrs = self.jobs_properties() + + # Create each job. + for job_attrs in jobs_attrs: + imported_job = model.Job() + imported_job.user = self.user + imported_job.history = history + imported_job.imported = True + imported_job.tool_id = job_attrs['tool_id'] + imported_job.tool_version = job_attrs['tool_version'] + raw_state = job_attrs['state'] + if raw_state not in model.Job.terminal_states: + raw_state = model.Job.states.ERROR + imported_job.set_state(raw_state) + imported_job.info = job_attrs.get('info', None) + imported_job.exit_code = job_attrs.get('exit_code', None) + imported_job.traceback = job_attrs.get('traceback', None) + if 'stdout' in job_attrs: + # Pre 19.05 export. + imported_job.tool_stdout = job_attrs.get('stdout', None) + imported_job.tool_stderr = job_attrs.get('stderr', None) + else: + # Post 19.05 export with separated I/O + imported_job.tool_stdout = job_attrs.get('tool_stdout', None) + imported_job.job_stdout = job_attrs.get('job_stdout', None) + imported_job.tool_stderr = job_attrs.get('tool_stderr', None) + imported_job.job_stderr = job_attrs.get('job_stderr', None) + + imported_job.command_line = job_attrs.get('command_line', None) + try: + imported_job.create_time = datetime.datetime.strptime(job_attrs["create_time"], "%Y-%m-%dT%H:%M:%S.%f") + imported_job.update_time = datetime.datetime.strptime(job_attrs["update_time"], "%Y-%m-%dT%H:%M:%S.%f") + except Exception: + pass + self._session_add(imported_job) + self._flush() + + def _find_hda(input_key): + hda = None + if input_key in object_import_tracker.hdas_by_key: + hda = object_import_tracker.hdas_by_key[input_key] + if input_key in object_import_tracker.hda_copied_from_sinks: + hda = object_import_tracker.hdas_by_key[object_import_tracker.hda_copied_from_sinks[input_key]] + return hda + + def _find_hdca(input_key): + hdca = None + if input_key in object_import_tracker.hdcas_by_key: + hdca = object_import_tracker.hdcas_by_key[input_key] + if input_key in object_import_tracker.hdca_copied_from_sinks: + hdca = object_import_tracker.hdca_copied_from_sinks[input_key] + return hdca + + # Connect jobs to input and output datasets. + params = self._normalize_job_parameters(imported_job, job_attrs, _find_hda, _find_hdca) + for name, value in params.items(): + # Transform parameter values when necessary. + imported_job.add_parameter(name, dumps(value)) + + self._connect_job_io(imported_job, job_attrs, _find_hda, _find_hdca) + self._flush() + + if object_key in job_attrs: + object_import_tracker.jobs_by_key[job_attrs[object_key]] = imported_job + + def _import_implicit_collection_jobs(self, object_import_tracker): + implicit_collection_jobs_attrs = self.implicit_collection_jobs_properties() + for icj_attrs in implicit_collection_jobs_attrs: + icj = model.ImplicitCollectionJobs() + icj.populated_state = icj_attrs["populated_state"] + + icj.jobs = [] + for order_index, job in enumerate(icj_attrs["jobs"]): + icja = model.ImplicitCollectionJobsJobAssociation() + icja.implicit_collection_jobs = icj + icja.job = object_import_tracker.jobs_by_key[job] + icja.order_index = order_index + icj.jobs.append(icja) + self._session_add(icja) + + self._session_add(icj) + self._flush() + + def _session_add(self, obj): + self.sa_session.add(obj) + + def _flush(self): + self.sa_session.flush() + + +def _copied_from_object_key(copied_from_chain, objects_by_key): + if len(copied_from_chain) == 0: + return None + + # Okay this gets fun, we need the last thing in the chain to reconnect jobs + # from outside the history to inputs/outputs in this history but there may + # be cycles in the chain that lead outside the original history, so just eliminate + # all IDs not from this history except the last one. + filtered_copied_from_chain = [] + for i, copied_from_key in enumerate(copied_from_chain): + filter_id = (i != len(copied_from_chain) - 1) and (copied_from_key not in objects_by_key) + if not filter_id: + filtered_copied_from_chain.append(copied_from_key) + + copied_from_chain = filtered_copied_from_chain + if len(copied_from_chain) == 0: + return None + + copied_from_object_key = copied_from_chain[0] + return copied_from_object_key + + +class ObjectImportTracker(object): + """Keep track or new and existing imported objects. + + Needed to re-establish connections and such in multiple passes. + """ + + def __init__(self): + self.libraries_by_key = {} + self.hdas_by_key = {} + self.hdas_by_id = {} + self.hdcas_by_key = {} + self.hdcas_by_id = {} + self.lddas_by_key = {} + self.hda_copied_from_sinks = {} + self.hdca_copied_from_sinks = {} + self.jobs_by_key = {} + self.requires_hid = [] + + +def get_import_model_store_for_directory(archive_dir, **kwd): + assert os.path.isdir(archive_dir) + if os.path.exists(os.path.join(archive_dir, ATTRS_FILENAME_EXPORT)): + return DirectoryImportModelStoreLatest(archive_dir, **kwd) + else: + return DirectoryImportModelStore1901(archive_dir, **kwd) + + +class BaseDirectoryImportModelStore(ModelImportStore): + + def defines_new_history(self): + new_history_attributes = os.path.join(self.archive_dir, ATTRS_FILENAME_HISTORY) + return os.path.exists(new_history_attributes) + + def new_history_properties(self): + new_history_attributes = os.path.join(self.archive_dir, ATTRS_FILENAME_HISTORY) + history_properties = load(open(new_history_attributes)) + return history_properties + + def datasets_properties(self): + datasets_attrs_file_name = os.path.join(self.archive_dir, ATTRS_FILENAME_DATASETS) + datasets_attrs = load(open(datasets_attrs_file_name)) + provenance_file_name = datasets_attrs_file_name + ".provenance" + + if os.path.exists(provenance_file_name): + provenance_attrs = load(open(provenance_file_name)) + datasets_attrs += provenance_attrs + + return datasets_attrs + + def collections_properties(self): + collections_attrs_file_name = os.path.join(self.archive_dir, ATTRS_FILENAME_COLLECTIONS) + if os.path.exists(collections_attrs_file_name): + collections_attrs = load(open(collections_attrs_file_name)) + else: + collections_attrs = [] + return collections_attrs + + def library_properties(self): + libraries_attrs_file_name = os.path.join(self.archive_dir, ATTRS_FILENAME_LIBRARIES) + if os.path.exists(libraries_attrs_file_name): + libraries_attrs = load(open(libraries_attrs_file_name)) + else: + libraries_attrs = [] + return libraries_attrs + + def jobs_properties(self): + jobs_attr_file_name = os.path.join(self.archive_dir, ATTRS_FILENAME_JOBS) + jobs_attrs = load(open(jobs_attr_file_name)) + return jobs_attrs + + def implicit_collection_jobs_properties(self): + implicit_collection_jobs_attrs_file_name = os.path.join(self.archive_dir, ATTRS_FILENAME_IMPLICIT_COLLECTION_JOBS) + if os.path.exists(implicit_collection_jobs_attrs_file_name): + implicit_collection_jobs_attrs = load(open(implicit_collection_jobs_attrs_file_name)) + else: + implicit_collection_jobs_attrs = [] + return implicit_collection_jobs_attrs + + +class DirectoryImportModelStore1901(BaseDirectoryImportModelStore): + object_key = 'hid' + + def __init__(self, archive_dir, **kwd): + super(DirectoryImportModelStore1901, self).__init__(**kwd) + archive_dir = os.path.realpath(archive_dir) + + # Bioblend previous to 17.01 exported histories with an extra subdir. + if not os.path.exists(os.path.join(archive_dir, ATTRS_FILENAME_HISTORY)): + for d in os.listdir(archive_dir): + if os.path.isdir(os.path.join(archive_dir, d)): + archive_dir = os.path.join(archive_dir, d) + break + + self.archive_dir = archive_dir + + def _connect_job_io(self, imported_job, job_attrs, _find_hda, _find_hdca): + for output_key in job_attrs['output_datasets']: + output_hda = _find_hda(output_key) + if output_hda: + imported_job.add_output_dataset(output_hda.name, output_hda) + + if 'input_mapping' in job_attrs: + for input_name, input_key in job_attrs['input_mapping'].items(): + input_hda = _find_hda(input_key) + if input_hda: + imported_job.add_input_dataset(input_name, input_hda) + + def _normalize_job_parameters(self, imported_job, job_attrs, _find_hda, _find_hdca): + def remap_objects(p, k, obj): + if isinstance(obj, dict) and obj.get('__HistoryDatasetAssociation__', False): + imported_hda = _find_hda(obj[self.object_key]) + if imported_hda: + return (k, {"src": "hda", "id": imported_hda.id}) + return (k, obj) + + params = job_attrs['params'] + params = remap(params, remap_objects) + return params + + def trust_hid(self, obj_attrs): + # We didn't do object tracking so we pretty much have to trust the HID and accept + # that it will be wrong a lot. + return True + + +class DirectoryImportModelStoreLatest(BaseDirectoryImportModelStore): + object_key = 'encoded_id' + + def __init__(self, archive_dir, **kwd): + super(DirectoryImportModelStoreLatest, self).__init__(**kwd) + archive_dir = os.path.realpath(archive_dir) + self.archive_dir = archive_dir + if self.defines_new_history(): + self.import_history_encoded_id = self.new_history_properties().get("encoded_id") + else: + self.import_history_encoded_id = None + + def _connect_job_io(self, imported_job, job_attrs, _find_hda, _find_hdca): + + if 'input_dataset_mapping' in job_attrs: + for input_name, input_keys in job_attrs['input_dataset_mapping'].items(): + input_keys = input_keys or [] + for input_key in input_keys: + input_hda = _find_hda(input_key) + if input_hda: + imported_job.add_input_dataset(input_name, input_hda) + + if 'input_dataset_collection_mapping' in job_attrs: + for input_name, input_keys in job_attrs['input_dataset_collection_mapping'].items(): + input_keys = input_keys or [] + for input_key in input_keys: + input_hdca = _find_hdca(input_key) + if input_hdca: + imported_job.add_input_dataset_collection(input_name, input_hdca) + + if 'output_dataset_mapping' in job_attrs: + for output_name, output_keys in job_attrs['output_dataset_mapping'].items(): + output_keys = output_keys or [] + for output_key in output_keys: + output_hda = _find_hda(output_key) + if output_hda: + imported_job.add_output_dataset(output_name, output_hda) + + if 'output_dataset_collection_mapping' in job_attrs: + for output_name, output_keys in job_attrs['output_dataset_collection_mapping'].items(): + output_keys = output_keys or [] + for output_key in output_keys: + output_hdca = _find_hdca(output_key) + if output_hdca: + imported_job.add_output_dataset_collection(output_name, output_hdca) + + def trust_hid(self, obj_attrs): + return self.import_history_encoded_id and obj_attrs.get("history_encoded_id") == self.import_history_encoded_id + + def _normalize_job_parameters(self, imported_job, job_attrs, _find_hda, _find_hdca): + def remap_objects(p, k, obj): + if isinstance(obj, dict) and "src" in obj and obj["src"] in ["hda", "hdca"]: + if obj["src"] == "hda": + imported_hda = _find_hda(obj["id"]) + if imported_hda: + new_id = imported_hda.id + else: + new_id = None + elif obj["src"] == "hdca": + imported_hdca = _find_hdca(obj["id"]) + if imported_hdca: + new_id = imported_hdca.id + else: + new_id = None + else: + raise NotImplementedError() + new_obj = obj.copy() + new_obj["id"] = new_id + return (k, new_obj) + + return (k, obj) + + params = job_attrs['params'] + params = remap(params, remap_objects) + return params + + +class BagArchiveImportModelStore(DirectoryImportModelStoreLatest): + + def __init__(self, bag_archive, **kwd): + archive_dir = tempfile.mkdtemp() + bdb.extract_bag(bag_archive, output_path=archive_dir) + # Why this line though...? + archive_dir = os.path.join(archive_dir, os.listdir(archive_dir)[0]) + bdb.revert_bag(archive_dir) + super(BagArchiveImportModelStore, self).__init__(archive_dir, **kwd) + + +@six.add_metaclass(abc.ABCMeta) +class ModelExportStore(object): + + @abc.abstractmethod + def export_history(self, history, include_hidden=False, include_deleted=False): + """Export history to store.""" + + @abc.abstractmethod + def add_dataset_collection(self, collection): + """Add HDCA to export store.""" + + @abc.abstractmethod + def add_dataset(self, dataset, include_files=True): + """Add HDA to export store.""" + + @abc.abstractmethod + def __enter__(self): + """Export store should be used as context manager.""" + + @abc.abstractmethod + def __exit__(self): + """Export store should be used as context manager.""" + + +class DirectoryModelExportStore(ModelExportStore): + + def __init__(self, export_directory, app=None, for_edit=False, serialize_dataset_objects=None, export_files=None): + if not os.path.exists(export_directory): + os.makedirs(export_directory) + + if app is not None: + self.app = app + self.security = app.security + else: + self.security = IdEncodingHelper(id_secret="randomdoesntmatter") + + self.export_directory = export_directory + self.serialization_options = model.SerializationOptions( + for_edit=for_edit, + serialize_dataset_objects=serialize_dataset_objects, + serialize_files_handler=self, + ) + self.export_files = export_files + self.included_datasets = {} + self.included_collections = [] + self.included_libraries = [] + self.included_library_folders = [] + self.collection_datasets = {} + self.collections_attrs = [] + self.dataset_id_to_path = {} + + def serialize_files(self, dataset, as_dict): + if self.export_files is None: + return None + elif self.export_files == "symlink": + add = os.symlink + elif self.export_files == "copy": + def add(src, dest): + if os.path.isdir(src): + shutil.copytree(src, dest) + else: + shutil.copyfile(src, dest) + + export_directory = self.export_directory + if dataset.id in self.included_datasets: + _, include_files = self.included_datasets[dataset.id] + if not include_files: + return + + file_name, extra_files_path = None, None + try: + _file_name = dataset.file_name + if os.path.exists(_file_name): + file_name = _file_name + except ObjectNotFound: + pass + + if dataset.extra_files_path_exists(): + extra_files_path = dataset.extra_files_path + else: + pass + + dir_name = 'datasets' + dir_path = os.path.join(export_directory, dir_name) + dataset_hid = as_dict['hid'] + assert dataset_hid, as_dict + + if dataset.dataset.id in self.dataset_id_to_path: + file_name, extra_files_path = self.dataset_id_to_path[dataset.dataset.id] + if file_name is not None: + as_dict['file_name'] = file_name + if extra_files_path is not None: + as_dict['extra_files_path'] = extra_files_path + return + + if file_name: + if not os.path.exists(dir_path): + os.makedirs(dir_path) + + target_filename = get_export_dataset_filename(as_dict['name'], as_dict['extension'], dataset_hid) + arcname = os.path.join(dir_name, target_filename) + + src = file_name + dest = os.path.join(export_directory, arcname) + add(src, dest) + as_dict['file_name'] = arcname + + if extra_files_path: + try: + file_list = os.listdir(extra_files_path) + except OSError: + file_list = [] + + if len(file_list): + arcname = os.path.join(dir_name, 'extra_files_path_%s' % dataset_hid) + add(extra_files_path, os.path.join(export_directory, arcname)) + as_dict['extra_files_path'] = arcname + else: + as_dict['extra_files_path'] = '' + + self.dataset_id_to_path[dataset.dataset.id] = (as_dict.get("file_name"), as_dict.get("extra_files_path")) + + def exported_key(self, obj): + return self.security.encode_id(obj.id, kind='model_export') + + def __enter__(self): + return self + + def export_history(self, history, include_hidden=False, include_deleted=False): + app = self.app + export_directory = self.export_directory + + history_attrs = history.serialize(app.security, self.serialization_options) + history_attrs_filename = os.path.join(export_directory, ATTRS_FILENAME_HISTORY) + with open(history_attrs_filename, 'w') as history_attrs_out: + dump(history_attrs, history_attrs_out) + + sa_session = app.model.context.current + + # Write collections' attributes (including datasets list) to file. + query = (sa_session.query(model.HistoryDatasetCollectionAssociation) + .filter(model.HistoryDatasetCollectionAssociation.history == history) + .filter(model.HistoryDatasetCollectionAssociation.deleted == expression.false())) + collections = query.all() + + for collection in collections: + # filter this ? + if not collection.populated: + break + if collection.state != 'ok': + break + + self.add_dataset_collection(collection) + + # export jobs for these datasets + for collection_dataset in collection.dataset_instances: + if collection_dataset.deleted and not include_deleted: + include_files = False + else: + include_files = True + + self.add_dataset(collection_dataset, include_files=include_files) + self.collection_datasets[collection_dataset.id] = True + + # Write datasets' attributes to file. + query = (sa_session.query(model.HistoryDatasetAssociation) + .filter(model.HistoryDatasetAssociation.history == history) + .join("dataset") + .options(eagerload_all("dataset.actions")) + .order_by(model.HistoryDatasetAssociation.hid) + .filter(model.Dataset.purged == expression.false())) + datasets = query.all() + for dataset in datasets: + dataset.annotation = get_item_annotation_str(sa_session, history.user, dataset) + add_dataset = (not dataset.visible or not include_hidden) and (not dataset.deleted or include_deleted) + if dataset.id in self.collection_datasets: + add_dataset = True + + if dataset.id not in self.included_datasets: + self.add_dataset(dataset, include_files=add_dataset) + + def export_library(self, library, include_hidden=False, include_deleted=False): + self.included_libraries.append(library) + self.included_library_folders.append(library.root_folder) + + def collect_datasets(library_folder): + for library_dataset in library_folder.datasets: + ldda = library_dataset.library_dataset_dataset_association + add_dataset = (not ldda.visible or not include_hidden) and (not ldda.deleted or include_deleted) + # TODO: competing IDs here between ldda and hdas - fix this! + self.included_datasets[ldda.id] = (ldda, add_dataset) + for folder in library_folder.folders: + collect_datasets(folder) + + collect_datasets(library.root_folder) + + def add_dataset_collection(self, collection): + self.collections_attrs.append(collection) + self.included_collections.append(collection) + + def add_dataset(self, dataset, include_files=True): + self.included_datasets[dataset.id] = (dataset, include_files) + + def _finalize(self): + export_directory = self.export_directory + + datasets_attrs = [] + provenance_attrs = [] + for dataset_id, (dataset, include_files) in self.included_datasets.items(): + if include_files: + datasets_attrs.append(dataset) + else: + provenance_attrs.append(dataset) + + datasets_attrs_filename = os.path.join(export_directory, ATTRS_FILENAME_DATASETS) + with open(datasets_attrs_filename, 'w') as datasets_attrs_out: + dump(list(map(lambda d: d.serialize(self.security, self.serialization_options), datasets_attrs)), datasets_attrs_out) + + with open(datasets_attrs_filename + ".provenance", 'w') as provenance_attrs_out: + dump(list(map(lambda d: d.serialize(self.security, self.serialization_options), provenance_attrs)), provenance_attrs_out) + + libraries_attrs_filename = os.path.join(export_directory, ATTRS_FILENAME_LIBRARIES) + with open(libraries_attrs_filename, 'w') as librariess_attrs_out: + dump(list(map(lambda d: d.serialize(self.security, self.serialization_options), self.included_libraries)), librariess_attrs_out) + + collections_attrs_filename = os.path.join(export_directory, ATTRS_FILENAME_COLLECTIONS) + with open(collections_attrs_filename, 'w') as collections_attrs_out: + dump(list(map(lambda d: d.serialize(self.security, self.serialization_options), self.collections_attrs)), collections_attrs_out) + + # + # Write jobs attributes file. + # + + # Get all jobs associated with included HDAs. + jobs_dict = {} + implicit_collection_jobs_dict = {} + + def record_associated_jobs(obj): + # Get the job object. + job = None + for assoc in obj.creating_job_associations: + job = assoc.job + break + if not job: + # No viable job. + return + + jobs_dict[job.id] = job + icja = job.implicit_collection_jobs_association + if icja: + implicit_collection_jobs = icja.implicit_collection_jobs + implicit_collection_jobs_dict[implicit_collection_jobs.id] = implicit_collection_jobs + + for hda_id, (hda, include_files) in self.included_datasets.items(): + # Get the associated job, if any. If this hda was copied from another, + # we need to find the job that created the origial hda + job_hda = hda + while job_hda.copied_from_history_dataset_association: # should this check library datasets as well? + job_hda = job_hda.copied_from_history_dataset_association + if not job_hda.creating_job_associations: + # No viable HDA found. + continue + + record_associated_jobs(job_hda) + + for hdca in self.included_collections: + record_associated_jobs(hdca) + + # Get jobs' attributes. + jobs_attrs = [] + for id, job in jobs_dict.items(): + # Don't attempt to serialize jobs for editing... yet at least. + if self.serialization_options.for_edit: + continue + + job_attrs = job.serialize(self.security, self.serialization_options) + + # -- Get input, output datasets. -- + + input_dataset_mapping = {} + output_dataset_mapping = {} + input_dataset_collection_mapping = {} + output_dataset_collection_mapping = {} + implicit_output_dataset_collection_mapping = {} + + for assoc in job.input_datasets: + # Optional data inputs will not have a dataset. + if assoc.dataset: + name = assoc.name + if name not in input_dataset_mapping: + input_dataset_mapping[name] = [] + + input_dataset_mapping[name].append(self.exported_key(assoc.dataset)) + + for assoc in job.output_datasets: + # Optional data inputs will not have a dataset. + if assoc.dataset: + name = assoc.name + if name not in output_dataset_mapping: + output_dataset_mapping[name] = [] + + output_dataset_mapping[name].append(self.exported_key(assoc.dataset)) + + for assoc in job.input_dataset_collections: + # Optional data inputs will not have a dataset. + if assoc.dataset_collection: + name = assoc.name + if name not in input_dataset_collection_mapping: + input_dataset_collection_mapping[name] = [] + + input_dataset_collection_mapping[name].append(self.exported_key(assoc.dataset_collection)) + + for assoc in job.output_dataset_collection_instances: + # Optional data outputs will not have a dataset. + if assoc.dataset_collection_instance: + name = assoc.name + if name not in output_dataset_collection_mapping: + output_dataset_collection_mapping[name] = [] + + output_dataset_collection_mapping[name].append(self.exported_key(assoc.dataset_collection_instance)) + + for assoc in job.output_dataset_collections: + if assoc.dataset_collection: + name = assoc.name + + if name not in implicit_output_dataset_collection_mapping: + implicit_output_dataset_collection_mapping[name] = [] + + implicit_output_dataset_collection_mapping[name].append(self.exported_key(assoc.dataset_collection)) + + job_attrs['input_dataset_mapping'] = input_dataset_mapping + job_attrs['input_dataset_collection_mapping'] = input_dataset_collection_mapping + job_attrs['output_dataset_mapping'] = output_dataset_mapping + job_attrs['output_dataset_collection_mapping'] = output_dataset_collection_mapping + job_attrs['implicit_output_dataset_collection_mapping'] = implicit_output_dataset_collection_mapping + + jobs_attrs.append(job_attrs) + + icjs_attrs = [] + for icj_id, icj in implicit_collection_jobs_dict.items(): + icj_attrs = icj.serialize(self.security, self.serialization_options) + icjs_attrs.append(icj_attrs) + + jobs_attrs_filename = os.path.join(export_directory, ATTRS_FILENAME_JOBS) + with open(jobs_attrs_filename, 'w') as jobs_attrs_out: + dump(jobs_attrs, jobs_attrs_out) + + icjs_attrs_filename = os.path.join(export_directory, ATTRS_FILENAME_IMPLICIT_COLLECTION_JOBS) + with open(icjs_attrs_filename, 'w') as icjs_attrs_out: + dump(icjs_attrs, icjs_attrs_out) + + export_attrs_filename = os.path.join(export_directory, ATTRS_FILENAME_EXPORT) + with open(export_attrs_filename, 'w') as export_attrs_out: + dump({"galaxy_version": VERSION_MAJOR}, export_attrs_out) + + def __exit__(self, exc_type, exc_val, exc_tb): + if exc_type is None: + self._finalize() + # http://effbot.org/zone/python-with-statement.htm + return isinstance(exc_val, TypeError) + + +class TarModelExportStore(DirectoryModelExportStore): + + def __init__(self, out_file, gzip=True, **kwds): + self.gzip = gzip + self.out_file = out_file + temp_output_dir = tempfile.mkdtemp() + super(TarModelExportStore, self).__init__(temp_output_dir, **kwds) + + def _finalize(self): + super(TarModelExportStore, self)._finalize() + tar_export_directory(self.export_directory, self.out_file, self.gzip) + shutil.rmtree(self.export_directory) + + +class BagDirectoryModelExportStore(DirectoryModelExportStore): + + def __init__(self, out_directory, **kwds): + self.out_directory = out_directory + super(BagDirectoryModelExportStore, self).__init__(out_directory, **kwds) + + def _finalize(self): + super(BagDirectoryModelExportStore, self)._finalize() + bdb.make_bag(self.out_directory) + + +class BagArchiveModelExportStore(BagDirectoryModelExportStore): + + def __init__(self, out_file, bag_archiver="tgz", **kwds): + # bag_archiver in tgz, zip, tar + self.bag_archiver = bag_archiver + self.out_file = out_file + temp_output_dir = tempfile.mkdtemp() + super(BagArchiveModelExportStore, self).__init__(temp_output_dir, **kwds) + + def _finalize(self): + super(BagArchiveModelExportStore, self)._finalize() + rval = bdb.archive_bag(self.export_directory, self.bag_archiver) + shutil.move(rval, self.out_file) + shutil.rmtree(self.export_directory) + + +def tar_export_directory(export_directory, out_file, gzip): + tarfile_mode = "w" + if gzip: + tarfile_mode += ":gz" + + with tarfile.open(out_file, tarfile_mode, dereference=True) as history_archive: + for export_path in os.listdir(export_directory): + history_archive.add(os.path.join(export_directory, export_path), arcname=export_path) + + +def get_export_dataset_filename(name, ext, hid): + """ + Builds a filename for a dataset using its name an extension. + """ + base = ''.join(c in FILENAME_VALID_CHARS and c or '_' for c in name) + return base + "_%s.%s" % (hid, ext) diff --git a/lib/galaxy/managers/tags.py b/lib/galaxy/model/tags.py similarity index 98% rename from lib/galaxy/managers/tags.py rename to lib/galaxy/model/tags.py index 36b58bfe16e..6192b25ba53 100644 --- a/lib/galaxy/managers/tags.py +++ b/lib/galaxy/model/tags.py @@ -22,7 +22,7 @@ class ItemTagAssocInfo(object): self.item_id_col = item_id_col -class TagManager(object): +class TagHandler(object): """ Manages CRUD operations related to tagging objects. """ @@ -50,7 +50,7 @@ class TagManager(object): def remove_tags_from_list(self, user, item, tag_to_remove_list): tag_to_remove_set = set(tag_to_remove_list) - tags_set = set(self.get_tags_str(item.tags).split(',')) + tags_set = set([_.strip() for _ in self.get_tags_str(item.tags).split(',')]) if item.tags: tags_set -= tag_to_remove_set return self.set_tags_from_list(user, item, tags_set) @@ -319,10 +319,10 @@ class TagManager(object): return name_value_pair -class GalaxyTagManager(TagManager): +class GalaxyTagHandler(TagHandler): def __init__(self, sa_session): from galaxy import model - TagManager.__init__(self, sa_session) + TagHandler.__init__(self, sa_session) self.item_tag_assoc_info["History"] = ItemTagAssocInfo(model.History, model.HistoryTagAssociation, model.HistoryTagAssociation.table.c.history_id) @@ -349,6 +349,6 @@ class GalaxyTagManager(TagManager): model.VisualizationTagAssociation.table.c.visualization_id) -class CommunityTagManager(TagManager): +class CommunityTagHandler(TagHandler): def __init__(self, sa_session): - TagManager.__init__(self, sa_session) + TagHandler.__init__(self, sa_session) diff --git a/lib/galaxy/model/tool_shed_install/__init__.py b/lib/galaxy/model/tool_shed_install/__init__.py index 054ffcb9564..4aef91b43b7 100644 --- a/lib/galaxy/model/tool_shed_install/__init__.py +++ b/lib/galaxy/model/tool_shed_install/__init__.py @@ -1,8 +1,6 @@ import logging import os -from six.moves.urllib.parse import urljoin - from galaxy.util import asbool from galaxy.util.bunch import Bunch from galaxy.util.dictifiable import Dictifiable @@ -79,13 +77,7 @@ class ToolShedRepository(object): return self.deleted def get_sharable_url(self, app): - tool_shed_url = common_util.get_tool_shed_url_from_tool_shed_registry(app, self.tool_shed) - if tool_shed_url: - # Append a slash to the tool shed URL, because urlparse.urljoin will eliminate - # the last part of a URL if it does not end with a forward slash. - tool_shed_url = '%s/' % tool_shed_url - return urljoin(tool_shed_url, 'view/%s/%s' % (self.owner, self.name)) - return tool_shed_url + return common_util.get_tool_shed_repository_url(app, self.tool_shed, self.owner, self.name) def get_shed_config_filename(self): shed_config_filename = None diff --git a/lib/galaxy/objectstore/__init__.py b/lib/galaxy/objectstore/__init__.py index 0bed99b2878..b73a4a8ad56 100644 --- a/lib/galaxy/objectstore/__init__.py +++ b/lib/galaxy/objectstore/__init__.py @@ -237,7 +237,12 @@ class ObjectStore(object): } def _get_object_id(self, obj): - return getattr(obj, self.store_by) + if hasattr(obj, self.store_by): + return getattr(obj, self.store_by) + else: + # job's don't have uuids, so always use ID in this case when creating + # job working directories. + return obj.id class DiskObjectStore(ObjectStore): @@ -354,6 +359,7 @@ class DiskObjectStore(ObjectStore): if alt_name and not safe_relpath(alt_name): log.warning('alt_name would locate path outside dir: %s', alt_name) raise ObjectInvalid("The requested object is invalid") + obj_id = self._get_object_id(obj) if old_style: if extra_dir is not None: path = os.path.join(base, extra_dir) @@ -361,7 +367,6 @@ class DiskObjectStore(ObjectStore): path = base else: # Construct hashed path - obj_id = self._get_object_id(obj) rel_path = os.path.join(*directory_hash_id(obj_id)) # Create a subdirectory for the object ID if obj_dir: @@ -374,6 +379,7 @@ class DiskObjectStore(ObjectStore): rel_path = os.path.join(rel_path, extra_dir) path = os.path.join(base, rel_path) if not dir_only: + assert obj_id is not None, "The effective dataset identifier consumed by object store [%s] must be set before a path can be constructed." % (self.store_by) path = os.path.join(path, alt_name if alt_name else "dataset_%s.dat" % obj_id) return os.path.abspath(path) diff --git a/lib/galaxy/objectstore/cloud.py b/lib/galaxy/objectstore/cloud.py index 3c8a3a15957..aaa9811641d 100644 --- a/lib/galaxy/objectstore/cloud.py +++ b/lib/galaxy/objectstore/cloud.py @@ -5,6 +5,7 @@ Object Store plugin for Cloud storage. import logging import multiprocessing import os +import os.path import shutil import subprocess import threading @@ -18,11 +19,11 @@ from galaxy.util import ( umask_fix_perms, ) from galaxy.util.sleeper import Sleeper -from .s3 import CloudConfigMixin, parse_config_xml +from .s3 import parse_config_xml from ..objectstore import convert_bytes, ObjectStore try: - from cloudbridge.cloud.factory import CloudProviderFactory, ProviderList - from cloudbridge.cloud.interfaces.exceptions import InvalidNameException + from cloudbridge.factory import CloudProviderFactory, ProviderList + from cloudbridge.interfaces.exceptions import InvalidNameException except ImportError: CloudProviderFactory = None ProviderList = None @@ -35,6 +36,30 @@ NO_CLOUDBRIDGE_ERROR_MESSAGE = ( ) +class CloudConfigMixin(object): + + def _config_to_dict(self): + return { + "provider": self.provider, + "auth": self.credentials, + "bucket": { + "name": self.bucket_name, + "use_reduced_redundancy": self.use_rr, + }, + "connection": { + "host": self.host, + "port": self.port, + "multipart": self.multipart, + "is_secure": self.is_secure, + "conn_path": self.conn_path, + }, + "cache": { + "size": self.cache_size, + "path": self.staging_path, + } + } + + class Cloud(ObjectStore, CloudConfigMixin): """ Object store that stores objects as items in an cloud storage. A local @@ -47,15 +72,13 @@ class Cloud(ObjectStore, CloudConfigMixin): super(Cloud, self).__init__(config, config_dict) self.transfer_progress = 0 - auth_dict = config_dict['auth'] bucket_dict = config_dict['bucket'] connection_dict = config_dict.get('connection', {}) cache_dict = config_dict['cache'] - self.access_key = auth_dict.get('access_key') - self.secret_key = auth_dict.get('secret_key') - - self.bucket = bucket_dict.get('name') + self.provider = config_dict["provider"] + self.credentials = config_dict["auth"] + self.bucket_name = bucket_dict.get('name') self.use_rr = bucket_dict.get('use_reduced_redundancy', False) self.max_chunk_size = bucket_dict.get('max_chunk_size', 250) @@ -74,8 +97,8 @@ class Cloud(ObjectStore, CloudConfigMixin): if CloudProviderFactory is None: raise Exception(NO_CLOUDBRIDGE_ERROR_MESSAGE) - self._configure_connection() - self.bucket = self._get_bucket(self.bucket) + self.conn = self._get_connection(self.provider, self.credentials) + self.bucket = self._get_bucket(self.bucket_name) # Clean cache only if value is set in galaxy.ini if self.cache_size != -1: # Convert GBs to bytes for comparison @@ -92,15 +115,128 @@ class Cloud(ObjectStore, CloudConfigMixin): except OSError: self.use_axel = False - def _configure_connection(self): - log.debug("Configuring AWS-S3 Connection") - aws_config = {'aws_access_key': self.access_key, - 'aws_secret_key': self.secret_key} - self.conn = CloudProviderFactory().create_provider(ProviderList.AWS, aws_config) + @staticmethod + def _get_connection(provider, credentials): + log.debug("Configuring `{}` Connection".format(provider)) + if provider == "aws": + config = {"aws_access_key": credentials["access_key"], + "aws_secret_key": credentials["secret_key"]} + connection = CloudProviderFactory().create_provider(ProviderList.AWS, config) + elif provider == "azure": + config = {"azure_subscription_id": credentials["subscription_id"], + "azure_client_id": credentials["client_id"], + "azure_secret": credentials["secret"], + "azure_tenant": credentials["tenant"]} + connection = CloudProviderFactory().create_provider(ProviderList.AZURE, config) + elif provider == "google": + config = {"gcp_service_creds_file": credentials["credentials_file"]} + connection = CloudProviderFactory().create_provider(ProviderList.GCP, config) + else: + raise Exception("Unsupported provider `{}`.".format(provider)) + + # Ideally it would be better to assert if the connection is + # authorized to perform operations required by ObjectStore + # before returning it (and initializing ObjectStore); hence + # any related issues can be handled properly here, and ObjectStore + # can "trust" the connection is established. + # + # However, the mechanism implemented in Cloudbridge to assert if + # a user/service is authorized to perform an operation, assumes + # the user/service is granted with an elevated privileges, such + # as admin/owner-level access to all resources. For a detailed + # discussion see: + # + # https://github.com/CloudVE/cloudbridge/issues/135 + # + # Hence, if a resource owner wants to only authorize Galaxy to r/w + # a bucket/container on the provider, but does not allow it to access + # other resources, Cloudbridge may fail asserting credentials. + # For instance, to r/w an Amazon S3 bucket, the resource owner + # also needs to authorize full access to Amazon EC2, because Cloudbridge + # leverages EC2-specific functions to assert the credentials. + # + # Therefore, to adhere with principle of least privilege, we do not + # assert credentials; instead, we handle exceptions raised as a + # result of signing API calls to cloud provider (e.g., GCP) using + # incorrect, invalid, or unauthorized credentials. + + return connection @classmethod def parse_xml(clazz, config_xml): - return parse_config_xml(config_xml) + # The following reads common cloud-based storage configuration + # as implemented for the S3 backend. Hence, it also attempts to + # parse S3-specific configuration (e.g., credentials); however, + # such provider-specific configuration is overwritten in the + # following. + config = parse_config_xml(config_xml) + + try: + provider = config_xml.attrib.get("provider") + if provider is None: + msg = "Missing `provider` attribute from the Cloud backend of the ObjectStore." + log.error(msg) + raise Exception(msg) + provider = provider.lower() + config["provider"] = provider + + # Read any provider-specific configuration. + auth_element = config_xml.findall("auth")[0] + missing_config = [] + if provider == "aws": + akey = auth_element.get("access_key") + if akey is None: + missing_config.append("access_key") + skey = auth_element.get("secret_key") + if skey is None: + missing_config.append("secret_key") + + config["auth"] = { + "access_key": akey, + "secret_key": skey} + elif provider == "azure": + sid = auth_element.get("subscription_id") + if sid is None: + missing_config.append("subscription_id") + cid = auth_element.get("client_id") + if cid is None: + missing_config.append("client_id") + sec = auth_element.get("secret") + if sec is None: + missing_config.append("secret") + ten = auth_element.get("tenant") + if ten is None: + missing_config.append("tenant") + config["auth"] = { + "subscription_id": sid, + "client_id": cid, + "secret": sec, + "tenant": ten} + elif provider == "google": + cre = auth_element.get("credentials_file") + if not os.path.isfile(cre): + msg = "The following file specified for GCP credentials not found: {}".format(cre) + log.error(msg) + raise IOError(msg) + if cre is None: + missing_config.append("credentials_file") + config["auth"] = { + "credentials_file": cre} + else: + msg = "Unsupported provider `{}`.".format(provider) + log.error(msg) + raise Exception(msg) + + if len(missing_config) > 0: + msg = "The following configuration required for {} cloud backend " \ + "are missing: {}".format(provider, missing_config) + log.error(msg) + raise Exception(msg) + else: + return config + except Exception: + log.exception("Malformed ObjectStore Configuration XML -- unable to continue") + raise def to_dict(self): as_dict = super(Cloud, self).to_dict() diff --git a/lib/galaxy/openid/__init__.py b/lib/galaxy/openid/__init__.py new file mode 100644 index 00000000000..d196c842445 --- /dev/null +++ b/lib/galaxy/openid/__init__.py @@ -0,0 +1,3 @@ +""" +OpenID functionality +""" diff --git a/openid/genomespace.xml b/lib/galaxy/openid/genomespace.xml similarity index 100% rename from openid/genomespace.xml rename to lib/galaxy/openid/genomespace.xml diff --git a/lib/galaxy/openid/openid_conf.xml b/lib/galaxy/openid/openid_conf.xml new file mode 100644 index 00000000000..7a1e3dd9909 --- /dev/null +++ b/lib/galaxy/openid/openid_conf.xml @@ -0,0 +1,4 @@ + + + + diff --git a/lib/galaxy/openid/openid_manager.py b/lib/galaxy/openid/openid_manager.py new file mode 100644 index 00000000000..03a2a0f55f0 --- /dev/null +++ b/lib/galaxy/openid/openid_manager.py @@ -0,0 +1,75 @@ +""" +Manage the OpenID consumer and related data stores. +""" + +import logging +import os +import pickle + +try: + from openid import oidutil + from openid.consumer import consumer + from openid.extensions import sreg + from openid.store import filestore +except ImportError: + oidutil = None + + class FakeConsumer(object): + def __getattr__(x, y): + return None + consumer = FakeConsumer() + + +OPENID_IMPORT_MESSAGE = ('The Python openid package is required to use this ' + 'feature, please install it') + +log = logging.getLogger(__name__) + + +def oidlog(message, level=0): + log.debug(message) + + +if oidutil is not None: + oidutil.log = oidlog + + +class OpenIDManager(object): + def __init__(self, cache_path): + assert oidutil is not None, OPENID_IMPORT_MESSAGE + self.session_path = os.path.join(cache_path, 'session') + self.store_path = os.path.join(cache_path, 'store') + for dir in self.session_path, self.store_path: + if not os.path.exists(dir): + os.makedirs(dir) + self.store = filestore.FileOpenIDStore(self.store_path) + + def get_session(self, trans): + session_file = os.path.join(self.session_path, str(trans.galaxy_session.id)) + if not os.path.exists(session_file): + pickle.dump(dict(), open(session_file, 'w')) + return pickle.load(open(session_file)) + + def persist_session(self, trans, oidconsumer): + session_file = os.path.join(self.session_path, str(trans.galaxy_session.id)) + pickle.dump(oidconsumer.session, open(session_file, 'w')) + + def get_consumer(self, trans): + return consumer.Consumer(self.get_session(trans), self.store) + + def add_sreg(self, trans, request, required=None, optional=None): + if required is None: + required = [] + if optional is None: + optional = [] + sreg_request = sreg.SRegRequest(required=required, optional=optional) + request.addExtension(sreg_request) + + def get_sreg(self, info): + return sreg.SRegResponse.fromSuccessResponse(info) + + # so I don't have to expose all of openid.consumer.consumer + FAILURE = consumer.FAILURE + SUCCESS = consumer.SUCCESS + CANCEL = consumer.CANCEL + SETUP_NEEDED = consumer.SETUP_NEEDED diff --git a/lib/galaxy/openid/providers.py b/lib/galaxy/openid/providers.py new file mode 100644 index 00000000000..1cd17795d3e --- /dev/null +++ b/lib/galaxy/openid/providers.py @@ -0,0 +1,147 @@ +""" +Contains OpenID provider functionality +""" +import logging +import os + +import six + +from galaxy.util import parse_xml, string_as_bool +from galaxy.util.odict import odict + + +log = logging.getLogger(__name__) + +NO_PROVIDER_ID = 'None' +RESERVED_PROVIDER_IDS = [NO_PROVIDER_ID] + + +class OpenIDProvider(object): + '''An OpenID Provider object.''' + @classmethod + def from_file(cls, filename): + return cls.from_elem(parse_xml(filename).getroot()) + + @classmethod + def from_elem(cls, xml_root): + provider_elem = xml_root + provider_id = provider_elem.get('id', None) + provider_name = provider_elem.get('name', provider_id) + op_endpoint_url = provider_elem.find('op_endpoint_url') + if op_endpoint_url is not None: + op_endpoint_url = op_endpoint_url.text + never_associate_with_user = string_as_bool(provider_elem.get('never_associate_with_user', 'False')) + assert (provider_id and provider_name and op_endpoint_url), Exception("OpenID Provider improperly configured") + assert provider_id not in RESERVED_PROVIDER_IDS, Exception('Specified OpenID Provider uses a reserved id: %s' % (provider_id)) + sreg_required = [] + sreg_optional = [] + use_for = {} + store_user_preference = {} + use_default_sreg = True + for elem in provider_elem.findall('sreg'): + use_default_sreg = False + for field_elem in elem.findall('field'): + sreg_name = field_elem.get('name') + assert sreg_name, Exception('A name is required for a sreg element') + if string_as_bool(field_elem.get('required')): + sreg_required.append(sreg_name) + else: + sreg_optional.append(sreg_name) + for use_elem in field_elem.findall('use_for'): + use_for[use_elem.get('name')] = sreg_name + for store_user_preference_elem in field_elem.findall('store_user_preference'): + store_user_preference[store_user_preference_elem.get('name')] = sreg_name + if use_default_sreg: + sreg_required = None + sreg_optional = None + use_for = None + return cls(provider_id, provider_name, op_endpoint_url, sreg_required=sreg_required, sreg_optional=sreg_optional, use_for=use_for, store_user_preference=store_user_preference, never_associate_with_user=never_associate_with_user) + + def __init__(self, id, name, op_endpoint_url, sreg_required=None, sreg_optional=None, use_for=None, store_user_preference=None, never_associate_with_user=None): + '''When sreg options are not specified, defaults are used.''' + self.id = id + self.name = name + self.op_endpoint_url = op_endpoint_url + if sreg_optional is None: + self.sreg_optional = ['nickname', 'email'] + else: + self.sreg_optional = sreg_optional + if sreg_required: + self.sreg_required = sreg_required + else: + self.sreg_required = [] + if use_for is not None: + self.use_for = use_for + else: + self.use_for = {} + if 'nickname' in (self.sreg_optional + self.sreg_required): + self.use_for['username'] = 'nickname' + if 'email' in (self.sreg_optional + self.sreg_required): + self.use_for['email'] = 'email' + if store_user_preference: + self.store_user_preference = store_user_preference + else: + self.store_user_preference = {} + if never_associate_with_user: + self.never_associate_with_user = True + else: + self.never_associate_with_user = False + + def post_authentication(self, trans, openid_manager, info): + sreg_attributes = openid_manager.get_sreg(info) + for store_pref_name, store_pref_value_name in self.store_user_preference.items(): + if store_pref_value_name in (self.sreg_optional + self.sreg_required): + trans.user.preferences[store_pref_name] = sreg_attributes.get(store_pref_value_name) + else: + raise Exception('Only sreg is currently supported.') + trans.sa_session.add(trans.user) + trans.sa_session.flush() + + def has_post_authentication_actions(self): + return bool(self.store_user_preference) + + +class OpenIDProviders(object): + '''Collection of OpenID Providers''' + NO_PROVIDER_ID = NO_PROVIDER_ID + + @classmethod + def from_file(cls, filename): + try: + return cls.from_elem(parse_xml(filename).getroot()) + except Exception as e: + log.error('Failed to load OpenID Providers: %s' % (e)) + return cls() + + @classmethod + def from_elem(cls, xml_root): + oid_elem = xml_root + providers = odict() + for elem in oid_elem.findall('provider'): + try: + provider = OpenIDProvider.from_file(os.path.join('lib/galaxy/openid', elem.get('file'))) + providers[provider.id] = provider + log.debug('Loaded OpenID provider: %s (%s)' % (provider.name, provider.id)) + except Exception as e: + log.error('Failed to add OpenID provider: %s' % (e)) + return cls(providers) + + def __init__(self, providers=None): + if providers: + self.providers = providers + else: + self.providers = odict() + self._banned_identifiers = [provider.op_endpoint_url for provider in self.providers.values() if provider.never_associate_with_user] + + def __iter__(self): + for provider in six.itervalues(self.providers): + yield provider + + def get(self, name, default=None): + if name in self.providers: + return self.providers[name] + else: + return default + + def new_provider_from_identifier(self, identifier): + return OpenIDProvider(None, identifier, identifier, never_associate_with_user=identifier in self._banned_identifiers) diff --git a/lib/galaxy/queue_worker.py b/lib/galaxy/queue_worker.py index 30fe2160c9d..9760a30d8cb 100644 --- a/lib/galaxy/queue_worker.py +++ b/lib/galaxy/queue_worker.py @@ -5,12 +5,17 @@ reloading the toolbox, etc., across multiple processes. import importlib import logging +import socket import sys import threading from inspect import ismodule -from kombu import Connection -from kombu.mixins import ConsumerMixin +from kombu import ( + Consumer, + Queue, + uuid, +) +from kombu.mixins import ConsumerProducerMixin from kombu.pools import producers from six.moves import reload_module @@ -29,39 +34,92 @@ def send_local_control_task(app, task, kwargs={}): log.info("Queuing async task %s for %s." % (task, app.config.server_name)) payload = {'task': task, 'kwargs': kwargs} - try: - c = Connection(app.config.amqp_internal_connection) - with producers[c].acquire(block=True) as producer: - producer.publish(payload, - exchange=galaxy.queues.galaxy_exchange, - declare=[galaxy.queues.galaxy_exchange] + [galaxy.queues.control_queue_from_config(app.config)], - routing_key='control.%s' % app.config.server_name) - except Exception: - log.exception("Error queueing async task: %s.", payload) + routing_key = 'control.%s' % app.config.server_name + control_task = ControlTask(app.control_worker) + control_task.send_task(payload, routing_key, local=True, get_response=False) -def send_control_task(app, task, noop_self=False, kwargs={}): +def send_control_task(app, task, noop_self=False, get_response=False, routing_key='control.*', kwargs={}): """ This sends a control task out to all processes, useful for things like reloading a data table, which needs to happen individually in all processes. + Set noop_self to True to not run task for current process. + Set get_response to True to wait for and return the task results + as a list. """ log.info("Sending %s control task." % task) payload = {'task': task, 'kwargs': kwargs} if noop_self: payload['noop'] = app.config.server_name - try: - c = Connection(app.config.amqp_internal_connection) - with producers[c].acquire(block=True) as producer: - control_queues = galaxy.queues.all_control_queues_for_declare(app.config, app.application_stack) - producer.publish(payload, exchange=galaxy.queues.galaxy_exchange, - declare=[galaxy.queues.galaxy_exchange] + control_queues, - routing_key='control') - except Exception: - # This is likely connection refused. - # TODO Use the specific Exception above. - log.exception("Error sending control task: %s.", payload) + control_task = ControlTask(app.control_worker) + return control_task.send_task(payload=payload, routing_key=routing_key, get_response=get_response) + + +class ControlTask(object): + + def __init__(self, control_worker): + self.control_worker = control_worker + self.correlation_id = None + self.callback_queue = Queue(uuid(), exclusive=True, auto_delete=True) + self.response = object() + self._response = self.response + self._connection = None + + @property + def connection(self): + if self._connection is None: + self._connection = self.control_worker.connection.clone() + return self._connection + + @property + def control_queues(self): + return self.control_worker.control_queues + + @property + def exchange(self): + return self.control_worker.exchange_queue.exchange + + @property + def declare_queues(self): + return self.control_worker.declare_queues + + def on_response(self, message): + if message.properties['correlation_id'] == self.correlation_id: + self.response = message.payload['result'] + + def send_task(self, payload, routing_key, local=False, get_response=False, timeout=10): + if local: + declare_queues = self.control_queues + else: + declare_queues = self.declare_queues + reply_to = None + callback_queue = [] + if get_response: + reply_to = self.callback_queue.name + callback_queue = [self.callback_queue] + self.correlation_id = uuid() + try: + with producers[self.connection].acquire(block=True) as producer: + producer.publish( + payload, + exchange=None if local else self.exchange, + declare=declare_queues, + routing_key=routing_key, + reply_to=reply_to, + correlation_id=self.correlation_id, + retry=True, + ) + if get_response: + with Consumer(self.connection, on_message=self.on_response, queues=callback_queue, no_ack=True): + while self.response is self._response: + self.connection.drain_events(timeout=timeout) + return self.response + except socket.timeout: + log.exception("Error waiting for task: '%s' sent with routing key '%s'", payload, routing_key) + except Exception: + log.exception("Error queueing async task: '%s'. for %s", payload, routing_key) # Tasks -- to be reorganized into a separate module as appropriate. This is @@ -235,61 +293,71 @@ control_message_to_task = {'create_panel_section': create_panel_section, 'rebuild_toolbox_search_index': rebuild_toolbox_search_index} -class GalaxyQueueWorker(ConsumerMixin, threading.Thread): +class GalaxyQueueWorker(ConsumerProducerMixin, threading.Thread): """ This is a flexible worker for galaxy's queues. Each process, web or handler, will have one of these used for dispatching so called 'control' tasks. """ - def __init__(self, app, queue=None, task_mapping=control_message_to_task, connection=None): + def __init__(self, app, task_mapping=control_message_to_task): super(GalaxyQueueWorker, self).__init__() log.info("Initializing %s Galaxy Queue Worker on %s", app.config.server_name, util.mask_password_from_url(app.config.amqp_internal_connection)) self.daemon = True - if connection: - self.connection = connection - else: - self.connection = app.amqp_internal_connection_obj - # explicitly force connection instead of lazy-connecting the first - # time it is required. + self.connection = app.amqp_internal_connection_obj + # Force connection instead of lazy-connecting the first time it is required. + # Fixes `'kombu.transport.sqlalchemy.Message' is not mapped` error. self.connection.connect() self.app = app - # Eventually we may want different workers w/ their own queues and task - # mappings. Right now, there's only the one. - if queue: - # Allows assignment of a particular queue for this worker. - self.control_queue = queue - else: - # Default to figuring out which control queue to use based on the app config. - queue = galaxy.queues.control_queue_from_config(app.config) self.task_mapping = task_mapping - self.declare_queues = galaxy.queues.all_control_queues_for_declare(app.config, app.application_stack) - # TODO we may want to purge the queue at the start to avoid executing - # stale 'reload_tool', etc messages. This can happen if, say, a web - # process goes down and messages get sent before it comes back up. - # Those messages will no longer be useful (in any current case) + self.exchange_queue = None + self.direct_queue = None + self.control_queues = [] + + @property + def declare_queues(self): + # dynamically produce queues, allows addressing all known processes at a given time + return galaxy.queues.all_control_queues_for_declare(self.app.config, self.app.application_stack) def bind_and_start(self): + # This is post-forking, so we got the correct sever name log.info("Binding and starting galaxy control worker for %s", self.app.config.server_name) - self.control_queue = galaxy.queues.control_queue_from_config(self.app.config) + self.exchange_queue, self.direct_queue = galaxy.queues.control_queues_from_config(self.app.config) + self.control_queues = [self.exchange_queue, self.direct_queue] + # Delete messages for the current workers' control queues on startup + for q in self.control_queues: + q(self.connection).delete() self.start() def get_consumers(self, Consumer, channel): - return [Consumer(queues=self.control_queue, - callbacks=[self.process_task])] + return [Consumer(queues=[q], + callbacks=[self.process_task], + accept={'application/json'}) for q in self.control_queues] def process_task(self, body, message): + result = 'NO_RESULT' if body['task'] in self.task_mapping: if body.get('noop', None) != self.app.config.server_name: try: f = self.task_mapping[body['task']] log.info("Instance '%s' received '%s' task, executing now.", self.app.config.server_name, body['task']) - f(self.app, **body['kwargs']) + result = f(self.app, **body['kwargs']) except Exception: # this shouldn't ever throw an exception, but... log.exception("Error running control task type: %s", body['task']) + else: + result = 'NO_OP' else: log.warning("Received a malformed task message:\n%s" % body) + if message.properties.get('reply_to'): + self.producer.publish( + {'result': result}, + exchange='', + routing_key=message.properties['reply_to'], + correlation_id=message.properties['correlation_id'], + serializer='json', + retry=True, + ) message.ack() def shutdown(self): diff --git a/lib/galaxy/queues.py b/lib/galaxy/queues.py index 333ac281eb3..56c08753c4a 100644 --- a/lib/galaxy/queues.py +++ b/lib/galaxy/queues.py @@ -18,21 +18,23 @@ def all_control_queues_for_declare(config, application_stack): """ For in-memory routing (used by sqlalchemy-based transports), we need to be able to build the entire routing table in producers. - - Refactor later to actually persist this somewhere instead of building it repeatedly. """ - possible_stack_queues = [Queue("control.%s.%s" % (config.server_name.split('.')[0], wkr['id']), galaxy_exchange, routing_key='control') for wkr in application_stack.workers()] - return possible_stack_queues + [Queue('control.%s' % q, galaxy_exchange, routing_key='control') for q in config.server_names] + # Get all active processes and construct queues for each process + if application_stack and application_stack.app: + server_names = (p.server_name for p in application_stack.app.database_heartbeat.get_active_processes()) + else: + server_names = config.server_names + return [Queue("control.%s" % server_name, galaxy_exchange, routing_key='control.*') for server_name in server_names] -def control_queue_from_config(config): +def control_queues_from_config(config): """ Returns a Queue instance with the correct name and routing key for this galaxy process's config """ - return Queue("control.%s" % config.server_name, - galaxy_exchange, - routing_key='control.%s' % config.server_name) + exchange_queue = Queue("control.%s" % config.server_name, galaxy_exchange, routing_key='control.%s' % config.server_name) + non_exchange_queue = Queue("control.%s" % config.server_name, routing_key='control.%s' % config.server_name) + return exchange_queue, non_exchange_queue def connection_from_config(config): diff --git a/lib/galaxy/security/__init__.py b/lib/galaxy/security/__init__.py index 44327ecfb68..d1efb9c7a6e 100644 --- a/lib/galaxy/security/__init__.py +++ b/lib/galaxy/security/__init__.py @@ -2,19 +2,8 @@ Galaxy Security """ -import logging -import socket -from datetime import datetime, timedelta - -from sqlalchemy import and_, false, not_, or_ -from sqlalchemy.orm import eagerload_all - -import galaxy.model -from galaxy.util import listify from galaxy.util.bunch import Bunch -log = logging.getLogger(__name__) - class Action(object): def __init__(self, action, description, model): @@ -146,1450 +135,6 @@ class RBACAgent(object): return [_ for _ in [self.permitted_actions.get(action_string) for action_string in permitted_action_strings] if _ is not None] -class GalaxyRBACAgent(RBACAgent): - def __init__(self, model, permitted_actions=None): - self.model = model - if permitted_actions: - self.permitted_actions = permitted_actions - # List of "library_item" objects and their associated permissions and info template objects - self.library_item_assocs = ( - (self.model.Library, self.model.LibraryPermissions), - (self.model.LibraryFolder, self.model.LibraryFolderPermissions), - (self.model.LibraryDataset, self.model.LibraryDatasetPermissions), - (self.model.LibraryDatasetDatasetAssociation, self.model.LibraryDatasetDatasetAssociationPermissions)) - - @property - def sa_session(self): - """Returns a SQLAlchemy session""" - return self.model.context - - def sort_by_attr(self, seq, attr): - """ - Sort the sequence of objects by object's attribute - Arguments: - seq - the list or any sequence (including immutable one) of objects to sort. - attr - the name of attribute to sort by - """ - # Use the "Schwartzian transform" - # Create the auxiliary list of tuples where every i-th tuple has form - # (seq[i].attr, i, seq[i]) and sort it. The second item of tuple is needed not - # only to provide stable sorting, but mainly to eliminate comparison of objects - # (which can be expensive or prohibited) in case of equal attribute values. - intermed = [(getattr(v, attr), i, v) for i, v in enumerate(seq)] - intermed.sort() - return [_[-1] for _ in intermed] - - def _get_npns_roles(self, trans): - """ - non-private, non-sharing roles - """ - return trans.sa_session.query(trans.app.model.Role) \ - .filter(and_(self.model.Role.table.c.deleted == false(), - self.model.Role.table.c.type != self.model.Role.types.PRIVATE, - self.model.Role.table.c.type != self.model.Role.types.SHARING)) \ - .order_by(self.model.Role.table.c.name) - - def get_all_roles(self, trans, cntrller): - admin_controller = cntrller in ['library_admin'] - roles = set() - if not trans.user: - return self._get_npns_roles(trans) - if admin_controller: - # The library is public and the user is an admin, so all roles are legitimate - for role in trans.sa_session.query(trans.app.model.Role) \ - .filter(self.model.Role.table.c.deleted == false()) \ - .order_by(self.model.Role.table.c.name): - roles.add(role) - else: - # Add the current user's private role - roles.add(self.get_private_user_role(trans.user)) - # Add the current user's sharing roles - for role in self.get_sharing_roles(trans.user): - roles.add(role) - # Add all remaining non-private, non-sharing roles - for role in self._get_npns_roles(trans): - roles.add(role) - return self.sort_by_attr([role for role in roles], 'name') - - def get_roles_for_action(self, item, action): - """ - Return a list containing the roles associated with given action on given item - where item is one of Library, LibraryFolder, LibraryDatasetDatasetAssociation, - LibraryDataset, Dataset. - """ - roles = [] - for item_permission in item.actions: - permission_action = self.get_action(item_permission.action) - if permission_action == action: - roles.append(item_permission.role) - return roles - - def get_valid_roles(self, trans, item, query=None, page=None, page_limit=None, is_library_access=False): - """ - This method retrieves the list of possible roles that user can select - in the item permissions form. Admins can select any role so the - results are paginated in order to save the bandwidth and to speed - things up. - Standard users can select their own private role, any of their - sharing roles and any public role (not private and not sharing). - """ - roles = [] - if query not in [None, '']: - query = query.strip().replace('_', '/_').replace('%', '/%').replace('/', '//') - search_query = query + '%' - else: - search_query = None - # Limit the query only to get the page needed - if page is not None and page_limit is not None: - limit = page * page_limit - else: - limit = None - total_count = None - if isinstance(item, self.model.Library) and self.library_is_public(item): - is_public_item = True - elif isinstance(item, self.model.Dataset) and self.dataset_is_public(item): - is_public_item = True - elif isinstance(item, self.model.LibraryFolder): - is_public_item = True - else: - is_public_item = False - # Admins can always choose from all non-deleted roles - if trans.user_is_admin or trans.app.config.expose_user_email: - if trans.user_is_admin: - db_query = trans.sa_session.query(trans.app.model.Role).filter(self.model.Role.table.c.deleted == false()) - else: - # User is not an admin but the configuration exposes all private roles to all users. - db_query = trans.sa_session.query(trans.app.model.Role) \ - .filter(and_(self.model.Role.table.c.deleted == false(), - self.model.Role.table.c.type == self.model.Role.types.PRIVATE)) - if search_query: - db_query = db_query.filter(self.model.Role.table.c.name.like(search_query, escape='/')) - total_count = db_query.count() - if limit is not None: - # Takes the least number of results from beginning that includes the requested page - roles = db_query.order_by(self.model.Role.table.c.name).limit(limit).all() - page_start = (page * page_limit) - page_limit - page_end = page_start + page_limit - if total_count < page_start + 1: - # Return empty list if there are less results than the requested position - roles = [] - else: - roles = roles[page_start:page_end] - else: - roles = db_query.order_by(self.model.Role.table.c.name) - # Non-admin and public item - elif is_public_item: - # Add the current user's private role - roles.append(self.get_private_user_role(trans.user)) - # Add the current user's sharing roles - for role in self.get_sharing_roles(trans.user): - roles.append(role) - # Add all remaining non-private, non-sharing roles - for role in self._get_npns_roles(trans): - roles.append(role) - # User will see all the roles derived from the access roles on the item - else: - # If item has roles associated with the access permission, we need to start with them. - access_roles = item.get_access_roles(trans) - for role in access_roles: - if self.ok_to_display(trans.user, role): - roles.append(role) - # Each role potentially has users. We need to find all roles that each of those users have. - for ura in role.users: - user = ura.user - for ura2 in user.roles: - if self.ok_to_display(trans.user, ura2.role): - roles.append(ura2.role) - # Each role also potentially has groups which, in turn, have members ( users ). We need to - # find all roles that each group's members have. - for gra in role.groups: - group = gra.group - for uga in group.users: - user = uga.user - for ura in user.roles: - if self.ok_to_display(trans.user, ura.role): - roles.append(ura.role) - - # Omit duplicated roles by converting to set - return_roles = set(roles) - if total_count is None: - total_count = len(return_roles) - return self.sort_by_attr([role for role in return_roles], 'name'), total_count - - def get_legitimate_roles(self, trans, item, cntrller): - """ - Return a sorted list of legitimate roles that can be associated with a permission on - item where item is a Library or a Dataset. The cntrller param is the controller from - which the request is sent. We cannot use trans.user_is_admin because the controller is - what is important since admin users do not necessarily have permission to do things - on items outside of the admin view. - - If cntrller is from the admin side ( e.g., library_admin ): - - - if item is public, all roles, including private roles, are legitimate. - - if item is restricted, legitimate roles are derived from the users and groups associated - with each role that is associated with the access permission ( i.e., DATASET_MANAGE_PERMISSIONS or - LIBRARY_MANAGE ) on item. Legitimate roles will include private roles. - - If cntrller is not from the admin side ( e.g., root, library ): - - - if item is public, all non-private roles, except for the current user's private role, - are legitimate. - - if item is restricted, legitimate roles are derived from the users and groups associated - with each role that is associated with the access permission on item. Private roles, except - for the current user's private role, will be excluded. - """ - admin_controller = cntrller in ['library_admin'] - roles = set() - if (isinstance(item, self.model.Library) and self.library_is_public(item)) or \ - (isinstance(item, self.model.Dataset) and self.dataset_is_public(item)): - return self.get_all_roles(trans, cntrller) - # If item has roles associated with the access permission, we need to start with them. - access_roles = item.get_access_roles(trans) - for role in access_roles: - if admin_controller or self.ok_to_display(trans.user, role): - roles.add(role) - # Each role potentially has users. We need to find all roles that each of those users have. - for ura in role.users: - user = ura.user - for ura2 in user.roles: - if admin_controller or self.ok_to_display(trans.user, ura2.role): - roles.add(ura2.role) - # Each role also potentially has groups which, in turn, have members ( users ). We need to - # find all roles that each group's members have. - for gra in role.groups: - group = gra.group - for uga in group.users: - user = uga.user - for ura in user.roles: - if admin_controller or self.ok_to_display(trans.user, ura.role): - roles.add(ura.role) - return self.sort_by_attr([role for role in roles], 'name') - - def ok_to_display(self, user, role): - """ - Method for checking if: - - a role is private and is the current user's private role - - a role is a sharing role and belongs to the current user - """ - if user: - if role.type == self.model.Role.types.PRIVATE: - return role == self.get_private_user_role(user) - if role.type == self.model.Role.types.SHARING: - return role in self.get_sharing_roles(user) - # If role.type is neither private nor sharing, it's ok to display - return True - return role.type != self.model.Role.types.PRIVATE and role.type != self.model.Role.types.SHARING - - def allow_action(self, roles, action, item): - """ - Method for checking a permission for the current user ( based on roles ) to perform a - specific action on an item, which must be one of: - Dataset, Library, LibraryFolder, LibraryDataset, LibraryDatasetDatasetAssociation - """ - # SM: Note that calling get_item_actions will emit a query. - item_actions = self.get_item_actions(action, item) - - if not item_actions: - return action.model == 'restrict' - ret_val = False - # For DATASET_ACCESS only, user must have ALL associated roles - if action == self.permitted_actions.DATASET_ACCESS: - for item_action in item_actions: - if item_action.role not in roles: - break - else: - ret_val = True - # For remaining actions, user must have any associated role - else: - for item_action in item_actions: - if item_action.role in roles: - ret_val = True - break - return ret_val - - def get_actions_for_items(self, trans, action, permission_items): - # TODO: Rename this; it's a replacement for get_item_actions, but it - # doesn't represent what it's really doing, which is confusing. - # TODO: Make this work for other classes besides lib_datasets. - # That should be as easy as checking the type and writing a query for each; - # we're avoiding using the SQLAlchemy backrefs because they can cause lots - # of queries to be generated. - # - # Originally, get_item_actions did: - # return [ permission for permission in item.actions if permission.action == action.action ] - # The "item" can be just about anything with permissions, and referencing - # item.actions causes the item's permissions to be retrieved. - # This method will retrieve all permissions for all "items" and only - # return the permissions associated with that given action. - # We initialize the permissions list to be empty; we will return an - # empty list by default. - # - # If the dataset id has no corresponding action in its permissions, - # then the returned permissions will not carry an entry for the dataset. - ret_permissions = {} - if (len(permission_items) > 0): - # SM: NB: LibraryDatasets became Datasets for some odd reason. - if (isinstance(permission_items[0], trans.model.LibraryDataset)): - ids = [item.library_dataset_id for item in permission_items] - permissions = trans.sa_session.query(trans.model.LibraryDatasetPermissions) \ - .filter(and_(trans.model.LibraryDatasetPermissions.library_dataset_id.in_(ids), - trans.model.LibraryDatasetPermissions.action == action.action)) \ - .all() - - # Massage the return data. We will return a list of permissions - # for each library dataset. So we initialize the return list to - # have an empty list for each dataset. Then each permission is - # appended to the right lib dataset. - # TODO: Consider eliminating the initialization and just return - # empty values for each library dataset id. - for item in permission_items: - ret_permissions[item.library_dataset_id] = [] - for permission in permissions: - ret_permissions[permission.library_dataset_id].append(permission) - elif (isinstance(permission_items[0], trans.model.Dataset)): - ids = [item.id for item in permission_items] - permissions = trans.sa_session.query(trans.model.DatasetPermissions) \ - .filter(and_(trans.model.DatasetPermissions.dataset_id.in_(ids), - trans.model.DatasetPermissions.action == action.action)) \ - .all() - - # Massage the return data. We will return a list of permissions - # for each library dataset. So we initialize the return list to - # have an empty list for each dataset. Then each permission is - # appended to the right lib dataset. - # TODO: Consider eliminating the initialization and just return - # empty values for each library dataset id. - for item in permission_items: - ret_permissions[item.id] = [] - for permission in permissions: - ret_permissions[permission.dataset_id].append(permission) - - # Test that we get the same response from get_item_actions each item: - test_code = False - if test_code: - try: - log.debug("get_actions_for_items: Test start") - for item in permission_items: - base_result = self.get_item_actions(action, item) - new_result = ret_permissions[item.library_dataset_id] - # For now, just test against LibraryDatasetIds; other classes - # are not tested yet. - if len(base_result) == len(new_result): - common_result = set(base_result).intersection(new_result) - if len(common_result) == len(base_result): - log.debug("Match on permissions for id %d" % - item.library_dataset_id) - # TODO: Fix this failure message: - else: - log.debug("Error: dataset %d; originally: %s; now: %s" - % (item.library_dataset_id, - base_result, new_result)) - else: - log.debug("Error: dataset %d: had %d entries, now %d entries" - % (item.library_dataset_id, len(base_result), - len(new_result))) - log.debug("get_actions_for_items: Test end") - except Exception as e: - log.debug("Exception in test code: %s" % e) - - return ret_permissions - - def allow_action_on_libitems(self, trans, user_roles, action, items): - """ - This should be the equivalent of allow_action defined on multiple items. - It is meant to specifically replace allow_action for multiple - LibraryDatasets, but it could be reproduced or modified for - allow_action's permitted classes - Dataset, Library, LibraryFolder, and - LDDAs. - """ - all_items_actions = self.get_actions_for_items(trans, action, items) - ret_allow_action = {} - - # Change item to lib_dataset or vice-versa. - for item in items: - if item.id in all_items_actions: - item_actions = all_items_actions[item.id] - - if self.permitted_actions.DATASET_ACCESS == action: - ret_allow_action[item.id] = True - for item_action in item_actions: - if item_action.role not in user_roles: - ret_allow_action[item.id] = False - break - - # Else look for just one dataset role to be in the list of - # acceptable user roles: - else: - ret_allow_action[item.id] = False - for item_action in item_actions: - if item_action.role in user_roles: - ret_allow_action[item.id] = True - break - - else: - if 'restrict' == action.model: - ret_allow_action[item.id] = True - else: - ret_allow_action[item.id] = False - - # Test it: the result for each dataset should match the result for - # allow_action: - test_code = False - if test_code: - log.debug("allow_action_for_items: test start") - for item in items: - orig_value = self.allow_action(user_roles, action, item) - if orig_value == ret_allow_action[item.id]: - log.debug("Item %d: success" % item.id) - else: - log.debug("Item %d: fail: original: %s; new: %s" - % (item.id, orig_value, ret_allow_action[item.id])) - log.debug("allow_action_for_items: test end") - return ret_allow_action - - # DELETEME: SM: DO NOT TOUCH! This actually works. - def dataset_access_mapping(self, trans, user_roles, datasets): - ''' - For the given list of datasets, return a mapping of the datasets' ids - to whether they can be accessed by the user or not. The datasets input - is expected to be a simple list of Dataset objects. - ''' - datasets_public_map = self.datasets_are_public(trans, datasets) - datasets_allow_action_map = self.allow_action_on_libitems(trans, user_roles, self.permitted_actions.DATASET_ACCESS, datasets) - can_access = {} - for dataset in datasets: - can_access[dataset.id] = datasets_public_map[dataset.id] or datasets_allow_action_map[dataset.id] - return can_access - - def dataset_permission_map_for_access(self, trans, user_roles, libitems): - ''' - For a given list of library items (e.g., Datasets), return a map of the - datasets' ids to whether they can have permission to use that action - (e.g., "access" or "modify") on the dataset. The libitems input is - expected to be a simple list of library items, such as Datasets or - LibraryDatasets. - NB: This is currently only usable for Datasets; it was intended to - be used for any library item. - ''' - # Map the library items to whether they are publicly accessible or not. - # Then determine what actions are allowed on the item (in case it's not - # public). Finally, the item is accessible if it's publicly available - # or the right permissions are enabled. - # TODO: This only works for Datasets; other code is using X_is_public, - # so this will have to be rewritten to support other items. - libitems_public_map = self.datasets_are_public(trans, libitems) - libitems_allow_action_map = self.allow_action_on_libitems( - trans, user_roles, self.permitted_actions.DATASET_ACCESS, libitems - ) - can_access = {} - for libitem in libitems: - can_access[libitem.id] = libitems_public_map[libitem.id] or libitems_allow_action_map[libitem.id] - return can_access - - def item_permission_map_for_modify(self, trans, user_roles, libitems): - return self.allow_action_on_libitems( - trans, user_roles, self.permitted_actions.LIBRARY_MODIFY, libitems - ) - - def item_permission_map_for_manage(self, trans, user_roles, libitems): - return self.allow_action_on_libitems( - trans, user_roles, self.permitted_actions.LIBRARY_MANAGE, libitems - ) - - def item_permission_map_for_add(self, trans, user_roles, libitems): - return self.allow_action_on_libitems( - trans, user_roles, self.permitted_actions.LIBRARY_ADD, libitems - ) - - def can_access_dataset(self, user_roles, dataset): - # SM: dataset_is_public will access dataset.actions, which is a - # backref that causes a query to be made to DatasetPermissions - retval = self.dataset_is_public(dataset) or self.allow_action(user_roles, self.permitted_actions.DATASET_ACCESS, dataset) - return retval - - def can_access_datasets(self, user_roles, action_tuples): - user_role_ids = [galaxy.model.cached_id(r) for r in user_roles] - - # For DATASET_ACCESS, user must have ALL associated roles - for action, user_role_id in action_tuples: - if action == self.permitted_actions.DATASET_ACCESS.action: - if user_role_id not in user_role_ids: - return False - - return True - - def can_manage_dataset(self, roles, dataset): - return self.allow_action(roles, self.permitted_actions.DATASET_MANAGE_PERMISSIONS, dataset) - - def can_access_library(self, roles, library): - return self.library_is_public(library) or self.allow_action(roles, self.permitted_actions.LIBRARY_ACCESS, library) - - def get_accessible_libraries(self, trans, user): - """Return all data libraries that the received user can access""" - accessible_libraries = [] - current_user_role_ids = [role.id for role in user.all_roles()] - library_access_action = self.permitted_actions.LIBRARY_ACCESS.action - restricted_library_ids = [lp.library_id for lp in trans.sa_session.query(trans.model.LibraryPermissions) - .filter(trans.model.LibraryPermissions.table.c.action == library_access_action).distinct()] - accessible_restricted_library_ids = [lp.library_id for lp in trans.sa_session.query(trans.model.LibraryPermissions) - .filter(and_( - trans.model.LibraryPermissions.table.c.action == library_access_action, - trans.model.LibraryPermissions.table.c.role_id.in_(current_user_role_ids)))] - # Filter to get libraries accessible by the current user. Get both - # public libraries and restricted libraries accessible by the current user. - for library in trans.sa_session.query(trans.model.Library) \ - .filter(and_(trans.model.Library.table.c.deleted == false(), - (or_(not_(trans.model.Library.table.c.id.in_(restricted_library_ids)), - trans.model.Library.table.c.id.in_(accessible_restricted_library_ids))))) \ - .order_by(trans.app.model.Library.name): - accessible_libraries.append(library) - return accessible_libraries - - def has_accessible_folders(self, trans, folder, user, roles, search_downward=True): - if self.has_accessible_library_datasets(trans, folder, user, roles, search_downward=search_downward) or \ - self.can_add_library_item(roles, folder) or \ - self.can_modify_library_item(roles, folder) or \ - self.can_manage_library_item(roles, folder): - return True - if search_downward: - for folder in folder.active_folders: - return self.has_accessible_folders(trans, folder, user, roles, search_downward=search_downward) - return False - - def has_accessible_library_datasets(self, trans, folder, user, roles, search_downward=True): - for library_dataset in trans.sa_session.query(trans.model.LibraryDataset) \ - .filter(and_(trans.model.LibraryDataset.table.c.deleted == false(), - trans.app.model.LibraryDataset.table.c.folder_id == folder.id)): - if self.can_access_library_item(roles, library_dataset, user): - return True - if search_downward: - return self.__active_folders_have_accessible_library_datasets(trans, folder, user, roles) - return False - - def __active_folders_have_accessible_library_datasets(self, trans, folder, user, roles): - for active_folder in folder.active_folders: - if self.has_accessible_library_datasets(trans, active_folder, user, roles): - return True - return False - - def can_access_library_item(self, roles, item, user): - if type(item) == self.model.Library: - return self.can_access_library(roles, item) - elif type(item) == self.model.LibraryFolder: - return self.can_access_library(roles, item.parent_library) and self.check_folder_contents(user, roles, item)[0] - elif type(item) == self.model.LibraryDataset: - return self.can_access_library(roles, item.folder.parent_library) and self.can_access_dataset(roles, item.library_dataset_dataset_association.dataset) - elif type(item) == self.model.LibraryDatasetDatasetAssociation: - return self.can_access_library(roles, item.library_dataset.folder.parent_library) and self.can_access_dataset(roles, item.dataset) - elif type(item) == self.model.LibraryDatasetCollectionAssociation: - return self.can_access_library(roles, item.folder.parent_library) - else: - log.warning('Unknown library item type: %s' % type(item)) - return False - - def can_add_library_item(self, roles, item): - return self.allow_action(roles, self.permitted_actions.LIBRARY_ADD, item) - - def can_modify_library_item(self, roles, item): - return self.allow_action(roles, self.permitted_actions.LIBRARY_MODIFY, item) - - def can_manage_library_item(self, roles, item): - return self.allow_action(roles, self.permitted_actions.LIBRARY_MANAGE, item) - - def get_item_actions(self, action, item): - # item must be one of: Dataset, Library, LibraryFolder, LibraryDataset, LibraryDatasetDatasetAssociation - # SM: Accessing item.actions emits a query to Library_Dataset_Permissions - # if the item is a LibraryDataset: - # TODO: Pass in the item's actions - the item isn't needed - return [permission for permission in item.actions if permission.action == action.action] - - def guess_derived_permissions_for_datasets(self, datasets=[]): - """Returns a dict of { action : [ role, role, ... ] } for the output dataset based upon provided datasets""" - perms = {} - for dataset in datasets: - if not isinstance(dataset, self.model.Dataset): - dataset = dataset.dataset - these_perms = {} - # initialize blank perms - for action in self.get_actions(): - these_perms[action] = [] - # collect this dataset's perms - these_perms = self.get_permissions(dataset) - # join or intersect this dataset's permissions with others - for action, roles in these_perms.items(): - if action not in perms.keys(): - perms[action] = roles - else: - if action.model == 'grant': - # intersect existing roles with new roles - perms[action] = [_ for _ in roles if _ in perms[action]] - elif action.model == 'restrict': - # join existing roles with new roles - perms[action].extend([_ for _ in roles if _ not in perms[action]]) - return perms - - def guess_derived_permissions(self, all_input_permissions): - """Returns a dict of { action : [ role_id, role_id, ... ] } for the output dataset based upon input dataset permissions. - - all_input_permissions should be of the form {action_name: set(role_ids)} - """ - perms = {} - for action_name, role_ids in all_input_permissions.items(): - if not role_ids: - continue - action = self.get_action(action_name) - if action not in perms.keys(): - perms[action] = list(role_ids) - else: - if action.model == 'grant': - # intersect existing roles with new roles - perms[action] = [_ for _ in role_ids if _ in perms[action]] - elif action.model == 'restrict': - # join existing roles with new roles - perms[action].extend([_ for _ in role_ids if _ not in perms[action]]) - return perms - - def associate_components(self, **kwd): - if 'user' in kwd: - if 'group' in kwd: - return self.associate_user_group(kwd['user'], kwd['group']) - elif 'role' in kwd: - return self.associate_user_role(kwd['user'], kwd['role']) - elif 'role' in kwd: - if 'group' in kwd: - return self.associate_group_role(kwd['group'], kwd['role']) - if 'action' in kwd: - if 'dataset' in kwd and 'role' in kwd: - return self.associate_action_dataset_role(kwd['action'], kwd['dataset'], kwd['role']) - raise Exception('No valid method of associating provided components: %s' % kwd) - - def associate_user_group(self, user, group): - assoc = self.model.UserGroupAssociation(user, group) - self.sa_session.add(assoc) - self.sa_session.flush() - return assoc - - def associate_user_role(self, user, role): - assoc = self.model.UserRoleAssociation(user, role) - self.sa_session.add(assoc) - self.sa_session.flush() - return assoc - - def associate_group_role(self, group, role): - assoc = self.model.GroupRoleAssociation(group, role) - self.sa_session.add(assoc) - self.sa_session.flush() - return assoc - - def associate_action_dataset_role(self, action, dataset, role): - assoc = self.model.DatasetPermissions(action, dataset, role) - self.sa_session.add(assoc) - self.sa_session.flush() - return assoc - - def create_private_user_role(self, user): - # Create private role - role = self.model.Role(name=user.email, description='Private Role for ' + user.email, type=self.model.Role.types.PRIVATE) - self.sa_session.add(role) - self.sa_session.flush() - # Add user to role - self.associate_components(role=role, user=user) - return role - - def get_private_user_role(self, user, auto_create=False): - role = self.sa_session.query(self.model.Role) \ - .filter(and_(self.model.UserRoleAssociation.table.c.user_id == user.id, - self.model.Role.table.c.id == self.model.UserRoleAssociation.table.c.role_id, - self.model.Role.table.c.type == self.model.Role.types.PRIVATE)) \ - .first() - if not role: - if auto_create: - return self.create_private_user_role(user) - else: - return None - return role - - def get_role(self, name, type=None): - type = type or self.model.Role.types.SYSTEM - # will raise exception if not found - return self.sa_session.query(self.model.Role) \ - .filter(and_(self.model.Role.table.c.name == name, - self.model.Role.table.c.type == type)) \ - .one() - - def create_role(self, name, description, in_users, in_groups, create_group_for_role=False, type=None): - type = type or self.model.Role.types.SYSTEM - role = self.model.Role(name=name, description=description, type=type) - self.sa_session.add(role) - # Create the UserRoleAssociations - for user in [self.sa_session.query(self.model.User).get(x) for x in in_users]: - self.associate_user_role(user, role) - # Create the GroupRoleAssociations - for group in [self.sa_session.query(self.model.Group).get(x) for x in in_groups]: - self.associate_group_role(group, role) - if create_group_for_role: - # Create the group - group = self.model.Group(name=name) - self.sa_session.add(group) - # Associate the group with the role - self.associate_group_role(group, role) - num_in_groups = len(in_groups) + 1 - else: - num_in_groups = len(in_groups) - self.sa_session.flush() - return role, num_in_groups - - def get_sharing_roles(self, user): - return self.sa_session.query(self.model.Role) \ - .filter(and_((self.model.Role.table.c.name).like("Sharing role for: %" + user.email + "%"), - self.model.Role.table.c.type == self.model.Role.types.SHARING)) - - def user_set_default_permissions(self, user, permissions={}, history=False, dataset=False, bypass_manage_permission=False, default_access_private=False): - # bypass_manage_permission is used to change permissions of datasets in a userless history when logging in - flush_needed = False - if user is None: - return None - if not permissions: - # default permissions - permissions = {self.permitted_actions.DATASET_MANAGE_PERMISSIONS: [self.get_private_user_role(user, auto_create=True)]} - # new_user_dataset_access_role_default_private is set as True in config file - if default_access_private: - permissions[self.permitted_actions.DATASET_ACCESS] = next(iter(permissions.values())) - # Delete all of the current default permissions for the user - for dup in user.default_permissions: - self.sa_session.delete(dup) - flush_needed = True - # Add the new default permissions for the user - for action, roles in permissions.items(): - if isinstance(action, Action): - action = action.action - for dup in [self.model.DefaultUserPermissions(user, action, role) for role in roles]: - self.sa_session.add(dup) - flush_needed = True - if flush_needed: - self.sa_session.flush() - if history: - for history in user.active_histories: - self.history_set_default_permissions(history, permissions=permissions, dataset=dataset, bypass_manage_permission=bypass_manage_permission) - - def user_get_default_permissions(self, user): - permissions = {} - for dup in user.default_permissions: - action = self.get_action(dup.action) - if action in permissions: - permissions[action].append(dup.role) - else: - permissions[action] = [dup.role] - return permissions - - def history_set_default_permissions(self, history, permissions={}, dataset=False, bypass_manage_permission=False): - # bypass_manage_permission is used to change permissions of datasets in a user-less history when logging in - flush_needed = False - user = history.user - if not user: - # default permissions on a user-less history are None - return None - if not permissions: - permissions = self.user_get_default_permissions(user) - # Delete all of the current default permission for the history - for dhp in history.default_permissions: - self.sa_session.delete(dhp) - flush_needed = True - # Add the new default permissions for the history - for action, roles in permissions.items(): - if isinstance(action, Action): - action = action.action - for dhp in [self.model.DefaultHistoryPermissions(history, action, role) for role in roles]: - self.sa_session.add(dhp) - flush_needed = True - if flush_needed: - self.sa_session.flush() - if dataset: - # Only deal with datasets that are not purged - for hda in history.activatable_datasets: - dataset = hda.dataset - if dataset.library_associations: - # Don't change permissions on a dataset associated with a library - continue - if [assoc for assoc in dataset.history_associations if assoc.history not in user.histories]: - # Don't change permissions on a dataset associated with a history not owned by the user - continue - if bypass_manage_permission or self.can_manage_dataset(user.all_roles(), dataset): - self.set_all_dataset_permissions(dataset, permissions) - - def history_get_default_permissions(self, history): - permissions = {} - for dhp in history.default_permissions: - action = self.get_action(dhp.action) - if action in permissions: - permissions[action].append(dhp.role) - else: - permissions[action] = [dhp.role] - return permissions - - def set_all_dataset_permissions(self, dataset, permissions={}, new=False, flush=True): - """ - Set new full permissions on a dataset, eliminating all current permissions. - Permission looks like: { Action : [ Role, Role ] } - """ - # Make sure that DATASET_MANAGE_PERMISSIONS is associated with at least 1 role - has_dataset_manage_permissions = False - for action, roles in permissions.items(): - if isinstance(action, Action): - if action == self.permitted_actions.DATASET_MANAGE_PERMISSIONS and roles: - has_dataset_manage_permissions = True - break - elif action == self.permitted_actions.DATASET_MANAGE_PERMISSIONS.action and roles: - has_dataset_manage_permissions = True - break - if not has_dataset_manage_permissions: - return "At least 1 role must be associated with manage permissions on this dataset." - flush_needed = False - # Delete all of the current permissions on the dataset - if not new: - for dp in dataset.actions: - self.sa_session.delete(dp) - flush_needed = True - # Add the new permissions on the dataset - for action, roles in permissions.items(): - if isinstance(action, Action): - action = action.action - for role in roles: - if hasattr(role, "id"): - role_id = role.id - else: - role_id = role - dp = self.model.DatasetPermissions(action, dataset, role_id=role_id) - self.sa_session.add(dp) - flush_needed = True - if flush_needed and flush: - self.sa_session.flush() - return "" - - def set_dataset_permission(self, dataset, permission={}): - """ - Set a specific permission on a dataset, leaving all other current permissions on the dataset alone. - Permission looks like: { Action.action : [ Role, Role ] } - """ - flush_needed = False - for action, roles in permission.items(): - if isinstance(action, Action): - action = action.action - # Delete the current specific permission on the dataset if one exists - for dp in dataset.actions: - if dp.action == action: - self.sa_session.delete(dp) - flush_needed = True - # Add the new specific permission on the dataset - for dp in [self.model.DatasetPermissions(action, dataset, role) for role in roles]: - self.sa_session.add(dp) - flush_needed = True - if flush_needed: - self.sa_session.flush() - - def get_permissions(self, item): - """ - Return a dictionary containing the actions and associated roles on item - where item is one of Library, LibraryFolder, LibraryDatasetDatasetAssociation, - LibraryDataset, Dataset. The dictionary looks like: { Action : [ Role, Role ] }. - """ - permissions = {} - for item_permission in item.actions: - action = self.get_action(item_permission.action) - if action in permissions: - permissions[action].append(item_permission.role) - else: - permissions[action] = [item_permission.role] - return permissions - - def copy_dataset_permissions(self, src, dst): - if not isinstance(src, self.model.Dataset): - src = src.dataset - if not isinstance(dst, self.model.Dataset): - dst = dst.dataset - self.set_all_dataset_permissions(dst, self.get_permissions(src)) - - def privately_share_dataset(self, dataset, users=[]): - intersect = None - for user in users: - roles = [ura.role for ura in user.roles if ura.role.type == self.model.Role.types.SHARING] - if intersect is None: - intersect = roles - else: - new_intersect = [] - for role in roles: - if role in intersect: - new_intersect.append(role) - intersect = new_intersect - sharing_role = None - if intersect: - for role in intersect: - if not [_ for _ in [ura.user for ura in role.users] if _ not in users]: - # only use a role if it contains ONLY the users we're sharing with - sharing_role = role - break - if sharing_role is None: - sharing_role = self.model.Role(name="Sharing role for: " + ", ".join([u.email for u in users]), - type=self.model.Role.types.SHARING) - self.sa_session.add(sharing_role) - self.sa_session.flush() - for user in users: - self.associate_components(user=user, role=sharing_role) - self.set_dataset_permission(dataset, {self.permitted_actions.DATASET_ACCESS: [sharing_role]}) - - def set_all_library_permissions(self, trans, library_item, permissions={}): - # Set new permissions on library_item, eliminating all current permissions - flush_needed = False - for role_assoc in library_item.actions: - self.sa_session.delete(role_assoc) - flush_needed = True - # Add the new permissions on library_item - for item_class, permission_class in self.library_item_assocs: - if isinstance(library_item, item_class): - for action, roles in list(permissions.items()): - if isinstance(action, Action): - action = action.action - for role_assoc in [permission_class(action, library_item, role) for role in roles]: - self.sa_session.add(role_assoc) - flush_needed = True - if isinstance(library_item, self.model.LibraryDatasetDatasetAssociation): - # Permission setting related to DATASET_MANAGE_PERMISSIONS was broken for a period of time, - # so it is possible that some Datasets have no roles associated with the DATASET_MANAGE_PERMISSIONS - # permission. In this case, we'll reset this permission to the library_item user's private role. - if not library_item.dataset.has_manage_permissions_roles(trans): - # Well this looks like a bug, this should be looked at. - # Default permissions above is single hash that keeps getting reeditted here - # because permission is being defined instead of permissions. -John - permissions[self.permitted_actions.DATASET_MANAGE_PERMISSIONS] = [trans.app.security_agent.get_private_user_role(library_item.user)] - self.set_dataset_permission(library_item.dataset, permissions) - if action == self.permitted_actions.LIBRARY_MANAGE.action and roles: - # Handle the special case when we are setting the LIBRARY_MANAGE_PERMISSION on a - # library_dataset_dataset_association since the roles need to be applied to the - # DATASET_MANAGE_PERMISSIONS permission on the associated dataset. - permissions = {} - permissions[self.permitted_actions.DATASET_MANAGE_PERMISSIONS] = roles - self.set_dataset_permission(library_item.dataset, permissions) - if flush_needed: - self.sa_session.flush() - - def set_library_item_permission(self, library_item, permission={}): - """ - Set a specific permission on a library item, leaving all other current permissions on the item alone. - Permission looks like: { Action.action : [ Role, Role ] } - """ - flush_needed = False - for action, roles in permission.items(): - if isinstance(action, Action): - action = action.action - # Delete the current specific permission on the library item if one exists - for item_permission in library_item.actions: - if item_permission.action == action: - self.sa_session.delete(item_permission) - flush_needed = True - # Add the new specific permission on the library item - if isinstance(library_item, self.model.LibraryDataset): - for item_permission in [self.model.LibraryDatasetPermissions(action, library_item, role) for role in roles]: - self.sa_session.add(item_permission) - flush_needed = True - elif isinstance(library_item, self.model.LibraryPermissions): - for item_permission in [self.model.LibraryPermissions(action, library_item, role) for role in roles]: - self.sa_session.add(item_permission) - flush_needed = True - if flush_needed: - self.sa_session.flush() - - def library_is_public(self, library, contents=False): - if contents: - # Check all contained folders and datasets to find any that are not public - if not self.folder_is_public(library.root_folder): - return False - # A library is considered public if there are no "access" actions associated with it. - return self.permitted_actions.LIBRARY_ACCESS.action not in [a.action for a in library.actions] - - def library_is_unrestricted(self, library): - # A library is considered unrestricted if there are no "access" actions associated with it. - return self.permitted_actions.LIBRARY_ACCESS.action not in [a.action for a in library.actions] - - def make_library_public(self, library, contents=False): - flush_needed = False - if contents: - # Make all contained folders (include deleted folders, but not purged folders), public - self.make_folder_public(library.root_folder) - # A library is considered public if there are no LIBRARY_ACCESS actions associated with it. - for lp in library.actions: - if lp.action == self.permitted_actions.LIBRARY_ACCESS.action: - self.sa_session.delete(lp) - flush_needed = True - if flush_needed: - self.sa_session.flush() - - def folder_is_public(self, folder): - for sub_folder in folder.folders: - if not self.folder_is_public(sub_folder): - return False - for library_dataset in folder.datasets: - ldda = library_dataset.library_dataset_dataset_association - if ldda and ldda.dataset and not self.dataset_is_public(ldda.dataset): - return False - return True - - def folder_is_unrestricted(self, folder): - # TODO implement folder restrictions - # for now all folders are _visible_ but the restricted datasets within are not visible - return True - - def make_folder_public(self, folder): - # Make all of the contents (include deleted contents, but not purged contents) of folder public - for sub_folder in folder.folders: - if not sub_folder.purged: - self.make_folder_public(sub_folder) - for library_dataset in folder.datasets: - dataset = library_dataset.library_dataset_dataset_association.dataset - if not dataset.purged and not self.dataset_is_public(dataset): - self.make_dataset_public(dataset) - - def dataset_is_public(self, dataset): - """ - A dataset is considered public if there are no "access" actions - associated with it. Any other actions ( 'manage permissions', - 'edit metadata' ) are irrelevant. Accessing dataset.actions - will cause a query to be emitted. - """ - return self.permitted_actions.DATASET_ACCESS.action not in [a.action for a in dataset.actions] - - def dataset_is_unrestricted(self, trans, dataset): - """ - Different implementation of the method above with signature: - def dataset_is_public( self, dataset ) - """ - return len(dataset.library_dataset_dataset_association.get_access_roles(trans)) == 0 - - def dataset_is_private_to_user(self, trans, dataset): - """ - If the Dataset object has exactly one access role and that is - the current user's private role then we consider the dataset private. - """ - private_role = self.get_private_user_role(trans.user) - access_roles = dataset.get_access_roles(trans) - - if len(access_roles) != 1: - return False - else: - if access_roles[0].id == private_role.id: - return True - else: - return False - - def datasets_are_public(self, trans, datasets): - ''' - Given a transaction object and a list of Datasets, return - a mapping from Dataset ids to whether the Dataset is public - or not. All Dataset ids should be returned in the mapping's keys. - ''' - # We go the other way around from dataset_is_public: we start with - # all datasets being marked as public. If there is an access action - # associated with the dataset, then we mark it as nonpublic: - datasets_public = {} - dataset_ids = [dataset.id for dataset in datasets] - for dataset_id in dataset_ids: - datasets_public[dataset_id] = True - - # Now get all datasets which have DATASET_ACCESS actions: - access_data_perms = trans.sa_session.query(trans.app.model.DatasetPermissions) \ - .filter(and_(trans.app.model.DatasetPermissions.dataset_id.in_(dataset_ids), - trans.app.model.DatasetPermissions.action == self.permitted_actions.DATASET_ACCESS.action)) \ - .all() - # Every dataset returned has "access" privileges associated with it, - # so it's not public. - for permission in access_data_perms: - datasets_public[permission.dataset_id] = False - return datasets_public - - def make_dataset_public(self, dataset): - # A dataset is considered public if there are no "access" actions associated with it. Any - # other actions ( 'manage permissions', 'edit metadata' ) are irrelevant. - flush_needed = False - for dp in dataset.actions: - if dp.action == self.permitted_actions.DATASET_ACCESS.action: - self.sa_session.delete(dp) - flush_needed = True - if flush_needed: - self.sa_session.flush() - - def derive_roles_from_access(self, trans, item_id, cntrller, library=False, **kwd): - # Check the access permission on a dataset. If library is true, item_id refers to a library. If library - # is False, item_id refers to a dataset ( item_id must currently be decoded before being sent ). The - # cntrller param is the calling controller, which needs to be passed to get_legitimate_roles(). - msg = '' - permissions = {} - # accessible will be True only if at least 1 user has every role in DATASET_ACCESS_in - accessible = False - # legitimate will be True only if all roles in DATASET_ACCESS_in are in the set of roles returned from - # get_legitimate_roles() - # legitimate = False # TODO: not used - # private_role_found will be true only if more than 1 role is being associated with the DATASET_ACCESS - # permission on item, and at least 1 of the roles is private. - private_role_found = False - error = False - for k, v in get_permitted_actions(filter='DATASET').items(): - # Change for removing the prefix '_in' from the roles select box - in_roles = [self.sa_session.query(self.model.Role).get(x) for x in listify(kwd[k])] - if not in_roles: - in_roles = [self.sa_session.query(self.model.Role).get(x) for x in listify(kwd.get(k + '_in', []))] - if v == self.permitted_actions.DATASET_ACCESS and in_roles: - if library: - item = self.sa_session.query(self.model.Library).get(item_id) - else: - item = self.sa_session.query(self.model.Dataset).get(item_id) - if (library and not self.library_is_public(item)) or (not library and not self.dataset_is_public(item)): - # Ensure that roles being associated with DATASET_ACCESS are a subset of the legitimate roles - # derived from the roles associated with the access permission on item if it's not public. This - # will keep illegitimate roles from being associated with the DATASET_ACCESS permission on the - # dataset (i.e., in the case where item is .a library, if Role1 is associated with LIBRARY_ACCESS, - # then only those users that have Role1 should be associated with DATASET_ACCESS. - legitimate_roles = self.get_legitimate_roles(trans, item, cntrller) - illegitimate_roles = [] - for role in in_roles: - if role not in legitimate_roles: - illegitimate_roles.append(role) - if illegitimate_roles: - # This condition should never occur since illegitimate roles are filtered out of the set of - # roles displayed on the forms, but just in case there is a bug somewhere that incorrectly - # filters, we'll display this message. - error = True - msg += "The following roles are not associated with users that have the 'access' permission on this " - msg += "item, so they were incorrectly displayed: " - for role in illegitimate_roles: - msg += "%s, " % role.name - msg = msg.rstrip(", ") - new_in_roles = [] - for role in in_roles: - if role in legitimate_roles: - new_in_roles.append(role) - in_roles = new_in_roles - if len(in_roles) > 1: - # At least 1 user must have every role associated with the access - # permission on this dataset, or the dataset is not accessible. - # Since we have more than 1 role, none of them can be private. - for role in in_roles: - if role.type == self.model.Role.types.PRIVATE: - private_role_found = True - break - if len(in_roles) == 1: - accessible = True - else: - # At least 1 user must have every role associated with the access - # permission on this dataset, or the dataset is not accessible. - in_roles_set = set() - for role in in_roles: - in_roles_set.add(role) - users_set = set() - for role in in_roles: - for ura in role.users: - users_set.add(ura.user) - for gra in role.groups: - group = gra.group - for uga in group.users: - users_set.add(uga.user) - # Make sure that at least 1 user has every role being associated with the dataset. - for user in users_set: - user_roles_set = set() - for ura in user.roles: - user_roles_set.add(ura.role) - if in_roles_set.issubset(user_roles_set): - accessible = True - break - if private_role_found or not accessible: - error = True - # Don't set the permissions for DATASET_ACCESS if inaccessible or multiple roles with - # at least 1 private, but set all other permissions. - permissions[self.get_action(v.action)] = [] - msg = "At least 1 user must have every role associated with accessing datasets. " - if private_role_found: - msg += "Since you are associating more than 1 role, no private roles are allowed." - if not accessible: - msg += "The roles you attempted to associate for access would make the datasets in-accessible by everyone." - else: - permissions[self.get_action(v.action)] = in_roles - else: - permissions[self.get_action(v.action)] = in_roles - return permissions, in_roles, error, msg - - def copy_library_permissions(self, trans, source_library_item, target_library_item, user=None): - # Copy all relevant permissions from source. - permissions = {} - for role_assoc in source_library_item.actions: - if role_assoc.action != self.permitted_actions.LIBRARY_ACCESS.action: - # LIBRARY_ACCESS is a special permission that is set only at the library level. - if role_assoc.action in permissions: - permissions[role_assoc.action].append(role_assoc.role) - else: - permissions[role_assoc.action] = [role_assoc.role] - self.set_all_library_permissions(trans, target_library_item, permissions) - if user: - item_class = None - for item_class, permission_class in self.library_item_assocs: - if isinstance(target_library_item, item_class): - break - if item_class: - # Make sure user's private role is included - private_role = self.model.security_agent.get_private_user_role(user) - for name, action in self.permitted_actions.items(): - if not permission_class.filter_by(role_id=private_role.id, action=action.action).first(): - lp = permission_class(action.action, target_library_item, private_role) - self.sa_session.add(lp) - self.sa_session.flush() - else: - raise Exception('Invalid class (%s) specified for target_library_item (%s)' % - (target_library_item.__class__, target_library_item.__class__.__name__)) - - def get_permitted_libraries(self, trans, user, actions): - """ - This method is historical (it is not currently used), but may be useful again at some - point. It returns a dictionary whose keys are library objects and whose values are a - comma-separated string of folder ids. This method works with the show_library_item() - method below, and it returns libraries for which the received user has permission to - perform the received actions. Here is an example call to this method to return all - libraries for which the received user has LIBRARY_ADD permission:: - - libraries = trans.app.security_agent.get_permitted_libraries( trans, user, - [ trans.app.security_agent.permitted_actions.LIBRARY_ADD ] ) - """ - all_libraries = trans.sa_session.query(trans.app.model.Library) \ - .filter(trans.app.model.Library.table.c.deleted == false()) \ - .order_by(trans.app.model.Library.name) - roles = user.all_roles() - actions_to_check = actions - # The libraries dictionary looks like: { library : '1,2' }, library : '3' } - # Its keys are the libraries that should be displayed for the current user and whose values are a - # string of comma-separated folder ids, of the associated folders the should NOT be displayed. - # The folders that should not be displayed may not be a complete list, but it is ultimately passed - # to the calling method to keep from re-checking the same folders when the library / folder - # select lists are rendered. - libraries = {} - for library in all_libraries: - can_show, hidden_folder_ids = self.show_library_item(self, roles, library, actions_to_check) - if can_show: - libraries[library] = hidden_folder_ids - return libraries - - def show_library_item(self, user, roles, library_item, actions_to_check, hidden_folder_ids=''): - """ - This method must be sent an instance of Library() or LibraryFolder(). Recursive execution produces a - comma-separated string of folder ids whose folders do NOT meet the criteria for showing. Along with - the string, True is returned if the current user has permission to perform any 1 of actions_to_check - on library_item. Otherwise, cycle through all sub-folders in library_item until one is found that meets - this criteria, if it exists. This method does not necessarily scan the entire library as it returns - when it finds the first library_item that allows user to perform any one action in actions_to_check. - """ - for action in actions_to_check: - if self.allow_action(roles, action, library_item): - return True, hidden_folder_ids - if isinstance(library_item, self.model.Library): - return self.show_library_item(user, roles, library_item.root_folder, actions_to_check, hidden_folder_ids='') - if isinstance(library_item, self.model.LibraryFolder): - for folder in library_item.active_folders: - can_show, hidden_folder_ids = self.show_library_item(user, roles, folder, actions_to_check, hidden_folder_ids=hidden_folder_ids) - if can_show: - return True, hidden_folder_ids - if hidden_folder_ids: - hidden_folder_ids = '%s,%d' % (hidden_folder_ids, folder.id) - else: - hidden_folder_ids = '%d' % folder.id - return False, hidden_folder_ids - - def get_showable_folders(self, user, roles, library_item, actions_to_check, hidden_folder_ids=[], showable_folders=[]): - """ - This method must be sent an instance of Library(), all the folders of which are scanned to determine if - user is allowed to perform any action in actions_to_check. The param hidden_folder_ids, if passed, should - contain a list of folder IDs which was generated when the library was previously scanned - using the same actions_to_check. A list of showable folders is generated. This method scans the entire library. - """ - if isinstance(library_item, self.model.Library): - return self.get_showable_folders(user, roles, library_item.root_folder, actions_to_check, showable_folders=[]) - if isinstance(library_item, self.model.LibraryFolder): - if library_item.id not in hidden_folder_ids: - for action in actions_to_check: - if self.allow_action(roles, action, library_item): - showable_folders.append(library_item) - break - for folder in library_item.active_folders: - self.get_showable_folders(user, roles, folder, actions_to_check, showable_folders=showable_folders) - return showable_folders - - def set_entity_user_associations(self, users=[], roles=[], groups=[], delete_existing_assocs=True): - for user in users: - if delete_existing_assocs: - flush_needed = False - for a in user.non_private_roles + user.groups: - self.sa_session.delete(a) - flush_needed = True - if flush_needed: - self.sa_session.flush() - self.sa_session.refresh(user) - for role in roles: - # Make sure we are not creating an additional association with a PRIVATE role - if role not in [x.role for x in user.roles]: - self.associate_components(user=user, role=role) - for group in groups: - self.associate_components(user=user, group=group) - - def set_entity_group_associations(self, groups=[], users=[], roles=[], delete_existing_assocs=True): - for group in groups: - if delete_existing_assocs: - flush_needed = False - for a in group.roles + group.users: - self.sa_session.delete(a) - flush_needed = True - if flush_needed: - self.sa_session.flush() - for role in roles: - self.associate_components(group=group, role=role) - for user in users: - self.associate_components(group=group, user=user) - - def set_entity_role_associations(self, roles=[], users=[], groups=[], delete_existing_assocs=True): - for role in roles: - if delete_existing_assocs: - flush_needed = False - for a in role.users + role.groups: - self.sa_session.delete(a) - flush_needed = True - if flush_needed: - self.sa_session.flush() - for user in users: - self.associate_components(user=user, role=role) - for group in groups: - self.associate_components(group=group, role=role) - - def get_component_associations(self, **kwd): - assert len(kwd) == 2, 'You must specify exactly 2 Galaxy security components to check for associations.' - if 'dataset' in kwd: - if 'action' in kwd: - return self.sa_session.query(self.model.DatasetPermissions).filter_by(action=kwd['action'].action, dataset_id=kwd['dataset'].id).first() - elif 'user' in kwd: - if 'group' in kwd: - return self.sa_session.query(self.model.UserGroupAssociation).filter_by(group_id=kwd['group'].id, user_id=kwd['user'].id).first() - elif 'role' in kwd: - return self.sa_session.query(self.model.UserRoleAssociation).filter_by(role_id=kwd['role'].id, user_id=kwd['user'].id).first() - elif 'group' in kwd: - if 'role' in kwd: - return self.sa_session.query(self.model.GroupRoleAssociation).filter_by(role_id=kwd['role'].id, group_id=kwd['group'].id).first() - raise Exception('No valid method of associating provided components: %s' % kwd) - - def check_folder_contents(self, user, roles, folder, hidden_folder_ids=''): - """ - This method must always be sent an instance of LibraryFolder(). Recursive execution produces a - comma-separated string of folder ids whose folders do NOT meet the criteria for showing. Along - with the string, True is returned if the current user has permission to access folder. Otherwise, - cycle through all sub-folders in folder until one is found that meets this criteria, if it exists. - This method does not necessarily scan the entire library as it returns when it finds the first - folder that is accessible to user. - """ - # If a folder is writeable, it's accessable and we need not go further - if self.can_add_library_item(roles, folder): - return True, '' - action = self.permitted_actions.DATASET_ACCESS - - lddas = self.sa_session.query(self.model.LibraryDatasetDatasetAssociation) \ - .join("library_dataset") \ - .filter(self.model.LibraryDataset.folder == folder) \ - .join("dataset") \ - .options(eagerload_all("dataset.actions")) \ - .all() - - for ldda in lddas: - ldda_access_permissions = self.get_item_actions(action, ldda.dataset) - if not ldda_access_permissions: - # Dataset is public - return True, hidden_folder_ids - for ldda_access_permission in ldda_access_permissions: - if ldda_access_permission.role in roles: - # The current user has access permission on the dataset - return True, hidden_folder_ids - for sub_folder in folder.active_folders: - can_access, hidden_folder_ids = self.check_folder_contents(user, roles, sub_folder, hidden_folder_ids=hidden_folder_ids) - if can_access: - return True, hidden_folder_ids - if hidden_folder_ids: - hidden_folder_ids = '%s,%d' % (hidden_folder_ids, sub_folder.id) - else: - hidden_folder_ids = '%d' % sub_folder.id - return False, hidden_folder_ids - - -class HostAgent(RBACAgent): - """ - A simple security agent which allows access to datasets based on host. - This exists so that externals sites such as UCSC can gain access to - datasets which have permissions which would normally prevent such access. - """ - # TODO: Make sites user configurable - sites = Bunch( - ucsc_main=('hgw1.cse.ucsc.edu', 'hgw2.cse.ucsc.edu', 'hgw3.cse.ucsc.edu', 'hgw4.cse.ucsc.edu', - 'hgw5.cse.ucsc.edu', 'hgw6.cse.ucsc.edu', 'hgw7.cse.ucsc.edu', 'hgw8.cse.ucsc.edu'), - ucsc_test=('hgwdev.cse.ucsc.edu', ), - ucsc_archaea=('lowepub.cse.ucsc.edu', ) - ) - - def __init__(self, model, permitted_actions=None): - self.model = model - if permitted_actions: - self.permitted_actions = permitted_actions - - @property - def sa_session(self): - """Returns a SQLAlchemy session""" - return self.model.context - - def allow_action(self, addr, action, **kwd): - if 'dataset' in kwd and action == self.permitted_actions.DATASET_ACCESS: - hda = kwd['dataset'] - if action == self.permitted_actions.DATASET_ACCESS and action.action not in [dp.action for dp in hda.dataset.actions]: - log.debug('Allowing access to public dataset with hda: %i.' % hda.id) - return True # dataset has no roles associated with the access permission, thus is already public - hdadaa = self.sa_session.query(self.model.HistoryDatasetAssociationDisplayAtAuthorization) \ - .filter_by(history_dataset_association_id=hda.id).first() - if not hdadaa: - log.debug('Denying access to private dataset with hda: %i. No hdadaa record for this dataset.' % hda.id) - return False # no auth - # We could just look up the reverse of addr, but then we'd also - # have to verify it with the forward address and special case any - # IPs (instead of hosts) in the server list. - # - # This would be improved by caching, but that's what the OS's name - # service cache daemon is for (you ARE running nscd, right?). - for server in HostAgent.sites.get(hdadaa.site, []): - # We're going to search in order, but if the remote site is load - # balancing their connections (as UCSC does), this is okay. - try: - if socket.gethostbyname(server) == addr: - break # remote host is in the server list - except (socket.error, socket.gaierror): - pass # can't resolve, try next - else: - log.debug('Denying access to private dataset with hda: %i. Remote addr is not a valid server for site: %s.' % (hda.id, hdadaa.site)) - return False # remote addr is not in the server list - if (datetime.utcnow() - hdadaa.update_time) > timedelta(seconds=60): - log.debug('Denying access to private dataset with hda: %i. Authorization was granted, but has expired.' % hda.id) - return False # not authz'd in the last 60 seconds - log.debug('Allowing access to private dataset with hda: %i. Remote server is: %s.' % (hda.id, server)) - return True - else: - raise Exception('The dataset access permission is the only valid permission in the host security agent.') - - def set_dataset_permissions(self, hda, user, site): - hdadaa = self.sa_session.query(self.model.HistoryDatasetAssociationDisplayAtAuthorization) \ - .filter_by(history_dataset_association_id=hda.id).first() - if hdadaa: - hdadaa.update_time = datetime.utcnow() - else: - hdadaa = self.model.HistoryDatasetAssociationDisplayAtAuthorization(hda=hda, user=user, site=site) - self.sa_session.add(hdadaa) - self.sa_session.flush() - - def get_permitted_actions(filter=None): '''Utility method to return a subset of RBACAgent's permitted actions''' if filter is None: diff --git a/lib/galaxy/web/security/__init__.py b/lib/galaxy/security/idencoding.py similarity index 99% rename from lib/galaxy/web/security/__init__.py rename to lib/galaxy/security/idencoding.py index d7486aacb1b..1f9a15739fc 100644 --- a/lib/galaxy/web/security/__init__.py +++ b/lib/galaxy/security/idencoding.py @@ -18,7 +18,7 @@ MAXIMUM_ID_SECRET_LENGTH = int(MAXIMUM_ID_SECRET_BITS / 8) KIND_TOO_LONG_MESSAGE = "Galaxy coding error, keep encryption 'kinds' smaller to utilize more bites of randomness from id_secret values." -class SecurityHelper(object): +class IdEncodingHelper(object): def __init__(self, **config): id_secret = config['id_secret'] diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index 4570b68a68c..7e22d209b4d 100755 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -26,9 +26,10 @@ from galaxy import ( model ) from galaxy.managers.jobs import JobSearch -from galaxy.managers.tags import GalaxyTagManager from galaxy.metadata import get_metadata_compute_strategy +from galaxy.model.tags import GalaxyTagHandler from galaxy.queue_worker import send_control_task +from galaxy.tools import expressions from galaxy.tools.actions import DefaultToolAction from galaxy.tools.actions.data_manager import DataManagerToolAction from galaxy.tools.actions.data_source import DataSourceToolAction @@ -62,8 +63,10 @@ from galaxy.tools.parameters.dataset_matcher import ( from galaxy.tools.parameters.grouping import Conditional, ConditionalWhen, Repeat, Section, UploadDataset from galaxy.tools.parameters.input_translation import ToolInputTranslator from galaxy.tools.parameters.meta import expand_meta_parameters +from galaxy.tools.parameters.wrapped_json import json_wrap from galaxy.tools.parser import ( get_tool_source, + get_tool_source_from_representation, ToolOutputCollectionPart ) from galaxy.tools.parser.xml import XmlPageSource @@ -81,13 +84,12 @@ from galaxy.util import ( from galaxy.util.bunch import Bunch from galaxy.util.dictifiable import Dictifiable from galaxy.util.expressions import ExpressionContext +from galaxy.util.form_builder import SelectField from galaxy.util.json import safe_loads from galaxy.util.odict import odict from galaxy.util.rules_dsl import RuleSet from galaxy.util.template import fill_template from galaxy.version import VERSION_MAJOR -from galaxy.web import url_for -from galaxy.web.form_builder import SelectField from galaxy.work.context import WorkRequestContext from tool_shed.util import common_util from .execute import ( @@ -99,9 +101,13 @@ from .loader import ( raw_tool_xml_tree, template_macro_params ) +from .provided_metadata import parse_tool_provided_metadata log = logging.getLogger(__name__) +REQUIRES_JS_RUNTIME_MESSAGE = ("The tool [%s] requires a nodejs runtime to execute " + "but node or nodejs could not be found. Please contact the Galaxy adminstrator") + HELP_UNINITIALIZED = threading.Lock() MODEL_TOOLS_PATH = os.path.abspath(os.path.dirname(__file__)) # Tools that require Galaxy's Python environment to be preserved. @@ -274,6 +280,23 @@ class ToolBox(BaseGalaxyToolBox): def _create_tool_from_source(self, tool_source, **kwds): return create_tool_from_source(self.app, tool_source, **kwds) + def create_dynamic_tool(self, dynamic_tool, **kwds): + tool_format = dynamic_tool.tool_format + tool_representation = dynamic_tool.value + tool_source = get_tool_source_from_representation( + tool_format=tool_format, + tool_representation=tool_representation, + ) + kwds["dynamic"] = True + tool = self._create_tool_from_source(tool_source, **kwds) + tool.dynamic_tool = dynamic_tool + tool.uuid = dynamic_tool.uuid + if not tool.id: + tool.id = dynamic_tool.tool_id + if not tool.name: + tool.name = tool.id + return tool + def get_tool_components(self, tool_id, tool_version=None, get_loaded_tools_by_lineage=False, set_selected=False): """ Retrieve all loaded versions of a tool from the toolbox and return a select list enabling @@ -390,11 +413,16 @@ class Tool(Dictifiable): default_tool_action = DefaultToolAction dict_collection_visible_keys = ['id', 'name', 'version', 'description', 'labels'] - def __init__(self, config_file, tool_source, app, guid=None, repository_id=None, allow_code_files=True): + def __init__(self, config_file, tool_source, app, guid=None, repository_id=None, tool_shed_repository=None, allow_code_files=True, dynamic=False): """Load a tool from the config named by `config_file`""" # Determine the full path of the directory where the tool config is - self.config_file = config_file - self.tool_dir = os.path.dirname(config_file) + if config_file is not None: + self.config_file = config_file + self.tool_dir = os.path.dirname(config_file) + else: + self.config_file = None + self.tool_dir = None + self.app = app self.repository_id = repository_id self._allow_code_files = allow_code_files @@ -414,6 +442,8 @@ class Tool(Dictifiable): self.display_interface = True self.require_login = False self.rerun = False + # This will be non-None for tools loaded from the database (DynamicTool objects). + self.dynamic_tool = None # Define a place to keep track of all input These # differ from the inputs dictionary in that inputs can be page # elements like conditionals, but input_params are basic form @@ -436,13 +466,13 @@ class Tool(Dictifiable): self._lineage = None self.dependencies = [] # populate toolshed repository info, if available - self.populate_tool_shed_info() + self.populate_tool_shed_info(tool_shed_repository) # add tool resource parameters self.populate_resource_parameters(tool_source) self.tool_errors = None # Parse XML element containing configuration try: - self.parse(tool_source, guid=guid) + self.parse(tool_source, guid=guid, dynamic=dynamic) except Exception as e: global_tool_errors.add_error(config_file, "Tool Loading", e) raise e @@ -489,8 +519,8 @@ class Tool(Dictifiable): tool_shed=self.tool_shed, name=self.repository_name, owner=self.repository_owner, - installed_changeset_revision=self.installed_changeset_revision) - return None + installed_changeset_revision=self.installed_changeset_revision, + repository_id=self.repository_id) @property def produces_collections_with_unknown_structure(self): @@ -521,7 +551,7 @@ class Tool(Dictifiable): preserve_python_environment = config.preserve_python_environment if preserve_python_environment == "always": return True - elif preserve_python_environment == "legacy_and_local" and self.repository_id is None: + elif preserve_python_environment == "legacy_and_local" and self.tool_shed is None: return True else: unversioned_legacy_tool = self.old_id in GALAXY_LIB_TOOLS_UNVERSIONED @@ -591,7 +621,7 @@ class Tool(Dictifiable): return False return True - def parse(self, tool_source, guid=None): + def parse(self, tool_source, guid=None, dynamic=False): """ Read tool configuration from the element `root` and fill in `self`. """ @@ -602,7 +632,8 @@ class Tool(Dictifiable): self.id = self.old_id else: self.id = guid - if not self.id: + + if not dynamic and not self.id: raise Exception("Missing tool 'id' for tool at '%s'" % tool_source) profile = packaging.version.parse(str(self.profile)) @@ -613,7 +644,9 @@ class Tool(Dictifiable): # Get the (user visible) name of the tool self.name = tool_source.parse_name() - if not self.name: + if not self.name and dynamic: + self.name = self.id + if not dynamic and not self.name: raise Exception("Missing tool 'name' for tool with id '%s' at '%s'" % (self.id, tool_source)) self.version = tool_source.parse_version() @@ -697,10 +730,13 @@ class Tool(Dictifiable): # a 'default' will be provided that uses the 'default' handler and # 'default' destination. I thought about moving this to the # job_config, but it makes more sense to store here. -nate - self_ids = [self.id.lower()] - if self.old_id != self.id: - # Handle toolshed guids - self_ids = [self.id.lower(), self.id.lower().rsplit('/', 1)[0], self.old_id.lower()] + if self.id: + self_ids = [self.id.lower()] + if self.old_id != self.id: + # Handle toolshed guids + self_ids = [self.id.lower(), self.id.lower().rsplit('/', 1)[0], self.old_id.lower()] + else: + self_ids = [] self.all_ids = self_ids # In the toolshed context, there is no job config. @@ -747,6 +783,12 @@ class Tool(Dictifiable): module, cls = action mod = __import__(module, globals(), locals(), [cls]) self.tool_action = getattr(mod, cls)() + if getattr(self.tool_action, "requires_js_runtime", False): + try: + expressions.find_engine(self.app.config) + except Exception: + message = REQUIRES_JS_RUNTIME_MESSAGE % self.tool_id or self.tool_uuid + raise Exception(message) # Tests self.__parse_tests(tool_source) @@ -850,7 +892,7 @@ class Tool(Dictifiable): """If tool shed installed tool, the base directory of the repository installed.""" repository_dir = None - if hasattr(self, 'tool_shed') and self.tool_shed: + if getattr(self, 'tool_shed', None): repository_dir = self.tool_dir while True: repository_dir_name = os.path.basename(repository_dir) @@ -897,12 +939,7 @@ class Tool(Dictifiable): def tool_provided_metadata(self, job_wrapper): meta_file = os.path.join(job_wrapper.tool_working_directory, self.provided_metadata_file) - if not os.path.exists(meta_file): - return output_collect.NullToolProvidedMetadata() - if self.provided_metadata_style == "legacy": - return output_collect.LegacyToolProvidedMetadata(job_wrapper, meta_file) - else: - return output_collect.ToolProvidedMetadata(job_wrapper, meta_file) + return parse_tool_provided_metadata(meta_file, provided_metadata_style=self.provided_metadata_style, job_wrapper=job_wrapper) def parse_command(self, tool_source): """ @@ -1172,18 +1209,16 @@ class Tool(Dictifiable): root.append(inputs) inputs.append(resource_xml) - def populate_tool_shed_info(self): - if self.repository_id is not None and self.app.name == 'galaxy': - repository_id = self.app.security.decode_id(self.repository_id) - if hasattr(self.app, 'tool_shed_repository_cache'): - tool_shed_repository = self.app.tool_shed_repository_cache.get_installed_repository(repository_id=repository_id) - if tool_shed_repository: - self.tool_shed = tool_shed_repository.tool_shed - self.repository_name = tool_shed_repository.name - self.repository_owner = tool_shed_repository.owner - self.changeset_revision = tool_shed_repository.changeset_revision - self.installed_changeset_revision = tool_shed_repository.installed_changeset_revision - self.sharable_url = tool_shed_repository.get_sharable_url(self.app) + def populate_tool_shed_info(self, tool_shed_repository): + if tool_shed_repository: + self.tool_shed = tool_shed_repository.tool_shed + self.repository_name = tool_shed_repository.name + self.repository_owner = tool_shed_repository.owner + self.changeset_revision = tool_shed_repository.changeset_revision + self.installed_changeset_revision = tool_shed_repository.installed_changeset_revision + self.sharable_url = common_util.get_tool_shed_repository_url( + self.app, self.tool_shed, self.repository_owner, self.repository_name + ) @property def help(self): @@ -1206,16 +1241,16 @@ class Tool(Dictifiable): tool_source = self.__help_source self.__help = None self.__help_by_page = [] - help_header = "" help_footer = "" help_text = tool_source.parse_help() if help_text is not None: - if self.repository_id and help_text.find('.. image:: ') >= 0: - # Handle tool help image display for tools that are contained in repositories in the tool shed or installed into Galaxy. - try: - help_text = tool_shed.util.shed_util_common.set_image_paths(self.app, self.repository_id, help_text) - except Exception: - log.exception("Exception in parse_help, so images may not be properly displayed") + try: + if help_text.find('.. image:: ') >= 0 and (self.tool_shed_repository or self.repository_id): + help_text = tool_shed.util.shed_util_common.set_image_paths( + self.app, help_text, encoded_repository_id=self.repository_id, tool_shed_repository=self.tool_shed_repository, tool_id=self.old_id, tool_version=self.version + ) + except Exception: + log.exception("Exception in parse_help, so images may not be properly displayed") try: self.__help = Template(rst_to_html(help_text), input_encoding='utf-8', default_filters=['decode.utf8'], @@ -1687,17 +1722,36 @@ class Tool(Dictifiable): """ pass - def collect_primary_datasets(self, output, tool_provided_metadata, job_working_directory, input_ext, input_dbkey="?"): + def discover_outputs(self, out_data, out_collections, tool_provided_metadata, tool_working_directory, job, input_ext, input_dbkey, inp_data=None): """ Find any additional datasets generated by a tool and attach (for cases where number of outputs is not known in advance). """ - return output_collect.collect_primary_datasets(self, output, tool_provided_metadata, job_working_directory, input_ext, input_dbkey=input_dbkey) - - def collect_dynamic_outputs(self, output, tool_provided_metadata, **kwds): - """Collect dynamic outputs associated with a job from this tool. - """ - return output_collect.collect_dynamic_outputs(self, output, tool_provided_metadata, **kwds) + tool = self + permission_provider = output_collect.PermissionProvider(inp_data, tool.app.security_agent, job) + metadata_source_provider = output_collect.MetadataSourceProvider(inp_data) + job_context = output_collect.JobContext( + tool, + tool_provided_metadata, + job, + tool_working_directory, + permission_provider, + metadata_source_provider, + input_dbkey, + object_store=tool.app.object_store, + ) + collected = output_collect.collect_primary_datasets( + job_context, + out_data, + input_ext, + ) + output_collect.collect_dynamic_outputs( + job_context, + out_collections, + ) + # Return value only used in unit tests. Probably should be returning number of collected + # bytes instead? + return collected def to_archive(self): tool = self @@ -1822,15 +1876,16 @@ class Tool(Dictifiable): # If an admin user, expose the path to the actual tool config XML file. if trans.user_is_admin: - tool_dict['config_file'] = os.path.abspath(self.config_file) + config_file = None if not self.config_file else os.path.abspath(self.config_file) + tool_dict['config_file'] = config_file # Add link details. if link_details: # Add details for creating a hyperlink to the tool. if not isinstance(self, DataSourceTool): - link = url_for(controller='tool_runner', tool_id=self.id) + link = self.app.url_for(controller='tool_runner', tool_id=self.id) else: - link = url_for(controller='tool_runner', action='data_source_redirect', tool_id=self.id) + link = self.app.url_for(controller='tool_runner', action='data_source_redirect', tool_id=self.id) # Basic information tool_dict.update({'link': link, @@ -1914,7 +1969,7 @@ class Tool(Dictifiable): # create tool help tool_help = '' if self.help: - tool_help = self.help.render(static_path=url_for('/static'), host_url=url_for('/', qualified=True)) + tool_help = self.help.render(static_path=self.app.url_for('/static'), host_url=self.app.url_for('/', qualified=True)) tool_help = unicodify(tool_help, 'utf-8') # update tool model @@ -1922,7 +1977,6 @@ class Tool(Dictifiable): 'id' : self.id, 'help' : tool_help, 'citations' : bool(self.citations), - 'biostar_url' : self.app.config.biostar_url, 'sharable_url' : self.sharable_url, 'message' : tool_message, 'warnings' : tool_warnings, @@ -1935,7 +1989,7 @@ class Tool(Dictifiable): 'job_remap' : self._get_job_remap(job), 'history_id' : trans.security.encode_id(history.id) if history else None, 'display' : self.display_interface, - 'action' : url_for(self.action), + 'action' : self.app.url_for(self.action), 'method' : self.method, 'enctype' : self.enctype }) @@ -2166,6 +2220,72 @@ class OutputParameterJSONTool(Tool): out.close() +class ExpressionTool(Tool): + requires_js_runtime = True + tool_type = 'expression' + EXPRESSION_INPUTS_NAME = "_expression_inputs_.json" + + def parse_command(self, tool_source): + self.command = "cd ../; %s" % expressions.EXPRESSION_SCRIPT_CALL + self.interpreter = None + self._expression = tool_source.parse_expression().strip() + + def parse_outputs(self, tool_source): + # Setup self.outputs and self.output_collections + super(ExpressionTool, self).parse_outputs(tool_source) + + # Validate these outputs for expression tools. + if len(self.output_collections) != 0: + message = "Expression tools may not declare output collections at this time." + raise Exception(message) + for output in self.outputs.values(): + if not hasattr(output, "from_expression"): + message = "Expression tools may not declare output datasets at this time." + raise Exception(message) + + def exec_before_job(self, app, inp_data, out_data, param_dict=None): + super(ExpressionTool, self).exec_before_job(app, inp_data, out_data, param_dict=param_dict) + local_working_directory = param_dict["__local_working_directory__"] + expression_inputs_path = os.path.join(local_working_directory, ExpressionTool.EXPRESSION_INPUTS_NAME) + + outputs = [] + for i, (out_name, data) in enumerate(out_data.items()): + output_def = self.outputs[out_name] + wrapped_data = param_dict.get(out_name) + file_name = str(wrapped_data) + + outputs.append(dict( + name=out_name, + from_expression=output_def.from_expression, + path=file_name, + )) + + if param_dict is None: + raise Exception("Internal error - param_dict is empty.") + + job = {} + json_wrap(self.inputs, param_dict, job, handle_files='OBJECT') + expression_inputs = { + 'job': job, + 'script': self._expression, + 'outputs': outputs, + } + expressions.write_evalute_script(os.path.join(local_working_directory)) + with open(expression_inputs_path, "w") as f: + json.dump(expression_inputs, f) + + def parse_environment_variables(self, tool_source): + """ Setup environment variable for inputs file. + """ + environmnt_variables_raw = super(ExpressionTool, self).parse_environment_variables(tool_source) + expression_script_inputs = dict( + name="GALAXY_EXPRESSION_INPUTS", + template=ExpressionTool.EXPRESSION_INPUTS_NAME, + ) + environmnt_variables_raw.append(expression_script_inputs) + return environmnt_variables_raw + + class DataSourceTool(OutputParameterJSONTool): """ Alternate implementation of Tool for data_source tools -- those that @@ -2264,7 +2384,7 @@ class SetMetadataTool(Tool): for name, dataset in inp_data.items(): external_metadata = get_metadata_compute_strategy(app, job.id) sa_session = app.model.context - if external_metadata.external_metadata_set_successfully(dataset, sa_session): + if external_metadata.external_metadata_set_successfully(dataset, name, sa_session, working_directory=working_directory): external_metadata.load_metadata(dataset, name, sa_session, working_directory=working_directory) else: dataset._state = model.Dataset.states.FAILED_METADATA @@ -2414,10 +2534,11 @@ class DatabaseOperationTool(Tool): map(check_dataset_instance, input_dataset_collection.dataset_instances) - def _add_datasets_to_history(self, history, elements): + def _add_datasets_to_history(self, history, elements, datasets_visible=False): datasets = [] for element_object in elements: if getattr(element_object, "history_content_type", None) == "dataset": + element_object.visible = datasets_visible datasets.append(element_object) if datasets: @@ -2509,8 +2630,7 @@ class ExtractDatasetCollectionTool(DatabaseOperationTool): raise Exception("Invalid tool parameters.") extracted = extracted_element.element_object extracted_o = extracted.copy(copy_tags=tags, new_name=extracted_element.element_identifier) - extracted_o.visible = True - self._add_datasets_to_history(history, [extracted_o]) + self._add_datasets_to_history(history, [extracted_o], datasets_visible=True) out_data["output"] = extracted_o @@ -2589,7 +2709,6 @@ class MergeCollectionTool(DatabaseOperationTool): for key, value in new_element_structure.items(): if getattr(value, "history_content_type", None) == "dataset": copied_value = value.copy(force_flush=False) - copied_value.visible = False else: copied_value = value.copy() new_elements[key] = copied_value @@ -2608,7 +2727,6 @@ class FilterDatasetsTool(DatabaseOperationTool): element_identifier = dce.element_identifier if getattr(dce.element_object, "history_content_type", None) == "dataset": copied_value = dce.element_object.copy(force_flush=False) - copied_value.visible = False else: copied_value = dce.element_object.copy() new_elements[element_identifier] = copied_value @@ -2684,7 +2802,6 @@ class FlattenTool(DatabaseOperationTool): add_elements(dce_object, prefix=identifier) else: copied_dataset = dce_object.copy(force_flush=False) - copied_dataset.visible = False new_elements[identifier] = copied_dataset copied_datasets.append(copied_dataset) @@ -2712,7 +2829,8 @@ class SortTool(DatabaseOperationTool): sorted_elements = [x[1] for x in sorted(presort_elements, key=lambda x: x[0])] if sorttype == 'file': hda = incoming["sort_type"]["sort_file"] - if hda.metadata and hda.metadata.get('data_lines', 0) == len(elements): + data_lines = hda.metadata.get('data_lines', 0) + if data_lines == len(elements): old_elements_dict = OrderedDict() for element in elements: old_elements_dict[element.element_identifier] = element @@ -2722,11 +2840,13 @@ class SortTool(DatabaseOperationTool): hdca_history_name = "%s: %s" % (hdca.hid, hdca.name) message = "List of element identifiers does not match element identifiers in collection '%s'" % hdca_history_name raise Exception(message) + else: + message = "Number of lines must match number of list elements (%i), but file has %i lines" + raise Exception(message % (data_lines, len(elements))) for dce in sorted_elements: dce_object = dce.element_object copied_dataset = dce_object.copy(force_flush=False) - copied_dataset.visible = False new_elements[dce.element_identifier] = copied_dataset self._add_datasets_to_history(history, itervalues(new_elements)) @@ -2751,7 +2871,6 @@ class RelabelFromFileTool(DatabaseOperationTool): raise Exception("New identifier [%s] appears twice in resulting collection, these values must be unique." % new_label) if getattr(dce_object, "history_content_type", None) == "dataset": copied_value = dce_object.copy(force_flush=False) - copied_value.visible = False else: copied_value = dce_object.copy() new_elements[new_label] = copied_value @@ -2797,7 +2916,6 @@ class ApplyRulesTool(DatabaseOperationTool): def copy_dataset(dataset): copied_dataset = dataset.copy(force_flush=False) - copied_dataset.visible = False copied_datasets.append(copied_dataset) return copied_dataset @@ -2818,7 +2936,7 @@ class TagFromFileTool(DatabaseOperationTool): how = incoming['how'] new_tags_dataset_assoc = incoming["tags"] new_elements = odict() - tags_manager = GalaxyTagManager(trans.app.model.context) + tags_manager = GalaxyTagHandler(trans.app.model.context) new_datasets = [] def add_copied_value_to_new_elements(new_tags_dict, dce): @@ -2894,7 +3012,6 @@ class FilterFromFileTool(DatabaseOperationTool): if getattr(dce_object, "history_content_type", None) == "dataset": copied_value = dce_object.copy(force_flush=False) - copied_value.visible = False else: copied_value = dce_object.copy() @@ -2915,7 +3032,7 @@ class FilterFromFileTool(DatabaseOperationTool): # Populate tool_type to ToolClass mappings tool_types = {} -for tool_class in [Tool, SetMetadataTool, OutputParameterJSONTool, +for tool_class in [Tool, SetMetadataTool, OutputParameterJSONTool, ExpressionTool, DataManagerTool, DataSourceTool, AsyncDataSourceTool, UnzipCollectionTool, ZipCollectionTool, MergeCollectionTool, RelabelFromFileTool, FilterFromFileTool, BuildListCollectionTool, ExtractDatasetCollectionTool, diff --git a/lib/galaxy/tools/actions/__init__.py b/lib/galaxy/tools/actions/__init__.py index 0ed09692a53..598071e70b8 100644 --- a/lib/galaxy/tools/actions/__init__.py +++ b/lib/galaxy/tools/actions/__init__.py @@ -8,6 +8,7 @@ from six import string_types from galaxy import model from galaxy.jobs.actions.post import ActionBox from galaxy.model import LibraryDatasetDatasetAssociation, WorkflowRequestInputParameter +from galaxy.model.dataset_collections.builder import CollectionBuilder from galaxy.objectstore import ObjectStorePopulator from galaxy.tools.parameters import update_dataset_ids from galaxy.tools.parameters.basic import DataCollectionToolParameter, DataToolParameter, RuntimeValue @@ -158,8 +159,10 @@ class DefaultToolAction(object): return collection = None + child_collection = False if hasattr(value, 'child_collection'): # if we are mapping a collection over a tool, we only require the child_collection + child_collection = True collection = value.child_collection else: # else the tool takes a collection as input so we need everything @@ -171,11 +174,27 @@ class DefaultToolAction(object): for action, role_id in action_tuples: record_permission(action, role_id) + replace_collection = False + processed_dataset_dict = {} for i, v in enumerate(collection.dataset_instances): - # Skipping implicit conversion stuff for now, revisit at - # some point and figure out if implicitly converting a - # dataset collection makes senese. - input_datasets[prefix + input.name + str(i + 1)] = v + processed_dataset = process_dataset(v) + if processed_dataset is not v: + replace_collection = True + processed_dataset_dict[v] = processed_dataset + input_datasets[prefix + input.name + str(i + 1)] = processed_dataset + + if replace_collection: + collection_type_description = trans.app.dataset_collections_service.collection_type_descriptions.for_collection_type(collection.collection_type) + collection_builder = CollectionBuilder(collection_type_description) + collection_builder.replace_elements_in_collection( + template_collection=collection, + replacement_dict=processed_dataset_dict, + ) + new_collection = collection_builder.build() + if child_collection: + value.child_collection = new_collection + else: + value.collection = new_collection tool.visit_inputs(param_values, visitor) return input_datasets, all_permissions @@ -666,6 +685,7 @@ class DefaultToolAction(object): job.tool_version = tool.version except AttributeError: job.tool_version = "1.0.0" + job.dynamic_tool = tool.dynamic_tool return job, galaxy_session def _record_inputs(self, trans, tool, job, incoming, inp_data, inp_dataset_collections): @@ -900,6 +920,16 @@ def filter_output(output, incoming): return False +def get_ext_or_implicit_ext(hda): + if hda.implicitly_converted_parent_datasets: + # implicitly_converted_parent_datasets is a list of ImplicitlyConvertedDatasetAssociation + # objects, and their type is the target_ext, so this should be correct even if there + # are multiple ImplicitlyConvertedDatasetAssociation objects (meaning 2 datasets had been converted + # to produce a dataset with the required datatype) + return hda.implicitly_converted_parent_datasets[0].type + return hda.ext + + def determine_output_format(output, parameter_context, input_datasets, input_dataset_collections, random_input_ext): """ Determines the output format for a dataset based on an abstract description of the output (galaxy.tools.parser.ToolOutput), the parameter @@ -917,11 +947,12 @@ def determine_output_format(output, parameter_context, input_datasets, input_dat if format_source is not None and format_source in input_datasets: try: input_dataset = input_datasets[output.format_source] - input_extension = input_dataset.ext - ext = input_extension + ext = get_ext_or_implicit_ext(input_dataset) except Exception: pass elif format_source is not None: + element_index = None + collection_name = format_source if re.match(r"^[^\[\]]*\[[^\[\]]*\]$", format_source): collection_name, element_index = format_source[0:-1].split("[") # Treat as json to interpret "forward" vs 0 with type @@ -929,10 +960,14 @@ def determine_output_format(output, parameter_context, input_datasets, input_dat element_index = element_index.replace("'", '"') element_index = json.loads(element_index) - if collection_name in input_dataset_collections: - try: - input_collection = input_dataset_collections[collection_name][0][0] - input_collection_collection = input_collection.collection + if collection_name in input_dataset_collections: + try: + input_collection = input_dataset_collections[collection_name][0][0] + input_collection_collection = input_collection.collection + if element_index is None: + # just pick the first HDA + input_dataset = input_collection_collection.dataset_instances[0] + else: try: input_element = input_collection_collection[element_index] except KeyError: @@ -941,11 +976,10 @@ def determine_output_format(output, parameter_context, input_datasets, input_dat input_element = element break input_dataset = input_element.element_object - input_extension = input_dataset.ext - ext = input_extension - except Exception as e: - log.debug("Exception while trying to determine format_source: %s", e) - pass + ext = get_ext_or_implicit_ext(input_dataset) + except Exception as e: + log.debug("Exception while trying to determine format_source: %s", e) + pass # process change_format tags if output.change_format is not None: diff --git a/lib/galaxy/tools/actions/history_imp_exp.py b/lib/galaxy/tools/actions/history_imp_exp.py index 1ed267d0495..e9bdbb218e3 100644 --- a/lib/galaxy/tools/actions/history_imp_exp.py +++ b/lib/galaxy/tools/actions/history_imp_exp.py @@ -119,8 +119,8 @@ class ExportHistoryToolAction(ToolAction): compressed=incoming['compress']) trans.sa_session.add(jeha) - job_wrapper = JobExportHistoryArchiveWrapper(job) - cmd_line = job_wrapper.setup_job(trans, jeha, include_hidden=incoming['include_hidden'], + job_wrapper = JobExportHistoryArchiveWrapper(trans.app, job) + cmd_line = job_wrapper.setup_job(jeha, include_hidden=incoming['include_hidden'], include_deleted=incoming['include_deleted']) # diff --git a/lib/galaxy/tools/actions/metadata.py b/lib/galaxy/tools/actions/metadata.py index 2b0afc46dec..f82206a8249 100644 --- a/lib/galaxy/tools/actions/metadata.py +++ b/lib/galaxy/tools/actions/metadata.py @@ -18,8 +18,11 @@ class SetMetadataToolAction(ToolAction): Execute using a web transaction. """ trans.check_user_activation() - job, odict = self.execute_via_app(tool, trans.app, trans.get_galaxy_session().id, - trans.history.id, trans.user, incoming, set_output_hid, + session = trans.get_galaxy_session() + session_id = session and session.id + history_id = trans.history and trans.history.id + job, odict = self.execute_via_app(tool, trans.app, session_id, + history_id, trans.user, incoming, set_output_hid, overwrite, history, job_params) # FIXME: can remove this when logging in execute_via_app method. trans.log_event("Added set external metadata job to the job queue, id: %s" % str(job.id), tool_id=job.tool_id) @@ -62,6 +65,7 @@ class SetMetadataToolAction(ToolAction): job.tool_version = tool.version except AttributeError: job.tool_version = "1.0.1" + job.dynamic_tool = tool.dynamic_tool job.state = job.states.WAITING # we need to set job state to something other than NEW, or else when tracking jobs in db it will be picked up before we have added input / output parameters sa_session.add(job) sa_session.flush() # ensure job.id is available @@ -87,7 +91,7 @@ class SetMetadataToolAction(ToolAction): config_root=app.config.root, config_file=app.config.config_file, datatypes_config=datatypes_config, - job_metadata=None, + job_metadata=os.path.join(job_working_dir, 'working', tool.provided_metadata_file), include_command=False, max_metadata_value_size=app.config.max_metadata_value_size, kwds={'overwrite': overwrite}) diff --git a/lib/galaxy/tools/actions/upload.py b/lib/galaxy/tools/actions/upload.py index 2d31bb1aa54..a9dc9c807e5 100644 --- a/lib/galaxy/tools/actions/upload.py +++ b/lib/galaxy/tools/actions/upload.py @@ -2,8 +2,8 @@ import json import logging import os -from galaxy.dataset_collections.structure import UninitializedTree from galaxy.exceptions import RequestParameterMissingException +from galaxy.model.dataset_collections.structure import UninitializedTree from galaxy.tools.actions import upload_common from galaxy.util import ExecutionTimer from galaxy.util.bunch import Bunch diff --git a/lib/galaxy/tools/actions/upload_common.py b/lib/galaxy/tools/actions/upload_common.py index 839c5a42f73..f24f11cf868 100644 --- a/lib/galaxy/tools/actions/upload_common.py +++ b/lib/galaxy/tools/actions/upload_common.py @@ -14,7 +14,7 @@ from webob.compat import cgi_FieldStorage from galaxy import datatypes, util from galaxy.exceptions import ConfigDoesNotAllowException, ObjectInvalid -from galaxy.managers import tags +from galaxy.model import tags from galaxy.util import unicodify from galaxy.util.odict import odict @@ -220,7 +220,7 @@ def __new_library_upload(trans, cntrller, uploaded_dataset, library_bunch, state sa_session=trans.sa_session) if uploaded_dataset.get('tag_using_filenames', False): tag_from_filename = os.path.splitext(os.path.basename(uploaded_dataset.name))[0] - tag_manager = tags.GalaxyTagManager(trans.sa_session) + tag_manager = tags.GalaxyTagHandler(trans.sa_session) tag_manager.apply_item_tag(item=ldda, user=trans.user, name='name', value=tag_from_filename) trans.sa_session.add(ldda) @@ -398,6 +398,7 @@ def create_job(trans, params, tool, json_file_path, outputs, folder=None, histor job.history_id = history.id job.tool_id = tool.id job.tool_version = tool.version + job.dynamic_tool = tool.dynamic_tool job.set_state(job.states.UPLOAD) trans.sa_session.add(job) trans.sa_session.flush() diff --git a/lib/galaxy/tools/cache.py b/lib/galaxy/tools/cache.py index b4acbab65b4..d03445c43da 100644 --- a/lib/galaxy/tools/cache.py +++ b/lib/galaxy/tools/cache.py @@ -1,10 +1,16 @@ +import logging import os -from threading import local +from threading import ( + local, + Lock, +) from sqlalchemy.orm.exc import DetachedInstanceError from galaxy.util.hash_util import md5_hash_file +log = logging.getLogger(__name__) + class ToolCache(object): """ @@ -13,11 +19,11 @@ class ToolCache(object): """ def __init__(self): + self._lock = Lock() self._hash_by_tool_paths = {} self._tools_by_path = {} self._tool_paths_by_id = {} self._macro_paths_by_id = {} - self._tool_ids_by_macro_paths = {} self._mod_time_by_path = {} self._new_tool_ids = set() self._removed_tool_ids = set() @@ -31,26 +37,30 @@ class ToolCache(object): """ removed_tool_ids = [] try: - paths_to_cleanup = {path: tool.all_ids for path, tool in self._tools_by_path.items() if self._should_cleanup(path)} - for config_filename, tool_ids in paths_to_cleanup.items(): - del self._hash_by_tool_paths[config_filename] - if os.path.exists(config_filename): - # This tool has probably been broken while editing on disk - # We record it here, so that we can recover it - self._removed_tools_by_path[config_filename] = self._tools_by_path[config_filename] - del self._tools_by_path[config_filename] - for tool_id in tool_ids: - if tool_id in self._tool_paths_by_id: - del self._tool_paths_by_id[tool_id] - removed_tool_ids.extend(tool_ids) - for tool_id in removed_tool_ids: - self._removed_tool_ids.add(tool_id) - if tool_id in self._new_tool_ids: - self._new_tool_ids.remove(tool_id) - except Exception: + with self._lock: + paths_to_cleanup = {path: tool.all_ids for path, tool in self._tools_by_path.items() if self._should_cleanup(path)} + for config_filename, tool_ids in paths_to_cleanup.items(): + del self._hash_by_tool_paths[config_filename] + if os.path.exists(config_filename): + # This tool has probably been broken while editing on disk + # We record it here, so that we can recover it + self._removed_tools_by_path[config_filename] = self._tools_by_path[config_filename] + del self._tools_by_path[config_filename] + for tool_id in tool_ids: + if tool_id in self._tool_paths_by_id: + del self._tool_paths_by_id[tool_id] + removed_tool_ids.extend(tool_ids) + for tool_id in removed_tool_ids: + self._removed_tool_ids.add(tool_id) + if tool_id in self._new_tool_ids: + self._new_tool_ids.remove(tool_id) + except Exception as e: + log.debug("Exception while checking tools to remove from cache: %s" % e) # If by chance the file is being removed while calculating the hash or modtime # we don't want the thread to die. pass + if removed_tool_ids: + log.debug("Removed the following tools from cache: %s" % removed_tool_ids) return removed_tool_ids def _should_cleanup(self, config_filename): @@ -80,43 +90,42 @@ class ToolCache(object): return self.get_tool(self._tool_paths_by_id.get(tool_id)) def expire_tool(self, tool_id): - if tool_id in self._tool_paths_by_id: - config_filename = self._tool_paths_by_id[tool_id] - del self._hash_by_tool_paths[config_filename] - del self._tool_paths_by_id[tool_id] - del self._tools_by_path[config_filename] - del self._mod_time_by_path[config_filename] - if tool_id in self._new_tool_ids: - self._new_tool_ids.remove(tool_id) + with self._lock: + if tool_id in self._tool_paths_by_id: + config_filename = self._tool_paths_by_id[tool_id] + del self._hash_by_tool_paths[config_filename] + del self._tool_paths_by_id[tool_id] + del self._tools_by_path[config_filename] + del self._mod_time_by_path[config_filename] + if tool_id in self._new_tool_ids: + self._new_tool_ids.remove(tool_id) def cache_tool(self, config_filename, tool): tool_hash = md5_hash_file(config_filename) if tool_hash is None: return tool_id = str(tool.id) - self._hash_by_tool_paths[config_filename] = tool_hash - self._mod_time_by_path[config_filename] = os.path.getmtime(config_filename) - self._tool_paths_by_id[tool_id] = config_filename - self._tools_by_path[config_filename] = tool - self._new_tool_ids.add(tool_id) - for macro_path in tool._macro_paths: - self._mod_time_by_path[macro_path] = os.path.getmtime(macro_path) - if tool_id not in self._macro_paths_by_id: - self._macro_paths_by_id[tool_id] = {macro_path} - else: - self._macro_paths_by_id[tool_id].add(macro_path) - if macro_path not in self._macro_paths_by_id: - self._tool_ids_by_macro_paths[macro_path] = {tool_id} - else: - self._tool_ids_by_macro_paths[macro_path].add(tool_id) + with self._lock: + self._hash_by_tool_paths[config_filename] = tool_hash + self._mod_time_by_path[config_filename] = os.path.getmtime(config_filename) + self._tool_paths_by_id[tool_id] = config_filename + self._tools_by_path[config_filename] = tool + self._new_tool_ids.add(tool_id) + for macro_path in tool._macro_paths: + self._mod_time_by_path[macro_path] = os.path.getmtime(macro_path) + if tool_id not in self._macro_paths_by_id: + self._macro_paths_by_id[tool_id] = {macro_path} + else: + self._macro_paths_by_id[tool_id].add(macro_path) def reset_status(self): """ Reset tracking of new and newly disabled tools. """ - self._new_tool_ids = set() - self._removed_tool_ids = set() - self._removed_tools_by_path = {} + with self._lock: + self._new_tool_ids = set() + self._removed_tool_ids = set() + self._removed_tools_by_path = {} class ToolShedRepositoryCache(object): @@ -128,6 +137,9 @@ class ToolShedRepositoryCache(object): self.app = app self.cache = local() + def add_local_repository(self, repository): + self.cache.repositories.append(repository) + @property def tool_shed_repositories(self): try: diff --git a/lib/galaxy/tools/data_fetch.py b/lib/galaxy/tools/data_fetch.py index fca2da18067..60a010e3b38 100644 --- a/lib/galaxy/tools/data_fetch.py +++ b/lib/galaxy/tools/data_fetch.py @@ -17,6 +17,7 @@ from galaxy.datatypes.upload_util import ( ) from galaxy.util import in_directory from galaxy.util.compression_utils import CompressedFile +from galaxy.util.hash_util import HASH_NAMES, memory_bound_hexdigest DESCRIPTION = """Data Import Script""" @@ -60,16 +61,16 @@ def _fetch_target(upload_config, target): items = None if elements_from: if elements_from == "archive": - decompressed_directory = _decompress_target(target_or_item) + decompressed_directory = _decompress_target(upload_config, target_or_item) items = _directory_to_items(decompressed_directory) elif elements_from == "bagit": - _, elements_from_path = _has_src_to_path(target_or_item) + _, elements_from_path = _has_src_to_path(upload_config, target_or_item, is_dataset=False) items = _bagit_to_items(elements_from_path) elif elements_from == "bagit_archive": - decompressed_directory = _decompress_target(target_or_item) + decompressed_directory = _decompress_target(upload_config, target_or_item) items = _bagit_to_items(decompressed_directory) elif elements_from == "directory": - _, elements_from_path = _has_src_to_path(target_or_item) + _, elements_from_path = _has_src_to_path(upload_config, target_or_item, is_dataset=False) items = _directory_to_items(elements_from_path) else: raise Exception("Unknown elements from type encountered [%s]" % elements_from) @@ -92,7 +93,19 @@ def _fetch_target(upload_config, target): def _resolve_src(item): converted_path = None - name, path = _has_src_to_path(item) + name, path = _has_src_to_path(upload_config, item, is_dataset=True) + sources = [] + + url = item.get("url") + if url: + sources.append({"source_uri": url}) + hashes = [] + for hash_function in HASH_NAMES: + hash_value = item.get(hash_function) + if hash_value: + hashes.append({"hash_function": hash_function, "hash_value": hash_value}) + _handle_hash_validation(upload_config, hash_function, hash_value, path) + dbkey = item.get("dbkey", "?") requested_ext = item.get("ext", "auto") info = item.get("info", None) @@ -143,7 +156,7 @@ def _fetch_target(upload_config, target): # Groom the dataset content if necessary datatype.groom_dataset_content(path) - rval = {"name": name, "filename": path, "dbkey": dbkey, "ext": ext, "link_data_only": link_data_only} + rval = {"name": name, "filename": path, "dbkey": dbkey, "ext": ext, "link_data_only": link_data_only, "sources": sources, "hashes": hashes} if info is not None: rval["info"] = info if object_id is not None: @@ -165,8 +178,8 @@ def _bagit_to_items(directory): return items -def _decompress_target(target): - elements_from_name, elements_from_path = _has_src_to_path(target) +def _decompress_target(upload_config, target): + elements_from_name, elements_from_path = _has_src_to_path(upload_config, target, is_dataset=False) temp_directory = tempfile.mkdtemp(prefix=elements_from_name, dir=".") decompressed_directory = CompressedFile(elements_from_path).extract(temp_directory) return decompressed_directory @@ -202,13 +215,20 @@ def _directory_to_items(directory): return items -def _has_src_to_path(item): +def _has_src_to_path(upload_config, item, is_dataset=False): assert "src" in item, item src = item.get("src") name = item.get("name") if src == "url": url = item.get("url") path = sniff.stream_url_to_file(url) + if not is_dataset: + # Actual target dataset will validate and put results in dict + # that gets passed back to Galaxy. + for hash_function in HASH_NAMES: + hash_value = item.get(hash_function) + if hash_value: + _handle_hash_validation(upload_config, hash_function, hash_value, path) if name is None: name = url.split("/")[-1] elif src == "pasted": @@ -223,6 +243,13 @@ def _has_src_to_path(item): return name, path +def _handle_hash_validation(upload_config, hash_function, hash_value, path): + if upload_config.validate_hashes: + calculated_hash_value = memory_bound_hexdigest(hash_func_name=hash_function, path=path) + if calculated_hash_value != hash_value: + raise Exception("Failed to validate upload with [%s] - expected [%s] got [%s]" % (hash_function, hash_value, calculated_hash_value)) + + def _arg_parser(): parser = argparse.ArgumentParser(description=DESCRIPTION) parser.add_argument("--galaxy-root") @@ -240,6 +267,7 @@ class UploadConfig(object): self.to_posix_lines = request.get("to_posix_lines", False) self.space_to_tab = request.get("space_to_tab", False) self.auto_decompress = request.get("auto_decompress", False) + self.validate_hashes = request.get("validate_hashes", False) self.link_data_only = _link_data_only(request) self.__workdir = os.path.abspath(".") diff --git a/lib/galaxy/tools/data_manager/manager.py b/lib/galaxy/tools/data_manager/manager.py index 4437a8e0002..d46418ae23f 100644 --- a/lib/galaxy/tools/data_manager/manager.py +++ b/lib/galaxy/tools/data_manager/manager.py @@ -57,11 +57,19 @@ class DataManagers(object): tool_path = '.' self.tool_path = tool_path for data_manager_elem in root.findall('data_manager'): - self.load_manager_from_elem(data_manager_elem, tool_path=self.tool_path) + if not self.load_manager_from_elem(data_manager_elem, tool_path=self.tool_path): + # Wasn't able to load manager, could happen when galaxy is managed by planemo. + # Fall back to loading relative to the data_manager_conf.xml file + tool_path = os.path.dirname(xml_filename) + self.load_manager_from_elem(data_manager_elem, tool_path=tool_path) def load_manager_from_elem(self, data_manager_elem, tool_path=None, add_manager=True): try: data_manager = DataManager(self, data_manager_elem, tool_path=tool_path) + except IOError as e: + if e.errno == errno.ENOENT: + # File does not exist + return None except Exception as e: log.error("Error loading data_manager '%s':\n%s" % (e, util.xml_to_string(data_manager_elem))) return None @@ -260,12 +268,13 @@ class DataManager(object): def id(self): return self.guid or self.declared_id # if we have a guid, we will use that as the data_manager id - def load_tool(self, tool_filename, guid=None, data_manager_id=None, tool_shed_repository_id=None): + def load_tool(self, tool_filename, guid=None, data_manager_id=None, tool_shed_repository_id=None, tool_shed_repository=None): toolbox = self.data_managers.app.toolbox tool = toolbox.load_hidden_tool(tool_filename, guid=guid, data_manager_id=data_manager_id, repository_id=tool_shed_repository_id, + tool_shed_repository=tool_shed_repository, use_cached=True) self.data_managers.app.toolbox.data_manager_tools[tool.id] = tool self.tool = tool diff --git a/lib/galaxy/tools/deps/container_classes.py b/lib/galaxy/tools/deps/container_classes.py index ecb10f46362..4d080fa01f3 100644 --- a/lib/galaxy/tools/deps/container_classes.py +++ b/lib/galaxy/tools/deps/container_classes.py @@ -163,9 +163,14 @@ class HasDockerLikeVolumes(object): if self.job_info.job_directory and self.job_info.job_directory_type == "pulsar": # We have a Pulsar job directory, so everything needed (excluding index # files) should be available in job_directory... - defaults = "$job_directory:default_ro,$tool_directory:default_ro,$job_directory/outputs:rw,$working_directory:rw" + defaults = "$job_directory:default_ro" + if self.job_info.tool_directory: + defaults += ",$tool_directory:default_ro" + defaults += ",$job_directory/outputs:rw,$working_directory:rw" else: - defaults = "$galaxy_root:default_ro,$tool_directory:default_ro" + defaults = "$galaxy_root:default_ro" + if self.job_info.tool_directory: + defaults += ",$tool_directory:default_ro" if self.job_info.job_directory: defaults += ",$job_directory:default_ro" if self.job_info.tmp_directory is not None: @@ -184,7 +189,18 @@ class HasDockerLikeVolumes(object): # index data without deployer worrying about above details. variables["defaults"] = string.Template(defaults).safe_substitute(variables) - return template.safe_substitute(variables) + volumes_str = template.safe_substitute(variables) + + # Not all tools have a tool_directory - strip this out if supplied by + # job_conf. + tool_directory_index = volumes_str.find("$tool_directory") + if tool_directory_index > 0: + end_index = volumes_str.find(",", tool_directory_index) + if end_index < 0: + end_index = len(volumes_str) + volumes_str = volumes_str[0:tool_directory_index] + volumes_str[end_index:len(volumes_str)] + + return volumes_str class DockerContainer(Container, HasDockerLikeVolumes): diff --git a/lib/galaxy/tools/deps/dependencies.py b/lib/galaxy/tools/deps/dependencies.py index 194cfab1f5e..bc1820cf52c 100644 --- a/lib/galaxy/tools/deps/dependencies.py +++ b/lib/galaxy/tools/deps/dependencies.py @@ -36,7 +36,11 @@ class ToolInfo(object): # variables they can consume (e.g. JVM options, license keys, etc..) # and add these to env_path_through - def __init__(self, container_descriptions=[], requirements=[], requires_galaxy_python_environment=False, env_pass_through=["GALAXY_SLOTS"]): + def __init__(self, container_descriptions=None, requirements=None, requires_galaxy_python_environment=False, env_pass_through=["GALAXY_SLOTS"]): + if container_descriptions is None: + container_descriptions = [] + if requirements is None: + requirements = [] self.container_descriptions = container_descriptions self.requirements = requirements self.requires_galaxy_python_environment = requires_galaxy_python_environment diff --git a/lib/galaxy/tools/deps/resolvers/modules.py b/lib/galaxy/tools/deps/resolvers/modules.py index 11abb6b4e9a..fc7e17b62da 100644 --- a/lib/galaxy/tools/deps/resolvers/modules.py +++ b/lib/galaxy/tools/deps/resolvers/modules.py @@ -36,6 +36,7 @@ DEFAULT_MODULECMD_PATH = "modulecmd" # Just check path DEFAULT_MODULE_PATH = '/usr/share/modules/modulefiles' DEFAULT_INDICATOR = '(default)' DEFAULT_MODULE_PREFETCH = "true" +DEFAULT_MAPPING_FILE = 'config/environment_modules_mapping.yml' UNKNOWN_FIND_BY_MESSAGE = "ModuleDependencyResolver does not know how to find modules by [%s], find_by should be one of %s" @@ -44,6 +45,8 @@ class ModuleDependencyResolver(DependencyResolver, MappableDependencyResolver): resolver_type = "modules" def __init__(self, dependency_manager, **kwds): + # Mapping file management + self._set_default_mapping_file(kwds) self._setup_mapping(dependency_manager, **kwds) self.versionless = _string_as_bool(kwds.get('versionless', 'false')) find_by = kwds.get('find_by', 'avail') @@ -67,6 +70,11 @@ class ModuleDependencyResolver(DependencyResolver, MappableDependencyResolver): module_path = DEFAULT_MODULE_PATH return module_path + def _set_default_mapping_file(self, resolver_attributes): + if 'mapping_files' not in resolver_attributes: + if exists(DEFAULT_MAPPING_FILE): + resolver_attributes['mapping_files'] = DEFAULT_MAPPING_FILE + def resolve(self, requirement, **kwds): requirement = self._expand_mappings(requirement) name, version, type = requirement.name, requirement.version, requirement.type diff --git a/lib/galaxy/tools/deps/views.py b/lib/galaxy/tools/deps/views.py index a6f41b84606..0ce83a3d121 100644 --- a/lib/galaxy/tools/deps/views.py +++ b/lib/galaxy/tools/deps/views.py @@ -104,6 +104,7 @@ class DependencyResolversView(object): return list(removed_environments) def install_dependencies(self, requirements, **kwds): + kwds['install'] = True return self._dependency_manager._requirements_to_dependencies_dict(requirements, **kwds) def install_dependency(self, index=None, **payload): diff --git a/lib/galaxy/tools/error_reports/plugins/base_git.py b/lib/galaxy/tools/error_reports/plugins/base_git.py new file mode 100644 index 00000000000..5038c05c01c --- /dev/null +++ b/lib/galaxy/tools/error_reports/plugins/base_git.py @@ -0,0 +1,89 @@ +"""This module defines the common functions for error reporting for Galaxy jobs towards Git applications (e.g. Github/GitLab). +""" + +from __future__ import absolute_import + +import logging +import sys +from abc import ( + ABCMeta, + abstractmethod +) + +import requests +import six +if sys.version_info[0] < 3: + import urllib as urllib + import urlparse as urlparse +else: + import urllib.parse as urllib + urlparse = urllib + +from galaxy.tools.errors import EmailErrorReporter +from galaxy.util import unicodify +from . import ErrorPlugin + +log = logging.getLogger(__name__) + + +@six.add_metaclass(ABCMeta) +class BaseGitPlugin(ErrorPlugin): + """Base definition to send error reports to a Git repository provider + """ + issue_cache = {} + ts_urls = {} + ts_repo_cache = {} + git_project_cache = {} + label_cache = {} + + # Git variables + git_default_repo_owner = False + git_default_repo_name = False + git_default_repo_only = True + + def _determine_ts_url(self, tool): + if not tool.tool_shed or self.git_default_repo_only: + return None + if tool.tool_shed not in self.ts_urls: + ts_url_request = requests.get('http://' + str(tool.tool_shed)) + self.ts_urls[tool.tool_shed] = ts_url_request.url + return self.ts_urls[tool.tool_shed] + + def _get_gitrepo_from_ts(self, job, ts_url): + if not ts_url or self.git_default_repo_only: + return None + if job.tool_id not in self.ts_repo_cache: + ts_repo_request_data = requests.get(ts_url + "/api/repositories?tool_ids=" + str(job.tool_id)).json() + + for changeset, repoinfo in ts_repo_request_data.items(): + if isinstance(repoinfo, dict): + self.ts_repo_cache[job.tool_id] = repoinfo.get('repository', {}).get('remote_repository_url', None) + return self.ts_repo_cache[job.tool_id] + + def _get_issue_cache_key(self, job, ts_repourl): + return job.tool_id if ts_repourl else "default" + + def _generate_error_message(self, dataset, job, kwargs): + # We'll re-use the email error reporter's template since most Git providers supports HTML + error_reporter = EmailErrorReporter(dataset.id, self.app) + error_reporter.create_report(job.get_user(), email=kwargs.get('email', None), + message=kwargs.get('message', None), + redact_user_details_in_bugreport=self.redact_user_details_in_bugreport) + # Return the HTML report + return error_reporter.html_report + + def _generate_error_title(self, job): + tool_kw = {'tool_id': unicodify(job.tool_id), 'tool_version': unicodify(job.tool_version)} + return u"""Galaxy Job Error: {tool_id} v{tool_version}""".format(**tool_kw) + + @abstractmethod + def _create_issue(self, issue_cache_key, error_title, error_mesage, project, **kwargs): + raise NotImplementedError("Method _create_issue is required") + + @abstractmethod + def _append_issue(self, issue_cache_key, error_title, error_message, **kwargs): + raise NotImplementedError("Method _append_issue is required") + + @abstractmethod + def _fill_issue_cache(self, git_project, issue_cache_key): + raise NotImplementedError("Method _fill_issue_cache is required") diff --git a/lib/galaxy/tools/error_reports/plugins/biostars.py b/lib/galaxy/tools/error_reports/plugins/biostars.py deleted file mode 100644 index b5c06d6ae1c..00000000000 --- a/lib/galaxy/tools/error_reports/plugins/biostars.py +++ /dev/null @@ -1,47 +0,0 @@ -"""The module describes the ``biostars`` error plugin.""" -from __future__ import absolute_import - -import logging - -from galaxy.util import biostar -from galaxy.util import string_as_bool -from galaxy.web.base.controller import url_for -from . import ErrorPlugin - -log = logging.getLogger(__name__) - - -class BiostarsPlugin(ErrorPlugin): - """Send error report as an issue on Biostars - """ - plugin_type = "biostars" - - def __init__(self, **kwargs): - self.app = kwargs['app'] - self.verbose = string_as_bool(kwargs.get('verbose', True)) - self.user_submission = string_as_bool(kwargs.get('user_submission', True)) - - def submit_report(self, dataset, job, tool, **kwargs): - """Doesn't do anything, just shows a link to submit on biostars. - """ - # This class specifically does nothing special for compliance purposes - # because the user is willingly posting their data on a public - # first/third-party site. Maybe should do something about the dialog - # linking to the biostars privacy policy during the "submit to - # biostars" dialog? - - try: - assert biostar.biostar_enabled(self.app), ValueError("Biostar is not configured for this galaxy instance") - assert self.app.config.biostar_enable_bug_reports, ValueError("Biostar is not configured to allow bug reporting for this galaxy instance") - print(kwargs) - - url = url_for(controller='biostar', - action='biostar_tool_bug_report', - hda=self.app.security.encode_id(dataset.id), - email=kwargs['email'], message=kwargs['message']) - return ('Click here to submit to BioStars' % url, 'success') - except Exception as e: - return ("An error occurred submitting the report to biostars: %s" % str(e), "danger") - - -__all__ = ('BiostarsPlugin', ) diff --git a/lib/galaxy/tools/error_reports/plugins/github.py b/lib/galaxy/tools/error_reports/plugins/github.py index 102412ed985..d8f3b80173b 100644 --- a/lib/galaxy/tools/error_reports/plugins/github.py +++ b/lib/galaxy/tools/error_reports/plugins/github.py @@ -2,15 +2,21 @@ from __future__ import absolute_import import logging +import sys +if sys.version_info[0] < 3: + import urllib as urllib + import urlparse as urlparse +else: + import urllib.parse as urllib + urlparse = urllib -from galaxy.tools.errors import EmailErrorReporter from galaxy.util import string_as_bool, unicodify -from . import ErrorPlugin +from .base_git import BaseGitPlugin log = logging.getLogger(__name__) -class GithubPlugin(ErrorPlugin): +class GithubPlugin(BaseGitPlugin): """Send error report to Github. """ plugin_type = "github" @@ -20,73 +26,125 @@ class GithubPlugin(ErrorPlugin): self.redact_user_details_in_bugreport = self.app.config.redact_user_details_in_bugreport self.verbose = string_as_bool(kwargs.get('verbose', False)) self.user_submission = string_as_bool(kwargs.get('user_submission', False)) + + # Github settings + self.github_base_url = kwargs.get('github_base_url', 'https://github.com') + self.github_api_url = kwargs.get('github_api_url', 'https://api.github.com') + self.git_default_repo_owner = kwargs.get('github_default_repo_owner', False) + self.git_default_repo_name = kwargs.get('github_default_repo_name', False) + self.git_default_repo_only = string_as_bool(kwargs.get('github_default_repo_only', True)) + try: import github self.github = github.Github( kwargs['github_oauth_token'], # Allow running against GH enterprise deployments. - base_url=kwargs.get('github_base_url', 'https://api.github.com') + base_url=self.github_api_url ) - self.repo = self.github.get_repo('{github_repo_owner}/{github_repo_name}'.format(**kwargs)) - log.info(self.repo) - # We want to ensure that we don't generate a thousand issues when - # multiple users report a bug. So, we need to de-dupe issues. In - # order to de-dupe, we need to know which are open. So, we'll keep - # a cache of open issues and just add to it whenever we create a - # new one. - self.issue_cache = {} - for issue in self.repo.get_issues(state='open'): - log.info(issue) - self.issue_cache[issue.title] = issue - log.info(self.issue_cache) + # Connect to the default repository and fill up the issue cache for it + repo = self.github.get_repo('%s/%s' % (self.git_default_repo_owner, self.git_default_repo_name)) + self._fill_issue_cache(repo, "default") # We'll also cache labels which we'll use for tagging issues. - self.label_cache = {} - for label in self.repo.get_labels(): - log.info(label) - self.label_cache[label.name] = label - log.info(self.label_cache) + self.label_cache["default"] = {} + self._fill_label_cache(repo, "default") except ImportError: log.error("Please install pygithub to submit bug reports to github") self.github = None - def get_label(self, label): - # If we don't have this label, then create it + cache it. - if label not in self.label_cache: - self.label_cache[label] = self.repo.create_label(name=label, color='ffffff') - - return self.label_cache[label] - def submit_report(self, dataset, job, tool, **kwargs): """Submit the error report to sentry """ log.info(self.github) if self.github: - tool_kw = {'tool_id': unicodify(job.tool_id), 'tool_version': unicodify(job.tool_version)} - label = self.get_label('{tool_id}/{tool_version}'.format(**tool_kw)) - error_title = u"""Galaxy Job Error: {tool_id} v{tool_version}""".format(**tool_kw) - # We'll re-use the email error reporter's template since github supports HTML - error_reporter = EmailErrorReporter(dataset.id, self.app) - error_reporter.create_report(job.get_user(), email=kwargs.get('email', None), message=kwargs.get('message', None), redact_user_details_in_bugreport=self.redact_user_details_in_bugreport) + # Determine the ToolShed url, initially we connect with HTTP and if redirect to HTTPS is set up, + # this will be detected by requests and used further down the line. Also cache this so everything is + # as fast as possible + log.info(tool.tool_shed) + ts_url = self._determine_ts_url(tool) + log.info("GitLab error reporting - Determined ToolShed is %s", ts_url) - # The HTML report - error_message = error_reporter.html_report + # Find the repo inside the ToolShed + ts_repourl = self._get_gitrepo_from_ts(job, ts_url) - log.info(error_title in self.issue_cache) - if error_title not in self.issue_cache: + # Determine the GitLab project URL and the issue cache key + github_projecturl = urlparse.urlparse(ts_repourl).path[1:] if (ts_repourl and not self.git_default_repo_only) \ + else "/".join([self.git_default_repo_owner, self.git_default_repo_name]) + issue_cache_key = self._get_issue_cache_key(job, ts_repourl) + + # Connect to the repo + if github_projecturl not in self.git_project_cache: + self.git_project_cache[github_projecturl] = self.github.get_repo('%s' % github_projecturl) + gh_project = self.git_project_cache[github_projecturl] + + # Make sure we keep a cache of the issues, per tool in this case + if issue_cache_key not in self.issue_cache: + self._fill_issue_cache(gh_project, issue_cache_key) + + # Retrieve label + label = self.get_label('%s/%s' % (unicodify(job.tool_id), unicodify(job.tool_version)), gh_project, issue_cache_key) + + # Generate information for the tool + error_title = self._generate_error_title(job) + + # Generate the error message + error_message = self._generate_error_message(dataset, job, kwargs) + + log.info(error_title in self.issue_cache[issue_cache_key]) + if error_title not in self.issue_cache[issue_cache_key]: # Create a new issue. - self.issue_cache[error_title] = self.repo.create_issue( - title=error_title, - body=error_message, - # Label it with a tag: tool_id/tool_version - labels=[label] - ) + self._create_issue(issue_cache_key, error_title, error_message, gh_project, label=label) else: - self.issue_cache[error_title].create_comment(error_message) - return ('Submitted bug report to Github. Your issue number is %s' % self.issue_cache[error_title].number, 'success') + self._append_issue(issue_cache_key, error_title, error_message) + return ('Submitted error report to Github. Your issue number is #%s.' % (self.github_base_url, github_projecturl, + self.issue_cache[issue_cache_key][error_title].number, + self.issue_cache[issue_cache_key][error_title].number), 'success') + + def _create_issue(self, issue_cache_key, error_title, error_mesage, project, **kwargs): + # Create a new issue. + self.issue_cache[issue_cache_key][error_title] = project.create_issue( + title=error_title, + body=error_mesage, + # Label it with a tag: tool_id/tool_version + labels=[kwargs.get('label')] + ) + + def _append_issue(self, issue_cache_key, error_title, error_message, **kwargs): + # Create comment on an issue + self.issue_cache[issue_cache_key][error_title].create_comment(error_message) + + def _fill_issue_cache(self, git_project, issue_cache_key): + # We want to ensure that we don't generate a thousand issues when + # multiple users report a bug. So, we need to de-dupe issues. In + # order to de-dupe, we need to know which are open. So, we'll keep + # a cache of open issues and just add to it whenever we create a + # new one. + self.issue_cache[issue_cache_key] = {} + for issue in git_project.get_issues(state='open'): + log.info(issue) + self.issue_cache[issue_cache_key][issue.title] = issue + log.info(self.issue_cache) + + def _fill_label_cache(self, git_project, issue_cache_key): + for label in git_project.get_labels(): + log.info(label) + self.label_cache[issue_cache_key][label.name] = label + log.info(self.label_cache) + + def get_label(self, label, git_project, issue_cache_key): + # If we don't have this label, then create it + cache it. + if issue_cache_key not in self.label_cache: + self.label_cache[issue_cache_key] = {} + self._fill_label_cache(git_project, issue_cache_key) + + if label not in self.label_cache[issue_cache_key]: + self.label_cache[issue_cache_key][label] = git_project.create_label(name=label, color='ffffff') + + return self.label_cache[issue_cache_key][label] __all__ = ('GithubPlugin', ) diff --git a/lib/galaxy/tools/error_reports/plugins/gitlab.py b/lib/galaxy/tools/error_reports/plugins/gitlab.py new file mode 100644 index 00000000000..06399324718 --- /dev/null +++ b/lib/galaxy/tools/error_reports/plugins/gitlab.py @@ -0,0 +1,195 @@ +"""The module describes the ``gitlab`` error plugin plugin.""" +from __future__ import absolute_import + +import logging +import os +import sys + +import requests +if sys.version_info[0] < 3: + import urllib as urllib + import urlparse as urlparse +else: + import urllib.parse as urllib + urlparse = urllib + +from galaxy.util import string_as_bool +from .base_git import BaseGitPlugin + +log = logging.getLogger(__name__) + + +class GitLabPlugin(BaseGitPlugin): + """Send error report to GitLab. + """ + plugin_type = "gitlab" + + def __init__(self, **kwargs): + self.app = kwargs['app'] + self.redact_user_details_in_bugreport = self.app.config.redact_user_details_in_bugreport + self.verbose = string_as_bool(kwargs.get('verbose', False)) + self.user_submission = string_as_bool(kwargs.get('user_submission', False)) + + # GitLab settings + self.gitlab_base_url = kwargs.get('gitlab_base_url', 'https://gitlab.com') + self.git_default_repo_owner = kwargs.get('gitlab_default_repo_owner', False) + self.git_default_repo_name = kwargs.get('gitlab_default_repo_name', False) + self.git_default_repo_only = string_as_bool(kwargs.get('gitlab_default_repo_only', True)) + + try: + import gitlab + + session = requests.Session() + session.proxies = { + 'https': os.environ.get('https_proxy'), + 'http': os.environ.get('http_proxy'), + } + self.gitlab = gitlab.Gitlab( + # Allow running against GL enterprise deployments + kwargs.get('gitlab_base_url', 'https://gitlab.com'), + private_token=kwargs['gitlab_private_token'], + session=session + ) + self.gitlab.auth() + + except ImportError: + log.error("GitLab error reporting - Please install python-gitlab to submit bug reports to GitLab.") + self.gitlab = None + except gitlab.GitlabAuthenticationError: + log.error("GitLab error reporting - Could not authenticate with GitLab.") + self.gitlab = None + except gitlab.GitlabParsingError: + log.error("GitLab error reporting - Could not parse GitLab message.") + self.gitlab = None + except (gitlab.GitlabConnectionError, gitlab.GitlabHttpError): + log.error("GitLab error reporting - Could not connect to GitLab.") + self.gitlab = None + except gitlab.GitlabError: + log.error("GitLab error reporting - General error communicating with GitLab.") + self.gitlab = None + + def submit_report(self, dataset, job, tool, **kwargs): + """Submit the error report to GitLab + """ + log.info(self.gitlab) + + if self.gitlab: + # Import GitLab here for the error handling + import gitlab + try: + log.info("GitLab error reporting - submit report - job tool id: %s - job tool version: %s - tool tool_shed: %s" % (job.tool_id, job.tool_version, tool.tool_shed)) + + # Determine the ToolShed url, initially we connect with HTTP and if redirect to HTTPS is set up, + # this will be detected by requests and used further down the line. Also cache this so everything is + # as fast as possible + ts_url = self._determine_ts_url(tool) + log.info("GitLab error reporting - Determined ToolShed is %s", ts_url) + + # Find the repo inside the ToolShed + ts_repourl = self._get_gitrepo_from_ts(job, ts_url) + + log.info("GitLab error reporting - Determine ToolShed Repository URL: %s", ts_repourl) + + # Determine the GitLab project URL and the issue cache key + gitlab_projecturl = urlparse.urlparse(ts_repourl).path[1:] if (ts_repourl and not self.git_default_repo_only)\ + else "/".join([self.git_default_repo_owner, self.git_default_repo_name]) + issue_cache_key = self._get_issue_cache_key(job, ts_repourl) + + gitlab_urlencodedpath = urllib.quote_plus(gitlab_projecturl) + + # Make sure we are always logged in, then retrieve the GitLab project if it isn't cached. + self.gitlab.auth() + if gitlab_projecturl not in self.git_project_cache: + self.git_project_cache[gitlab_projecturl] = self.gitlab.projects.get(gitlab_urlencodedpath) + gl_project = self.git_project_cache[gitlab_projecturl] + + # Make sure we keep a cache of the issues, per tool in this case + if issue_cache_key not in self.issue_cache: + self._fill_issue_cache(gl_project, issue_cache_key) + + # Generate information for the tool + error_title = self._generate_error_title(job) + + # Generate the error message + error_message = self._generate_error_message(dataset, job, kwargs) + + log.info(error_title in self.issue_cache[issue_cache_key]) + if error_title not in self.issue_cache[issue_cache_key]: + # Create a new issue. + self._create_issue(issue_cache_key, error_title, error_message, gl_project) + else: + # Add a comment to an issue... + self._append_issue(issue_cache_key, error_title, error_message, gitlab_urlencodedpath=gitlab_urlencodedpath) + + return ('Submitted error report to GitLab. Your issue number is #%s.' % (self.gitlab_base_url, gitlab_projecturl, + self.issue_cache[issue_cache_key][error_title], + self.issue_cache[issue_cache_key][error_title]), 'success') + + except gitlab.GitlabCreateError as e: + log.error("GitLab error reporting - Could not create the issue on GitLab. " + "Exception information: " + e.message) + return ('Internal Error.', 'danger') + except gitlab.GitlabOwnershipError as e: + log.error("GitLab error reporting - Could not create the issue on GitLab due to ownership issues. " + "Exception information: " + e.message) + return ('Internal Error.', 'danger') + except gitlab.GitlabSearchError as e: + log.error("GitLab error reporting - Could not find repository on GitLab. " + "Exception information: " + e.message) + return ('Internal Error.', 'danger') + except gitlab.GitlabAuthenticationError as e: + log.error("GitLab error reporting - Could not authenticate with GitLab. " + "Exception information: " + e.message) + return ('Internal Error.', 'danger') + except gitlab.GitlabParsingError as e: + log.error("GitLab error reporting - Could not parse GitLab message. " + "Exception information: " + e.message) + return ('Internal Error.', 'danger') + except (gitlab.GitlabConnectionError, gitlab.GitlabHttpError) as e: + log.error("GitLab error reporting - Could not connect to GitLab. Exception information: " + e.message) + return ('Internal Error.', 'danger') + except gitlab.GitlabError as e: + log.error("GitLab error reporting - General error communicating with GitLab. " + "Exception information: " + e.message) + return ('Internal Error.', 'danger') + except Exception as e: + log.error("GitLab error reporting - Error reporting to GitLab had an exception that could not be " + "determined. Exception information: " + e.message) + return ('Internal Error.', 'danger') + else: + log.error("GitLab error reporting - No connection to GitLab. Cannot report error to GitLab.") + return ('Internal Error.', 'danger') + + def _create_issue(self, issue_cache_key, error_title, error_mesage, project, **kwargs): + # Create the issue on GitLab + issue = project.issues.create({ + 'title': error_title, + 'description': error_mesage + }) + self.issue_cache[issue_cache_key][error_title] = issue.iid + + def _append_issue(self, issue_cache_key, error_title, error_message, **kwargs): + # Add a comment to an existing issue + gl_url = "/".join([ + self.gitlab_base_url, + "api", + "v4", + "projects", + kwargs.get('gitlab_urlencodedpath'), + "issues", + str(self.issue_cache[issue_cache_key][error_title]), + "notes" + ]) + self.gitlab.http_post(gl_url, post_data={'body': error_message}) + + def _fill_issue_cache(self, git_project, issue_cache_key): + self.issue_cache[issue_cache_key] = {} + # Loop over all open issues and add the issue iid to the cache + for issue in git_project.issues.list(): + if issue.state != 'closed': + log.info("GitLab error reporting - Repo issue: %s", str(issue.iid)) + self.issue_cache[issue_cache_key][issue.title] = issue.iid + + +__all__ = ('GitLabPlugin', ) diff --git a/lib/galaxy/tools/evaluation.py b/lib/galaxy/tools/evaluation.py index b244364ac6e..1b741b7dc97 100644 --- a/lib/galaxy/tools/evaluation.py +++ b/lib/galaxy/tools/evaluation.py @@ -78,13 +78,7 @@ class ToolEvaluator(object): visit_input_values(self.tool.inputs, incoming, validate_inputs) # Restore input / output data lists - inp_data = dict([(da.name, da.dataset) for da in job.input_datasets]) - out_data = dict([(da.name, da.dataset) for da in job.output_datasets]) - inp_data.update([(da.name, da.dataset) for da in job.input_library_datasets]) - out_data.update([(da.name, da.dataset) for da in job.output_library_datasets]) - - out_collections = dict([(obj.name, obj.dataset_collection_instance) for obj in job.output_dataset_collection_instances]) - out_collections.update([(obj.name, obj.dataset_collection) for obj in job.output_dataset_collections]) + inp_data, out_data, out_collections = job.io_dicts() if get_special: @@ -357,7 +351,9 @@ class ToolEvaluator(object): param_dict[name] = DatasetFilenameWrapper(hda) # Provide access to a path to store additional files # TODO: path munging for cluster/dataset server relocatability - param_dict[name].files_path = os.path.abspath(os.path.join(job_working_directory, "dataset_%s_files" % (hda.dataset.id))) + store_by = getattr(hda.dataset.object_store, "store_by", "id") + file_name = "dataset_%s_files" % getattr(hda.dataset, store_by) + param_dict[name].files_path = os.path.abspath(os.path.join(job_working_directory, file_name)) for out_name, output in self.tool.outputs.items(): if out_name not in param_dict and output.filters: # Assume the reason we lack this output is because a filter @@ -380,7 +376,7 @@ class ToolEvaluator(object): param_dict['__tool_directory__'] = self.compute_environment.tool_directory() param_dict['__get_data_table_entry__'] = get_data_table_entry - + param_dict['__local_working_directory__'] = self.local_working_directory # We add access to app here, this allows access to app.config, etc param_dict['__app__'] = RawObjectWrapper(self.app) # More convienent access to app.config.new_file_path; we don't need to diff --git a/lib/galaxy/tools/execute.py b/lib/galaxy/tools/execute.py index 54794f8ea07..16012e25e49 100644 --- a/lib/galaxy/tools/execute.py +++ b/lib/galaxy/tools/execute.py @@ -5,14 +5,12 @@ collections from matched collections. """ import collections import logging -from threading import Thread import six import six.moves -from six.moves.queue import Queue from galaxy import model -from galaxy.dataset_collections.structure import get_structure, tool_output_to_structure +from galaxy.model.dataset_collections.structure import get_structure, tool_output_to_structure from galaxy.tools.actions import filter_output, on_text_for_names, ToolExecutionCache from galaxy.tools.parser import ToolOutputCollectionPart from galaxy.util import ExecutionTimer @@ -46,7 +44,6 @@ def execute(trans, tool, mapping_params, history, rerun_remap_job_id=None, colle execution_tracker = ToolExecutionTracker(trans, tool, mapping_params, collection_info) else: execution_tracker = WorkflowStepExecutionTracker(trans, tool, mapping_params, collection_info, invocation_step, job_callback=job_callback) - app = trans.app execution_cache = ToolExecutionCache(trans) def execute_single_job(execution_slice, completed_job): @@ -86,46 +83,17 @@ def execute(trans, tool, mapping_params, history, rerun_remap_job_id=None, colle ) execution_tracker.ensure_implicit_collections_populated(history, mapping_params.param_template) - config = app.config - burst_at = getattr(config, 'tool_submission_burst_at', 10) - burst_threads = getattr(config, 'tool_submission_burst_threads', 1) - job_count = len(execution_tracker.param_combinations) jobs_executed = 0 has_remaining_jobs = False - if (job_count < burst_at or burst_threads < 2): - for i, execution_slice in enumerate(execution_tracker.new_execution_slices()): - if max_num_jobs and jobs_executed >= max_num_jobs: - has_remaining_jobs = True - break - else: - execute_single_job(execution_slice, completed_jobs[i]) - else: - # TODO: re-record success... - q = Queue() - - def worker(): - while True: - params = q.get() - execute_single_job(params) - q.task_done() - - for i in range(burst_threads): - t = Thread(target=worker) - t.daemon = True - t.start() - - for i, execution_slice in enumerate(execution_tracker.new_execution_slices()): - if max_num_jobs and jobs_executed >= max_num_jobs: - has_remaining_jobs = True - break - else: - q.put(execution_slice, completed_jobs[i]) - jobs_executed += 1 - - q.join() + for i, execution_slice in enumerate(execution_tracker.new_execution_slices()): + if max_num_jobs and jobs_executed >= max_num_jobs: + has_remaining_jobs = True + break + else: + execute_single_job(execution_slice, completed_jobs[i]) if has_remaining_jobs: raise PartialJobExecution(execution_tracker) @@ -346,7 +314,9 @@ class ExecutionTracker(object): implicit_collection_jobs = implicit_collection.implicit_collection_jobs implicit_collection_jobs.populated_state = "ok" trans.sa_session.add(implicit_collection_jobs) - implicit_collection.collection.finalize() + implicit_collection.collection.finalize( + collection_type_description=self.collection_info.structure.collection_type_description + ) trans.sa_session.add(implicit_collection.collection) trans.sa_session.flush() diff --git a/lib/galaxy/tools/expressions/__init__.py b/lib/galaxy/tools/expressions/__init__.py new file mode 100644 index 00000000000..bf20411a392 --- /dev/null +++ b/lib/galaxy/tools/expressions/__init__.py @@ -0,0 +1,16 @@ +from .evaluation import evaluate +from .script import ( + EXPRESSION_SCRIPT_CALL, + EXPRESSION_SCRIPT_NAME, + write_evalute_script, +) +from .util import find_engine + + +__all__ = ( + 'evaluate', + 'EXPRESSION_SCRIPT_CALL', + 'EXPRESSION_SCRIPT_NAME', + 'find_engine', + 'write_evalute_script', +) diff --git a/lib/galaxy/tools/expressions/cwlNodeEngine.js b/lib/galaxy/tools/expressions/cwlNodeEngine.js new file mode 100644 index 00000000000..1129584977f --- /dev/null +++ b/lib/galaxy/tools/expressions/cwlNodeEngine.js @@ -0,0 +1,46 @@ +#!/usr/bin/env nodejs + +"use strict"; + +process.stdin.setEncoding('utf8'); + +var incoming = ""; + +process.stdin.on('readable', function() { + var chunk = process.stdin.read(); + if (chunk !== null) { + incoming += chunk; + } +}); + +process.stdin.on('end', function() { + var j = JSON.parse(incoming); + var exp = "" + + if (j.script[0] == "{") { + exp = "{return function()" + j.script + "();}"; + } + else { + exp = "{return " + j.script + ";}"; + } + + var fn = '"use strict";\n'; + + if (j.engineConfig) { + for (var index = 0; index < j.engineConfig.length; ++index) { + fn += j.engineConfig[index] + "\n"; + } + } + + fn += "var $job = " + JSON.stringify(j.job) + ";\n"; + fn += "var $self = " + JSON.stringify(j.context) + ";\n" + + fn += "var $runtime = " + JSON.stringify(j.runtime) + ";\n" + fn += "var $tmpdir = " + JSON.stringify(j.tmpdir) + ";\n" + fn += "var $outdir = " + JSON.stringify(j.outdir) + ";\n" + + + fn += "(function()" + exp + ")()"; + + process.stdout.write(JSON.stringify(require("vm").runInNewContext(fn, {}))); +}); diff --git a/lib/galaxy/tools/expressions/evaluation.py b/lib/galaxy/tools/expressions/evaluation.py new file mode 100644 index 00000000000..54066d5094b --- /dev/null +++ b/lib/galaxy/tools/expressions/evaluation.py @@ -0,0 +1,38 @@ +import json +import os +import subprocess + +from .util import find_engine + +FILE_DIRECTORY = os.path.normpath(os.path.dirname(os.path.join(__file__))) +NODE_ENGINE = os.path.join(FILE_DIRECTORY, "cwlNodeEngine.js") + + +def evaluate(config, input): + application = find_engine(config) + + default_context = { + "engineConfig": [], + "job": {}, + "context": None, + "outdir": None, + "tmpdir": None, + } + + new_input = default_context + new_input.update(input) + + sp = subprocess.Popen([application, NODE_ENGINE], + shell=False, + close_fds=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + input_str = json.dumps(new_input) + "\n\n" + input_bytes = input_str.encode("utf-8") + (stdoutdata, stderrdata) = sp.communicate(input_bytes) + if sp.returncode != 0: + args = (json.dumps(new_input, indent=4), stdoutdata, stderrdata) + message = "Expression engine returned non-zero exit code on evaluation of\n%s%s%s" % args + raise Exception(message) + + return json.loads(stdoutdata.decode("utf-8")) diff --git a/lib/galaxy/tools/expressions/script.py b/lib/galaxy/tools/expressions/script.py new file mode 100644 index 00000000000..0cb18ae7464 --- /dev/null +++ b/lib/galaxy/tools/expressions/script.py @@ -0,0 +1,15 @@ +import os + +EXPRESSION_SCRIPT_NAME = "_evaluate_expression_.py" +EXPRESSION_SCRIPT_CALL = "python %s" % EXPRESSION_SCRIPT_NAME + + +def write_evalute_script(in_directory): + """ Responsible for writing the script that evaluates expressions + in Galaxy jobs. + """ + script = os.path.join(in_directory, EXPRESSION_SCRIPT_NAME) + with open(script, "w") as f: + f.write('from galaxy_ext.expressions.handle_job import run; run()') + + return script diff --git a/lib/galaxy/tools/expressions/util.py b/lib/galaxy/tools/expressions/util.py new file mode 100644 index 00000000000..082ed3c916f --- /dev/null +++ b/lib/galaxy/tools/expressions/util.py @@ -0,0 +1,10 @@ +from galaxy.tools.deps.commands import which + + +def find_engine(config): + nodejs_path = getattr(config, "nodejs_path", None) + if nodejs_path is None: + nodejs_path = which("nodejs") or which("node") or None + if nodejs_path is None: + raise Exception("nodejs or node not found on PATH") + return nodejs_path diff --git a/lib/galaxy/tools/imp_exp/__init__.py b/lib/galaxy/tools/imp_exp/__init__.py index 16ee4cdbb09..3c2cbf651b2 100644 --- a/lib/galaxy/tools/imp_exp/__init__.py +++ b/lib/galaxy/tools/imp_exp/__init__.py @@ -1,23 +1,18 @@ -import datetime -import json import logging import os import shutil import tempfile -from json import dumps, load - -from sqlalchemy.orm import eagerload_all -from sqlalchemy.sql import expression from galaxy import model -from galaxy.exceptions import MalformedContents -from galaxy.model.item_attrs import UsesAnnotations -from galaxy.web.framework.helpers import to_unicode +from galaxy.model import store +from galaxy.version import VERSION_MAJOR log = logging.getLogger(__name__) +ATTRS_FILENAME_HISTORY = 'history_attrs.txt' -class JobImportHistoryArchiveWrapper(UsesAnnotations): + +class JobImportHistoryArchiveWrapper: """ Class provides support for performing jobs that import a history from an archive. @@ -29,477 +24,92 @@ class JobImportHistoryArchiveWrapper(UsesAnnotations): self.sa_session = self.app.model.context def cleanup_after_job(self): - """ Set history, datasets, and jobs' attributes and clean up archive directory. """ - - # - # Helper methods. - # - - def file_in_dir(file_path, a_dir): - """ Returns true if file is in directory. """ - abs_file_path = os.path.abspath(file_path) - return os.path.split(abs_file_path)[0] == a_dir - - def get_tag_str(tag, value): - """ Builds a tag string for a tag, value pair. """ - if not value: - return tag - else: - return tag + ":" + value + """ Set history, datasets, collections and jobs' attributes + and clean up archive directory. + """ # # Import history. # jiha = self.sa_session.query(model.JobImportHistoryArchive).filter_by(job_id=self.job_id).first() - if jiha: - try: - archive_dir = jiha.archive_dir - archive_dir = os.path.realpath(archive_dir) - user = jiha.job.user + if not jiha: + return None + user = jiha.job.user - # Bioblend previous to 17.01 exported histories with an extra subdir. - if not os.path.exists(os.path.join(archive_dir, 'history_attrs.txt')): - for d in os.listdir(archive_dir): - if os.path.isdir(os.path.join(archive_dir, d)): - archive_dir = os.path.join(archive_dir, d) - break + new_history = None + try: + archive_dir = jiha.archive_dir + model_store = store.get_import_model_store_for_directory(archive_dir, app=self.app, user=user) + job = jiha.job + with model_store.target_history(default_history=job.history) as new_history: - # - # Create history. - # - history_attr_file_name = os.path.join(archive_dir, 'history_attrs.txt') - history_attrs = load(open(history_attr_file_name)) - - # Create history. - new_history = model.History(name='imported from archive: %s' % history_attrs['name'], - user=user) - new_history.importing = True - new_history.hid_counter = history_attrs['hid_counter'] - new_history.genome_build = history_attrs['genome_build'] - self.sa_session.add(new_history) jiha.history = new_history self.sa_session.flush() - # Add annotation, tags. - if user: - self.add_item_annotation(self.sa_session, user, new_history, history_attrs['annotation']) - """ - TODO: figure out to how add tags to item. - for tag, value in history_attrs[ 'tags' ].items(): - trans.app.tag_handler.apply_item_tags( trans, trans.user, new_history, get_tag_str( tag, value ) ) - """ - - # - # Create datasets. - # - datasets_attrs_file_name = os.path.join(archive_dir, 'datasets_attrs.txt') - datasets_attrs = load(open(datasets_attrs_file_name)) - provenance_file_name = datasets_attrs_file_name + ".provenance" - - if os.path.exists(provenance_file_name): - provenance_attrs = load(open(provenance_file_name)) - datasets_attrs += provenance_attrs - - # Create datasets. - for dataset_attrs in datasets_attrs: - metadata = dataset_attrs['metadata'] - - # Create dataset and HDA. - hda = model.HistoryDatasetAssociation(name=dataset_attrs['name'], - extension=dataset_attrs['extension'], - info=dataset_attrs['info'], - blurb=dataset_attrs['blurb'], - peek=dataset_attrs['peek'], - designation=dataset_attrs['designation'], - visible=dataset_attrs['visible'], - dbkey=metadata['dbkey'], - metadata=metadata, - history=new_history, - create_dataset=True, - sa_session=self.sa_session) - if 'uuid' in dataset_attrs: - hda.dataset.uuid = dataset_attrs["uuid"] - if dataset_attrs.get('exported', True) is False: - hda.state = hda.states.DISCARDED - hda.deleted = True - hda.purged = True - else: - hda.state = hda.states.OK - self.sa_session.add(hda) - self.sa_session.flush() - new_history.add_dataset(hda, genome_build=None) - hda.hid = dataset_attrs['hid'] # Overwrite default hid set when HDA added to history. - # TODO: Is there a way to recover permissions? Is this needed? - # permissions = trans.app.security_agent.history_get_default_permissions( new_history ) - # trans.app.security_agent.set_all_dataset_permissions( hda.dataset, permissions ) - self.sa_session.flush() - if dataset_attrs.get('exported', True) is True: - # Do security check and move/copy dataset data. - temp_dataset_file_name = \ - os.path.realpath(os.path.abspath(os.path.join(archive_dir, dataset_attrs['file_name']))) - if not file_in_dir(temp_dataset_file_name, os.path.join(archive_dir, "datasets")): - raise MalformedContents("Invalid dataset path: %s" % temp_dataset_file_name) - self.app.object_store.update_from_file(hda.dataset, file_name=temp_dataset_file_name, create=True) - - # Import additional files if present. Histories exported previously might not have this attribute set. - dataset_extra_files_path = dataset_attrs.get('extra_files_path', None) - if dataset_extra_files_path: - try: - file_list = os.listdir(os.path.join(archive_dir, dataset_extra_files_path)) - except OSError: - file_list = [] - - if file_list: - for extra_file in file_list: - self.app.object_store.update_from_file( - hda.dataset, extra_dir='dataset_%s_files' % hda.dataset.id, - alt_name=extra_file, file_name=os.path.join(archive_dir, dataset_extra_files_path, extra_file), - create=True) - hda.dataset.set_total_size() # update the filesize record in the database - - # Set tags, annotations. - if user: - self.add_item_annotation(self.sa_session, user, hda, dataset_attrs['annotation']) - # TODO: Set tags. - """ - for tag, value in dataset_attrs[ 'tags' ].items(): - trans.app.tag_handler.apply_item_tags( trans, trans.user, hda, get_tag_str( tag, value ) ) - self.sa_session.flush() - """ - - self.app.datatypes_registry.set_external_metadata_tool.regenerate_imported_metadata_if_needed( - hda, new_history, jiha.job - ) - - # - # Create jobs. - # - - # Decode jobs attributes. - def as_hda(obj_dct): - """ Hook to 'decode' an HDA; method uses history and HID to get the HDA represented by - the encoded object. This only works because HDAs are created above. """ - if obj_dct.get('__HistoryDatasetAssociation__', False): - return self.sa_session.query(model.HistoryDatasetAssociation) \ - .filter_by(history=new_history, hid=obj_dct['hid']).first() - return obj_dct - jobs_attr_file_name = os.path.join(archive_dir, 'jobs_attrs.txt') - jobs_attrs = load(open(jobs_attr_file_name), object_hook=as_hda) - - # Create each job. - for job_attrs in jobs_attrs: - imported_job = model.Job() - imported_job.user = user - # TODO: set session? - # imported_job.session = trans.get_galaxy_session().id - imported_job.history = new_history - imported_job.imported = True - imported_job.tool_id = job_attrs['tool_id'] - imported_job.tool_version = job_attrs['tool_version'] - imported_job.set_state(job_attrs['state']) - imported_job.info = job_attrs.get('info', None) - imported_job.exit_code = job_attrs.get('exit_code', None) - imported_job.traceback = job_attrs.get('traceback', None) - imported_job.stdout = job_attrs.get('stdout', None) - imported_job.stderr = job_attrs.get('stderr', None) - imported_job.command_line = job_attrs.get('command_line', None) - try: - imported_job.create_time = datetime.datetime.strptime(job_attrs["create_time"], "%Y-%m-%dT%H:%M:%S.%f") - imported_job.update_time = datetime.datetime.strptime(job_attrs["update_time"], "%Y-%m-%dT%H:%M:%S.%f") - except Exception: - pass - self.sa_session.add(imported_job) - self.sa_session.flush() - - class HistoryDatasetAssociationIDEncoder(json.JSONEncoder): - """ Custom JSONEncoder for a HistoryDatasetAssociation that encodes an HDA as its ID. """ - - def default(self, obj): - """ Encode an HDA, default encoding for everything else. """ - if isinstance(obj, model.HistoryDatasetAssociation): - return obj.id - return json.JSONEncoder.default(self, obj) - - # Set parameters. May be useful to look at metadata.py for creating parameters. - # TODO: there may be a better way to set parameters, e.g.: - # for name, value in tool.params_to_strings( incoming, trans.app ).items(): - # job.add_parameter( name, value ) - # to make this work, we'd need to flesh out the HDA objects. The code below is - # relatively similar. - for name, value in job_attrs['params'].items(): - # Transform parameter values when necessary. - if isinstance(value, model.HistoryDatasetAssociation): - # HDA input: use hid to find input. - input_hda = self.sa_session.query(model.HistoryDatasetAssociation) \ - .filter_by(history=new_history, hid=value.hid).first() - value = input_hda.id - imported_job.add_parameter(name, dumps(value, cls=HistoryDatasetAssociationIDEncoder)) - - # TODO: Connect jobs to input datasets. - - # Connect jobs to output datasets. - for output_hid in job_attrs['output_datasets']: - output_hda = self.sa_session.query(model.HistoryDatasetAssociation) \ - .filter_by(history=new_history, hid=output_hid).first() - if output_hda: - imported_job.add_output_dataset(output_hda.name, output_hda) - - # Connect jobs to input datasets. - if 'input_mapping' in job_attrs: - for input_name, input_hid in job_attrs['input_mapping'].items(): - input_hda = self.sa_session.query(model.HistoryDatasetAssociation) \ - .filter_by(history=new_history, hid=input_hid).first() - if input_hda: - imported_job.add_input_dataset(input_name, input_hda) - - self.sa_session.flush() - - # Done importing. - new_history.importing = False - self.sa_session.flush() + model_store.perform_import(new_history, job=job, new_history=True) # Cleanup. if os.path.exists(archive_dir): shutil.rmtree(archive_dir) - except Exception as e: - jiha.job.stderr += "Error cleaning up history import job: %s" % e - self.sa_session.flush() - raise + + except Exception as e: + jiha.job.tool_stderr += "Error cleaning up history import job: %s" % e + self.sa_session.flush() + raise + + return new_history -class JobExportHistoryArchiveWrapper(UsesAnnotations): +class JobExportHistoryArchiveWrapper: """ Class provides support for performing jobs that export a history to an archive. """ - def __init__(self, job_id): + def __init__(self, app, job_id): + self.app = app self.job_id = job_id + self.sa_session = self.app.model.context - def get_history_datasets(self, trans, history): - """ - Returns history's datasets. - """ - query = (trans.sa_session.query(trans.model.HistoryDatasetAssociation) - .filter(trans.model.HistoryDatasetAssociation.history == history) - .join("dataset") - .options(eagerload_all("dataset.actions")) - .order_by(trans.model.HistoryDatasetAssociation.hid) - .filter(trans.model.HistoryDatasetAssociation.deleted == expression.false()) - .filter(trans.model.Dataset.purged == expression.false())) - return query.all() - - # TODO: should use db_session rather than trans in this method. - def setup_job(self, trans, jeha, include_hidden=False, include_deleted=False): + def setup_job(self, jeha, include_hidden=False, include_deleted=False): """ Perform setup for job to export a history into an archive. Method generates attribute files for export, sets the corresponding attributes in the jeha object, and returns a command line for running the job. The command line includes the command, inputs, and options; it does not include the output file because it must be set at runtime. """ - # - # Helper methods/classes. - # - - def get_item_tag_dict(item): - """ Create dictionary of an item's tags. """ - tags = {} - for tag in item.tags: - tag_user_tname = to_unicode(tag.user_tname) - tag_user_value = to_unicode(tag.user_value) - tags[tag_user_tname] = tag_user_value - return tags - - def prepare_metadata(metadata): - """ Prepare metatdata for exporting. """ - for name, value in list(metadata.items()): - # Metadata files are not needed for export because they can be - # regenerated. - if isinstance(value, trans.app.model.MetadataFile): - del metadata[name] - return metadata - - class HistoryDatasetAssociationEncoder(json.JSONEncoder): - """ Custom JSONEncoder for a HistoryDatasetAssociation. """ - - def default(self, obj): - """ Encode an HDA, default encoding for everything else. """ - if isinstance(obj, trans.app.model.HistoryDatasetAssociation): - rval = { - "__HistoryDatasetAssociation__": True, - "create_time": obj.create_time.__str__(), - "update_time": obj.update_time.__str__(), - "hid": obj.hid, - "name": to_unicode(obj.name), - "info": to_unicode(obj.info), - "blurb": obj.blurb, - "peek": obj.peek, - "extension": obj.extension, - "metadata": prepare_metadata(dict(obj.metadata.items())), - "parent_id": obj.parent_id, - "designation": obj.designation, - "deleted": obj.deleted, - "visible": obj.visible, - "file_name": obj.file_name, - "uuid": (lambda uuid: str(uuid) if uuid else None)(obj.dataset.uuid), - "annotation": to_unicode(getattr(obj, 'annotation', '')), - "tags": get_item_tag_dict(obj), - "extra_files_path": obj.extra_files_path - } - if not obj.visible and not include_hidden: - rval['exported'] = False - elif obj.deleted and not include_deleted: - rval['exported'] = False - else: - rval['exported'] = True - return rval - return json.JSONEncoder.default(self, obj) + app = self.app # # Create attributes/metadata files for export. # - temp_output_dir = tempfile.mkdtemp() + # Use abspath because mkdtemp() does not, contrary to the documentation, + # always return an absolute path. + temp_output_dir = os.path.abspath(tempfile.mkdtemp()) - # Write history attributes to file. history = jeha.history - history_attrs = { - "create_time": history.create_time.__str__(), - "update_time": history.update_time.__str__(), - "name": to_unicode(history.name), - "hid_counter": history.hid_counter, - "genome_build": history.genome_build, - "annotation": to_unicode(self.get_item_annotation_str(trans.sa_session, history.user, history)), - "tags": get_item_tag_dict(history), - "includes_hidden_datasets": include_hidden, - "includes_deleted_datasets": include_deleted - } - history_attrs_filename = tempfile.NamedTemporaryFile(dir=temp_output_dir).name - history_attrs_out = open(history_attrs_filename, 'w') - history_attrs_out.write(dumps(history_attrs)) - history_attrs_out.close() + history_attrs_filename = os.path.join(temp_output_dir, ATTRS_FILENAME_HISTORY) jeha.history_attrs_filename = history_attrs_filename - # Write datasets' attributes to file. - datasets = self.get_history_datasets(trans, history) - included_datasets = [] - datasets_attrs = [] - provenance_attrs = [] - for dataset in datasets: - dataset.annotation = self.get_item_annotation_str(trans.sa_session, history.user, dataset) - if (not dataset.visible and not include_hidden) or (dataset.deleted and not include_deleted): - provenance_attrs.append(dataset) - else: - datasets_attrs.append(dataset) - included_datasets.append(dataset) - datasets_attrs_filename = tempfile.NamedTemporaryFile(dir=temp_output_dir).name - datasets_attrs_out = open(datasets_attrs_filename, 'w') - datasets_attrs_out.write(dumps(datasets_attrs, cls=HistoryDatasetAssociationEncoder)) - datasets_attrs_out.close() - jeha.datasets_attrs_filename = datasets_attrs_filename - - provenance_attrs_out = open(datasets_attrs_filename + ".provenance", 'w') - provenance_attrs_out.write(dumps(provenance_attrs, cls=HistoryDatasetAssociationEncoder)) - provenance_attrs_out.close() - - # - # Write jobs attributes file. - # - - # Get all jobs associated with included HDAs. - jobs_dict = {} - for hda in included_datasets: - # Get the associated job, if any. If this hda was copied from another, - # we need to find the job that created the origial hda - job_hda = hda - while job_hda.copied_from_history_dataset_association: # should this check library datasets as well? - job_hda = job_hda.copied_from_history_dataset_association - if not job_hda.creating_job_associations: - # No viable HDA found. - continue - - # Get the job object. - job = None - for assoc in job_hda.creating_job_associations: - job = assoc.job - break - if not job: - # No viable job. - continue - - jobs_dict[job.id] = job - - # Get jobs' attributes. - jobs_attrs = [] - for id, job in jobs_dict.items(): - job_attrs = {} - job_attrs['tool_id'] = job.tool_id - job_attrs['tool_version'] = job.tool_version - job_attrs['state'] = job.state - job_attrs['info'] = job.info - job_attrs['traceback'] = job.traceback - job_attrs['command_line'] = job.command_line - job_attrs['stderr'] = job.stderr - job_attrs['stdout'] = job.stdout - job_attrs['exit_code'] = job.exit_code - job_attrs['create_time'] = job.create_time.isoformat() - job_attrs['update_time'] = job.update_time.isoformat() - - # Get the job's parameters - try: - params_objects = job.get_param_values(trans.app) - except Exception: - # Could not get job params. - continue - - params_dict = {} - for name, value in params_objects.items(): - params_dict[name] = value - job_attrs['params'] = params_dict - - # -- Get input, output datasets. -- - - input_datasets = [] - input_mapping = {} - for assoc in job.input_datasets: - # Optional data inputs will not have a dataset. - if assoc.dataset: - input_datasets.append(assoc.dataset.hid) - input_mapping[assoc.name] = assoc.dataset.hid - job_attrs['input_datasets'] = input_datasets - job_attrs['input_mapping'] = input_mapping - output_datasets = [assoc.dataset.hid for assoc in job.output_datasets] - job_attrs['output_datasets'] = output_datasets - - jobs_attrs.append(job_attrs) - - jobs_attrs_filename = tempfile.NamedTemporaryFile(dir=temp_output_dir).name - jobs_attrs_out = open(jobs_attrs_filename, 'w') - jobs_attrs_out.write(dumps(jobs_attrs, cls=HistoryDatasetAssociationEncoder)) - jobs_attrs_out.close() - jeha.jobs_attrs_filename = jobs_attrs_filename + # symlink files on export, on worker files will tarred up in a dereferenced manner. + with store.DirectoryModelExportStore(temp_output_dir, app=app, export_files="symlink") as export_store: + export_store.export_history(history, include_hidden=include_hidden, include_deleted=include_deleted) # # Create and return command line for running tool. # - options = "" + options = "--galaxy-version '%s'" % VERSION_MAJOR if jeha.compressed: - options = "-G" - return "%s %s %s %s" % (options, history_attrs_filename, - datasets_attrs_filename, - jobs_attrs_filename) + options += " -G" + return "%s %s" % (options, temp_output_dir) - def cleanup_after_job(self, db_session): + def cleanup_after_job(self): """ Remove temporary directory and attribute files generated during setup for this job. """ # Get jeha for job. - jeha = db_session.query(model.JobExportHistoryArchive).filter_by(job_id=self.job_id).first() + jeha = self.sa_session.query(model.JobExportHistoryArchive).filter_by(job_id=self.job_id).first() if jeha: - for filename in [jeha.history_attrs_filename, jeha.datasets_attrs_filename, jeha.jobs_attrs_filename]: - try: - os.remove(filename) - except Exception as e: - log.debug('Failed to cleanup attributes file (%s): %s' % (filename, e)) - temp_dir = os.path.split(jeha.history_attrs_filename)[0] + temp_dir = jeha.temp_directory try: shutil.rmtree(temp_dir) except Exception as e: diff --git a/lib/galaxy/tools/imp_exp/export_history.py b/lib/galaxy/tools/imp_exp/export_history.py index ac70477db47..e01911fb4cf 100644 --- a/lib/galaxy/tools/imp_exp/export_history.py +++ b/lib/galaxy/tools/imp_exp/export_history.py @@ -9,101 +9,59 @@ from __future__ import print_function import optparse import os +import shutil import sys -import tarfile -from json import dumps, loads -from galaxy.util import FILENAME_VALID_CHARS +from galaxy.model.store import tar_export_directory -def get_dataset_filename(name, ext, hid): - """ - Builds a filename for a dataset using its name an extension. - """ - base = ''.join(c in FILENAME_VALID_CHARS and c or '_' for c in name) - return base + "_%s.%s" % (hid, ext) - - -def create_archive(history_attrs_file, datasets_attrs_file, jobs_attrs_file, out_file, gzip=False): +def create_archive(export_directory, out_file, gzip=False): """Create archive from the given attribute/metadata files and save it to out_file.""" - tarfile_mode = "w" - if gzip: - tarfile_mode += ":gz" try: - - history_archive = tarfile.open(out_file, tarfile_mode) - - # Read datasets attributes from file. - with open(datasets_attrs_file) as datasets_attr_in: - datasets_attr_str = '' - buffsize = 1048576 - try: - while True: - datasets_attr_str += datasets_attr_in.read(buffsize) - if not datasets_attr_str or len(datasets_attr_str) % buffsize != 0: - break - except OverflowError: - pass - datasets_attrs = loads(datasets_attr_str) - - # Add datasets to archive and update dataset attributes. - # TODO: security check to ensure that files added are in Galaxy dataset directory? - for dataset_attrs in datasets_attrs: - if dataset_attrs['exported']: - dataset_file_name = dataset_attrs['file_name'] # Full file name. - dataset_hid = dataset_attrs['hid'] - dataset_archive_name = os.path.join('datasets', - get_dataset_filename(dataset_attrs['name'], dataset_attrs['extension'], dataset_hid)) - history_archive.add(dataset_file_name, arcname=dataset_archive_name) - - # Include additional files for example, files/images included in HTML output. - extra_files_path = dataset_attrs['extra_files_path'] - if extra_files_path: - try: - file_list = os.listdir(extra_files_path) - except OSError: - file_list = [] - - if len(file_list): - dataset_extra_files_path = 'datasets/extra_files_path_%s' % dataset_hid - for fname in file_list: - history_archive.add(os.path.join(extra_files_path, fname), - arcname=(os.path.join(dataset_extra_files_path, fname))) - dataset_attrs['extra_files_path'] = dataset_extra_files_path - else: - dataset_attrs['extra_files_path'] = '' - - # Update dataset filename to be archive name. - dataset_attrs['file_name'] = dataset_archive_name - - # Rewrite dataset attributes file. - with open(datasets_attrs_file, 'w') as datasets_attrs_out: - datasets_attrs_out.write(dumps(datasets_attrs)) - - # Finish archive. - history_archive.add(history_attrs_file, arcname="history_attrs.txt") - history_archive.add(datasets_attrs_file, arcname="datasets_attrs.txt") - if os.path.exists(datasets_attrs_file + ".provenance"): - history_archive.add(datasets_attrs_file + ".provenance", arcname="datasets_attrs.txt.provenance") - history_archive.add(jobs_attrs_file, arcname="jobs_attrs.txt") - history_archive.close() - + tar_export_directory(export_directory, out_file, gzip) # Status. print('Created history archive.') + return 0 except Exception as e: print('Error creating history archive: %s' % str(e), file=sys.stderr) + return 1 -def main(): +def main(argv=None): # Parse command line. parser = optparse.OptionParser() parser.add_option('-G', '--gzip', dest='gzip', action="store_true", help='Compress archive using gzip.') - (options, args) = parser.parse_args() + parser.add_option('--galaxy-version', dest='galaxy_version', help='Galaxy version that initiated the command.', default=None) + (options, args) = parser.parse_args(argv) + galaxy_version = options.galaxy_version + if galaxy_version is None: + galaxy_version = "19.01" if len(args) == 4 else "19.05" + gzip = bool(options.gzip) - history_attrs, dataset_attrs, job_attrs, out_file = args + if galaxy_version == "19.01": + # This job was created pre 18.0X with old argument style. + out_file = args[3] + temp_directory = os.path.dirname(args[0]) + else: + assert len(args) >= 2 + # We have a 19.0X directory argument instead of individual arguments. + temp_directory = args[0] + out_file = args[1] + + if galaxy_version == "19.01": + history_attrs = os.path.join(temp_directory, 'history_attrs.txt') + dataset_attrs = os.path.join(temp_directory, 'datasets_attrs.txt') + job_attrs = os.path.join(temp_directory, 'jobs_attrs.txt') + + shutil.move(args[0], history_attrs) + shutil.move(args[1], dataset_attrs) + provenance_path = args[1] + ".provenance" + if os.path.exists(provenance_path): + shutil.move(provenance_path, dataset_attrs + ".provenance") + shutil.move(args[2], job_attrs) # Create archive. - create_archive(history_attrs, dataset_attrs, job_attrs, out_file, gzip) + return create_archive(temp_directory, out_file, gzip=gzip) if __name__ == "__main__": diff --git a/lib/galaxy/tools/linters/tests.py b/lib/galaxy/tools/linters/tests.py index 73c230566d1..aca2351e7a8 100644 --- a/lib/galaxy/tools/linters/tests.py +++ b/lib/galaxy/tools/linters/tests.py @@ -19,7 +19,7 @@ def lint_tsts(tool_xml, lint_ctx): has_test = True if len(test.findall("assert_stdout")) > 0: has_test = True - if len(test.findall("assert_stdout")) > 0: + if len(test.findall("assert_stderr")) > 0: has_test = True if len(test.findall("assert_command")) > 0: has_test = True diff --git a/lib/galaxy/tools/loader_directory.py b/lib/galaxy/tools/loader_directory.py index 4820f17f4b2..1cdbc7d6b35 100644 --- a/lib/galaxy/tools/loader_directory.py +++ b/lib/galaxy/tools/loader_directory.py @@ -19,6 +19,7 @@ PATH_AND_RECURSIVE_ERROR = "Cannot specify a single file and recursive." LOAD_FAILURE_ERROR = "Failed to load tool with path %s." TOOL_LOAD_ERROR = object() TOOL_REGEX = re.compile(r">> p = ColorToolParameter(None, XML('')) >>> print(p.name) _name - >>> print(p.to_param_dict_string("#fdeada")) - #fdeada + >>> print(p.to_param_dict_string("#ffffff")) + #ffffff >>> assert sorted(p.to_dict(trans).items()) == [('argument', None), ('help', ''), ('hidden', False), ('is_dynamic', False), ('label', ''), ('model_class', 'ColorToolParameter'), ('name', '_name'), ('optional', False), ('refresh_on_change', False), ('type', 'color'), ('value', u'#ffffff')] + >>> p = ColorToolParameter(None, XML('')) + >>> print(p.get_initial_value(trans, {})) + #000000 >>> p = ColorToolParameter(None, XML('')) - >>> print(p.to_param_dict_string("#fdeada")) - (253, 234, 218) + >>> print(p.to_param_dict_string("#ffffff")) + (255, 255, 255) >>> print(p.to_param_dict_string(None)) Traceback (most recent call last): ... @@ -703,12 +706,14 @@ class ColorToolParameter(ToolParameter): """ def __init__(self, tool, input_source): + input_source = ensure_input_source(input_source) super(ColorToolParameter, self).__init__(tool, input_source) - self.value = input_source.get('value', '#fdeada') + self.value = input_source.get('value', '#000000') self.rgb = input_source.get('rgb', False) def get_initial_value(self, trans, other_values): - return self.value.lower() + if self.value is not None: + return self.value.lower() def to_param_dict_string(self, value, other_values={}): if self.rgb: @@ -1075,6 +1080,8 @@ class SelectTagParameter(SelectToolParameter): # Get the value of the associated data reference (a dataset) history_items = other_values.get(self.data_ref, None) # Check if a dataset is selected + if is_runtime_value(history_items): + return [] if not history_items: return [] tags = set() @@ -1105,7 +1112,7 @@ class SelectTagParameter(SelectToolParameter): return SelectToolParameter.get_initial_value(self, trans, other_values) def get_legal_values(self, trans, other_values): - if self.data_ref not in other_values: + if self.data_ref not in other_values and not trans.workflow_building_mode: raise ValueError("Value for associated data reference not found (data_ref).") return set(self.get_tag_list(other_values)) @@ -1715,7 +1722,7 @@ class DataToolParameter(BaseDataToolParameter): self.conversions.append((name, conv_extension, [conv_type])) def from_json(self, value, trans, other_values={}): - if trans.workflow_building_mode is workflow_building_modes.ENABLED: + if trans.workflow_building_mode is workflow_building_modes.ENABLED or is_runtime_value(value): return None if not value and not self.optional: raise ValueError("Specify a dataset of the required format / build for parameter %s." % self.name) diff --git a/lib/galaxy/tools/parameters/meta.py b/lib/galaxy/tools/parameters/meta.py index c5da5c10b46..9de16848221 100644 --- a/lib/galaxy/tools/parameters/meta.py +++ b/lib/galaxy/tools/parameters/meta.py @@ -10,6 +10,7 @@ from galaxy import ( model, util ) +from galaxy.model.dataset_collections import matching, subcollections from galaxy.util import permutations from . import visit_input_values @@ -50,7 +51,7 @@ def expand_workflow_inputs(inputs): else: if linked_n is None: linked_n = nval - elif linked_n != nval or nval is 0: + elif linked_n != nval or nval == 0: raise exceptions.RequestParameterInvalidException('Failed to match linked batch selections. Please select equal number of data files.') linked.append(value['values']) linked_keys.append((step_id, key)) @@ -152,7 +153,6 @@ def expand_meta_parameters(trans, tool, incoming): values = value return classification, values - from galaxy.dataset_collections import matching collections_to_match = matching.CollectionsToMatch() # Stick an unexpanded version of multirun keys so they can be replaced, @@ -186,7 +186,6 @@ def __expand_collection_parameter(trans, input_key, incoming_val, collections_to hdc = trans.sa_session.query(model.HistoryDatasetCollectionAssociation).get(hdc_id) collections_to_match.add(input_key, hdc, subcollection_type=subcollection_type, linked=linked) if subcollection_type is not None: - from galaxy.dataset_collections import subcollections subcollection_elements = subcollections.split_dataset_collection_instance(hdc, subcollection_type) return subcollection_elements else: diff --git a/lib/galaxy/tools/parameters/output_collect.py b/lib/galaxy/tools/parameters/output_collect.py index 15a2467b6ed..d40e5ba5218 100644 --- a/lib/galaxy/tools/parameters/output_collect.py +++ b/lib/galaxy/tools/parameters/output_collect.py @@ -1,14 +1,14 @@ """ Code allowing tools to define extra files associated with an output datset. """ -import json import logging import operator import os import re from collections import namedtuple +import galaxy.model from galaxy import util -from galaxy.dataset_collections.structure import UninitializedTree +from galaxy.model.dataset_collections.structure import UninitializedTree from galaxy.tools.parser.output_collection_def import ( DEFAULT_DATASET_COLLECTOR_DESCRIPTION, INPUT_DBKEY_TOKEN, @@ -24,149 +24,81 @@ DATASET_ID_TOKEN = "DATASET_ID" log = logging.getLogger(__name__) -class NullToolProvidedMetadata(object): +# PermissionProvider and MetadataSourceProvider are abstractions over input data used to +# collect and produce dynamic outputs. +class PermissionProvider(object): - def get_new_datasets(self, output_name): - return [] + def __init__(self, inp_data, security_agent, job): + self._job = job + self._security_agent = security_agent + self._inp_data = inp_data + self._user = job.user + self._permissions = None - def get_new_dataset_meta_by_basename(self, output_name, basename): - return {} - - def has_failed_outputs(self): - return False - - def get_unnamed_outputs(self): - return [] - - -class LegacyToolProvidedMetadata(object): - - def __init__(self, job_wrapper, meta_file): - self.job_wrapper = job_wrapper - self.tool_provided_job_metadata = [] - - with open(meta_file, 'r') as f: - for line in f: - try: - line = json.loads(line) - assert 'type' in line - except Exception: - log.exception('(%s) Got JSON data from tool, but data is improperly formatted or no "type" key in data' % job_wrapper.job_id) - log.debug('Offending data was: %s' % line) - continue - # Set the dataset id if it's a dataset entry and isn't set. - # This isn't insecure. We loop the job's output datasets in - # the finish method, so if a tool writes out metadata for a - # dataset id that it doesn't own, it'll just be ignored. - if line['type'] == 'dataset' and 'dataset_id' not in line: - try: - line['dataset_id'] = job_wrapper.get_output_file_id(line['dataset']) - except KeyError: - log.warning('(%s) Tool provided job dataset-specific metadata without specifying a dataset' % job_wrapper.job_id) - continue - self.tool_provided_job_metadata.append(line) - - def get_meta_by_dataset_id(self, dataset_id): - for meta in self.tool_provided_job_metadata: - if meta['type'] == 'dataset' and meta['dataset_id'] == dataset_id: - return meta - - def get_new_dataset_meta_by_basename(self, output_name, basename): - for meta in self.tool_provided_job_metadata: - if meta['type'] == 'new_primary_dataset' and meta['filename'] == basename: - return meta - - def get_new_datasets(self, output_name): - log.warning("Called get_new_datasets with legacy tool metadata provider - that is unimplemented.") - return [] - - def has_failed_outputs(self): - found_failed = False - for meta in self.tool_provided_job_metadata: - if meta.get("failed", False): - found_failed = True - - return found_failed - - def get_unnamed_outputs(self): - return [] - - -class ToolProvidedMetadata(object): - - def __init__(self, job_wrapper, meta_file): - self.job_wrapper = job_wrapper - with open(meta_file, 'r') as f: - self.tool_provided_job_metadata = json.load(f) - - def get_meta_by_name(self, name): - return self.tool_provided_job_metadata.get(name, {}) - - def get_new_dataset_meta_by_basename(self, output_name, basename): - datasets = self.tool_provided_job_metadata.get(output_name, {}).get("datasets", []) - for meta in datasets: - if meta['filename'] == basename: - return meta - - def get_new_datasets(self, output_name): - datasets = self.tool_provided_job_metadata.get(output_name, {}).get("datasets", []) - if not datasets: - elements = self.tool_provided_job_metadata.get(output_name, {}).get("elements", []) - if elements: - datasets = self._elements_to_datasets(elements) - return datasets - - def _elements_to_datasets(self, elements, level=0): - for element in elements: - extra_kwds = {"identifier_%d" % level: element["name"]} - if "elements" in element: - for inner_element in self._elements_to_datasets(element["elements"], level=level + 1): - dataset = extra_kwds.copy() - dataset.update(inner_element) - yield dataset + @property + def permissions(self): + if self._permissions is None: + inp_data = self._inp_data + existing_datasets = [inp for inp in inp_data.values() if inp] + if existing_datasets: + permissions = self._security_agent.guess_derived_permissions_for_datasets(existing_datasets) else: - dataset = extra_kwds - extra_kwds.update(element) - yield extra_kwds + # No valid inputs, we will use history defaults + permissions = self._security_agent.history_get_default_permissions(self._job.history) + self._permissions = permissions - def has_failed_outputs(self): - found_failed = False - for output_name, meta in self.tool_provided_job_metadata.items(): - if output_name == "__unnamed_outputs": - continue + return self._permissions - if meta.get("failed", False): - found_failed = True + def set_default_hda_permissions(self, primary_data): + permissions = self.permissions + if permissions is not UNSET: + self._security_agent.set_all_dataset_permissions(primary_data.dataset, permissions, new=True, flush=False) - return found_failed + def copy_dataset_permissions(self, init_from, primary_data): + self._security_agent.copy_dataset_permissions(init_from.dataset, primary_data.dataset) - def get_unnamed_outputs(self): - log.debug("unnamed outputs [%s]" % self.tool_provided_job_metadata) - return self.tool_provided_job_metadata.get("__unnamed_outputs", []) + +class UnusedPermissionProvider(object): + + @property + def permissions(self): + return UNSET + + def set_default_hda_permissions(self, primary_data): + return + + def copy_dataset_permissions(self, init_from, primary_data): + raise NotImplementedError() + + +class MetadataSourceProvider(object): + + def __init__(self, inp_data): + self._inp_data = inp_data + + def get_metadata_source(self, input_name): + return self._inp_data[input_name] + + +class UnusedMetadataSourceProvider(object): + + def get_metadata_source(self, input_name): + raise NotImplementedError() def collect_dynamic_outputs( - tool, + job_context, output_collections, - tool_provided_metadata, - job_working_directory, - inp_data={}, - job=None, - input_dbkey="?", ): - app = tool.app - collections_service = tool.app.dataset_collections_service - job_context = JobContext( - tool, - tool_provided_metadata, - job, - job_working_directory, - inp_data, - input_dbkey, - ) + tool = job_context.tool + app = job_context.app + sa_session = job_context.sa_session + collections_service = app.dataset_collections_service + job_working_directory = job_context.job_working_directory + # unmapped outputs do not correspond to explicit outputs of the tool, they were inferred entirely # from the tool provided metadata (e.g. galaxy.json). - for unnamed_output_dict in tool_provided_metadata.get_unnamed_outputs(): + for unnamed_output_dict in job_context.tool_provided_metadata.get_unnamed_outputs(): assert "destination" in unnamed_output_dict assert "elements" in unnamed_output_dict destination = unnamed_output_dict["destination"] @@ -185,49 +117,14 @@ def collect_dynamic_outputs( library_folder_manager = app.library_folder_manager library_folder = library_folder_manager.get(trans, app.security.decode_id(destination.get("library_folder_id"))) - - def add_elements_to_folder(elements, library_folder): - for element in elements: - if "elements" in element: - assert "name" in element - name = element["name"] - description = element.get("description") - nested_folder = library_folder_manager.create(trans, library_folder.id, name, description) - add_elements_to_folder(element["elements"], nested_folder) - else: - discovered_file = discovered_file_for_unnamed_output(element, job_working_directory) - fields_match = discovered_file.match - designation = fields_match.designation - visible = fields_match.visible - ext = fields_match.ext - dbkey = fields_match.dbkey - info = element.get("info", None) - link_data = discovered_file.match.link_data - - # Create new primary dataset - name = fields_match.name or designation - - job_context.create_dataset( - ext=ext, - designation=designation, - visible=visible, - dbkey=dbkey, - name=name, - filename=discovered_file.path, - info=info, - library_folder=library_folder, - link_data=link_data - ) - - add_elements_to_folder(elements, library_folder) + persist_elements_to_folder(job_context, elements, library_folder) elif destination_type == "hdca": # create or populate a dataset collection in the history - history = job.history + history = job_context.job.history assert "collection_type" in unnamed_output_dict object_id = destination.get("object_id") if object_id: - sa_session = tool.app.model.context - hdca = sa_session.query(app.model.HistoryDatasetCollectionAssociation).get(int(object_id)) + hdca = sa_session.query(galaxy.model.HistoryDatasetCollectionAssociation).get(int(object_id)) else: name = unnamed_output_dict.get("name", "unnamed collection") collection_type = unnamed_output_dict["collection_type"] @@ -259,50 +156,7 @@ def collect_dynamic_outputs( ) collection_builder.populate() elif destination_type == "hdas": - # discover files as individual datasets for the target history - history = job.history - - datasets = [] - - def collect_elements_for_history(elements): - for element in elements: - if "elements" in element: - collect_elements_for_history(element["elements"]) - else: - discovered_file = discovered_file_for_unnamed_output(element, job_working_directory) - fields_match = discovered_file.match - designation = fields_match.designation - ext = fields_match.ext - dbkey = fields_match.dbkey - info = element.get("info", None) - link_data = discovered_file.match.link_data - - # Create new primary dataset - name = fields_match.name or designation - - hda_id = discovered_file.match.object_id - primary_dataset = None - if hda_id: - sa_session = tool.app.model.context - primary_dataset = sa_session.query(app.model.HistoryDatasetAssociation).get(hda_id) - - dataset = job_context.create_dataset( - ext=ext, - designation=designation, - visible=True, - dbkey=dbkey, - name=name, - filename=discovered_file.path, - info=info, - link_data=link_data, - primary_data=primary_dataset, - ) - dataset.raw_set_dataset_state('ok') - if not hda_id: - datasets.append(dataset) - - collect_elements_for_history(elements) - job.history.add_datasets(job_context.sa_session, datasets) + persist_hdas(elements, job_context) for name, has_collection in output_collections.items(): if name not in tool.output_collections: @@ -342,36 +196,262 @@ def collect_dynamic_outputs( collection.handle_population_failed("Problem building datasets for collection.") -class JobContext(object): +def persist_elements_to_folder(job_context, elements, library_folder): + for element in elements: + if "elements" in element: + assert "name" in element + name = element["name"] + description = element.get("description") + nested_folder = job_context.create_library_folder(library_folder, name, description) + persist_elements_to_folder(job_context, element["elements"], nested_folder) + else: + discovered_file = discovered_file_for_unnamed_output(element, job_context.job_working_directory) + fields_match = discovered_file.match + designation = fields_match.designation + visible = fields_match.visible + ext = fields_match.ext + dbkey = fields_match.dbkey + info = element.get("info", None) + link_data = discovered_file.match.link_data - def __init__(self, tool, tool_provided_metadata, job, job_working_directory, inp_data, input_dbkey): - self.inp_data = inp_data + # Create new primary dataset + name = fields_match.name or designation + + sources = fields_match.sources + hashes = fields_match.hashes + + job_context.create_dataset( + ext=ext, + designation=designation, + visible=visible, + dbkey=dbkey, + name=name, + filename=discovered_file.path, + info=info, + library_folder=library_folder, + link_data=link_data, + sources=sources, + hashes=hashes, + ) + + +def persist_hdas(elements, model_create_context): + # discover files as individual datasets for the target history + datasets = [] + + def collect_elements_for_history(elements): + for element in elements: + if "elements" in element: + collect_elements_for_history(element["elements"]) + else: + discovered_file = discovered_file_for_unnamed_output(element, model_create_context.job_working_directory) + fields_match = discovered_file.match + designation = fields_match.designation + ext = fields_match.ext + dbkey = fields_match.dbkey + info = element.get("info", None) + link_data = discovered_file.match.link_data + + # Create new primary dataset + name = fields_match.name or designation + + hda_id = discovered_file.match.object_id + primary_dataset = None + if hda_id: + primary_dataset = model_create_context.sa_session.query(galaxy.model.HistoryDatasetAssociation).get(hda_id) + + sources = fields_match.sources + hashes = fields_match.hashes + + dataset = model_create_context.create_dataset( + ext=ext, + designation=designation, + visible=True, + dbkey=dbkey, + name=name, + filename=discovered_file.path, + info=info, + link_data=link_data, + primary_data=primary_dataset, + sources=sources, + hashes=hashes, + ) + dataset.raw_set_dataset_state('ok') + if not hda_id: + datasets.append(dataset) + + collect_elements_for_history(elements) + model_create_context.add_datasets_to_history(datasets) + + +class ModelPersistenceContext(object): + + def create_dataset( + self, + ext, + designation, + visible, + dbkey, + name, + filename, + metadata_source_name=None, + info=None, + library_folder=None, + link_data=False, + primary_data=None, + init_from=None, + dataset_attributes=None, + tag_list=[], + sources=[], + hashes=[], + ): + sa_session = self.sa_session + + # You can initialize a dataset or initialize from a dataset but not both. + if init_from: + assert primary_data is None + if primary_data: + assert init_from is None + + if metadata_source_name: + assert init_from is None + if init_from: + assert metadata_source_name is None + + if primary_data is not None: + primary_data.extension = ext + primary_data.visible = visible + primary_data.dbkey = dbkey + else: + if not library_folder: + primary_data = galaxy.model.HistoryDatasetAssociation(extension=ext, + designation=designation, + visible=visible, + dbkey=dbkey, + create_dataset=True, + flush=False, + sa_session=sa_session) + + self.persist_object(primary_data) + if init_from: + self.permission_provider.copy_dataset_permissions(init_from, primary_data) + primary_data.state = init_from.state + else: + self.permission_provider.set_default_hda_permissions(primary_data) + else: + ld = galaxy.model.LibraryDataset(folder=library_folder, name=name) + ldda = galaxy.model.LibraryDatasetDatasetAssociation(name=name, + extension=ext, + dbkey=dbkey, + # library_dataset=ld, + user=self.user, + create_dataset=True, + flush=False, + sa_session=sa_session) + ld.library_dataset_dataset_association = ldda + ldda.raw_set_dataset_state(ldda.states.OK) + + self.add_library_dataset_to_folder(library_folder, ld) + primary_data = ldda + + for source_dict in sources: + source = galaxy.model.DatasetSource() + source.source_uri = source_dict["source_uri"] + primary_data.dataset.sources.append(source) + + for hash_dict in hashes: + hash_object = galaxy.model.DatasetHash() + hash_object.hash_function = hash_dict["hash_function"] + hash_object.hash_value = hash_dict["hash_value"] + primary_data.dataset.hashes.append(hash_object) + + self.flush() + + if tag_list: + self.tag_handler.add_tags_from_list(self.job.user, primary_data, tag_list) + + # Move data from temp location to dataset location + if not link_data: + self.object_store.update_from_file(primary_data.dataset, file_name=filename, create=True) + else: + primary_data.link_to(filename) + + # We are sure there are no extra files, so optimize things that follow by settting total size also. + primary_data.set_size(no_extra_files=True) + # If match specified a name use otherwise generate one from + # designation. + primary_data.name = name + + # Copy metadata from one of the inputs if requested. + if metadata_source_name: + metadata_source = self.metadata_source_provider.get_metadata_source(metadata_source_name) + primary_data.init_meta(copy_from=metadata_source) + elif init_from: + metadata_source = init_from + primary_data.init_meta(copy_from=init_from) + # when coming from primary dataset - respect pattern of output - this makes sense + primary_data.dbkey = dbkey + else: + primary_data.init_meta() + + if info is not None: + primary_data.info = info + + # add tool/metadata provided information + dataset_attributes = dataset_attributes or {} + if dataset_attributes: + # TODO: discover_files should produce a match that encorporates this - + # would simplify ToolProvidedMetadata interface and eliminate this + # crap path. + dataset_att_by_name = dict(ext='extension') + for att_set in ['name', 'info', 'ext', 'dbkey']: + dataset_att_name = dataset_att_by_name.get(att_set, att_set) + setattr(primary_data, dataset_att_name, dataset_attributes.get(att_set, getattr(primary_data, dataset_att_name))) + + metadata_dict = dataset_attributes.get('metadata', None) + if metadata_dict: + if "dbkey" in dataset_attributes: + metadata_dict["dbkey"] = dataset_attributes["dbkey"] + # branch tested with tool_provided_metadata_3 / tool_provided_metadata_10 + primary_data.metadata.from_JSON_dict(json_dict=metadata_dict) + else: + primary_data.set_meta() + + primary_data.set_peek() + + return primary_data + + +class JobContext(ModelPersistenceContext): + + def __init__(self, tool, tool_provided_metadata, job, job_working_directory, permission_provider, metadata_source_provider, input_dbkey, object_store): + self.tool = tool + self.metadata_source_provider = metadata_source_provider + self.permission_provider = permission_provider self.input_dbkey = input_dbkey self.app = tool.app self.sa_session = tool.sa_session self.job = job self.job_working_directory = job_working_directory self.tool_provided_metadata = tool_provided_metadata - self._permissions = None + self.object_store = object_store @property def work_context(self): from galaxy.work.context import WorkRequestContext - return WorkRequestContext(self.app, user=self.job.user) + return WorkRequestContext(self.app, user=self.user) @property - def permissions(self): - if self._permissions is None: - inp_data = self.inp_data - existing_datasets = [inp for inp in inp_data.values() if inp] - if existing_datasets: - permissions = self.app.security_agent.guess_derived_permissions_for_datasets(existing_datasets) - else: - # No valid inputs, we will use history defaults - permissions = self.app.security_agent.history_get_default_permissions(self.job.history) - self._permissions = permissions + def user(self): + if self.job: + user = self.job.user + else: + user = None + return user - return self._permissions + @property + def tag_handler(self): + return self.app.tag_handler def find_files(self, output_name, collection, dataset_collectors): filenames = odict.odict() @@ -407,6 +487,10 @@ class JobContext(object): link_data = discovered_file.match.link_data tag_list = discovered_file.match.tag_list + + sources = discovered_file.match.sources + hashes = discovered_file.match.hashes + dataset = self.create_dataset( ext=ext, designation=designation, @@ -417,6 +501,8 @@ class JobContext(object): metadata_source_name=metadata_source_name, link_data=link_data, tag_list=tag_list, + sources=sources, + hashes=hashes, ) log.debug( "(%s) Created dynamic collection dataset for path [%s] with element identifier [%s] for output [%s] %s", @@ -428,19 +514,16 @@ class JobContext(object): ) element_datasets.append((element_identifiers, dataset)) - app = self.app sa_session = self.sa_session - job = self.job - if job: - add_datasets_timer = ExecutionTimer() - job.history.add_datasets(sa_session, [d for (ei, d) in element_datasets]) - log.debug( - "(%s) Add dynamic collection datasets to history for output [%s] %s", - self.job.id, - name, - add_datasets_timer, - ) + add_datasets_timer = ExecutionTimer() + self.add_datasets_to_history([d for (ei, d) in element_datasets]) + log.debug( + "(%s) Add dynamic collection datasets to history for output [%s] %s", + self.job.id, + name, + add_datasets_timer, + ) for (element_identifiers, dataset) in element_datasets: current_builder = root_collection_builder @@ -449,84 +532,73 @@ class JobContext(object): current_builder.add_dataset(element_identifiers[-1], dataset) # Associate new dataset with job - if job: - element_identifier_str = ":".join(element_identifiers) - # Below was changed from '__new_primary_file_%s|%s__' % (name, designation ) - assoc = app.model.JobToOutputDatasetAssociation('__new_primary_file_%s|%s__' % (name, element_identifier_str), dataset) - assoc.job = self.job - sa_session.add(assoc) + element_identifier_str = ":".join(element_identifiers) + association_name = '__new_primary_file_%s|%s__' % (name, element_identifier_str) + self.add_output_dataset_association(association_name, dataset) dataset.raw_set_dataset_state('ok') sa_session.flush() - def create_dataset( - self, - ext, - designation, - visible, - dbkey, - name, - filename, - metadata_source_name=None, - info=None, - library_folder=None, - link_data=False, - primary_data=None, - tag_list=[], - ): - app = self.app + def persist_object(self, obj): + self.sa_session.add(obj) + + def flush(self): + self.sa_session.flush() + + def create_library_folder(self, parent_folder, name, description): + assert parent_folder.id + library_folder_manager = self.app.library_folder_manager + nested_folder = library_folder_manager.create(self.work_context, parent_folder.id, name, description) + return nested_folder + + def add_output_dataset_association(self, name, dataset): + assoc = galaxy.model.JobToOutputDatasetAssociation(name, dataset) + assoc.job = self.job + self.sa_session.add(assoc) + + def add_library_dataset_to_folder(self, library_folder, ld): + trans = self.work_context + ldda = ld.library_dataset_dataset_association + trans.sa_session.add(ldda) + + trans = self.work_context + trans.app.security_agent.copy_library_permissions(trans, library_folder, ld) + trans.sa_session.add(ld) + trans.sa_session.flush() + + # Permissions must be the same on the LibraryDatasetDatasetAssociation and the associated LibraryDataset + trans.app.security_agent.copy_library_permissions(trans, ld, ldda) + # Copy the current user's DefaultUserPermissions to the new LibraryDatasetDatasetAssociation.dataset + trans.app.security_agent.set_all_dataset_permissions(ldda.dataset, trans.app.security_agent.user_get_default_permissions(trans.user)) + library_folder.add_library_dataset(ld, genome_build=ldda.dbkey) + trans.sa_session.add(library_folder) + trans.sa_session.flush() + + trans.sa_session.add(ld) + trans.sa_session.flush() + + def add_datasets_to_history(self, datasets, for_output_dataset=None): sa_session = self.sa_session - - if primary_data is None: - if not library_folder: - primary_data = _new_hda(app, sa_session, ext, designation, visible, dbkey, self.permissions) - else: - primary_data = _new_ldda(self.work_context, name, ext, visible, dbkey, library_folder) - else: - primary_data.extension = ext - primary_data.visible = visible - primary_data.dbkey = dbkey - - # Copy metadata from one of the inputs if requested. - metadata_source = None - if metadata_source_name: - metadata_source = self.inp_data[metadata_source_name] - - sa_session.flush() - - if tag_list: - app.tag_handler.add_tags_from_list(self.job.user, primary_data, tag_list) - - # Move data from temp location to dataset location - if not link_data: - app.object_store.update_from_file(primary_data.dataset, file_name=filename, create=True) - else: - primary_data.link_to(filename) - - # We are sure there are no extra files, so optimize things that follow by settting total size also. - primary_data.set_size(no_extra_files=True) - # If match specified a name use otherwise generate one from - # designation. - primary_data.name = name - - if metadata_source: - primary_data.init_meta(copy_from=metadata_source) - else: - primary_data.init_meta() - - if info is not None: - primary_data.info = info - - primary_data.set_meta() - primary_data.set_peek() - - return primary_data + self.job.history.add_datasets(sa_session, datasets) + if for_output_dataset is not None: + # Need to update all associated output hdas, i.e. history was + # shared with job running + for copied_dataset in for_output_dataset.dataset.history_associations: + if copied_dataset == for_output_dataset: + continue + for dataset in datasets: + new_data = dataset.copy() + copied_dataset.history.add_dataset(new_data) + sa_session.add(new_data) + sa_session.flush() -def collect_primary_datasets(tool, output, tool_provided_metadata, job_working_directory, input_ext, input_dbkey="?"): - app = tool.app - sa_session = tool.sa_session +def collect_primary_datasets(job_context, output, input_ext): + tool = job_context.tool + job_working_directory = job_context.job_working_directory + sa_session = job_context.sa_session + # Loop through output file names, looking for generated primary # datasets in form specified by discover dataset patterns or in tool provided metadata. primary_output_assigned = False @@ -537,7 +609,7 @@ def collect_primary_datasets(tool, output, tool_provided_metadata, job_working_d if name in tool.outputs: dataset_collectors = [dataset_collector(description) for description in tool.outputs[name].dataset_collector_descriptions] filenames = odict.odict() - for discovered_file in discover_files(name, tool_provided_metadata, dataset_collectors, job_working_directory, outdata): + for discovered_file in discover_files(name, job_context.tool_provided_metadata, dataset_collectors, job_working_directory, outdata): filenames[discovered_file.path] = discovered_file for filename_index, (filename, discovered_file) in enumerate(filenames.items()): extra_file_collector = discovered_file.collector @@ -549,7 +621,7 @@ def collect_primary_datasets(tool, output, tool_provided_metadata, job_working_d if filename_index == 0 and extra_file_collector.assign_primary_output and output_index == 0: new_outdata_name = fields_match.name or "%s (%s)" % (outdata.name, designation) # Move data from temp location to dataset location - app.object_store.update_from_file(outdata.dataset, file_name=filename, create=True) + job_context.object_store.update_from_file(outdata.dataset, file_name=filename, create=True) primary_output_assigned = True continue if name not in primary_datasets: @@ -560,41 +632,30 @@ def collect_primary_datasets(tool, output, tool_provided_metadata, job_working_d ext = input_ext dbkey = fields_match.dbkey if dbkey == INPUT_DBKEY_TOKEN: - dbkey = input_dbkey + dbkey = job_context.input_dbkey # Create new primary dataset - primary_data = _new_hda(app, sa_session, ext, designation, visible, dbkey) - app.security_agent.copy_dataset_permissions(outdata.dataset, primary_data.dataset) - sa_session.flush() - # Move data from temp location to dataset location - app.object_store.update_from_file(primary_data.dataset, file_name=filename, create=True) - # We are sure there are no extra files, so optimize things that follow by settting total size also. - primary_data.set_size(no_extra_files=True) - # If match specified a name use otherwise generate one from - # designation. - primary_data.name = fields_match.name or "%s (%s)" % (outdata.name, designation) - primary_data.info = outdata.info - primary_data.init_meta(copy_from=outdata) - primary_data.dbkey = dbkey - # Associate new dataset with job - job = None - for assoc in outdata.creating_job_associations: - job = assoc.job - break - if job: - assoc = app.model.JobToOutputDatasetAssociation('__new_primary_file_%s|%s__' % (name, designation), primary_data) - assoc.job = job - sa_session.add(assoc) - sa_session.flush() - primary_data.state = outdata.state + new_primary_name = fields_match.name or "%s (%s)" % (outdata.name, designation) + info = outdata.info + # TODO: should be able to disambiguate files in different directories... new_primary_filename = os.path.split(filename)[-1] - new_primary_datasets_attributes = tool_provided_metadata.get_new_dataset_meta_by_basename(name, new_primary_filename) - # add tool/metadata provided information + new_primary_datasets_attributes = job_context.tool_provided_metadata.get_new_dataset_meta_by_basename(name, new_primary_filename) + + primary_data = job_context.create_dataset( + ext, + designation, + visible, + dbkey, + new_primary_name, + filename, + info=info, + init_from=outdata, + dataset_attributes=new_primary_datasets_attributes, + ) + # Associate new dataset with job + job_context.add_output_dataset_association('__new_primary_file_%s|%s__' % (name, designation), primary_data) + if new_primary_datasets_attributes: - dataset_att_by_name = dict(ext='extension') - for att_set in ['name', 'info', 'ext', 'dbkey']: - dataset_att_name = dataset_att_by_name.get(att_set, att_set) - setattr(primary_data, dataset_att_name, new_primary_datasets_attributes.get(att_set, getattr(primary_data, dataset_att_name))) extra_files_path = new_primary_datasets_attributes.get('extra_files', None) if extra_files_path: extra_files_path_joined = os.path.join(job_working_directory, extra_files_path) @@ -602,7 +663,7 @@ def collect_primary_datasets(tool, output, tool_provided_metadata, job_working_d extra_dir = os.path.join(primary_data.extra_files_path, root.replace(extra_files_path_joined, '', 1).lstrip(os.path.sep)) extra_dir = os.path.normpath(extra_dir) for f in files: - app.object_store.update_from_file( + job_context.object_store.update_from_file( primary_data.dataset, extra_dir=extra_dir, alt_name=f, @@ -610,26 +671,9 @@ def collect_primary_datasets(tool, output, tool_provided_metadata, job_working_d create=True, preserve_symlinks=True ) - metadata_dict = new_primary_datasets_attributes.get('metadata', None) - if metadata_dict: - if "dbkey" in new_primary_datasets_attributes: - metadata_dict["dbkey"] = new_primary_datasets_attributes["dbkey"] - primary_data.metadata.from_JSON_dict(json_dict=metadata_dict) - else: - primary_data.set_meta() - primary_data.set_peek() - outdata.history.add_dataset(primary_data) + job_context.add_datasets_to_history([primary_data], for_output_dataset=outdata) # Add dataset to return dict primary_datasets[name][designation] = primary_data - # Need to update all associated output hdas, i.e. history was - # shared with job running - for dataset in outdata.dataset.history_associations: - if outdata == dataset: - continue - new_data = primary_data.copy() - dataset.history.add_dataset(new_data) - sa_session.add(new_data) - sa_session.flush() if primary_output_assigned: outdata.name = new_outdata_name outdata.init_meta() @@ -860,6 +904,14 @@ class JsonCollectedDatasetMatch(object): def object_id(self): return self.as_dict.get("object_id", None) + @property + def sources(self): + return self.as_dict.get("sources", []) + + @property + def hashes(self): + return self.as_dict.get("hashes", []) + class RegexCollectedDatasetMatch(JsonCollectedDatasetMatch): @@ -871,67 +923,5 @@ class RegexCollectedDatasetMatch(JsonCollectedDatasetMatch): UNSET = object() - -def _new_ldda( - trans, - name, - ext, - visible, - dbkey, - library_folder, -): - ld = trans.app.model.LibraryDataset(folder=library_folder, name=name) - trans.sa_session.add(ld) - trans.sa_session.flush() - trans.app.security_agent.copy_library_permissions(trans, library_folder, ld) - - ldda = trans.app.model.LibraryDatasetDatasetAssociation(name=name, - extension=ext, - dbkey=dbkey, - library_dataset=ld, - user=trans.user, - create_dataset=True, - sa_session=trans.sa_session) - trans.sa_session.add(ldda) - ldda.state = ldda.states.OK - # Permissions must be the same on the LibraryDatasetDatasetAssociation and the associated LibraryDataset - trans.app.security_agent.copy_library_permissions(trans, ld, ldda) - # Copy the current user's DefaultUserPermissions to the new LibraryDatasetDatasetAssociation.dataset - trans.app.security_agent.set_all_dataset_permissions(ldda.dataset, trans.app.security_agent.user_get_default_permissions(trans.user)) - library_folder.add_library_dataset(ld, genome_build=dbkey) - trans.sa_session.add(library_folder) - trans.sa_session.flush() - - ld.library_dataset_dataset_association_id = ldda.id - trans.sa_session.add(ld) - trans.sa_session.flush() - return ldda - - -def _new_hda( - app, - sa_session, - ext, - designation, - visible, - dbkey, - permissions=UNSET, -): - """Return a new unflushed HDA with dataset and permissions setup. - """ - # Create new primary dataset - primary_data = app.model.HistoryDatasetAssociation(extension=ext, - designation=designation, - visible=visible, - dbkey=dbkey, - create_dataset=True, - flush=False, - sa_session=sa_session) - if permissions is not UNSET: - app.security_agent.set_all_dataset_permissions(primary_data.dataset, permissions, new=True, flush=False) - sa_session.add(primary_data) - return primary_data - - DEFAULT_DATASET_COLLECTOR = DatasetCollector(DEFAULT_DATASET_COLLECTOR_DESCRIPTION) DEFAULT_TOOL_PROVIDED_DATASET_COLLECTOR = ToolMetadataDatasetCollector(ToolProvidedMetadataDatasetCollection()) diff --git a/lib/galaxy/tools/parameters/validation.py b/lib/galaxy/tools/parameters/validation.py index 95d6670bbe0..538471752bb 100644 --- a/lib/galaxy/tools/parameters/validation.py +++ b/lib/galaxy/tools/parameters/validation.py @@ -22,11 +22,30 @@ class Validator(object): @classmethod def from_element(cls, param, elem): + """ + Initialize the appropiate Validator class + + example call `validation.Validator.from_element(ToolParameter_object, Validator_object)` + + needs to be implemented in the subclasses and should return the + corresponding Validator object by a call to `cls( ... )` which calls the + `__init__` method of the corresponding validator + + param cls the Validator class + param param the element to be evaluated (which contains the validator) + param elem the validator element + return an object of a Validator subclass that corresponds to the type attribute of the validator element + """ type = elem.get('type', None) assert type is not None, "Required 'type' attribute missing from validator" return validator_types[type].from_element(param, elem) def validate(self, value, trans=None): + """ + validate a value + + return None if positive validation, otherwise a ValueError is raised + """ raise TypeError("Abstract Method") @@ -103,7 +122,7 @@ class ExpressionValidator(Validator): class InRangeValidator(Validator): """ - Validator that ensures a number is in a specific range + Validator that ensures a number is in a specified range >>> from xml.etree.ElementTree import XML >>> from galaxy.tools.parameters.basic import ToolParameter @@ -393,6 +412,72 @@ class MetadataInFileColumnValidator(Validator): raise ValueError(self.message) +class ValueInDataTableColumnValidator(Validator): + """ + Validator that checks if a value is in a tool data table column. + """ + + @classmethod + def from_element(cls, param, elem): + table_name = elem.get("table_name", None) + assert table_name, 'You must specify a table_name.' + tool_data_table = param.tool.app.tool_data_tables[table_name] + column = elem.get("metadata_column", 0) + try: + column = int(column) + except ValueError: + pass + message = elem.get("message", "Value was not found in %s." % (table_name)) + line_startswith = elem.get("line_startswith", None) + if line_startswith: + line_startswith = line_startswith.strip() + return cls(tool_data_table, column, message, line_startswith) + + def __init__(self, tool_data_table, column, message="Value not found.", line_startswith=None): + self.message = message + self.valid_values = [] + self._data_table_content_version = None + self._tool_data_table = tool_data_table + if isinstance(column, string_types): + column = tool_data_table.columns[column] + self._column = column + self._load_values() + + def _load_values(self): + self._data_table_content_version, data_fields = self._tool_data_table.get_version_fields() + self.valid_values = [] + for fields in data_fields: + if self._column < len(fields): + self.valid_values.append(fields[self._metadata_column]) + + def validate(self, value, trans=None): + if not value: + return + if not self._tool_data_table.is_current_version(self._data_table_content_version): + log.debug('MetadataInDataTableColumnValidator values are out of sync with data table (%s), updating validator.', self._tool_data_table.name) + self._load_values() + if value in self.valid_values: + return + raise ValueError(self.message) + + +class ValueNotInDataTableColumnValidator(ValueInDataTableColumnValidator): + """ + Validator that checks if a value is NOT in a tool data table column. + """ + + def __init__(self, tool_data_table, metadata_column, message="Value already present.", line_startswith=None): + super(ValueNotInDataTableColumnValidator, self).__init__(tool_data_table, metadata_column, message, line_startswith) + + def validate(self, value, trans=None): + try: + super(ValueInDataTableColumnValidator, self).validate(value, trans) + except ValueError: + return + else: + raise ValueError(self.message) + + class MetadataInDataTableColumnValidator(Validator): """ Validator that checks if the value for a dataset's metadata item exists in a file. @@ -448,19 +533,76 @@ class MetadataInDataTableColumnValidator(Validator): raise ValueError(self.message) -validator_types = dict(expression=ExpressionValidator, - regex=RegexValidator, - in_range=InRangeValidator, - length=LengthValidator, - metadata=MetadataValidator, - unspecified_build=UnspecifiedBuildValidator, - no_options=NoOptionsValidator, - empty_field=EmptyTextfieldValidator, - empty_dataset=DatasetEmptyValidator, - empty_extra_files_path=DatasetExtraFilesPathEmptyValidator, - dataset_metadata_in_file=MetadataInFileColumnValidator, - dataset_metadata_in_data_table=MetadataInDataTableColumnValidator, - dataset_ok_validator=DatasetOkValidator,) +class MetadataNotInDataTableColumnValidator(MetadataInDataTableColumnValidator): + """ + Validator that checks if the value for a dataset's metadata item doesn't exists in a file. + """ + requires_dataset_metadata = True + + def __init__(self, tool_data_table, metadata_name, metadata_column, message="Value for metadata not found.", line_startswith=None): + super(MetadataInDataTableColumnValidator, self).__init__(tool_data_table, metadata_name, metadata_column, message, line_startswith) + + def validate(self, value, trans=None): + try: + super(MetadataInDataTableColumnValidator, self).validate(value, trans) + except ValueError: + return + else: + raise ValueError(self.message) + + +class MetadataInRangeValidator(InRangeValidator): + """ + Validator that ensures metadata is in a specified range + """ + requires_dataset_metadata = True + + @classmethod + def from_element(cls, param, elem): + metadata_name = elem.get('metadata_name', None) + assert metadata_name, "dataset_metadata_in_range validator requires metadata_name attribute." + metadata_name = metadata_name.strip() + return cls(metadata_name, + elem.get('message', None), elem.get('min'), + elem.get('max'), elem.get('exclude_min', 'false'), + elem.get('exclude_max', 'false')) + + def __init__(self, metadata_name, message, range_min, range_max, exclude_min=False, exclude_max=False): + self.metadata_name = metadata_name + super(MetadataInRangeValidator, self).__init__(message, range_min, range_max, exclude_min, exclude_max) + + def validate(self, value, trans=None): + if value: + if not isinstance(value, model.DatasetInstance): + raise ValueError('A non-dataset value was provided.') + try: + value_to_check = float(value.metadata.spec[self.metadata_name].param.to_string(value.metadata.get(self.metadata_name))) + except KeyError: + raise ValueError('{} Metadata missing'.format(self.metadata_name)) + except ValueError: + raise ValueError('{} must be a float or an integer'.format(self.metadata_name)) + super(MetadataInRangeValidator, self).validate(value_to_check, trans) + + +validator_types = dict( + expression=ExpressionValidator, + regex=RegexValidator, + in_range=InRangeValidator, + length=LengthValidator, + metadata=MetadataValidator, + unspecified_build=UnspecifiedBuildValidator, + no_options=NoOptionsValidator, + empty_field=EmptyTextfieldValidator, + empty_dataset=DatasetEmptyValidator, + empty_extra_files_path=DatasetExtraFilesPathEmptyValidator, + dataset_metadata_in_file=MetadataInFileColumnValidator, + dataset_metadata_in_data_table=MetadataInDataTableColumnValidator, + dataset_metadata_not_in_data_table=MetadataNotInDataTableColumnValidator, + dataset_metadata_in_range=MetadataInRangeValidator, + value_in_data_table=ValueInDataTableColumnValidator, + value_not_in_data_table=ValueInDataTableColumnValidator, + dataset_ok_validator=DatasetOkValidator, +) def get_suite(): diff --git a/lib/galaxy/tools/parameters/wrapped_json.py b/lib/galaxy/tools/parameters/wrapped_json.py index c045c9595ef..f9c90d4abe1 100644 --- a/lib/galaxy/tools/parameters/wrapped_json.py +++ b/lib/galaxy/tools/parameters/wrapped_json.py @@ -62,6 +62,13 @@ def _json_wrap_input(input, value, handle_files="skip"): json_value = _data_input_to_path(value) elif handle_files == "skip": return SKIP_INPUT + elif handle_files == "OBJECT": + if value: + if isinstance(value, list): + value = value[0] + return _hda_to_object(value) + else: + return None else: raise NotImplementedError() elif input_type == "data_collection": @@ -88,6 +95,21 @@ def _json_wrap_input(input, value, handle_files="skip"): return json_value +def _hda_to_object(hda): + hda_dict = hda.to_dict() + metadata_dict = {} + + for key, value in hda_dict.items(): + if key.startswith("metadata_"): + metadata_dict[key[len("metadata_"):]] = value + + return { + 'file_ext': hda_dict['file_ext'], + 'name': hda_dict['name'], + 'metadata': metadata_dict, + } + + def _cast_if_not_none(value, cast_to, empty_to_none=False): # log.debug("value [%s], type[%s]" % (value, type(value))) if value is None or (empty_to_none and str(value) == ''): diff --git a/lib/galaxy/tools/parser/__init__.py b/lib/galaxy/tools/parser/__init__.py index 7f7fdf15579..771fbaf3202 100644 --- a/lib/galaxy/tools/parser/__init__.py +++ b/lib/galaxy/tools/parser/__init__.py @@ -1,6 +1,6 @@ """ Package responsible for parsing tools from files/abstract tool sources. """ -from .factory import get_input_source, get_tool_source +from .factory import get_input_source, get_tool_source, get_tool_source_from_representation from .interface import ToolSource from .output_objects import ( ToolOutputCollectionPart, @@ -9,6 +9,7 @@ from .output_objects import ( __all__ = ( "get_input_source", "get_tool_source", + "get_tool_source_from_representation", "ToolOutputCollectionPart", "ToolSource", ) diff --git a/lib/galaxy/tools/parser/factory.py b/lib/galaxy/tools/parser/factory.py index 28c3e877257..10ab5587063 100644 --- a/lib/galaxy/tools/parser/factory.py +++ b/lib/galaxy/tools/parser/factory.py @@ -63,6 +63,17 @@ def ordered_load(stream): return yaml.load(stream, OrderedLoader) +def get_tool_source_from_representation(tool_format, tool_representation): + # TODO: make sure whatever is consuming this method uses ordered load. + log.info("Loading dynamic tool - this is experimental - tool may not function in future.") + if tool_format == "GalaxyTool": + if "version" not in tool_representation: + tool_representation["version"] = "1.0.0" # Don't require version for embedded tools. + return YamlToolSource(tool_representation) + else: + raise Exception("Unknown tool representation format [%s]." % tool_format) + + def get_input_source(content): """Wrap an XML element in a XmlInputSource if needed. diff --git a/lib/galaxy/tools/parser/interface.py b/lib/galaxy/tools/parser/interface.py index 2cd31a1aa6f..aa6ade42b69 100644 --- a/lib/galaxy/tools/parser/interface.py +++ b/lib/galaxy/tools/parser/interface.py @@ -88,6 +88,11 @@ class ToolSource(object): """ Return string contianing command to run. """ + def parse_expression(self): + """ Return string contianing command to run. + """ + return None + @abstractmethod def parse_environment_variables(self): """ Return environment variable templates to expose. @@ -338,12 +343,23 @@ class ToolStdioRegex(object): attribute that contains "warning" or "fatal". """ - def __init__(self): - self.match = "" - self.stdout_match = False - self.stderr_match = False - self.error_level = StdioErrorLevel.FATAL - self.desc = "" + def __init__(self, as_dict=None): + as_dict = as_dict or {} + self.match = as_dict.get("match", "") + self.stdout_match = as_dict.get("stdout_match", False) + self.stderr_match = as_dict.get("stderr_match", False) + self.error_level = as_dict.get("error_level", StdioErrorLevel.FATAL) + self.desc = as_dict.get("desc", "") + + def to_dict(self): + return { + "class": "ToolStdioRegex", + "match": self.match, + "stdout_match": self.stdout_match, + "stderr_match": self.stderr_match, + "error_level": self.error_level, + "desc": self.desc, + } class ToolStdioExitCode(object): @@ -352,11 +368,21 @@ class ToolStdioExitCode(object): The exit_code element has a range of exit codes and the error level. """ - def __init__(self): - self.range_start = float("-inf") - self.range_end = float("inf") - self.error_level = StdioErrorLevel.FATAL - self.desc = "" + def __init__(self, as_dict=None): + as_dict = as_dict or {} + self.range_start = as_dict.get("range_start", float("-inf")) + self.range_end = as_dict.get("range_end", float("inf")) + self.error_level = as_dict.get("error_level", StdioErrorLevel.FATAL) + self.desc = as_dict.get("desc", "") + + def to_dict(self): + return { + "class": "ToolStdioExitCode", + "range_start": self.range_start, + "range_end": self.range_end, + "error_level": self.error_level, + "desc": self.desc, + } class TestCollectionDef(object): diff --git a/lib/galaxy/tools/parser/output_collection_def.py b/lib/galaxy/tools/parser/output_collection_def.py index 78e33104cee..bc2afcd2a6a 100644 --- a/lib/galaxy/tools/parser/output_collection_def.py +++ b/lib/galaxy/tools/parser/output_collection_def.py @@ -58,7 +58,9 @@ def dataset_collector_descriptions_from_list(discover_datasets_dicts): def dataset_collection_description(**kwargs): - if asbool(kwargs.get("from_provided_metadata", False)): + from_provided_metadata = asbool(kwargs.get("from_provided_metadata", False)) + discover_via = kwargs.get("discover_via", "tool_provided_metadata" if from_provided_metadata else "pattern") + if discover_via == "tool_provided_metadata": for key in ["pattern", "sort_by"]: if kwargs.get(key): raise Exception("Cannot specify attribute [%s] if from_provided_metadata is True" % key) @@ -79,6 +81,17 @@ class DatasetCollectionDescription(object): self.directory = kwargs.get("directory", None) self.recurse = False + def to_dict(self): + return { + 'discover_via': self.discover_via, + 'dbkey': self.default_dbkey, + 'format': self.default_ext, + 'visible': self.default_visible, + 'assign_primary_output': self.assign_primary_output, + 'directory': self.directory, + 'recurse': self.recurse, + } + class ToolProvidedMetadataDatasetCollection(DatasetCollectionDescription): @@ -116,6 +129,16 @@ class FilePatternDatasetCollectionDescription(DatasetCollectionDescription): self.sort_key = sort_by self.sort_comp = sort_comp + def to_dict(self): + as_dict = super(FilePatternDatasetCollectionDescription, self).to_dict() + as_dict.update({ + "sort_key": self.sort_key, + "sort_comp": self.sort_comp, + "pattern": self.pattern, + "recurse": self.recurse, + }) + return as_dict + DEFAULT_DATASET_COLLECTOR_DESCRIPTION = FilePatternDatasetCollectionDescription( default_dbkey=LEGACY_DEFAULT_DBKEY, diff --git a/lib/galaxy/tools/parser/output_objects.py b/lib/galaxy/tools/parser/output_objects.py index bfe51934ff9..9431f97be77 100644 --- a/lib/galaxy/tools/parser/output_objects.py +++ b/lib/galaxy/tools/parser/output_objects.py @@ -1,5 +1,7 @@ from galaxy.util.dictifiable import Dictifiable from galaxy.util.odict import odict +from .output_actions import ToolOutputActionGroup +from .output_collection_def import dataset_collector_descriptions_from_output_dict class ToolOutputBase(Dictifiable): @@ -24,12 +26,14 @@ class ToolOutput(ToolOutputBase): (format, metadata_source, parent) """ - dict_collection_visible_keys = ['name', 'format', 'label', 'hidden'] + dict_collection_visible_keys = ['name', 'format', 'label', 'hidden', 'output_type', 'format_source', + 'default_identifier_source', 'metadata_source', 'parent', 'count', 'from_work_dir'] def __init__(self, name, format=None, format_source=None, metadata_source=None, parent=None, label=None, filters=None, actions=None, hidden=False, implicit=False): super(ToolOutput, self).__init__(name, label=label, filters=filters, hidden=hidden) + self.output_type = "data" self.format = format self.format_source = format_source self.metadata_source = metadata_source @@ -67,8 +71,50 @@ class ToolOutput(ToolOutputBase): as_dict["edam_format"] = edam_format edam_data = app.datatypes_registry.edam_data.get(self.format) as_dict["edam_data"] = edam_data + as_dict['discover_datasets'] = list(map(lambda d: d.to_dict(), self.dataset_collector_descriptions)) return as_dict + @staticmethod + def from_dict(name, output_dict, tool=None): + output = ToolOutput(name) + output.format = output_dict.get("format", "data") + output.change_format = [] + output.format_source = output_dict.get("format_source", None) + output.default_identifier_source = output_dict.get("default_identifier_source", None) + output.metadata_source = output_dict.get("metadata_source", "") + output.parent = output_dict.get("parent", None) + output.label = output_dict.get("label", None) + output.count = output_dict.get("count", 1) + output.filters = [] + output.tool = tool + output.from_work_dir = output_dict.get("from_work_dir", None) + output.hidden = output_dict.get("hidden", "") + # TODO: implement tool output action group fixes + output.actions = ToolOutputActionGroup(output, None) + output.dataset_collector_descriptions = dataset_collector_descriptions_from_output_dict(output_dict) + return output + + +class ToolExpressionOutput(ToolOutputBase): + dict_collection_visible_keys = ('name', 'format', 'label', 'hidden', 'output_type') + + def __init__(self, name, output_type, from_expression, + label=None, filters=None, actions=None, hidden=False): + super(ToolExpressionOutput, self).__init__(name, label=label, filters=filters, hidden=hidden) + self.output_type = output_type # JSON type... + self.from_expression = from_expression + self.format = "expression.json" # galaxy.datatypes.text.ExpressionJson.file_ext + + self.format_source = None + self.metadata_source = None + self.parent = None + self.actions = actions + + # Initialize default values + self.change_format = [] + self.implicit = False + self.from_work_dir = None + class ToolOutputCollection(ToolOutputBase): """ @@ -85,8 +131,8 @@ class ToolOutputCollection(ToolOutputBase): """ - - dict_collection_visible_keys = ['name', 'default_format', 'label', 'hidden', 'inherit_format', 'inherit_metadata'] + dict_collection_visible_keys = ['name', 'format', 'label', 'hidden', 'output_type', 'default_format', + 'default_format_source', 'default_metadata_source', 'inherit_format', 'inherit_metadata'] def __init__( self, @@ -102,6 +148,7 @@ class ToolOutputCollection(ToolOutputBase): inherit_metadata=False ): super(ToolOutputCollection, self).__init__(name, label=label, filters=filters, hidden=hidden) + self.output_type = "collection" self.collection = True self.default_format = default_format self.structure = structure @@ -173,6 +220,28 @@ class ToolOutputCollection(ToolOutputBase): raise Exception("dataset_collector_descriptions called for output collection with static structure") return self.structure.dataset_collector_descriptions + def to_dict(self, view='collection', value_mapper=None, app=None): + as_dict = super(ToolOutputCollection, self).to_dict(view=view, value_mapper=value_mapper, app=app) + as_dict['structure'] = self.structure.to_dict() + return as_dict + + @staticmethod + def from_dict(name, output_dict, tool=None): + structure = ToolOutputCollectionStructure.from_dict(output_dict["structure"]) + rval = ToolOutputCollection( + name, + structure=structure, + label=output_dict.get("label", None), + filters=None, + hidden=output_dict.get("hidden", False), + default_format=output_dict.get("default_format", "data"), + default_format_source=output_dict.get("default_format_source", None), + default_metadata_source=output_dict.get("default_metadata_source", None), + inherit_format=output_dict.get("inherit_format", False), + inherit_metadata=output_dict.get("inherit_metadata", False), + ) + return rval + class ToolOutputCollectionStructure(object): @@ -188,14 +257,14 @@ class ToolOutputCollectionStructure(object): self.collection_type_source = collection_type_source self.collection_type_from_rules = collection_type_from_rules self.structured_like = structured_like - self.dataset_collector_descriptions = dataset_collector_descriptions + self.dataset_collector_descriptions = dataset_collector_descriptions or [] if collection_type and collection_type_source: raise ValueError("Cannot set both type and type_source on collection output.") if collection_type is None and structured_like is None and dataset_collector_descriptions is None and collection_type_source is None and collection_type_from_rules is None: raise ValueError("Output collection types must specify source of collection type information (e.g. structured_like or type_source).") if dataset_collector_descriptions and (structured_like or collection_type_from_rules): raise ValueError("Cannot specify dynamic structure (discovered_datasets) and collection type attributes structured_like or collection_type_from_rules.") - self.dynamic = dataset_collector_descriptions is not None + self.dynamic = bool(dataset_collector_descriptions) def collection_prototype(self, inputs, type_registry): # either must have specified structured_like or something worse @@ -208,6 +277,26 @@ class ToolOutputCollectionStructure(object): collection_prototype.collection_type = collection_type return collection_prototype + def to_dict(self): + return { + 'collection_type': self.collection_type, + 'collection_type_source': self.collection_type_source, + 'collection_type_from_rules': self.collection_type_from_rules, + 'structured_like': self.structured_like, + 'discover_datasets': [d.to_dict() for d in self.dataset_collector_descriptions], + } + + @staticmethod + def from_dict(as_dict): + structure = ToolOutputCollectionStructure( + collection_type=as_dict['collection_type'], + collection_type_source=as_dict['collection_type_source'], + collection_type_from_rules=as_dict['collection_type_from_rules'], + structured_like=as_dict['structured_like'], + dataset_collector_descriptions=dataset_collector_descriptions_from_output_dict(as_dict), + ) + return structure + class ToolOutputCollectionPart(object): diff --git a/lib/galaxy/tools/parser/xml.py b/lib/galaxy/tools/parser/xml.py index 997eb760406..c142c97745d 100644 --- a/lib/galaxy/tools/parser/xml.py +++ b/lib/galaxy/tools/parser/xml.py @@ -22,6 +22,7 @@ from .interface import ( from .output_actions import ToolOutputActionGroup from .output_collection_def import dataset_collector_descriptions_from_elem from .output_objects import ( + ToolExpressionOutput, ToolOutput, ToolOutputCollection, ToolOutputCollectionStructure @@ -111,6 +112,17 @@ class XmlToolSource(ToolSource): command_el = self._command_el return ((command_el is not None) and command_el.text) or None + def parse_expression(self): + """ Return string contianing command to run. + """ + expression_el = self.root.find("expression") + if expression_el is not None: + expression_type = expression_el.get("type") + if expression_type != "ecma5.1": + raise Exception("Unknown expression type [%s] encountered" % expression_type) + return expression_el.text + return None + def parse_environment_variables(self): environment_variables_el = self.root.find("environment_variables") if environment_variables_el is None: @@ -256,7 +268,12 @@ class XmlToolSource(ToolSource): for _ in out_elem.findall("data"): _parse(_) - for collection_elem in out_elem.findall("collection"): + def _parse_expression(output_elem, **kwds): + output_def = self._parse_expression_output(output_elem, tool, **kwds) + data_dict[output_def.name] = output_def + return output_def + + def _parse_collection(collection_elem): name = collection_elem.get("name") label = xml_text(collection_elem, "label") default_format = collection_elem.get("format", "data") @@ -312,6 +329,24 @@ class XmlToolSource(ToolSource): output_collection.outputs[output_name] = data output_collections[name] = output_collection + for out_child in out_elem.getchildren(): + if out_child.tag == "data": + _parse(out_child) + elif out_child.tag == "collection": + _parse_collection(out_child) + elif out_child.tag == "output": + output_type = out_child.get("type") + if output_type == "data": + _parse(out_child) + elif output_type == "collection": + out_child.attrib["type"] = out_child.get("collection_type") + out_child.attrib["type_source"] = out_child.get("collection_type_source") + _parse_collection(out_child) + else: + _parse_expression(out_child) + else: + log.warn("Unknown output tag encountered [%s]" % out_child.tag) + for output_def in data_dict.values(): outputs[output_def.name] = output_def return outputs, output_collections @@ -323,6 +358,7 @@ class XmlToolSource(ToolSource): default_format="data", default_format_source=None, default_metadata_source="", + expression_type=None, ): output = ToolOutput(data_elem.get("name")) output_format = data_elem.get("format", default_format) @@ -347,6 +383,22 @@ class XmlToolSource(ToolSource): output.dataset_collector_descriptions = dataset_collector_descriptions_from_elem(data_elem, legacy=self.legacy_defaults) return output + def _parse_expression_output(self, output_elem, tool, **kwds): + output_type = output_elem.get("type") + from_expression = output_elem.get("from") + output = ToolExpressionOutput( + output_elem.get("name"), + output_type, + from_expression, + ) + output.path = output_elem.get("value") + output.label = xml_text(output_elem, "label") + + output.hidden = string_as_bool(output_elem.get("hidden", "")) + output.actions = ToolOutputActionGroup(output, output_elem.find('actions')) + output.dataset_collector_descriptions = [] + return output + def parse_stdio(self): """ parse error handling from command and stdio tag diff --git a/lib/galaxy/tools/parser/yaml.py b/lib/galaxy/tools/parser/yaml.py index 63e642e5648..65ce7747644 100644 --- a/lib/galaxy/tools/parser/yaml.py +++ b/lib/galaxy/tools/parser/yaml.py @@ -4,7 +4,6 @@ from .interface import InputSource from .interface import PageSource from .interface import PagesSource from .interface import ToolSource -from .output_actions import ToolOutputActionGroup from .output_collection_def import dataset_collector_descriptions_from_output_dict from .output_objects import ( ToolOutput, @@ -54,6 +53,9 @@ class YamlToolSource(ToolSource): def parse_command(self): return self.root_dict.get("command") + def parse_expression(self): + return self.root_dict.get("expression") + def parse_environment_variables(self): return [] @@ -107,23 +109,7 @@ class YamlToolSource(ToolSource): return outputs, output_collections def _parse_output(self, tool, name, output_dict): - # TODO: handle filters, actions, change_format - output = ToolOutput(name) - output.format = output_dict.get("format", "data") - output.change_format = [] - output.format_source = output_dict.get("format_source", None) - output.default_identifier_source = output_dict.get("default_identifier_source", None) - output.metadata_source = output_dict.get("metadata_source", "") - output.parent = output_dict.get("parent", None) - output.label = output_dict.get("label", None) - output.count = output_dict.get("count", 1) - output.filters = [] - output.tool = tool - output.from_work_dir = output_dict.get("from_work_dir", None) - output.hidden = output_dict.get("hidden", "") - # TODO: implement tool output action group fixes - output.actions = ToolOutputActionGroup(output, None) - output.dataset_collector_descriptions = dataset_collector_descriptions_from_output_dict(output_dict) + output = ToolOutput.from_dict(name, output_dict, tool=tool) return output def _parse_output_collection(self, tool, name, output_dict): diff --git a/lib/galaxy/tools/provided_metadata.py b/lib/galaxy/tools/provided_metadata.py new file mode 100644 index 00000000000..2c5e587e7c3 --- /dev/null +++ b/lib/galaxy/tools/provided_metadata.py @@ -0,0 +1,224 @@ +import json +import logging +import os +import re + +from galaxy.util import stringify_dictionary_keys + +log = logging.getLogger(__name__) + + +def parse_tool_provided_metadata(meta_file, provided_metadata_style=None, job_wrapper=None): + """Return a ToolProvidedMetadata object for specified file path. + + If meta_file is absent, return a NullToolProvidedMetadata. If provided_metadata_style is None + attempt to guess tool provided metadata type. + """ + if not os.path.exists(meta_file): + return NullToolProvidedMetadata() + if provided_metadata_style is None: + provided_metadata_style = _guess_tool_provided_metadata_style(meta_file) + + assert provided_metadata_style in ["legacy", "default"] + + if provided_metadata_style == "legacy": + return LegacyToolProvidedMetadata(meta_file, job_wrapper=job_wrapper) + elif provided_metadata_style == "default": + return ToolProvidedMetadata(meta_file) + + +def _guess_tool_provided_metadata_style(path): + try: + with open(path, "r") as f: + metadata = json.load(f) + metadata_type = metadata.get("type", None) + return "legacy" if metadata_type in ["dataset", "new_primary_dataset"] else "default" + except ValueError: + # Either empty or multiple JSON lines, either way we can safely treat + # it as legacy style. + return "legacy" + + +class BaseToolProvidedMetadata(object): + + def get_new_datasets(self, output_name): + """Find new datasets for dataset discovery for specified output. + + Return a list of such datasets. + + Called only in the context of discovering datasets when + discover_via="tool_provided_metadata" is defined in the tool. + """ + return [] + + def has_failed_outputs(self): + """Determine if generation of any of the outputs failed. + """ + return False + + def get_new_dataset_meta_by_basename(self, output_name, basename): + """For a discovered dataset, get the corresponding metadata entry. + + The discovery may have been from explicit listing in this file (returned + from get_new_datasets) or via file regex, either way the basename of the + file is used to index the fetching of the metadata entry. + """ + return {} + + def get_unnamed_outputs(self): + """Return unnamed outputs dataset introduced for upload 2.0. + + Needs more formal specification but see output_collect for how destinations, + types, elements, etc... are consumed. + """ + return [] + + def get_dataset_meta(self, output_name, dataset_id): + """Return primary dataset metadata for specified output. + """ + return {} + + def rewrite(self): + """Write metadata back to the file system. + + If metadata has not changed via outputs specified as mutable, the + implementation class may opt to not re-write the file. + """ + return None + + def get_new_datasets_for_metadata_collection(self): + """Return all datasets tracked that are not explicit primary outputs. + """ + return [] + + +class NullToolProvidedMetadata(BaseToolProvidedMetadata): + pass + + +class LegacyToolProvidedMetadata(BaseToolProvidedMetadata): + + def __init__(self, meta_file, job_wrapper=None): + self.meta_file = meta_file + self.tool_provided_job_metadata = [] + + with open(meta_file, 'r') as f: + for line in f: + try: + line = stringify_dictionary_keys(json.loads(line)) + assert 'type' in line + except Exception: + log.exception('(%s) Got JSON data from tool, but data is improperly formatted or no "type" key in data' % job_wrapper.job_id) + log.debug('Offending data was: %s' % line) + continue + # Set the dataset id if it's a dataset entry and isn't set. + # This isn't insecure. We loop the job's output datasets in + # the finish method, so if a tool writes out metadata for a + # dataset id that it doesn't own, it'll just be ignored. + dataset_id_not_specified = line['type'] == 'dataset' and 'dataset_id' not in line + if dataset_id_not_specified: + dataset_basename = line['dataset'] + if job_wrapper: + try: + line['dataset_id'] = job_wrapper.get_output_file_id(dataset_basename) + except KeyError: + log.warning('(%s) Tool provided job dataset-specific metadata without specifying a dataset' % job_wrapper.job_id) + continue + else: + match = re.match(r'dataset_(\d+)\.dat', dataset_basename) + line['dataset_id'] = int(match.group(1)) + + self.tool_provided_job_metadata.append(line) + + def get_dataset_meta(self, output_name, dataset_id): + for meta in self.tool_provided_job_metadata: + if meta['type'] == 'dataset' and int(meta['dataset_id']) == dataset_id: + return meta + return {} + + def get_new_dataset_meta_by_basename(self, output_name, basename): + for meta in self.tool_provided_job_metadata: + if meta['type'] == 'new_primary_dataset' and meta['filename'] == basename: + return meta + + def get_new_datasets(self, output_name): + log.warning("Called get_new_datasets with legacy tool metadata provider - that is unimplemented.") + return [] + + def has_failed_outputs(self): + found_failed = False + for meta in self.tool_provided_job_metadata: + if meta.get("failed", False): + found_failed = True + + return found_failed + + def get_unnamed_outputs(self): + return [] + + def rewrite(self): + with open(self.meta_file, 'wt') as job_metadata_fh: + for meta in self.tool_provided_job_metadata: + job_metadata_fh.write("%s\n" % (json.dumps(meta))) + + def get_new_datasets_for_metadata_collection(self): + for meta in self.tool_provided_job_metadata: + if meta['type'] == 'new_primary_dataset': + yield meta + + +class ToolProvidedMetadata(BaseToolProvidedMetadata): + + def __init__(self, meta_file): + self.meta_file = meta_file + with open(meta_file, 'r') as f: + self.tool_provided_job_metadata = json.load(f) + + def get_dataset_meta(self, output_name, dataset_id): + return self.tool_provided_job_metadata.get(output_name, {}) + + def get_new_dataset_meta_by_basename(self, output_name, basename): + datasets = self.tool_provided_job_metadata.get(output_name, {}).get("datasets", []) + for meta in datasets: + if meta['filename'] == basename: + return meta + + def get_new_datasets(self, output_name): + datasets = self.tool_provided_job_metadata.get(output_name, {}).get("datasets", []) + if not datasets: + elements = self.tool_provided_job_metadata.get(output_name, {}).get("elements", []) + if elements: + datasets = self._elements_to_datasets(elements) + return datasets + + def _elements_to_datasets(self, elements, level=0): + for element in elements: + extra_kwds = {"identifier_%d" % level: element["name"]} + if "elements" in element: + for inner_element in self._elements_to_datasets(element["elements"], level=level + 1): + dataset = extra_kwds.copy() + dataset.update(inner_element) + yield dataset + else: + dataset = extra_kwds + extra_kwds.update(element) + yield extra_kwds + + def has_failed_outputs(self): + found_failed = False + for output_name, meta in self.tool_provided_job_metadata.items(): + if output_name == "__unnamed_outputs": + continue + + if meta.get("failed", False): + found_failed = True + + return found_failed + + def get_unnamed_outputs(self): + log.debug("unnamed outputs [%s]" % self.tool_provided_job_metadata) + return self.tool_provided_job_metadata.get("__unnamed_outputs", []) + + def rewrite(self): + with open(self.meta_file, 'wt') as job_metadata_fh: + json.dump(self.tool_provided_job_metadata, job_metadata_fh) diff --git a/lib/galaxy/tools/repositories.py b/lib/galaxy/tools/repositories.py index 2766be49d79..00b82fd8d6b 100644 --- a/lib/galaxy/tools/repositories.py +++ b/lib/galaxy/tools/repositories.py @@ -30,6 +30,7 @@ class ValidationContext(object): self.config.tool_data_table_config = os.path.join(self.temporary_path, 'tool_data_table_conf.xml') self.config.shed_tool_data_table_config = os.path.join(self.temporary_path, 'shed_tool_data_table_conf.xml') self.tool_data_tables = tool_data_tables + self.tool_shed_registry = Bunch(tool_sheds={}) self.datatypes_registry = registry self.hgweb_config_manager = hgweb_config_manager self.config.len_file_path = os.path.join(self.temporary_path, 'chromlen.txt') diff --git a/lib/galaxy/tools/toolbox/base.py b/lib/galaxy/tools/toolbox/base.py index 651540254c2..9e2ae51a2dc 100644 --- a/lib/galaxy/tools/toolbox/base.py +++ b/lib/galaxy/tools/toolbox/base.py @@ -2,6 +2,7 @@ import logging import os import string import time +from collections import namedtuple from errno import ENOENT from xml.etree.ElementTree import ParseError @@ -35,6 +36,15 @@ from .tags import tool_tag_manager log = logging.getLogger(__name__) +# A fake ToolShedRepository constructed from a shed tool conf +ToolConfRepository = namedtuple( + 'ToolConfRepository', + ( + 'tool_shed', 'name', 'owner', 'installed_changeset_revision', 'changeset_revision', + 'tool_dependencies_installed_or_in_error', + ) +) + class AbstractToolBox(Dictifiable, ManagesIntegratedToolPanelMixin): """ @@ -52,6 +62,7 @@ class AbstractToolBox(Dictifiable, ManagesIntegratedToolPanelMixin): # shed_tool_conf.xml file. self._dynamic_tool_confs = [] self._tools_by_id = {} + self._tools_by_uuid = {} self._integrated_section_by_tool = {} # Tool lineages can contain chains of related tools with different ids # so each will be present once in the above dictionary. The following @@ -94,7 +105,10 @@ class AbstractToolBox(Dictifiable, ManagesIntegratedToolPanelMixin): interacting with the rest of the Galaxy app or message queues, etc.... """ - def create_tool(self, config_file, repository_id=None, guid=None, **kwds): + def create_tool(self, config_file, tool_shed_repository=None, guid=None, **kwds): + raise NotImplementedError() + + def create_dynamic_tool(self, dynamic_tool): raise NotImplementedError() def _init_tools_from_configs(self, config_filenames): @@ -175,6 +189,25 @@ class AbstractToolBox(Dictifiable, ManagesIntegratedToolPanelMixin): config_elems=config_elems) self._dynamic_tool_confs.append(shed_tool_conf_dict) + def _get_tool_by_uuid(self, tool_uuid): + if tool_uuid in self._tools_by_uuid: + return self._tools_by_uuid[tool_uuid] + + dynamic_tool = self.app.dynamic_tool_manager.get_tool_by_uuid(tool_uuid) + if dynamic_tool: + return self.load_dynamic_tool(dynamic_tool) + + return None + + def load_dynamic_tool(self, dynamic_tool): + if not dynamic_tool.active: + return None + + tool = self.create_dynamic_tool(dynamic_tool) + self.register_tool(tool) + self._tools_by_uuid[dynamic_tool.uuid] = tool + return tool + def load_item(self, item, tool_path, panel_dict=None, integrated_panel_dict=None, load_panel_dict=True, guid=None, index=None, internal=False): with self.app._toolbox_lock: item = ensure_tool_conf_item(item) @@ -402,7 +435,7 @@ class AbstractToolBox(Dictifiable, ManagesIntegratedToolPanelMixin): elif elem.tag == 'label': self._integrated_tool_panel.stub_label(key) - def get_tool(self, tool_id, tool_version=None, get_all_versions=False, exact=False): + def get_tool(self, tool_id, tool_version=None, get_all_versions=False, exact=False, tool_uuid=None): """Attempt to locate a tool in the tool box. Note that `exact` only refers to the `tool_id`, not the `tool_version`.""" if tool_version: tool_version = str(tool_version) @@ -410,6 +443,15 @@ class AbstractToolBox(Dictifiable, ManagesIntegratedToolPanelMixin): if get_all_versions and exact: raise AssertionError("Cannot specify get_tool with both get_all_versions and exact as True") + if tool_id is None: + if tool_uuid is not None: + tool_from_uuid = self._get_tool_by_uuid(tool_uuid) + if tool_from_uuid is None: + raise ObjectNotFound("Failed to find a tool with uuid [%s]" % tool_uuid) + tool_id = tool_from_uuid.id + if tool_id is None: + raise AssertionError("get_tool called with tool_id as None") + if "/repos/" in tool_id: # test if tool came from a toolshed tool_id_without_tool_shed = tool_id.split("/repos/")[1] available_tool_sheds = [urlparse(_) for _ in self.app.tool_shed_registry.tool_sheds.values()] @@ -562,10 +604,11 @@ class AbstractToolBox(Dictifiable, ManagesIntegratedToolPanelMixin): if guid and not from_cache: # tool was not in cache and is a tool shed tool tool_shed_repository = self.get_tool_repository_from_xml_item(item, path) if tool_shed_repository: - # Only load tools if the repository is not deactivated or uninstalled. - can_load_into_panel_dict = not tool_shed_repository.deleted - repository_id = self.app.security.encode_id(tool_shed_repository.id) - tool = self.load_tool(concrete_path, guid=guid, repository_id=repository_id, use_cached=False) + if hasattr(tool_shed_repository, 'deleted'): + # The shed tool is in the install database + # Only load tools if the repository is not deactivated or uninstalled. + can_load_into_panel_dict = not tool_shed_repository.deleted + tool = self.load_tool(concrete_path, guid=guid, tool_shed_repository=tool_shed_repository, use_cached=False) if not tool: # tool was not in cache and is not a tool shed tool. tool = self.load_tool(concrete_path, use_cached=False) if string_as_bool(item.get('hidden', False)): @@ -603,30 +646,18 @@ class AbstractToolBox(Dictifiable, ManagesIntegratedToolPanelMixin): # Backward compatibility issue - the tag used to be named 'changeset_revision'. installed_changeset_revision_elem = item.elem.find("changeset_revision") installed_changeset_revision = installed_changeset_revision_elem.text - if "/repos/" in path: # The only time "/repos/" should not be in path is during testing! - try: - tool_shed_path, reduced_path = path.split('/repos/', 1) - splitted_path = reduced_path.split('/') - assert tool_shed_path == tool_shed - assert splitted_path[0] == repository_owner - assert splitted_path[1] == repository_name - if splitted_path[2] != installed_changeset_revision: - # This can happen if the Tool Shed repository has been - # updated to a new revision and the installed_changeset_revision - # element in shed_tool_conf.xml file has been updated too - log.debug("The installed_changeset_revision for tool %s is %s, using %s instead", path, - installed_changeset_revision, splitted_path[2]) - installed_changeset_revision = splitted_path[2] - except AssertionError: - log.debug("Error while loading tool %s", path) - pass repository = self._get_tool_shed_repository(tool_shed=tool_shed, name=repository_name, owner=repository_owner, installed_changeset_revision=installed_changeset_revision) if not repository: - msg = "Attempted to load tool shed tool, but the repository with name '%s' from owner '%s' was not found in database" % (repository_name, repository_owner) - raise Exception(msg) + msg = "Attempted to load tool shed tool, but the repository with name '%s' from owner '%s' was not found " \ + "in database. Tool will be loaded without install database." + log.warning(msg, repository_name, repository_owner) + repository = ToolConfRepository( + tool_shed, repository_name, repository_owner, installed_changeset_revision, installed_changeset_revision, None, + ) + self.app.tool_shed_repository_cache.add_local_repository(repository) return repository def _get_tool_shed_repository(self, tool_shed, name, owner, installed_changeset_revision): @@ -744,7 +775,7 @@ class AbstractToolBox(Dictifiable, ManagesIntegratedToolPanelMixin): if (tool_loaded or force_watch) and self._tool_watcher: self._tool_watcher.watch_directory(directory, quick_load) - def load_tool(self, config_file, guid=None, repository_id=None, use_cached=False, **kwds): + def load_tool(self, config_file, guid=None, tool_shed_repository=None, use_cached=False, **kwds): """Load a single tool from the file named by `config_file` and return an instance of `Tool`.""" # Parse XML configuration file and get the root element tool = None @@ -752,7 +783,7 @@ class AbstractToolBox(Dictifiable, ManagesIntegratedToolPanelMixin): tool = self.load_tool_from_cache(config_file) if not tool or guid and guid != tool.guid: try: - tool = self.create_tool(config_file=config_file, repository_id=repository_id, guid=guid, **kwds) + tool = self.create_tool(config_file=config_file, tool_shed_repository=tool_shed_repository, guid=guid, **kwds) except Exception: # If the tool is broken but still exists we can load it from the cache tool = self.load_tool_from_cache(config_file, recover_tool=True) diff --git a/lib/galaxy/tools/toolbox/filters/__init__.py b/lib/galaxy/tools/toolbox/filters/__init__.py index 2184e37e846..1ea4aeb9f22 100644 --- a/lib/galaxy/tools/toolbox/filters/__init__.py +++ b/lib/galaxy/tools/toolbox/filters/__init__.py @@ -46,9 +46,8 @@ class FilterFactory(object): if category: validate = getattr(trans.app.config, 'user_tool_%s_filters' % category, []) self.__init_filters(category, user_filters, filters, validate=validate) - else: - if kwds.get("trackster", False): - filters["tool"].append(_has_trackster_conf) + if kwds.get("trackster", False): + filters["tool"].append(_has_trackster_conf) return filters diff --git a/lib/galaxy/tools/verify/__init__.py b/lib/galaxy/tools/verify/__init__.py index 91e8be6eae0..f78d43b7f4f 100644 --- a/lib/galaxy/tools/verify/__init__.py +++ b/lib/galaxy/tools/verify/__init__.py @@ -3,8 +3,10 @@ import difflib import filecmp import hashlib +import io import logging import os +import os.path import re import shutil import tempfile @@ -29,6 +31,7 @@ def verify( attributes, filename=None, get_filecontent=None, + get_filename=None, keep_outputs_dir=None, verify_extra_files=None, mode='file', @@ -37,8 +40,16 @@ def verify( Throw an informative assertion error if any of these tests fail. """ - if get_filecontent is None: - get_filecontent = DEFAULT_TEST_DATA_RESOLVER.get_filecontent + if get_filename is None: + if get_filecontent is None: + get_filecontent = DEFAULT_TEST_DATA_RESOLVER.get_filecontent + + def get_filename(filename): + file_content = get_filecontent(filename) + local_name = make_temp_fname(fname=filename) + with open(local_name, 'wb') as f: + f.write(file_content) + return local_name # Check assertions... assertions = attributes.get("assert_list", None) @@ -79,17 +90,17 @@ def verify( # filename already point to a file that exists on disk local_name = filename else: - file_content = get_filecontent(filename) - local_name = make_temp_fname(fname=filename) - with open(local_name, 'wb') as f: - f.write(file_content) + local_name = get_filename(filename) temp_name = make_temp_fname(fname=filename) with open(temp_name, 'wb') as f: f.write(output_content) # if the server's env has GALAXY_TEST_SAVE, save the output file to that dir if keep_outputs_dir: - ofn = os.path.join(keep_outputs_dir, os.path.basename(local_name)) + ofn = os.path.join(keep_outputs_dir, filename) + out_dir = os.path.dirname(ofn) + if not os.path.exists(out_dir): + os.makedirs(out_dir) log.debug('keep_outputs_dir: %s, ofn: %s', keep_outputs_dir, ofn) try: shutil.copy(temp_name, ofn) @@ -102,8 +113,11 @@ def verify( try: compare = attributes.get('compare', 'diff') if attributes.get('ftype', None) in ['bam', 'qname_sorted.bam', 'qname_input_sorted.bam', 'unsorted.bam']: - local_fh, temp_name = _bam_to_sam(local_name, temp_name) - local_name = local_fh.name + try: + local_fh, temp_name = _bam_to_sam(local_name, temp_name) + local_name = local_fh.name + except Exception as e: + log.warning("%s. Will compare BAM files" % e) if compare == 'diff': files_diff(local_name, temp_name, attributes=attributes) elif compare == 're_match': @@ -149,11 +163,13 @@ def _bam_to_sam(local_name, temp_name): try: pysam.view('-h', '-o%s' % temp_local.name, local_name) except Exception as e: - raise Exception("Converting local (test-data) BAM to SAM failed: %s" % e) + msg = "Converting local (test-data) BAM to SAM failed: %s" % e + raise Exception(msg) try: pysam.view('-h', '-o%s' % temp_temp, temp_name) except Exception as e: - raise Exception("Converting history BAM to SAM failed: %s" % e) + msg = "Converting history BAM to SAM failed: %s" % e + raise Exception(msg) os.remove(temp_name) return temp_local, temp_temp @@ -191,8 +207,8 @@ def files_diff(file1, file2, attributes=None): compressed_formats = [] is_pdf = False try: - local_file = get_fileobj(file1, 'U', compressed_formats=compressed_formats).readlines() - history_data = get_fileobj(file2, 'U', compressed_formats=compressed_formats).readlines() + local_file = get_fileobj(file1, compressed_formats=compressed_formats).readlines() + history_data = get_fileobj(file2, compressed_formats=compressed_formats).readlines() except UnicodeDecodeError: if file1.endswith('.pdf') or file2.endswith('.pdf'): is_pdf = True @@ -240,21 +256,21 @@ def files_diff(file1, file2, attributes=None): break if not valid_diff: invalid_diff_lines += 1 - log.info('## files diff on %s and %s lines_diff=%d, found diff = %d, found pdf invalid diff = %d' % (file1, file2, allowed_diff_count, diff_lines, invalid_diff_lines)) + log.info("## files diff on '%s' and '%s': lines_diff = %d, found diff = %d, found pdf invalid diff = %d" % (file1, file2, allowed_diff_count, diff_lines, invalid_diff_lines)) if invalid_diff_lines > allowed_diff_count: # Print out diff_slice so we can see what failed log.info("###### diff_slice ######") raise AssertionError("".join(diff_slice)) else: - log.info('## files diff on %s and %s lines_diff=%d, found diff = %d' % (file1, file2, allowed_diff_count, diff_lines)) + log.info("## files diff on '%s' and '%s': lines_diff = %d, found diff = %d" % (file1, file2, allowed_diff_count, diff_lines)) raise AssertionError("".join(diff_slice)) def files_re_match(file1, file2, attributes=None): """Check the contents of 2 files for differences using re.match.""" - local_file = open(file1, 'U').readlines() # regex file - history_data = open(file2, 'U').readlines() - assert len(local_file) == len(history_data), 'Data File and Regular Expression File contain a different number of lines (%s != %s)\nHistory Data (first 40 lines):\n%s' % (len(local_file), len(history_data), ''.join(history_data[:40])) + local_file = io.open(file1, encoding='utf-8').readlines() # regex file + history_data = io.open(file2, encoding='utf-8').readlines() + assert len(local_file) == len(history_data), 'Data File and Regular Expression File contain a different number of lines (%d != %d)\nHistory Data (first 40 lines):\n%s' % (len(local_file), len(history_data), ''.join(history_data[:40])) if attributes is None: attributes = {} if attributes.get('sort', False): @@ -272,24 +288,24 @@ def files_re_match(file1, file2, attributes=None): def files_re_match_multiline(file1, file2, attributes=None): """Check the contents of 2 files for differences using re.match in multiline mode.""" - local_file = open(file1, 'U').read() # regex file + local_file = io.open(file1, encoding='utf-8').read() # regex file if attributes is None: attributes = {} if attributes.get('sort', False): - history_data = open(file2, 'U').readlines() + history_data = io.open(file2, encoding='utf-8').readlines() history_data.sort() history_data = ''.join(history_data) else: - history_data = open(file2, 'U').read() + history_data = io.open(file2, encoding='utf-8').read() # lines_diff not applicable to multiline matching assert re.match(local_file, history_data, re.MULTILINE), "Multiline Regular expression did not match data file" def files_contains(file1, file2, attributes=None): """Check the contents of file2 for substrings found in file1, on a per-line basis.""" - local_file = open(file1, 'U').readlines() # regex file + local_file = io.open(file1, encoding='utf-8').readlines() # regex file # TODO: allow forcing ordering of contains - history_data = open(file2, 'U').read() + history_data = io.open(file2, encoding='utf-8').read() lines_diff = int(attributes.get('lines_diff', 0)) line_diff_count = 0 while local_file: diff --git a/lib/galaxy/tools/verify/asserts/__init__.py b/lib/galaxy/tools/verify/asserts/__init__.py index e43afde1b4a..8810a032c44 100644 --- a/lib/galaxy/tools/verify/asserts/__init__.py +++ b/lib/galaxy/tools/verify/asserts/__init__.py @@ -6,7 +6,7 @@ from galaxy.util import unicodify log = logging.getLogger(__name__) -assertion_module_names = ['text', 'tabular', 'xml'] +assertion_module_names = ['text', 'tabular', 'xml', 'hdf5', 'archive'] # Code for loading modules containing assertion checking functions, to # create a new module of assertion functions, create the needed python @@ -15,7 +15,6 @@ assertion_module_names = ['text', 'tabular', 'xml'] assertion_modules = [] for assertion_module_name in assertion_module_names: full_assertion_module_name = 'galaxy.tools.verify.asserts.' + assertion_module_name - log.debug(full_assertion_module_name) try: # Dynamically import module __import__(full_assertion_module_name) @@ -68,11 +67,15 @@ def verify_assertion(data, assertion_description): # - .) if "output" in assert_function_args: - # This was read in as bytes for checksum and such, but all current - # assertions expect text data. If binary assertions are added at - # some point, just checkout for "output_bytes" for instance and pass - # data in unchanged. + # If the assert_function will have an attribute called "output" + # the data passed from the test to the function will be unicodified. + # This is because most of the assert functions are working on pure + # text files. args["output"] = unicodify(data) + if "output_bytes" in assert_function_args: + # This will read in data as bytes and will not change it prior passing + # it to the assert_function + args["output_bytes"] = data if "verify_assertions_function" in assert_function_args: args["verify_assertions_function"] = verify_assertions diff --git a/lib/galaxy/tools/verify/asserts/archive.py b/lib/galaxy/tools/verify/asserts/archive.py new file mode 100644 index 00000000000..840e93dfcb9 --- /dev/null +++ b/lib/galaxy/tools/verify/asserts/archive.py @@ -0,0 +1,37 @@ +import io +import re +import tarfile +import zipfile + + +def _extract_from_tar(tar_temp, path): + for fn in tar_temp.getnames(): + if re.match(path, fn): + # Will only match on first hit, probably fine for now + return tar_temp.extractfile(fn) + + +def _extract_from_zip(zip_temp, path): + for fn in zip_temp.namelist(): + if re.match(path, fn): + # Will only match on first hit, probably fine for now + return zip_temp.open(fn) + + +def assert_has_archive_member(output_bytes, path, verify_assertions_function, children): + """ Recursively checks the specified children assertions against the text of + the first element matching the specified path found within the archive. + Currently supported formats: .zip, .tar, .tar.gz.""" + + output_temp = io.BytesIO(output_bytes) + try: # tar / tar.gz + temp = tarfile.open(fileobj=output_temp, mode='r') + contents = _extract_from_tar(temp, path) + except tarfile.TarError: # zip + temp = zipfile.ZipFile(output_temp, mode='r') + contents = _extract_from_zip(temp, path) + finally: + verify_assertions_function(contents.read(), children) + contents.close() + temp.close() + output_temp.close() diff --git a/lib/galaxy/tools/verify/asserts/hdf5.py b/lib/galaxy/tools/verify/asserts/hdf5.py new file mode 100644 index 00000000000..f64314fe1ea --- /dev/null +++ b/lib/galaxy/tools/verify/asserts/hdf5.py @@ -0,0 +1,33 @@ +import io + +try: + import h5py +except ImportError: + h5py = None + +IMPORT_MISSING_MESSAGE = "h5 assertion requires unavailable optional dependency h5py" + + +def _assert_h5py(): + if h5py is None: + raise Exception(IMPORT_MISSING_MESSAGE) + + +def assert_has_h5_attribute(output_bytes, key, value): + """Asserts the specified HDF5 output has a given key-value pair as HDF5 + attribute""" + _assert_h5py() + output_temp = io.BytesIO(output_bytes) + local_attrs = h5py.File(output_temp, 'r').attrs + assert key in local_attrs and str(local_attrs[key]) == value, ( + "Not a HDF5 file or H5 attributes do not match:\n\t%s\n\n\t(%s : %s)" % (local_attrs.items(), key, value)) + + +def assert_has_h5_keys(output_bytes, keys): + """ Asserts the specified HDF5 output has exactly the given keys.""" + _assert_h5py() + keys = [k.strip() for k in keys.strip().split(',')] + h5_keys = sorted(keys) + output_temp = io.BytesIO(output_bytes) + local_keys = sorted(list(h5py.File(output_temp, 'r').keys())) + assert local_keys == h5_keys, "Not a HDF5 file or H5 keys do not match:\n\t%s\n\t%s" % (local_keys, h5_keys) diff --git a/lib/galaxy/tools/verify/interactor.py b/lib/galaxy/tools/verify/interactor.py index 08f67ec690f..edfb87c284a 100644 --- a/lib/galaxy/tools/verify/interactor.py +++ b/lib/galaxy/tools/verify/interactor.py @@ -31,8 +31,8 @@ from six import ( from galaxy import util from galaxy.tools.parser.interface import TestCollectionDef, TestCollectionOutputDef from galaxy.util.bunch import Bunch +from . import verify from .asserts import verify_assertions -from ..verify import verify log = getLogger(__name__) @@ -73,7 +73,7 @@ class OutputsDict(OrderedDict): return item -def stage_data_in_history(galaxy_interactor, tool_id, all_test_data, history): +def stage_data_in_history(galaxy_interactor, tool_id, all_test_data, history=None, force_path_paste=False): # Upload any needed files upload_waits = [] @@ -81,12 +81,12 @@ def stage_data_in_history(galaxy_interactor, tool_id, all_test_data, history): if UPLOAD_ASYNC: for test_data in all_test_data: - upload_waits.append(galaxy_interactor.stage_data_async(test_data, history, tool_id)) + upload_waits.append(galaxy_interactor.stage_data_async(test_data, history, tool_id, force_path_paste=force_path_paste)) for upload_wait in upload_waits: upload_wait() else: for test_data in all_test_data: - upload_wait = galaxy_interactor.stage_data_async(test_data, history, tool_id) + upload_wait = galaxy_interactor.stage_data_async(test_data, history, tool_id, force_path_paste=force_path_paste) upload_wait() @@ -269,11 +269,16 @@ class GalaxyInteractorApi(object): history_json = self._post("histories", {"name": "test_history"}).json() return history_json['id'] + @nottest + def test_data_path(self, tool_id, filename): + response = self._get("tools/%s/test_data_path?filename=%s" % (tool_id, filename), admin=True) + return response.json() + @nottest def test_data_download(self, tool_id, filename, mode='file'): if self.supports_test_data_download: response = self._get("tools/%s/test_data_download?filename=%s" % (tool_id, filename), admin=True) - assert response.status_code == 200 + assert response.status_code == 200, "Test file (%s) is missing. If you use planemo try --update_test_data to generate one." % filename if mode == 'file': return response.content elif mode == 'directory': @@ -284,9 +289,7 @@ class GalaxyInteractorApi(object): return path else: # We can only use local data - response = self._get("tools/%s/test_data_path?filename=%s" % (tool_id, filename), admin=True) - assert response.status_code == 200 - file_name = response.json() + file_name = self.test_data_path(tool_id, filename) if mode == 'file': return open(file_name, mode='rb') elif mode == 'directory': @@ -304,7 +307,7 @@ class GalaxyInteractorApi(object): output_id = output_data return output_id - def stage_data_async(self, test_data, history_id, tool_id): + def stage_data_async(self, test_data, history_id, tool_id, force_path_paste=False): fname = test_data['fname'] tool_input = { "file_type": test_data['ftype'], @@ -320,28 +323,40 @@ class GalaxyInteractorApi(object): if composite_data: files = {} for i, file_name in enumerate(composite_data): - file_content = self.test_data_download(tool_id, file_name) - files["files_%s|file_data" % i] = file_content + if force_path_paste: + file_path = self.test_data_path(tool_id, file_name) + tool_input.update({ + "files_%d|url_paste" % i: "file://" + file_path + }) + else: + file_content = self.test_data_download(tool_id, file_name) + files["files_%s|file_data" % i] = file_content tool_input.update({ "files_%d|type" % i: "upload_dataset", }) name = test_data['name'] else: name = fname - file_content = self.test_data_download(tool_id, fname) tool_input.update({ "files_0|NAME": name, "files_0|type": "upload_dataset", - }) - files = { - "files_0|file_data": file_content, - } + files = {} + if force_path_paste: + file_name = self.test_data_path(tool_id, fname) + tool_input.update({ + "files_0|url_paste": "file://" + file_name + }) + else: + file_content = self.test_data_download(tool_id, fname) + files = { + "files_0|file_data": file_content + } submit_response_object = self.__submit_tool(history_id, "upload1", tool_input, extra_data={"type": "upload_dataset"}, files=files) if submit_response_object.status_code != 200: raise Exception("Request to upload dataset failed [%s]" % submit_response_object.content) submit_response = submit_response_object.json() - assert "outputs" in submit_response, "Invalid response from server [%s], expecteding outputs in response." % submit_response + assert "outputs" in submit_response, "Invalid response from server [%s], expecting outputs in response." % submit_response outputs = submit_response["outputs"] assert len(outputs) > 0, "Invalid response from server [%s], expecting an output dataset." % submit_response dataset = outputs[0] @@ -702,7 +717,7 @@ def _verify_extra_files_content(extra_files, hda_id, dataset_fetcher, test_data_ shutil.rmtree(path) -def verify_tool(tool_id, galaxy_interactor, resource_parameters=None, register_job_data=None, test_index=0, tool_version=None, quiet=False, test_history=None): +def verify_tool(tool_id, galaxy_interactor, resource_parameters=None, register_job_data=None, test_index=0, tool_version=None, quiet=False, test_history=None, force_path_paste=False): if resource_parameters is None: resource_parameters = {} tool_test_dicts = galaxy_interactor.get_tool_tests(tool_id, tool_version=tool_version) @@ -714,7 +729,7 @@ def verify_tool(tool_id, galaxy_interactor, resource_parameters=None, register_j if test_history is None: test_history = galaxy_interactor.new_history() - stage_data_in_history(galaxy_interactor, tool_id, testdef.test_data(), test_history) + stage_data_in_history(galaxy_interactor, tool_id, testdef.test_data(), history=test_history, force_path_paste=force_path_paste) # Once data is ready, run the tool and check the outputs - record API # input, job info, tool run exception, as well as exceptions related to @@ -868,11 +883,36 @@ def _verify_outputs(testdef, history, jobs, tool_id, data_list, data_collection_ "stdout": "Standard output of the job", "stderr": "Standard error of the job", } + # TODO: Only hack the stdio like this for older profile, for newer tool profiles + # add some syntax for asserting job messages maybe - or just drop this because exit + # code and regex on stdio can be tested directly - so this is really testing Galaxy + # core handling more than the tool. + job_messages = job_stdio.get("job_messages") or [] + stdout_prefix = "" + stderr_prefix = "" + for job_message in job_messages: + message_type = job_message.get("type") + if message_type == "regex" and job_message.get("stream") == "stderr": + stderr_prefix += (job_message.get("desc") or '') + "\n" + elif message_type == "regex" and job_message.get("stream") == "stdout": + stdout_prefix += (job_message.get("desc") or '') + "\n" + elif message_type == "exit_code": + stderr_prefix += (job_message.get("desc") or '') + "\n" + else: + raise Exception("Unknown job message type [%s] in [%s]" % (message_type, job_message)) + for what, description in other_checks.items(): if getattr(testdef, what, None) is not None: try: - data = job_stdio[what] - verify_assertions(data, getattr(testdef, what)) + raw_data = job_stdio[what] + assertions = getattr(testdef, what) + if what == "stdout": + data = stdout_prefix + raw_data + elif what == "stderr": + data = stderr_prefix + raw_data + else: + data = raw_data + verify_assertions(data, assertions) except AssertionError as err: errmsg = '%s different than expected\n' % description errmsg += str(err) diff --git a/lib/galaxy/tools/verify/script.py b/lib/galaxy/tools/verify/script.py index 64873a368f4..d4d36ad15e1 100644 --- a/lib/galaxy/tools/verify/script.py +++ b/lib/galaxy/tools/verify/script.py @@ -60,7 +60,7 @@ def main(argv=None): try: verify_tool( tool_id, galaxy_interactor, test_index=test_index, tool_version=tool_version, - register_job_data=register, quiet=not verbose + register_job_data=register, quiet=not verbose, force_path_paste=args.force_path_paste ) if verbose: @@ -93,6 +93,7 @@ def _arg_parser(): parser.add_argument('-u', '--galaxy-url', default="http://localhost:8080", help='Galaxy URL') parser.add_argument('-k', '--key', default=None, help='Galaxy User API Key') parser.add_argument('-a', '--admin-key', default=None, help='Galaxy Admin API Key') + parser.add_argument('--force_path_paste', default=False, action="store_true", help='This requires Galaxy-side config option "allow_path_paste" enabled. Allows for fetching test data locally. Only for admins.') parser.add_argument('-t', '--tool-id', default=None, help='Tool ID') parser.add_argument('--tool-version', default=None, help='Tool Version') parser.add_argument('-i', '--test-index', default=ALL_TESTS, help='Tool Test Index (starting at 0) - by default all tests will run.') diff --git a/lib/galaxy/tools/wrappers.py b/lib/galaxy/tools/wrappers.py index 6f10b668031..79f3883c70c 100644 --- a/lib/galaxy/tools/wrappers.py +++ b/lib/galaxy/tools/wrappers.py @@ -108,7 +108,7 @@ class InputValueWrapper(ToolParameterValueWrapper): return getattr(self.value, key) def __int__(self): - return int(str(self)) + return int(float(self)) def __float__(self): return float(str(self)) diff --git a/lib/galaxy/tools/xsd/galaxy.xsd b/lib/galaxy/tools/xsd/galaxy.xsd index 4fa7f826a7e..337134b22b7 100644 --- a/lib/galaxy/tools/xsd/galaxy.xsd +++ b/lib/galaxy/tools/xsd/galaxy.xsd @@ -67,7 +67,8 @@ the tool menu immediately following the hyperlink for the tool (based on the - + + @@ -279,7 +280,7 @@ and can be configured locally to adapt to any other package management system. ``` This older example shows a tool that requires R version 2.15.1. The -``tool_depensencies.xml`` should contain matching declarations for Galaxy to +``tool_dependencies.xml`` should contain matching declarations for Galaxy to actually install the R runtime. The ``set_envirornment`` type is only respected by the tool shed and is ignored by the newer and preferred conda dependency resolver. @@ -314,7 +315,7 @@ resolver. This tag set is contained within the 'requirements' tag set. Galaxy can be configured to run tools within [Docker](https://www.docker.com/) or [Singularity](https://www.sylabs.io/singularity/) containers - this tag allows the tool to suggest possible valid containers for this tool. The contents of the tag should -be a container image identifier appropriate for the particular container runtime being used, e.g. +be a container image identifier appropriate for the particular container runtime being used, e.g. ``quay.io/biocontainers/fastqc:0.11.2--1`` for Docker or ``docker://quay.io/biocontainers/fastqc:0.11.2--1`` (or alternatively ``/opt/containers/fastqc.simg`` if your Galaxy installation will be loading the image from a filesystem path) for Singularity. The ``requirements`` tag can contain multiple ``container`` tags describing suitable container options, in @@ -1218,9 +1219,7 @@ it may be inconvenient to upload the entiry file and this can be used instead. @@ -1243,7 +1242,7 @@ $assertions ### Examples -The following demonstrtes a wide variety of text-based and tabular +The following demonstrates a wide variety of text-based and tabular assertion statements. ```xml @@ -1260,7 +1259,7 @@ assertion statements. ``` -The following demonstrtes a wide variety of XML assertion statements. +The following demonstrates a wide variety of XML assertion statements. ```xml @@ -1278,7 +1277,7 @@ The following demonstrtes a wide variety of XML assertion statements. ``` -The following demonstrtes verifying XML content with XPath-like expressions. +The following demonstrates verifying XML content with XPath-like expressions. ```xml @@ -1584,8 +1583,8 @@ module. `` ).]]> - - + + @@ -1651,8 +1650,52 @@ module. ``).]]> + + + + + + + + + ``).]]> + + + + Comma-separated list of HDF5 attributes to check for. + + + + + + ``).]]> + + + + HDF5 attribute to check value of. + + + + + Expected value of HDF5 attribute to check. + + + + + + ``). Valid archive formats include ``.zip``, ``.tar``, and ``.tar.gz``.]]> + + + + + + + The regular expression specifying the archive member. + + + + + + + + + + + + Type of expression defined by this expression block. The only current valid option is ecma5.1 - which will evaluate the expression in a sandbox using node. The option still must be specified to allow a different default in the future. + + + + + + + + + + + + + + @@ -3106,13 +3176,10 @@ expressed with the validator. In general, ``dataset_metadata_in_file`` should be considered deprecated in favor of ```xml - + message="Sequences are not currently available for the specified build." /> ``` A very common validator is simply ensure a Python expression is valid for a @@ -3133,8 +3200,10 @@ validators is in the ``validator_types`` dictionary in [/lib/galaxy/tools/parameters/validation.py](https://github.com/galaxyproject/galaxy/blob/dev/lib/galaxy/tools/parameters/validation.py). Valid values include: ``expression``, ``regex``, ``in_range``, ``length``, ``metadata``, ``unspecified_build``, ``no_options``, ``empty_field``, -``dataset_metadata_in_file``, ``dataset_metadata_in_data_table``, -``dataset_ok_validator``]]> +``dataset_metadata_in_file``, +``dataset_metadata_in_data_table``, ``dataset_metadata_not_in_data_table``, +``value_in_data_table``, ``value_not_in_data_table``, +``dataset_ok_validator``, ``dataset_metadata_in_range``]]> @@ -3154,7 +3223,7 @@ specified by the ``skip`` attribute. Tool data table name to check against -if ``type`` is ``dataset_metadata_in_tool_data``. See the documentation for +if ``type`` is ``dataset_metadata_in_tool_data``, ``dataset_metadata_not_in_tool_data``, ``value_in_tool_data``, or ``value_not_in_tool_data``. See the documentation for [tool data tables](https://galaxyproject.org/admin/tools/data-tables) and [data managers](https://galaxyproject.org/admin/tools/data-managers/) for more information. @@ -3170,13 +3239,13 @@ if ``type`` is ``dataset_metadata_in_file``. File should be present Galaxy's Target metadata attribute name for -``dataset_metadata_in_data_table`` and ``dataset_metadata_in_file`` options. +``dataset_metadata_in_data_table``, ``dataset_metadata_not_in_data_table`` and ``dataset_metadata_in_file`` options. Target column for metadata attribute -in ``dataset_metadata_in_data_table`` and ``dataset_metadata_in_file`` options. +in ``dataset_metadata_in_data_table``, ``dataset_metadata_not_in_data_table``, ``value_in_data_table``, ``value__not_in_data_table``, and ``dataset_metadata_in_file`` options. This can be an integer index to the column or a column name. @@ -3641,11 +3710,12 @@ The default is ``galaxy.json``. - - + + + - + @@ -3653,7 +3723,101 @@ The default is ``galaxy.json``. - + + + + + This sets the data type of the output dataset(s) to be the same format as that of the specified tool input. + + + + + .}``, as +discussed for command line parameters in the ```` tag set section +above. The default label is ``${tool.name} on ${on_string}``. + +]]> + + + + + + + + + + + + + + + + + + The short name for the output datatype. +The valid values for format can be found in +[/config/datatypes_conf.xml.sample](https://github.com/galaxyproject/galaxy/blob/dev/config/datatypes_conf.xml.sample) +(e.g. ``format="pdf"`` or ``format="fastqsanger"``). + + + + + Sets the source of element identifier to the specified input. +This only applies to collections that are mapped over a non-collection input and that have equivalent structures. If this references input elements in conditionals, this value should be qualified (e.g. ``cond|input`` instead of ``input`` if ``input`` is in a conditional with ``name="cond"``). + + + + + This copies the metadata information +from the tool's input dataset. This is particularly useful for interval data +types where the order of the columns is not set. + + + + + Relative path to a file produced by the +tool in its working directory. Output's contents are set to this file's +contents. + + + + + Boolean indicating whether to hide +dataset in the history view. (Default is ``false``.) + + + + + + + + This is the name of input collection or +dataset to derive "structure" of the output from (output element count and +identifiers). For instance, if the referenced input has three ordered items with +identifiers ``sample1``, ``sample2``, and ``sample3``. If this references input +elements in conditionals, this value should be qualified (e.g. ``cond|input`` instead +of ``input`` if ``input`` is in a conditional with ``name="cond"``). + + + + + If ``structured_like`` is set, inherit +format of outputs from format of corresponding input. + + + + + - + - - - - - - - - - - - - - The short name for the output datatype. -The valid values for format can be found in -[/config/datatypes_conf.xml.sample](https://github.com/galaxyproject/galaxy/blob/dev/config/datatypes_conf.xml.sample) -(e.g. ``format="pdf"`` or ``format="fastqsanger"``). - - - - - This sets the data type of the output file to be the same format as that of a tool input dataset. - - - - - Sets the source of element identifier to the specified input. -This only applies to collections that are mapped over a non-collection input and that have equivalent structures. If this references input elements in conditionals, this value should be qualified (e.g. ``cond|input`` instead of ``input`` if ``input`` is in a conditional with ``name="cond"``). - - - - - This copies the metadata information -from the tool's input dataset. This is particularly useful for interval data -types where the order of the columns is not set. - - - - - .}``, as -discussed for command line parameters in the ```` tag set section -above. The default label is ``${tool.name} on ${on_string}``. - -]]> - - - - - Relative path to a file produced by the -tool in its working directory. Output's contents are set to this file's -contents. - - - - - Boolean indicating whether to hide -dataset in the history view. (Default is ``false``.) - - + + - + - + - + - + - - - - - + + Collection type for output (e.g. ``paired``, ``list``, or ``list:list``). - - - .}``, as -discussed for command line parameters in the ```` tag set section -above. The default label is ``${tool.name} on ${on_string}``. - -]]> - - - - - This is the name of input collection or -dataset to derive output dataset collection's element's format/datatype from. - - This is the name of input collection to derive collection's type (e.g. ``collection_type``) from. - - - This is the name of input collection or -dataset to derive "structure" of the output from (output element count and -identifiers). For instance, if the referenced input has three ordered items with -identifiers ``sample1``, ``sample2``, and ``sample3``. If this references input -elements in conditionals, this value should be qualified (e.g. ``cond|input`` instead -of ``input`` if ``input`` is in a conditional with ``name="cond"``). - - - - - If ``structured_like`` is set, inherit -format of outputs from format of corresponding input. - - + + + + + + + + + + + + + Output type. This could be older more established Galaxy types (e.g. data and collection) - in which case the semantics of this largely reflect the corresponding ``data`` and ``collection`` tags. This could also be newer non-data types such as ``integer`` or ``boolean``. + + + + + In expression tools, use this to specify a dictionary value to populate this output from. The semantics may change for other expression types in the future. + + + + + Collection type for output (e.g. ``paired``, ``list``, or ``list:list``). + + + + + This is the name of input collection to +derive collection's type (e.g. ``collection_type``) from. + + + + + @@ -5305,6 +5400,10 @@ and ``bibtex`` are the only supported options. + + + + diff --git a/lib/galaxy/util/__init__.py b/lib/galaxy/util/__init__.py index d0e6dbe9822..cfc9657be0e 100644 --- a/lib/galaxy/util/__init__.py +++ b/lib/galaxy/util/__init__.py @@ -222,6 +222,11 @@ def parse_xml(fname): tree = ElementTree.ElementTree() try: root = tree.parse(fname, parser=ElementTree.XMLParser(target=DoctypeSafeCallbackTarget())) + for elem in root.iter('*'): + if elem.text is not None: + elem.text = elem.text.strip() + if elem.tail is not None: + elem.tail = elem.tail.strip() except ParseError: log.exception("Error parsing file %s", fname) raise @@ -231,11 +236,18 @@ def parse_xml(fname): def parse_xml_string(xml_string): tree = ElementTree.fromstring(xml_string) + for elem in tree.iter('*'): + if elem.text is not None: + elem.text = elem.text.strip() + if elem.tail is not None: + elem.tail = elem.tail.strip() return tree def xml_to_string(elem, pretty=False): - """Returns a string from an xml tree""" + """ + Returns a string from an xml tree. + """ try: if elem is not None: if PY2: @@ -1046,7 +1058,7 @@ def smart_str(s, encoding=DEFAULT_ENCODING, strings_only=False, errors='strict') def strip_control_characters(s): """Strip unicode control characters from a string.""" - return "".join(c for c in unicodify(s) if unicodedata.category(c)[0] != "C") + return "".join(c for c in unicodify(s) if unicodedata.category(c) != "Cc") def strip_control_characters_nested(item): @@ -1512,7 +1524,10 @@ galaxy_root_path = os.path.join(__path__[0], "..", "..", "..") def galaxy_directory(): - return os.path.abspath(galaxy_root_path) + root_path = os.path.abspath(galaxy_root_path) + if os.path.basename(root_path) == "packages": + root_path = os.path.abspath(os.path.join(root_path, "..")) + return root_path def config_directories_from_setting(directories_setting, galaxy_root=galaxy_root_path): diff --git a/lib/galaxy/util/biostar.py b/lib/galaxy/util/biostar.py deleted file mode 100644 index 81344b0b718..00000000000 --- a/lib/galaxy/util/biostar.py +++ /dev/null @@ -1,196 +0,0 @@ -""" -Support for integration with the Biostar application -""" -from __future__ import absolute_import - -import hmac -import logging -import re -from unicodedata import normalize - -from six import text_type -from six.moves.urllib.parse import ( - urljoin, - urlsplit -) - -from galaxy.tools.errors import ErrorReporter -from galaxy.web.base.controller import url_for -from . import smart_str - -log = logging.getLogger(__name__) - -_punct_re = re.compile(r'[\t !"#$%&\'()*\-/<=>?@\[\\\]^_`{|},.]+') - -DEFAULT_GALAXY_TAG = '' - -# Default values for new posts to Biostar -DEFAULT_PAYLOAD = { - 'title': '', - 'tag_val': DEFAULT_GALAXY_TAG, - 'content': '', -} - -BIOSTAR_ACTIONS = { - None: {'url': lambda x: '', 'uses_payload': False}, - 'new_post': {'url': lambda x: 'p/new/external/post/', 'uses_payload': True, 'hmac_values': {'content': 'digest'}}, - 'show_tags': {'url': lambda x: 't/%s/' % ("+".join((x.get('tag_val') or DEFAULT_GALAXY_TAG).split(','))), 'uses_payload': False}, - 'log_out': {'url': lambda x: 'site/logout/', 'uses_payload': False} -} - -DEFAULT_BIOSTAR_COOKIE_AGE = 1 - - -def biostar_enabled(app): - return bool(app.config.biostar_url) - - -# Slugifying from Armin Ronacher (http://flask.pocoo.org/snippets/5/) -def slugify(text, delim=u'-'): - """Generates an slightly worse ASCII-only slug.""" - if not isinstance(text, text_type): - text = text_type(text) - result = [] - for word in _punct_re.split(text.lower()): - word = normalize('NFKD', word).encode('ascii', 'ignore') - if word: - result.append(word) - return text_type(delim.join(result)) - - -def get_biostar_url(app, payload=None, biostar_action=None): - # Ensure biostar integration is enabled - if not biostar_enabled(app): - raise Exception("Biostar integration is not enabled") - if biostar_action not in BIOSTAR_ACTIONS: - raise Exception("Invalid action specified (%s)." % (biostar_action)) - biostar_action = BIOSTAR_ACTIONS[biostar_action] - # Start building up the payload - payload = payload or {} - payload = dict(DEFAULT_PAYLOAD, **payload) - payload['name'] = app.config.biostar_key_name - for hmac_value_name, hmac_parameter_name in biostar_action.get('hmac_values', {}).items(): - # Biostar requires ascii only on HMAC'd things - payload[hmac_value_name] = smart_str(payload.get(hmac_value_name, ''), encoding='ascii', errors='replace') - payload[hmac_parameter_name] = hmac.new(app.config.biostar_key, payload[hmac_value_name]).hexdigest() - # generate url, can parse payload info - url = str(urljoin(app.config.biostar_url, biostar_action.get('url')(payload))) - if not biostar_action.get('uses_payload'): - payload = {} - url = url_for(url) - return url, payload - - -def tag_for_tool(tool): - """ - Generate a reasonable biostar tag for a tool. - """ - # Biostar can now handle tags with spaces, do we want to generate tags differently now? - return slugify(tool.name, delim='-') - - -def populate_tag_payload(payload=None, tool=None): - if payload is None: - payload = {} - if DEFAULT_GALAXY_TAG: - tag_val = [DEFAULT_GALAXY_TAG] - else: - tag_val = [] - if tool: - tag_val.append(tag_for_tool(tool)) - payload['tag_val'] = ','.join(tag_val) - return payload - - -def populate_tool_payload(payload=None, tool=None): - payload = populate_tag_payload(payload=payload, tool=tool) - payload['title'] = 'Need help with "%s" tool' % (tool.name) - tool_url = None - if tool.tool_shed_repository: - tool_url = tool.tool_shed_repository.get_sharable_url(tool.app) - if tool_url: - tool_url = '
    ToolShed URL: %s' % (tool_url, tool_url) - if not tool_url: - tool_url = '' - payload['content'] = '


    Tool name: %s
    Tool version: %s
    Tool ID: %s%s


    ' % (tool.name, tool.version, tool.id, tool_url) - return payload - - -def determine_cookie_domain(galaxy_hostname, biostar_hostname): - if galaxy_hostname == biostar_hostname: - return galaxy_hostname - - sub_biostar_hostname = biostar_hostname.split('.', 1)[-1] - if sub_biostar_hostname == galaxy_hostname: - return galaxy_hostname - - sub_galaxy_hostname = galaxy_hostname.split('.', 1)[-1] - if sub_biostar_hostname == sub_galaxy_hostname: - return sub_galaxy_hostname - - return galaxy_hostname - - -def create_cookie(trans, key_name, key, email, age=DEFAULT_BIOSTAR_COOKIE_AGE, override_never_authenticate=False): - if trans.app.config.biostar_never_authenticate and not override_never_authenticate: - log.debug('A BioStar link was clicked, but never authenticate has been enabled, so we will not create the login cookie.') - return - digest = hmac.new(key, email).hexdigest() - value = "%s:%s" % (email, digest) - trans.set_cookie(value, name=key_name, path='/', age=age, version='1') - # We need to explicitly set the domain here, in order to allow for biostar in a subdomain to work - galaxy_hostname = urlsplit(url_for('/', qualified=True)).hostname - biostar_hostname = urlsplit(trans.app.config.biostar_url).hostname - trans.response.cookies[key_name]['domain'] = determine_cookie_domain(galaxy_hostname, biostar_hostname) - - -def delete_cookie(trans, key_name): - # Set expiration of Cookie to time in past, to cause browser to delete - if key_name in trans.request.cookies: - create_cookie(trans, trans.app.config.biostar_key_name, '', '', age=-90, override_never_authenticate=True) - - -def biostar_logged_in(trans): - if biostar_enabled(trans.app): - if trans.app.config.biostar_key_name in trans.request.cookies: - return True - return False - - -def biostar_logout(trans): - if biostar_enabled(trans.app): - delete_cookie(trans, trans.app.config.biostar_key_name) - return get_biostar_url(trans.app, biostar_action='log_out')[0] - return None - - -class BiostarErrorReporter(ErrorReporter): - def _send_report(self, user, email=None, message=None, **kwd): - assert biostar_enabled(self.app), ValueError("Biostar is not configured for this galaxy instance") - assert self.app.config.biostar_enable_bug_reports, ValueError("Biostar is not configured to allow bug reporting for this galaxy instance") - assert self._can_access_dataset(user), Exception("You are not allowed to access this dataset.") - tool_version_select_field, tools, tool = \ - self.app.toolbox.get_tool_components(self.tool_id, tool_version=None, get_loaded_tools_by_lineage=False, set_selected=True) - - # Strip out unwanted HTML characters - html_remove = ['', '', '', '', '\n', '\r'] - report = self.html_report - for tag in html_remove: - report = report.replace(tag, '') - # Must lstrip spaces or it isn't recognised as HTML - report = report.lstrip() - - payload = { - 'title': 'Bug report on "%s" tool' % (tool.name), - 'content': report, - 'tag_val': slugify('bug report') - } - # Get footer for email from here - payload2 = populate_tool_payload(tool=tool) - if 'content' in payload2: - payload['content'] = "%s
    %s" % (payload['content'], payload2['content']) - if 'tag_val' in payload2: - payload['tag_val'] = ','.join([payload2['tag_val'], payload['tag_val']]) - if 'action' not in payload: - payload['action'] = 1 # Automatically post bug reports to biostar - return payload diff --git a/lib/galaxy/util/bytesize.py b/lib/galaxy/util/bytesize.py new file mode 100644 index 00000000000..bd360e05b4d --- /dev/null +++ b/lib/galaxy/util/bytesize.py @@ -0,0 +1,68 @@ +SUFFIX_TO_BYTES = { + 'KI': 1024, + 'MI': 1024**2, + 'GI': 1024**3, + 'TI': 1024**4, + 'PI': 1024**5, + 'EI': 1024**6, + 'K': 1000, + 'M': 1000**2, + 'G': 1000**3, + 'T': 1000**4, + 'P': 1000**5, + 'E': 1000**6, +} + + +class ByteSize(object): + """Convert multiples of bytes to various units.""" + + def __init__(self, value): + """ + Represents a quantity of bytes. + + `value` may be an integer, in which case it is assumed to be bytes. + If value is a string, it is parsed as bytes if no suffix (Mi, M, Gi, G ...) + is found. + + >>> values = [128974848, '129e6', '129M', '123Mi' ] + >>> [ByteSize(v).to_unit('M') for v in values] + ['128M', '129M', '129M', '128M'] + """ + self.value = parse_bytesize(value) + + def to_unit(self, unit=None, as_string=True): + """unit must be `None` or one of Ki,Mi,Gi,Ti,Pi,Ei,K,M,G,T,P.""" + if unit is None: + if as_string: + return str(self.value) + return self.value + unit = unit.upper() + new_value = int(self.value / SUFFIX_TO_BYTES[unit]) + if not as_string: + return new_value + return "{value}{suffix}".format(value=new_value, suffix=unit) + + +def parse_bytesize(value): + if isinstance(value, int): + # Assume bytes + return value + value = value.upper() + found_suffix = None + for suffix in SUFFIX_TO_BYTES: + if value.endswith(suffix): + found_suffix = suffix + break + if found_suffix: + value = value[:-len(found_suffix)] + try: + value = int(value) + except ValueError: + try: + value = float(value) + except ValueError: + raise ValueError("{value} is not a valid integer or float value".format(value=value)) + if found_suffix: + value = value * SUFFIX_TO_BYTES[found_suffix] + return value diff --git a/lib/galaxy/util/compression_utils.py b/lib/galaxy/util/compression_utils.py index baa7e9ab639..aa6f658b67d 100644 --- a/lib/galaxy/util/compression_utils.py +++ b/lib/galaxy/util/compression_utils.py @@ -35,23 +35,20 @@ def get_fileobj_raw(filename, mode="r", compressed_formats=None): compressed_formats = ['bz2', 'gzip', 'zip'] # Remove 't' from mode, which may cause an error for compressed files mode = mode.replace('t', '') - # the various compression readers don't support 'U' mode, - # so we open in 'r'. + # 'U' mode is deprecated, we open in 'r'. if mode == 'U': - cmode = 'r' - else: - cmode = mode + mode = 'r' compressed_format = None if 'gzip' in compressed_formats and is_gzip(filename): - fh = gzip.GzipFile(filename, cmode) + fh = gzip.GzipFile(filename, mode) compressed_format = 'gzip' elif 'bz2' in compressed_formats and is_bz2(filename): - fh = bz2.BZ2File(filename, cmode) + fh = bz2.BZ2File(filename, mode) compressed_format = 'bz2' elif 'zip' in compressed_formats and zipfile.is_zipfile(filename): # Return fileobj for the first file in a zip file. - with zipfile.ZipFile(filename, cmode) as zh: - fh = zh.open(zh.namelist()[0], cmode) + with zipfile.ZipFile(filename, mode) as zh: + fh = zh.open(zh.namelist()[0], mode) compressed_format = 'zip' elif 'b' in mode: return compressed_format, open(filename, mode) @@ -84,11 +81,32 @@ class CompressedFile(object): else: raise NameError('File type %s specified, no open method found.' % self.file_type) + @property + def common_prefix_dir(self): + """ + Get the common prefix directory for all the files in the archive, if any. + + Returns '' if the archive contains multiple files and/or directories at + the root of the archive. + """ + contents = self.getmembers() + common_prefix = '' + if len(contents) > 1: + common_prefix = os.path.commonprefix([self.getname(item) for item in contents]) + # If the common_prefix does not end with a slash, check that is a + # directory and all other files are contained in it + if len(common_prefix) >= 1 and not common_prefix.endswith(os.sep) and self.isdir(self.getmember(common_prefix)) \ + and all(self.getname(item).startswith(common_prefix + os.sep) for item in contents if self.isfile(item)): + common_prefix += os.sep + if not common_prefix.endswith(os.sep): + common_prefix = '' + return common_prefix + def extract(self, path): '''Determine the path to which the archive should be extracted.''' contents = self.getmembers() extraction_path = path - common_prefix = '' + common_prefix_dir = self.common_prefix_dir if len(contents) == 1: # The archive contains a single file, return the extraction path. if self.isfile(contents[0]): @@ -97,14 +115,7 @@ class CompressedFile(object): os.makedirs(extraction_path) self.archive.extractall(extraction_path, members=self.safemembers()) else: - # Get the common prefix for all the files in the archive. If the common prefix ends with a slash, - # or self.isdir() returns True, the archive contains a single directory with the desired contents. - # Otherwise, it contains multiple files and/or directories at the root of the archive. - common_prefix = os.path.commonprefix([self.getname(item) for item in contents]) - if len(common_prefix) >= 1 and not common_prefix.endswith(os.sep) and self.isdir(self.getmember(common_prefix)): - common_prefix += os.sep - if not common_prefix.endswith(os.sep): - common_prefix = '' + if not common_prefix_dir: extraction_path = os.path.join(path, self.file_name) if not os.path.exists(extraction_path): os.makedirs(extraction_path) @@ -123,24 +134,25 @@ class CompressedFile(object): os.chmod(absolute_filepath, unix_permissions) else: log.warning("Unable to change permission on extracted file '%s' as it does not exist" % absolute_filepath) - return os.path.abspath(os.path.join(extraction_path, common_prefix)) + return os.path.abspath(os.path.join(extraction_path, common_prefix_dir)) def safemembers(self): members = self.archive + common_prefix_dir = self.common_prefix_dir if self.file_type == "tar": for finfo in members: if not safe_relpath(finfo.name): - raise Exception(finfo.name + " is blocked (illegal path).") - elif (finfo.issym() or finfo.islnk()) and not safe_relpath(finfo.linkname): - raise Exception(finfo.name + " is blocked.") - else: - yield finfo + raise Exception("Path '%s' is blocked (illegal path)." % finfo.name) + if finfo.issym() or finfo.islnk(): + link_target = os.path.join(os.path.dirname(finfo.name), finfo.linkname) + if not safe_relpath(link_target) or not os.path.normpath(link_target).startswith(common_prefix_dir): + raise Exception("Link '%s' to '%s' is blocked." % (finfo.name, finfo.linkname)) + yield finfo elif self.file_type == "zip": for name in members.namelist(): if not safe_relpath(name): raise Exception(name + " is blocked (illegal path).") - else: - yield name + yield name def getmembers_tar(self): return self.archive.getmembers() diff --git a/lib/galaxy/util/dictifiable.py b/lib/galaxy/util/dictifiable.py index 55073f50b85..58093d5560e 100644 --- a/lib/galaxy/util/dictifiable.py +++ b/lib/galaxy/util/dictifiable.py @@ -2,6 +2,14 @@ import datetime import uuid +def dict_for(obj, **kwds): + # Create dict to represent item. + return dict( + model_class=obj.__class__.__name__, + **kwds + ) + + class Dictifiable(object): """ Mixin that enables objects to be converted to dictionaries. This is useful when for sharing objects across boundaries, such as the API, tool scripts, @@ -38,9 +46,7 @@ class Dictifiable(object): return item # Create dict to represent item. - rval = dict( - model_class=self.__class__.__name__ - ) + rval = dict_for(self) # Fill item dict with visible keys. try: diff --git a/lib/galaxy/util/filelock.py b/lib/galaxy/util/filelock.py index be6dc512b83..e6edcae9281 100644 --- a/lib/galaxy/util/filelock.py +++ b/lib/galaxy/util/filelock.py @@ -46,7 +46,7 @@ class FileLock(object): if e.errno != errno.EEXIST: raise if (time.time() - start_time) >= self.timeout: - raise FileLockException("Timeout occured.") + raise FileLockException("Timeout occurred.") time.sleep(self.delay) self.is_locked = True diff --git a/lib/galaxy/util/form_builder.py b/lib/galaxy/util/form_builder.py new file mode 100644 index 00000000000..33657b93c93 --- /dev/null +++ b/lib/galaxy/util/form_builder.py @@ -0,0 +1,196 @@ +""" +Classes for generating HTML forms +""" +from __future__ import absolute_import +from __future__ import print_function + +import logging + +from galaxy.util import ( + asbool +) + +log = logging.getLogger(__name__) + + +class BaseField(object): + def __init__(self, name, value=None, label=None, **kwds): + self.name = name + self.label = label + self.value = value + self.disabled = kwds.get('disabled', False) + if 'optional' in kwds: + self.optional = asbool(kwds.get('optional')) + else: + self.optional = kwds.get('required', 'optional') == 'optional' + self.help = kwds.get('helptext') + + def to_dict(self): + return { + 'name' : self.name, + 'label' : self.label, + 'disabled' : self.disabled, + 'optional' : self.optional, + 'value' : self.value, + 'help' : self.help + } + + +class TextField(BaseField): + """ + A standard text input box. + """ + def to_dict(self): + d = super(TextField, self).to_dict() + d['type'] = 'text' + return d + + +class PasswordField(BaseField): + """ + A password input box. text appears as "******" + """ + def to_dict(self): + d = super(PasswordField, self).to_dict() + d['type'] = 'password' + return d + + +class TextArea(BaseField): + """ + A standard text area box. + """ + def to_dict(self): + d = super(TextArea, self).to_dict() + d['type'] = 'text' + d['area'] = True + return d + + +class CheckboxField(BaseField): + """ + A checkbox (boolean input) + """ + @staticmethod + def is_checked(value): + if value in [True, "True", "true"]: + return True + return False + + def to_dict(self): + d = super(CheckboxField, self).to_dict() + d['type'] = 'boolean' + return d + + +class SelectField(BaseField): + """ + A select field. + """ + + def __init__(self, name, multiple=None, display=None, field_id=None, value=None, selectlist=None, refresh_on_change=False, **kwds): + super(SelectField, self).__init__(name, value, **kwds) + self.field_id = field_id + self.multiple = multiple or False + self.refresh_on_change = refresh_on_change + self.selectlist = selectlist or [] + self.options = list() + if display == "checkboxes": + assert multiple, "Checkbox display only supported for multiple select" + elif display == "radio": + assert not(multiple), "Radio display only supported for single select" + elif display is not None: + raise Exception("Unknown display type: %s" % display) + self.display = display + + def add_option(self, text, value, selected=False): + self.options.append((text, value, selected)) + + def to_dict(self): + d = super(SelectField, self).to_dict() + d['type'] = 'select' + d['display'] = self.display + d['multiple'] = self.multiple + d['data'] = [] + for value in self.selectlist: + d['data'].append({'label': value, 'value': value}) + d['options'] = [{'label': t[0], 'value': t[1]} for t in self.options] + return d + + +class AddressField(BaseField): + @staticmethod + def fields(): + return [("desc", "Short address description", "Required"), + ("name", "Name", ""), + ("institution", "Institution", ""), + ("address", "Address", ""), + ("city", "City", ""), + ("state", "State/Province/Region", ""), + ("postal_code", "Postal Code", ""), + ("country", "Country", ""), + ("phone", "Phone", "")] + + def __init__(self, name, user=None, value=None, security=None, **kwds): + super(AddressField, self).__init__(name, value, **kwds) + self.user = user + self.security = security + + def to_dict(self): + d = super(AddressField, self).to_dict() + d['type'] = 'select' + d['data'] = [] + if self.user and self.security: + for a in self.user.addresses: + if not a.deleted: + d['data'].append({'label': a.desc, 'value': self.security.encode_id(a.id)}) + return d + + +class WorkflowField(BaseField): + def __init__(self, name, user=None, value=None, security=None, **kwds): + super(WorkflowField, self).__init__(name, value, **kwds) + self.user = user + self.value = value + self.security = security + + def to_dict(self): + d = super(WorkflowField, self).to_dict() + d['type'] = 'select' + d['data'] = [] + if self.user and self.security: + for a in self.user.stored_workflows: + if not a.deleted: + d['data'].append({'label': a.name, 'value': self.security.encode_id(a.id)}) + return d + + +class WorkflowMappingField(BaseField): + def __init__(self, name, user=None, value=None, **kwds): + super(WorkflowMappingField, self).__init__(name, value, **kwds) + self.user = user + + +class HistoryField(BaseField): + def __init__(self, name, user=None, value=None, security=None, **kwds): + super(HistoryField, self).__init__(name, value, **kwds) + self.user = user + self.value = value + self.security = security + + def to_dict(self): + d = super(HistoryField, self).to_dict() + d['type'] = 'select' + d['data'] = [{'label': 'New History', 'value': 'new'}] + if self.user and self.security: + for a in self.user.histories: + if not a.deleted: + d['data'].append({'label': a.name, 'value': self.security.encode_id(a.id)}) + return d + + +def get_suite(): + """Get unittest suite for this module""" + import doctest + import sys + return doctest.DocTestSuite(sys.modules[__name__]) diff --git a/lib/galaxy/util/hash_util.py b/lib/galaxy/util/hash_util.py index d9e4e6978e8..9867b1b8cf9 100644 --- a/lib/galaxy/util/hash_util.py +++ b/lib/galaxy/util/hash_util.py @@ -17,11 +17,24 @@ BLOCK_SIZE = 1024 * 1024 sha1 = hashlib.sha1 sha256 = hashlib.sha256 +sha512 = hashlib.sha512 sha = sha1 md5 = hashlib.md5 +HASH_NAME_MAP = { + "MD5": md5, + "SHA-1": sha1, + "SHA-256": sha256, + "SHA-512": sha512, +} +HASH_NAMES = list(HASH_NAME_MAP.keys()) + + +def memory_bound_hexdigest(hash_func=None, hash_func_name=None, path=None, file=None): + if hash_func is None: + assert hash_func_name is not None + hash_func = HASH_NAME_MAP[hash_func_name] -def memory_bound_hexdigest(hash_func, path=None, file=None): hasher = hash_func() if file is None: assert path is not None @@ -31,7 +44,7 @@ def memory_bound_hexdigest(hash_func, path=None, file=None): try: for block in iter(lambda: file.read(BLOCK_SIZE), b''): - hasher.update(block) + hasher.update(block) return hasher.hexdigest() finally: file.close() diff --git a/lib/galaxy/util/monitors.py b/lib/galaxy/util/monitors.py index 0a8834d65cf..04a1f76359a 100644 --- a/lib/galaxy/util/monitors.py +++ b/lib/galaxy/util/monitors.py @@ -16,7 +16,6 @@ class Monitors(object): def _init_monitor_thread(self, name, target_name=None, target=None, start=False, config=None): self.monitor_join_sleep = getattr(config, "monitor_thread_join_timeout", DEFAULT_MONITOR_THREAD_JOIN_TIMEOUT) self.monitor_join = self.monitor_join_sleep > 0 - self.monitor_sleeper = Sleeper() self.monitor_running = True if target is not None: @@ -31,6 +30,10 @@ class Monitors(object): self._start = start register_postfork_function(self.start_monitoring) + def _init_noop_monitor(self): + self.sleeper = None + self.monitor_join = False + def start_monitoring(self): if self._start: self.monitor_thread.start() @@ -43,7 +46,8 @@ class Monitors(object): def shutdown_monitor(self): self.stop_monitoring() - self.sleeper.wake() + if self.sleeper is not None: + self.sleeper.wake() if self.monitor_join: log.debug("Joining monitor thread") self.monitor_thread.join(self.monitor_join_sleep) diff --git a/lib/galaxy/web/base/controller.py b/lib/galaxy/web/base/controller.py index 237858b2877..5dcddd403f2 100644 --- a/lib/galaxy/web/base/controller.py +++ b/lib/galaxy/web/base/controller.py @@ -24,7 +24,6 @@ from galaxy.managers import ( api_keys, base as managers_base, configuration, - tags, users, workflows ) @@ -32,7 +31,8 @@ from galaxy.model import ( ExtendedMetadata, ExtendedMetadataIndex, HistoryDatasetAssociation, - LibraryDatasetDatasetAssociation + LibraryDatasetDatasetAssociation, + tags, ) from galaxy.model.item_attrs import UsesAnnotations from galaxy.util.dictifiable import Dictifiable @@ -259,15 +259,28 @@ class JSAppLauncher(BaseUIController): self.config_serializer = configuration.ConfigSerializer(app) self.admin_config_serializer = configuration.AdminConfigSerializer(app) + def _check_require_login(self, trans): + if self.app.config.require_login and self.user_manager.is_anonymous(trans.user): + # TODO: this doesn't properly redirect when login is done + # (see webapp __ensure_logged_in_user for the initial redirect - not sure why it doesn't redirect to login?) + login_url = web.url_for(controller="root", action="login") + trans.response.send_redirect(login_url) + @web.expose def client(self, trans, **kwd): """ - Endpoint for clientside routes. Currently a passthrough to index - (minus kwargs) though we can differentiate it more in the future. + Endpoint for clientside routes. This ships the primary SPA client. + Should not be used with url_for -- see (https://github.com/galaxyproject/galaxy/issues/1878) for why. """ - return self.index(trans) + self._check_require_login(trans) + return self._bootstrapped_client(trans, **kwd) + + def _bootstrapped_client(self, trans, app_name='analysis', **kwd): + js_options = self._get_js_options(trans) + js_options['config'].update(self._get_extended_config(trans)) + return self.template(trans, app_name, options=js_options, **kwd) def _get_js_options(self, trans, root=None): """ @@ -286,6 +299,30 @@ class JSAppLauncher(BaseUIController): } return js_options + def _get_extended_config(self, trans): + config = { + 'active_view' : 'analysis', + 'enable_cloud_launch' : trans.app.config.get_bool('enable_cloud_launch', False), + 'enable_webhooks' : True if trans.app.webhooks_registry.webhooks else False, + # TODO: next two should be redundant - why can't we build one from the other? + 'toolbox' : trans.app.toolbox.to_dict(trans, in_panel=False), + 'toolbox_in_panel' : trans.app.toolbox.to_dict(trans), + 'message_box_visible' : trans.app.config.message_box_visible, + 'show_inactivity_warning' : trans.app.config.user_activation_on and trans.user and not trans.user.active + } + + # TODO: move to user + stored_workflow_menu_entries = config['stored_workflow_menu_entries'] = [] + for menu_item in getattr(trans.user, 'stored_workflow_menu_entries', []): + stored_workflow_menu_entries.append({ + 'encoded_stored_workflow_id': trans.security.encode_id(menu_item.stored_workflow_id), + 'stored_workflow': { + 'name': util.unicodify(menu_item.stored_workflow.name) + } + }) + + return config + def _get_site_configuration(self, trans): """ Return a dictionary representing Galaxy's current configuration. @@ -1435,10 +1472,10 @@ class SharableMixin(object): try: trans.app.security_agent.make_dataset_public(hda.dataset) except Exception: - log.warning("Unable to make dataset with id: %s public.").format(dataset.id) + log.warning("Unable to make dataset with id: %s public", dataset.id) skipped = True else: - log.warning("User without permissions tried to make dataset with id: %s public.").format(dataset.id) + log.warning("User without permissions tried to make dataset with id: %s public", dataset.id) skipped = True return item, skipped @@ -1514,8 +1551,8 @@ class UsesTagsMixin(SharableItemSecurityMixin): return self.get_tag_handler(trans)._get_item_tag_assoc(user, tagged_item, tag_name) def set_tags_from_list(self, trans, item, new_tags_list, user=None): - tags_manager = tags.GalaxyTagManager(trans.app.model.context) - return tags_manager.set_tags_from_list(user, item, new_tags_list) + tag_handler = tags.GalaxyTagHandler(trans.app.model.context) + return tag_handler.set_tags_from_list(user, item, new_tags_list) def get_user_tags_used(self, trans, user=None): """ diff --git a/lib/galaxy/web/form_builder.py b/lib/galaxy/web/form_builder.py index e9949ebea37..7f71d8201b7 100644 --- a/lib/galaxy/web/form_builder.py +++ b/lib/galaxy/web/form_builder.py @@ -1,195 +1,25 @@ -""" -Classes for generating HTML forms -""" -from __future__ import print_function - -import logging - -from galaxy.util import ( - asbool +from galaxy.util.form_builder import ( + AddressField, + BaseField, + CheckboxField, + HistoryField, + PasswordField, + SelectField, + TextArea, + TextField, + WorkflowField, + WorkflowMappingField, ) -log = logging.getLogger(__name__) - - -class BaseField(object): - def __init__(self, name, value=None, label=None, **kwds): - self.name = name - self.label = label - self.value = value - self.disabled = kwds.get('disabled', False) - if 'optional' in kwds: - self.optional = asbool(kwds.get('optional')) - else: - self.optional = kwds.get('required', 'optional') == 'optional' - self.help = kwds.get('helptext') - - def to_dict(self): - return { - 'name' : self.name, - 'label' : self.label, - 'disabled' : self.disabled, - 'optional' : self.optional, - 'value' : self.value, - 'help' : self.help - } - - -class TextField(BaseField): - """ - A standard text input box. - """ - def to_dict(self): - d = super(TextField, self).to_dict() - d['type'] = 'text' - return d - - -class PasswordField(BaseField): - """ - A password input box. text appears as "******" - """ - def to_dict(self): - d = super(PasswordField, self).to_dict() - d['type'] = 'password' - return d - - -class TextArea(BaseField): - """ - A standard text area box. - """ - def to_dict(self): - d = super(TextArea, self).to_dict() - d['type'] = 'text' - d['area'] = True - return d - - -class CheckboxField(BaseField): - """ - A checkbox (boolean input) - """ - @staticmethod - def is_checked(value): - if value in [True, "True", "true"]: - return True - return False - - def to_dict(self): - d = super(CheckboxField, self).to_dict() - d['type'] = 'boolean' - return d - - -class SelectField(BaseField): - """ - A select field. - """ - - def __init__(self, name, multiple=None, display=None, field_id=None, value=None, selectlist=None, refresh_on_change=False, **kwds): - super(SelectField, self).__init__(name, value, **kwds) - self.field_id = field_id - self.multiple = multiple or False - self.refresh_on_change = refresh_on_change - self.selectlist = selectlist or [] - self.options = list() - if display == "checkboxes": - assert multiple, "Checkbox display only supported for multiple select" - elif display == "radio": - assert not(multiple), "Radio display only supported for single select" - elif display is not None: - raise Exception("Unknown display type: %s" % display) - self.display = display - - def add_option(self, text, value, selected=False): - self.options.append((text, value, selected)) - - def to_dict(self): - d = super(SelectField, self).to_dict() - d['type'] = 'select' - d['display'] = self.display - d['multiple'] = self.multiple - d['data'] = [] - for value in self.selectlist: - d['data'].append({'label': value, 'value': value}) - d['options'] = [{'label': t[0], 'value': t[1]} for t in self.options] - return d - - -class AddressField(BaseField): - @staticmethod - def fields(): - return [("desc", "Short address description", "Required"), - ("name", "Name", ""), - ("institution", "Institution", ""), - ("address", "Address", ""), - ("city", "City", ""), - ("state", "State/Province/Region", ""), - ("postal_code", "Postal Code", ""), - ("country", "Country", ""), - ("phone", "Phone", "")] - - def __init__(self, name, user=None, value=None, security=None, **kwds): - super(AddressField, self).__init__(name, value, **kwds) - self.user = user - self.security = security - - def to_dict(self): - d = super(AddressField, self).to_dict() - d['type'] = 'select' - d['data'] = [] - if self.user and self.security: - for a in self.user.addresses: - if not a.deleted: - d['data'].append({'label': a.desc, 'value': self.security.encode_id(a.id)}) - return d - - -class WorkflowField(BaseField): - def __init__(self, name, user=None, value=None, security=None, **kwds): - super(WorkflowField, self).__init__(name, value, **kwds) - self.user = user - self.value = value - self.security = security - - def to_dict(self): - d = super(WorkflowField, self).to_dict() - d['type'] = 'select' - d['data'] = [] - if self.user and self.security: - for a in self.user.stored_workflows: - if not a.deleted: - d['data'].append({'label': a.name, 'value': self.security.encode_id(a.id)}) - return d - - -class WorkflowMappingField(BaseField): - def __init__(self, name, user=None, value=None, **kwds): - super(WorkflowMappingField, self).__init__(name, value, **kwds) - self.user = user - - -class HistoryField(BaseField): - def __init__(self, name, user=None, value=None, security=None, **kwds): - super(HistoryField, self).__init__(name, value, **kwds) - self.user = user - self.value = value - self.security = security - - def to_dict(self): - d = super(HistoryField, self).to_dict() - d['type'] = 'select' - d['data'] = [{'label': 'New History', 'value': 'new'}] - if self.user and self.security: - for a in self.user.histories: - if not a.deleted: - d['data'].append({'label': a.name, 'value': self.security.encode_id(a.id)}) - return d - - -def get_suite(): - """Get unittest suite for this module""" - import doctest - import sys - return doctest.DocTestSuite(sys.modules[__name__]) +__all__ = ( + "AddressField", + "BaseField", + "CheckboxField", + "HistoryField", + "PasswordField", + "SelectField", + "TextArea", + "TextField", + "WorkflowField", + "WorkflowMappingField", +) diff --git a/lib/galaxy/web/framework/__init__.py b/lib/galaxy/web/framework/__init__.py index 4ea0d0b355d..b4dc9cec0ab 100644 --- a/lib/galaxy/web/framework/__init__.py +++ b/lib/galaxy/web/framework/__init__.py @@ -3,4 +3,5 @@ Galaxy web application framework """ from . import base + url_for = base.routes.url_for diff --git a/lib/galaxy/web/framework/base.py b/lib/galaxy/web/framework/base.py index 7f08994f20c..af3578c0996 100644 --- a/lib/galaxy/web/framework/base.py +++ b/lib/galaxy/web/framework/base.py @@ -433,7 +433,7 @@ class Response(object): Create a new Response defaulting to HTML content and "200 OK" status """ self.status = "200 OK" - self.headers = HeaderDict({"content-type": "text/html"}) + self.headers = HeaderDict({"content-type": "text/html; charset=UTF-8"}) self.cookies = SimpleCookie() def set_content_type(self, type_): @@ -451,7 +451,7 @@ class Response(object): """ if "\n" in url or "\r" in url: raise webob.exc.HTTPInternalServerError("Invalid redirect URL encountered.") - raise webob.exc.HTTPFound(location=url) + raise webob.exc.HTTPFound(location=url, headers=self.wsgi_headeritems()) def wsgi_headeritems(self): """ diff --git a/lib/galaxy/web/framework/decorators.py b/lib/galaxy/web/framework/decorators.py index 04eb6bb5ef7..e45a30388ac 100644 --- a/lib/galaxy/web/framework/decorators.py +++ b/lib/galaxy/web/framework/decorators.py @@ -17,7 +17,7 @@ from galaxy.web.framework import url_for log = logging.getLogger(__name__) -JSON_CONTENT_TYPE = "application/json" +JSON_CONTENT_TYPE = "application/json; charset=UTF-8" JSONP_CONTENT_TYPE = "application/javascript" JSONP_CALLBACK_KEY = 'callback' diff --git a/lib/galaxy/web/framework/helpers/grids.py b/lib/galaxy/web/framework/helpers/grids.py index 1538ebc3d5d..08f7603b2c4 100644 --- a/lib/galaxy/web/framework/helpers/grids.py +++ b/lib/galaxy/web/framework/helpers/grids.py @@ -6,7 +6,7 @@ from markupsafe import escape from six import string_types, text_type from sqlalchemy.sql.expression import and_, false, func, null, or_, true -from galaxy.model.item_attrs import RuntimeException, UsesAnnotations, UsesItemRatings +from galaxy.model.item_attrs import get_foreign_key, UsesAnnotations, UsesItemRatings from galaxy.util import restore_text, sanitize_text, unicodify from galaxy.util.odict import odict from galaxy.web.framework import decorators, url_for @@ -584,16 +584,6 @@ class CommunityRatingColumn(GridColumn, UsesItemRatings): item_id=trans.security.encode_id(item.id)) def sort(self, trans, query, ascending, column_name=None): - def get_foreign_key(source_class, target_class): - """ Returns foreign key in source class that references target class. """ - target_fk = None - for fk in source_class.table.foreign_keys: - if fk.references(target_class.table): - target_fk = fk - break - if not target_fk: - raise RuntimeException("No foreign key found between objects: %s, %s" % source_class.table, target_class.table) - return target_fk # Get the columns that connect item's table and item's rating association table. item_rating_assoc_class = getattr(trans.model, '%sRatingAssociation' % self.model_class.__name__) foreign_key = get_foreign_key(item_rating_assoc_class, self.model_class) @@ -654,7 +644,7 @@ class CommunityTagsColumn(TextColumn): def get_value(self, trans, grid, item): return trans.fill_template("/tagging_common.mako", tag_type="community", trans=trans, user=trans.get_user(), tagged_item=item, elt_context=self.grid_name, - in_form=True, input_size="20", tag_click_fn="add_tag_to_grid_filter", use_toggle_link=True) + tag_click_fn="add_tag_to_grid_filter", use_toggle_link=True) def filter(self, trans, user, query, column_filter): """ Modify query to filter model_class by tag. Multiple filters are ANDed. """ @@ -690,8 +680,6 @@ class IndividualTagsColumn(CommunityTagsColumn): user=trans.user, tagged_item=item, elt_context=self.grid_name, - in_form=True, - input_size="20", tag_click_fn="add_tag_to_grid_filter", use_toggle_link=True) diff --git a/lib/galaxy/web/framework/webapp.py b/lib/galaxy/web/framework/webapp.py index ae72c429f71..95662602a6d 100644 --- a/lib/galaxy/web/framework/webapp.py +++ b/lib/galaxy/web/framework/webapp.py @@ -899,18 +899,12 @@ class GalaxyWebTransaction(base.DefaultWebTransaction, ) return token - def check_csrf_token(self): - session_csrf_token = self.request.params.get("session_csrf_token", None) - problem = False + def check_csrf_token(self, payload): + session_csrf_token = payload.get("session_csrf_token") if not session_csrf_token: - log.warning("No session_csrf_token set, denying request.") - problem = True + return "No session token set, denying request." elif session_csrf_token != self.session_csrf_token: - log.warning("Wrong session token found, denying request.") - problem = True - - if problem: - return self.show_warn_message("Failed to authorize action.") + return "Wrong session token found, denying request." def fill_template(self, filename, **kwargs): """ diff --git a/lib/galaxy/web/proxy/js/package.json b/lib/galaxy/web/proxy/js/package.json index 46bb56e05a9..e74db4d284f 100644 --- a/lib/galaxy/web/proxy/js/package.json +++ b/lib/galaxy/web/proxy/js/package.json @@ -11,9 +11,9 @@ "url": "https://bitbucket.org/galaxy/galaxy-central" }, "dependencies": { - "commander": "~2.14.1", - "eventemitter3": "0.1.6", - "http-proxy": "1.16.2", - "sqlite3": "3.1.13" + "commander": "~2.19.0", + "eventemitter3": "3.1.0", + "http-proxy": "1.17.0", + "sqlite3": "4.0.4" } } diff --git a/lib/galaxy/web/stack/__init__.py b/lib/galaxy/web/stack/__init__.py index 161957aa5ed..6ea6c4a0aa5 100644 --- a/lib/galaxy/web/stack/__init__.py +++ b/lib/galaxy/web/stack/__init__.py @@ -19,9 +19,9 @@ from six import string_types from galaxy.util import unicodify from galaxy.util.facts import get_facts -from galaxy.util.handlers import HANDLER_ASSIGNMENT_METHODS from galaxy.util.path import has_ext from galaxy.util.properties import nice_config_parser +from .handlers import HANDLER_ASSIGNMENT_METHODS from .message import ApplicationStackMessage, ApplicationStackMessageDispatcher from .transport import ApplicationStackTransport, UWSGIFarmMessageTransport @@ -324,7 +324,7 @@ class UWSGIApplicationStack(MessageApplicationStack): parser = nice_config_parser(config_file) if not parser.has_section(config_section) and parser.has_section('app:main'): kwds['config_section'] = 'app:main' - kwds['config_file'] = config_file + kwds['config_file'] = unicodify(config_file) return kwds @classmethod diff --git a/lib/galaxy/web/stack/database_heartbeat.py b/lib/galaxy/web/stack/database_heartbeat.py new file mode 100644 index 00000000000..483e8a5d0ea --- /dev/null +++ b/lib/galaxy/web/stack/database_heartbeat.py @@ -0,0 +1,56 @@ +import datetime +import threading + +from galaxy.model import WorkerProcess +from galaxy.model.orm.now import now + + +class DatabaseHeartbeat(object): + + def __init__(self, application_stack, heartbeat_interval=60): + self.application_stack = application_stack + self.heartbeat_interval = heartbeat_interval + self.exit = threading.Event() + self.thread = None + self.active = False + + @property + def sa_session(self): + return self.application_stack.app.model.context + + @property + def server_name(self): + # Application stack manipulates server name after forking + return self.application_stack.app.config.server_name + + def start(self): + if not self.active: + self.thread = threading.Thread(target=self.send_database_heartbeat, name="database_heartbeart_%s.thread" % self.server_name) + self.thread.daemon = True + self.active = True + self.thread.start() + + def shutdown(self): + self.active = False + self.exit.set() + if self.thread: + self.thread.join() + + def get_active_processes(self, last_seen_seconds=None): + """Return all processes seen in ``last_seen_seconds`` seconds.""" + if last_seen_seconds is None: + last_seen_seconds = self.heartbeat_interval + seconds_ago = now() - datetime.timedelta(seconds=last_seen_seconds) + return self.sa_session.query(WorkerProcess).filter(WorkerProcess.table.c.update_time > seconds_ago).all() + + def send_database_heartbeat(self): + if self.active: + while not self.exit.isSet(): + worker_process = self.sa_session.query(WorkerProcess).filter_by( + server_name=self.server_name).first() + if not worker_process: + worker_process = WorkerProcess(server_name=self.server_name) + worker_process.update_time = now() + self.sa_session.add(worker_process) + self.sa_session.flush() + self.exit.wait(self.heartbeat_interval) diff --git a/lib/galaxy/util/handlers.py b/lib/galaxy/web/stack/handlers.py similarity index 99% rename from lib/galaxy/util/handlers.py rename to lib/galaxy/web/stack/handlers.py index 6418c43458c..9c6bbce60d5 100644 --- a/lib/galaxy/util/handlers.py +++ b/lib/galaxy/web/stack/handlers.py @@ -12,11 +12,11 @@ from collections import namedtuple from sqlalchemy.orm import object_session -from . import ( +from galaxy.exceptions import HandlerAssignmentError +from galaxy.util import ( ExecutionTimer, listify ) -from ..exceptions import HandlerAssignmentError log = logging.getLogger(__name__) @@ -287,7 +287,7 @@ class ConfiguresHandlers(object): :returns: str -- The assigned handler ID. """ if configured: - return self.handler_assignment_method_methods[HANDLER_ASSIGNMENT_METHODS.DB_PREASSIGN]( + return self._handler_assignment_method_methods[HANDLER_ASSIGNMENT_METHODS.DB_PREASSIGN]( obj, method, configured, **kwargs ) obj.set_handler(self.app.config.server_name) diff --git a/lib/galaxy/webapps/config_manage.py b/lib/galaxy/webapps/config_manage.py index 3bd5dbc3dbe..614a051d898 100644 --- a/lib/galaxy/webapps/config_manage.py +++ b/lib/galaxy/webapps/config_manage.py @@ -111,6 +111,16 @@ UWSGI_OPTIONS = OrderedDict([ 'default': '$uwsgi_module', 'type': 'str', }), + ('#mount', { + 'desc': """Mount the web application (e.g. Galaxy, Reports, etc.) at the given URL prefix. Cannot be used together with 'module:' above.""", + 'default': '/galaxy=galaxy.webapps.galaxy.buildapp:uwsgi_app()', + 'type': 'str', + }), + ('manage-script-name', { + 'desc': """Make uWSGI rewrite PATH_INFO and SCRIPT_NAME according to mount-points. Set this to true if a URL prefix is used.""", + 'default': False, + 'type': 'bool', + }), ('thunder-lock', { 'desc': """It is usually a good idea to set this to ``true`` if processes is greater than 1.""", 'default': False, @@ -276,8 +286,8 @@ OPTION_ACTIONS = { 'trust_ipython_notebook_conversion': _RenameAction("trust_jupyter_notebook_conversion"), 'enable_beta_tool_command_isolation': _DeprecatedAndDroppedAction(), 'single_user': _ProductionUnsafe(True), - 'tool_submission_burst_threads': _ProductionPerformance(), - 'tool_submission_burst_at': _ProductionPerformance(), + 'tool_submission_burst_threads': _DeprecatedAndDroppedAction(), + 'tool_submission_burst_at': _DeprecatedAndDroppedAction(), 'toolform_upgrade': _DeprecatedAndDroppedAction(), } diff --git a/lib/galaxy/webapps/galaxy/api/authenticate.py b/lib/galaxy/webapps/galaxy/api/authenticate.py index ffa420396d8..6e032015369 100644 --- a/lib/galaxy/webapps/galaxy/api/authenticate.py +++ b/lib/galaxy/webapps/galaxy/api/authenticate.py @@ -53,7 +53,7 @@ class AuthenticationController(BaseAPIController): raise exceptions.ObjectNotFound('The user does not exist.') elif len(user) > 1: # DB is inconsistent and we have more users with the same email. - raise exceptions.InconsistentDatabase('An error occured, please contact your administrator.') + raise exceptions.InconsistentDatabase('An error occurred, please contact your administrator.') else: user = user[0] is_valid_user = self.app.auth_manager.check_password(user, password) diff --git a/lib/galaxy/webapps/galaxy/api/cloudauthz.py b/lib/galaxy/webapps/galaxy/api/cloudauthz.py index 13fe5350be6..7467403dc53 100644 --- a/lib/galaxy/webapps/galaxy/api/cloudauthz.py +++ b/lib/galaxy/webapps/galaxy/api/cloudauthz.py @@ -35,6 +35,7 @@ class CloudAuthzController(BaseAPIController): super(CloudAuthzController, self).__init__(app) self.cloudauthz_manager = cloudauthzs.CloudAuthzManager(app) self.cloudauthz_serializer = cloudauthzs.CloudAuthzsSerializer(app) + self.cloudauthz_deserializer = cloudauthzs.CloudAuthzsDeserializer(app) @expose_api def index(self, trans, **kwargs): @@ -68,13 +69,18 @@ class CloudAuthzController(BaseAPIController): :type payload: dict :param payload: A dictionary structure containing the following keys: * provider: the cloud-based resource provider to which this configuration belongs to. + * config: a dictionary containing all the configuration required to request temporary credentials - from the provider. + from the provider. See the following page for details: + https://galaxyproject.org/cloud/authnz/ + * authn_id: the (encoded) ID of a third-party authentication of a user. To have this ID, user must have logged-in to this Galaxy server using third-party identity (e.g., Google), or has associated his/her Galaxy account with a third-party OIDC-based identity. See this page: https://galaxyproject.org/admin/authentication/ + * description: [Optional] a brief description for this configuration. + :param kwargs: empty dict :rtype: dict @@ -146,3 +152,101 @@ class CloudAuthzController(BaseAPIController): log.exception(msg_template.format("exception while creating the new cloudauthz record")) raise InternalServerError('An unexpected error has occurred while responding to the create request of the ' 'cloudauthz API.' + str(e)) + + @expose_api + def delete(self, trans, encoded_authz_id, **kwargs): + """ + * DELETE /api/cloud/authz/{encoded_authz_id} + Deletes the CloudAuthz record with the given ``encoded_authz_id`` from database. + + :type trans: galaxy.web.framework.webapp.GalaxyWebTransaction + :param trans: Galaxy web transaction + + :type encoded_authz_id: string + :param encoded_authz_id: The encoded ID of the CloudAuthz record to be marked deleted. + + :rtype JSON + :return The cloudauthz record marked as deleted, serialized as a JSON object. + """ + + msg_template = "Rejected user `" + str(trans.user.id) + "`'s request to delete cloudauthz config because of {}." + try: + authz_id = self.decode_id(encoded_authz_id) + except Exception: + log.debug(msg_template.format("cannot decode authz_id `" + str(encoded_authz_id) + "`")) + raise MalformedId('Invalid `authz_id`!') + + try: + cloudauthz = trans.app.authnz_manager.try_get_authz_config(trans.sa_session, trans.user.id, authz_id) + trans.sa_session.delete(cloudauthz) + trans.sa_session.flush() + log.debug('Deleted a cloudauthz record with id `{}` for the user id `{}` '.format(authz_id, str(trans.user.id))) + view = self.cloudauthz_serializer.serialize_to_view(cloudauthz, trans=trans, **self._parse_serialization_params(kwargs, 'summary')) + trans.response.status = '200' + return view + except Exception as e: + log.exception(msg_template.format("exception while deleting the cloudauthz record with " + "ID: `{}`.".format(encoded_authz_id))) + raise InternalServerError('An unexpected error has occurred while responding to the DELETE request of the ' + 'cloudauthz API.' + str(e)) + + @expose_api + def update(self, trans, encoded_authz_id, payload, **kwargs): + """ + * PUT /api/cloud/authz/{encoded_authz_id} + Updates the values for the cloudauthz configuration with the given ``encoded_authz_id``. + + With this API only the following attributes of a cloudauthz configuration + can be updated: `authn_id`, `provider`, `config`, `deleted`. + + :type trans: galaxy.web.framework.webapp.GalaxyWebTransaction + :param trans: Galaxy web transaction + + :type encoded_authz_id: string + :param encoded_authz_id: The encoded ID of the CloudAuthz record to be updated. + + :type payload: dict + :param payload: A dictionary structure containing the attributes to modified with their new values. + It can contain any number of the following attributes: + * provider: the cloud-based resource provider + to which this configuration belongs to. + + * authn_id: the (encoded) ID of a third-party authentication of a user. + To have this ID, user must have logged-in to this Galaxy server + using third-party identity (e.g., Google), or has associated + their Galaxy account with a third-party OIDC-based identity. + See this page: https://galaxyproject.org/admin/authentication/ + + Note: A user can associate a cloudauthz record with their own + authentications only. If the given authentication with authn_id + belongs to a different user, Galaxy will throw the + ItemAccessibilityException exception. + + * config: a dictionary containing all the configuration required to + request temporary credentials from the provider. + See the following page for details: + https://galaxyproject.org/cloud/authnz/ + + * deleted: a boolean type marking the specified cloudauthz as (un)deleted. + + """ + + msg_template = "Rejected user `" + str(trans.user.id) + "`'s request to delete cloudauthz config because of {}." + try: + authz_id = self.decode_id(encoded_authz_id) + except Exception: + log.debug(msg_template.format("cannot decode authz_id `" + str(encoded_authz_id) + "`")) + raise MalformedId('Invalid `authz_id`!') + + try: + cloudauthz_to_update = trans.app.authnz_manager.try_get_authz_config(trans.sa_session, trans.user.id, authz_id) + self.cloudauthz_deserializer.deserialize(cloudauthz_to_update, payload, trans=trans) + self.cloudauthz_serializer.serialize_to_view(cloudauthz_to_update, view='summary') + return self.cloudauthz_serializer.serialize_to_view(cloudauthz_to_update, view='summary') + except MalformedId as e: + raise e + except Exception as e: + log.exception(msg_template.format("exception while updating the cloudauthz record with " + "ID: `{}`.".format(encoded_authz_id))) + raise InternalServerError('An unexpected error has occurred while responding to the PUT request of the ' + 'cloudauthz API.' + str(e)) diff --git a/lib/galaxy/webapps/galaxy/api/datasets.py b/lib/galaxy/webapps/galaxy/api/datasets.py index 23173121b18..0a754c17879 100644 --- a/lib/galaxy/webapps/galaxy/api/datasets.py +++ b/lib/galaxy/webapps/galaxy/api/datasets.py @@ -345,9 +345,12 @@ class DatasetsController(BaseAPIController, UsesVisualizationMixin): if raw: if filename and filename != 'index': - file_path = trans.app.object_store.get_filename(hda.dataset, - extra_dir=('dataset_%s_files' % hda.dataset.id), - alt_name=filename) + object_store = trans.app.object_store + store_by = getattr(object_store, "store_by", "id") + dir_name = 'dataset_%s_files' % getattr(hda.dataset, store_by) + file_path = object_store.get_filename(hda.dataset, + extra_dir=dir_name, + alt_name=filename) else: file_path = hda.file_name rval = open(file_path, 'rb') diff --git a/lib/galaxy/webapps/galaxy/api/dynamic_tools.py b/lib/galaxy/webapps/galaxy/api/dynamic_tools.py new file mode 100644 index 00000000000..1882616f992 --- /dev/null +++ b/lib/galaxy/webapps/galaxy/api/dynamic_tools.py @@ -0,0 +1,86 @@ +import logging + +from galaxy import util, web +from galaxy.exceptions import ObjectNotFound +from galaxy.web import _future_expose_api as expose_api +from galaxy.web import _future_expose_api_anonymous_and_sessionless as expose_api_anonymous_and_sessionless +from galaxy.web.base.controller import BaseAPIController + +log = logging.getLogger(__name__) + + +class DynamicToolsController(BaseAPIController): + """ + RESTful controller for interactions with dynamic tools. + + Dynamic tools are tools defined in the database. Use the tools controller + to run these tools and view functional information. + """ + + def __init__(self, app): + super(DynamicToolsController, self).__init__(app) + + @expose_api_anonymous_and_sessionless + def index(self, trans, **kwds): + """ + GET /api/dynamic_tools + + This returns meta-information about the dynamic tool, such as + tool_uuid. To use the tool or view funtional information such as + inputs and outputs, use the standard tools API indexed by the + ID (and optionally version) returned from this endpoint. + """ + manager = self.app.dynamic_tools_manager + return list( + map(lambda t: t.to_dict(), manager.list_tools()) + ) + + @expose_api_anonymous_and_sessionless + def show(self, trans, id, **kwd): + """ + GET /api/dynamic_tools/{encoded_dynamic_tool_id|tool_uuid} + """ + self._get_dynamic_tool(trans, id).to_dict() + + @web.require_admin + @expose_api + def create(self, trans, payload, **kwd): + """ + POST /api/dynamic_tools + + The payload is expected to be a tool definition to dynamically load + into Galaxy's toolbox. + + :type representation: dict + :param representation: a JSON-ified tool description to load + :type uuid: str + :param uuid: the uuid to associate with the tool being created + """ + dynamic_tool = self.app.dynamic_tools_manager.create_tool( + payload + ) + return dynamic_tool.to_dict() + + @expose_api + @web.require_admin + def delete(self, trans, id, **kwd): + """ + DELETE /api/dynamic_tools/{encoded_dynamic_tool_id|tool_uuid} + + Deactivate the specified dynamic tool. Deactivated tools will not + be loaded into the toolbox. + """ + manager = self.app.dynamic_tools_manager + dynamic_tool = self._get_dynamic_tool(trans, id) + updated_dynamic_tool = manager.deactivate(dynamic_tool) + return updated_dynamic_tool.to_dict() + + def _get_dynamic_tool(self, trans, request_id): + manager = self.app.dynamic_tools_manager + if util.is_uuid(request_id): + dynamic_tool = manager.get_tool_by_uuid(request_id) + else: + dynamic_tool = manager.get_tool_by_id(trans.security.decode_id(request_id)) + if dynamic_tool is None: + raise ObjectNotFound() + return dynamic_tool diff --git a/lib/galaxy/webapps/galaxy/api/folder_contents.py b/lib/galaxy/webapps/galaxy/api/folder_contents.py index eb13ce58eeb..f833582ee01 100644 --- a/lib/galaxy/webapps/galaxy/api/folder_contents.py +++ b/lib/galaxy/webapps/galaxy/api/folder_contents.py @@ -109,14 +109,15 @@ class FolderContentsController(BaseAPIController, UsesLibraryMixin, UsesLibraryM nice_size = util.nice_size(int(content_item.library_dataset_dataset_association.get_size())) library_dataset_dict = content_item.to_dict() - + encoded_ldda_id = trans.security.encode_id(content_item.library_dataset_dataset_association.id) return_item.update(dict(file_ext=library_dataset_dict['file_ext'], date_uploaded=library_dataset_dict['date_uploaded'], is_unrestricted=is_unrestricted, is_private=is_private, can_manage=can_manage, state=library_dataset_dict['state'], - file_size=nice_size)) + file_size=nice_size, + ldda_id=encoded_ldda_id)) if content_item.library_dataset_dataset_association.message: return_item.update(dict(message=content_item.library_dataset_dataset_association.message)) diff --git a/lib/galaxy/webapps/galaxy/api/history_contents.py b/lib/galaxy/webapps/galaxy/api/history_contents.py index d875bb6882d..20b20c0cb74 100644 --- a/lib/galaxy/webapps/galaxy/api/history_contents.py +++ b/lib/galaxy/webapps/galaxy/api/history_contents.py @@ -600,15 +600,57 @@ class HistoryContentsController(BaseAPIController, UsesLibraryMixin, UsesLibrary self.hda_manager.update_permissions(trans, hda, **kwd) return self.hda_manager.serialize_dataset_association_roles(trans, hda) + @expose_api_anonymous + def update_batch(self, trans, history_id, payload, **kwd): + """ + update( self, trans, history_id, id, payload, **kwd ) + * PUT /api/histories/{history_id}/contents + + :type history_id: str + :param history_id: encoded id string of the history containing supplied items + :type id: str + :param id: the encoded id of the history to update + :type payload: dict + :param payload: a dictionary containing any or all the + + :rtype: dict + :returns: an error object if an error occurred or a dictionary containing + any values that were different from the original and, therefore, updated + """ + items = payload.get("items") + hda_ids = [] + hdca_ids = [] + for item in items: + contents_type = item["history_content_type"] + if contents_type == "dataset": + decoded_id = self.decode_id(item["id"]) + hda_ids.append(decoded_id) + else: + hdca_ids.append(item["id"]) + + history = self.history_manager.get_owned(self.decode_id(history_id), trans.user, + current_history=trans.history) + hdas = self.__datasets_for_update(trans, history, hda_ids, payload) + rval = [] + for hda in hdas: + self.__deserialize_dataset(hda, payload, trans) + rval.append(self.hda_serializer.serialize_to_view(hda, + user=trans.user, trans=trans, **self._parse_serialization_params(kwd, 'summary'))) + for hdca_id in hdca_ids: + self.__update_dataset_collection(trans, history_id, hdca_id, payload, **kwd) + dataset_collection_instance = self.__get_accessible_collection(trans, hdca_id, history_id) + rval.append(self.__collection_dict(trans, dataset_collection_instance, view="summary")) + return rval + @expose_api_anonymous def update(self, trans, history_id, id, payload, **kwd): """ update( self, trans, history_id, id, payload, **kwd ) * PUT /api/histories/{history_id}/contents/{id} - updates the values for the HDA with the given ``id`` + updates the values for the history content item with the given ``id`` :type history_id: str - :param history_id: encoded id string of the HDA's History + :param history_id: encoded id string of the items's History :type id: str :param id: the encoded id of the history to update :type payload: dict @@ -634,13 +676,20 @@ class HistoryContentsController(BaseAPIController, UsesLibraryMixin, UsesLibrary def __update_dataset(self, trans, history_id, id, payload, **kwd): # anon user: ensure that history ids match up and the history is the current, # check for uploading, and use only the subset of attribute keys manipulatable by anon users + decoded_id = self.decode_id(id) + history = self.history_manager.get_owned(self.decode_id(history_id), trans.user, + current_history=trans.history) + hda = self.__datasets_for_update(trans, history, [decoded_id], payload)[0] + if hda: + self.__deserialize_dataset(hda, payload, trans) + return self.hda_serializer.serialize_to_view(hda, + user=trans.user, trans=trans, **self._parse_serialization_params(kwd, 'detailed')) + + return {} + + def __datasets_for_update(self, trans, history, hda_ids, payload): anonymous_user = not trans.user_is_admin and trans.user is None if anonymous_user: - hda = self.hda_manager.by_id(self.decode_id(id)) - if hda.history != trans.history: - raise exceptions.AuthenticationRequired('API authentication required for this request') - hda = self.hda_manager.error_if_uploading(hda) - anon_allowed_payload = {} if 'deleted' in payload: anon_allowed_payload['deleted'] = payload['deleted'] @@ -648,25 +697,21 @@ class HistoryContentsController(BaseAPIController, UsesLibraryMixin, UsesLibrary anon_allowed_payload['visible'] = payload['visible'] payload = anon_allowed_payload - # logged in user: use full payload, check state if deleting, and make sure the history is theirs - else: - hda = self.hda_manager.get_owned(self.decode_id(id), trans.user, current_history=trans.history, trans=trans) + hdas = self.hda_manager.get_owned_ids(hda_ids, history=history) - # only check_state if not deleting, otherwise cannot delete uploading files - check_state = not payload.get('deleted', False) - if check_state: + # only check_state if not deleting, otherwise cannot delete uploading files + check_state = not payload.get('deleted', False) + if check_state: + for hda in hdas: hda = self.hda_manager.error_if_uploading(hda) - # make the actual changes - if hda: - self.hda_deserializer.deserialize(hda, payload, user=trans.user, trans=trans) - # TODO: this should be an effect of deleting the hda - if payload.get('deleted', False): - self.hda_manager.stop_creating_job(hda) - return self.hda_serializer.serialize_to_view(hda, - user=trans.user, trans=trans, **self._parse_serialization_params(kwd, 'detailed')) + return hdas - return {} + def __deserialize_dataset(self, hda, payload, trans): + self.hda_deserializer.deserialize(hda, payload, user=trans.user, trans=trans) + # TODO: this should be an effect of deleting the hda + if payload.get('deleted', False): + self.hda_manager.stop_creating_job(hda) def __update_dataset_collection(self, trans, history_id, id, payload, **kwd): return trans.app.dataset_collections_service.update(trans, "history", id, payload) @@ -700,8 +745,8 @@ class HistoryContentsController(BaseAPIController, UsesLibraryMixin, UsesLibrary :rtype: dict :returns: an error object if an error occurred or a dictionary containing: * id: the encoded id of the history, - * deleted: if the history was marked as deleted, - * purged: if the history was purged + * deleted: if the history content was marked as deleted, + * purged: if the history content was purged """ contents_type = kwd.get('type', 'dataset') if contents_type == "dataset": diff --git a/lib/galaxy/webapps/galaxy/api/jobs.py b/lib/galaxy/webapps/galaxy/api/jobs.py index db25f4c479c..9254ca9630a 100644 --- a/lib/galaxy/webapps/galaxy/api/jobs.py +++ b/lib/galaxy/webapps/galaxy/api/jobs.py @@ -134,7 +134,17 @@ class JobController(BaseAPIController, UsesLibraryMixinItems): job_dict = self.encode_all_ids(trans, job.to_dict('element', system_details=is_admin), True) full_output = util.asbool(kwd.get('full', 'false')) if full_output: - job_dict.update(dict(stderr=job.stderr, stdout=job.stdout)) + + job_dict.update(dict( + tool_stdout=job.tool_stdout, + tool_stderr=job.tool_stderr, + job_stdout=job.job_stdout, + job_stderr=job.job_stderr, + stderr=job.stderr, + stdout=job.stdout, + job_messages=job.job_messages + )) + if is_admin: if job.user: job_dict['user_email'] = job.user.email diff --git a/lib/galaxy/webapps/galaxy/api/libraries.py b/lib/galaxy/webapps/galaxy/api/libraries.py index 6ad873ef471..3ce60eaaa00 100644 --- a/lib/galaxy/webapps/galaxy/api/libraries.py +++ b/lib/galaxy/webapps/galaxy/api/libraries.py @@ -295,7 +295,7 @@ class LibrariesController(BaseAPIController): elif action == 'remove_restrictions': is_public = self.library_manager.make_public(trans, library) if not is_public: - raise exceptions.InternalServerError('An error occured while making library public.') + raise exceptions.InternalServerError('An error occurred while making library public.') elif action == 'set_permissions': # ACCESS LIBRARY ROLES diff --git a/lib/galaxy/webapps/galaxy/api/library_contents.py b/lib/galaxy/webapps/galaxy/api/library_contents.py index 81c86d15dac..4fb2c7de9d2 100644 --- a/lib/galaxy/webapps/galaxy/api/library_contents.py +++ b/lib/galaxy/webapps/galaxy/api/library_contents.py @@ -230,6 +230,8 @@ class LibraryContentsController(BaseAPIController, UsesLibraryMixin, UsesLibrary # The rest of the security happens in the library_common controller. real_folder_id = trans.security.encode_id(parent.id) + payload['tag_using_filenames'] = util.string_as_bool(payload.get('tag_using_filenames', None)) + # are we copying an HDA to the library folder? # we'll need the id and any message to attach, then branch to that private function from_hda_id, from_hdca_id, ldda_message = (payload.pop('from_hda_id', None), payload.pop('from_hdca_id', None), payload.pop('ldda_message', '')) diff --git a/lib/galaxy/webapps/galaxy/api/library_datasets.py b/lib/galaxy/webapps/galaxy/api/library_datasets.py index 3302b9cdd9e..cbc6352c60f 100644 --- a/lib/galaxy/webapps/galaxy/api/library_datasets.py +++ b/lib/galaxy/webapps/galaxy/api/library_datasets.py @@ -230,7 +230,7 @@ class LibraryDatasetsController(BaseAPIController, UsesVisualizationMixin, Libra if action == 'remove_restrictions': trans.app.security_agent.make_dataset_public(dataset) if not trans.app.security_agent.dataset_is_public(dataset): - raise exceptions.InternalServerError('An error occured while making dataset public.') + raise exceptions.InternalServerError('An error occurred while making dataset public.') elif action == 'make_private': if not trans.app.security_agent.dataset_is_private_to_user(trans, dataset): private_role = trans.app.security_agent.get_private_user_role(trans.user) @@ -239,7 +239,7 @@ class LibraryDatasetsController(BaseAPIController, UsesVisualizationMixin, Libra trans.sa_session.flush() if not trans.app.security_agent.dataset_is_private_to_user(trans, dataset): # Check again and inform the user if dataset is not private. - raise exceptions.InternalServerError('An error occured and the dataset is NOT private.') + raise exceptions.InternalServerError('An error occurred and the dataset is NOT private.') elif action == 'set_permissions': # ACCESS DATASET ROLES valid_access_roles = [] @@ -706,7 +706,7 @@ class LibraryDatasetsController(BaseAPIController, UsesVisualizationMixin, Libra fname = ''.join(c in util.FILENAME_VALID_CHARS and c or '_' for c in fname)[0:150] trans.response.headers["Content-Disposition"] = 'attachment; filename="%s"' % fname try: - return open(dataset.file_name) + return open(dataset.file_name, 'rb') except Exception: raise exceptions.InternalServerError("This dataset contains no content.") else: diff --git a/lib/galaxy/webapps/galaxy/api/tools.py b/lib/galaxy/webapps/galaxy/api/tools.py index 519af7bfb6e..362b71ec446 100644 --- a/lib/galaxy/webapps/galaxy/api/tools.py +++ b/lib/galaxy/webapps/galaxy/api/tools.py @@ -1,6 +1,6 @@ import logging import os -from json import dumps +from json import dumps, loads import galaxy.queue_worker from galaxy import exceptions, managers, util, web @@ -8,7 +8,6 @@ from galaxy.managers.collections_util import dictify_dataset_collection_instance from galaxy.tools import global_tool_errors from galaxy.util.json import safe_dumps from galaxy.util.odict import odict -from galaxy.visualization.genomes import GenomeRegion from galaxy.web import _future_expose_api as expose_api from galaxy.web import _future_expose_api_anonymous as expose_api_anonymous from galaxy.web import _future_expose_api_anonymous_and_sessionless as expose_api_anonymous_and_sessionless @@ -22,6 +21,8 @@ log = logging.getLogger(__name__) # Do not allow these tools to be called directly - they (it) enforces extra security and # provides access via a different API endpoint. PROTECTED_TOOLS = ["__DATA_FETCH__"] +# Tool search bypasses the fulltext for the following list of terms +SEARCH_RESERVED_TERMS_FAVORITES = ['#favs', '#favorites', '#favourites'] class ToolsController(BaseAPIController, UsesVisualizationMixin): @@ -58,7 +59,14 @@ class ToolsController(BaseAPIController, UsesVisualizationMixin): # Find whether to search. if q: - hits = self._search(q) + if trans.user and q in SEARCH_RESERVED_TERMS_FAVORITES: + if 'favorites' in trans.user.preferences: + favorites = loads(trans.user.preferences['favorites']) + hits = favorites['tools'] + else: + hits = None + else: + hits = self._search(q) results = [] if hits: for hit in hits: @@ -256,7 +264,6 @@ class ToolsController(BaseAPIController, UsesVisualizationMixin): force_rebuild: If true and cache dir exists, attempts to delete cache dir """ tool = self._get_tool(id, user=trans.user) - kwds['install'] = True tool._view.install_dependencies(tool.requirements, **kwds) if kwds.get('build_dependency_cache'): tool.build_dependency_cache(**kwds) @@ -446,26 +453,35 @@ class ToolsController(BaseAPIController, UsesVisualizationMixin): def create(self, trans, payload, **kwd): """ POST /api/tools - Executes tool using specified inputs and returns tool's outputs. """ tool_id = payload.get("tool_id") + tool_uuid = payload.get("tool_uuid") if tool_id in PROTECTED_TOOLS: raise exceptions.RequestParameterInvalidException("Cannot execute tool [%s] directly, must use alternative endpoint." % tool_id) - if tool_id is None: + if tool_id is None and tool_uuid is None: raise exceptions.RequestParameterInvalidException("Must specify a valid tool_id to use this endpoint.") return self._create(trans, payload, **kwd) def _create(self, trans, payload, **kwd): - # HACK: for now, if action is rerun, rerun tool. action = payload.get('action', None) if action == 'rerun': - return self._rerun_tool(trans, payload, **kwd) + raise Exception("'rerun' action has been deprecated") # -- Execute tool. -- # Get tool. tool_version = payload.get('tool_version', None) - tool = trans.app.toolbox.get_tool(payload['tool_id'], tool_version) if 'tool_id' in payload else None + tool_id = payload.get('tool_id', None) + tool_uuid = payload.get('tool_uuid', None) + get_kwds = dict( + tool_id=tool_id, + tool_uuid=tool_uuid, + tool_version=tool_version, + ) + if tool_id is None and tool_uuid is None: + raise exceptions.RequestParameterMissingException("Must specify either a tool_id or a tool_uuid.") + + tool = trans.app.toolbox.get_tool(**get_kwds) if not tool or not tool.allow_user_access(trans.user): raise exceptions.MessageException('Tool not found or not accessible.') if trans.app.config.user_activation_on: @@ -559,264 +575,3 @@ class ToolsController(BaseAPIController, UsesVisualizationMixin): if not tool.allow_user_access(user): raise exceptions.AuthenticationFailed("Access denied, please login for tool with id '%s'." % id) return tool - - def _rerun_tool(self, trans, payload, **kwargs): - """ - Rerun a tool to produce a new output dataset that corresponds to a - dataset that a user is currently viewing. - """ - - # - # TODO: refactor to use same code as run_tool. - # - - # Run tool on region if region is specificied. - run_on_regions = False - regions = payload.get('regions', None) - if regions: - if isinstance(regions, dict): - # Regions is a single region. - regions = [GenomeRegion.from_dict(regions)] - elif isinstance(regions, list): - # There is a list of regions. - regions = [GenomeRegion.from_dict(r) for r in regions] - - if len(regions) > 1: - # Sort by chrom name, start so that data is not fetched out of order. - regions = sorted(regions, key=lambda r: (r.chrom.lower(), r.start)) - - # Merge overlapping regions so that regions do not overlap - # and hence data is not included multiple times. - prev = regions[0] - cur = regions[1] - index = 1 - while True: - if cur.chrom == prev.chrom and cur.start <= prev.end: - # Found overlapping regions, so join them into prev. - prev.end = cur.end - del regions[index] - else: - # No overlap, move to next region. - prev = cur - index += 1 - - # Get next region or exit. - if index == len(regions): - # Done. - break - else: - cur = regions[index] - - run_on_regions = True - - # Dataset check. - decoded_dataset_id = self.decode_id(payload.get('target_dataset_id')) - original_dataset = self.hda_manager.get_accessible(decoded_dataset_id, user=trans.user) - original_dataset = self.hda_manager.error_if_uploading(original_dataset) - msg = self.hda_manager.data_conversion_status(original_dataset) - if msg: - return msg - - # Set tool parameters--except non-hidden dataset parameters--using combination of - # job's previous parameters and incoming parameters. Incoming parameters - # have priority. - # - original_job = self.hda_manager.creating_job(original_dataset) - tool = trans.app.toolbox.get_tool(original_job.tool_id) - if not tool or not tool.allow_user_access(trans.user): - return trans.app.model.Dataset.conversion_messages.NO_TOOL - tool_params = dict([(p.name, p.value) for p in original_job.parameters]) - - # TODO: rather than set new inputs using dict of json'ed value, unpack parameters and set using set_param_value below. - # TODO: need to handle updates to conditional parameters; conditional - # params are stored in dicts (and dicts within dicts). - new_inputs = payload['inputs'] - tool_params.update(dict([(key, dumps(value)) for key, value in new_inputs.items() if key in tool.inputs and value is not None])) - tool_params = tool.params_from_strings(tool_params, self.app) - - # - # If running tool on region, convert input datasets (create indices) so - # that can regions of data can be quickly extracted. - # - data_provider_registry = trans.app.data_provider_registry - messages_list = [] - if run_on_regions: - for jida in original_job.input_datasets: - input_dataset = jida.dataset - data_provider = data_provider_registry.get_data_provider(trans, original_dataset=input_dataset, source='data') - if data_provider and (not data_provider.converted_dataset or - data_provider.converted_dataset.state != trans.app.model.Dataset.states.OK): - # Can convert but no converted dataset yet, so return message about why. - data_sources = input_dataset.datatype.data_sources - msg = input_dataset.convert_dataset(trans, data_sources['data']) - if msg is not None: - messages_list.append(msg) - - # Return any messages generated during conversions. - return_message = self._get_highest_priority_msg(messages_list) - if return_message: - return return_message - - # - # Set target history (the history that tool will use for inputs/outputs). - # If user owns dataset, put new data in original dataset's history; if - # user does not own dataset (and hence is accessing dataset via sharing), - # put new data in user's current history. - # - if original_dataset.history.user == trans.user: - target_history = original_dataset.history - else: - target_history = trans.get_history(create=True) - hda_permissions = trans.app.security_agent.history_get_default_permissions(target_history) - - def set_param_value(param_dict, param_name, param_value): - """ - Set new parameter value in a tool's parameter dictionary. - """ - - # Recursive function to set param value. - def set_value(param_dict, group_name, group_index, param_name, param_value): - if group_name in param_dict: - param_dict[group_name][group_index][param_name] = param_value - return True - elif param_name in param_dict: - param_dict[param_name] = param_value - return True - else: - # Recursive search. - return_val = False - for value in param_dict.values(): - if isinstance(value, dict): - return_val = set_value(value, group_name, group_index, param_name, param_value) - if return_val: - return return_val - return False - - # Parse parameter name if necessary. - if param_name.find("|") == -1: - # Non-grouping parameter. - group_name = group_index = None - else: - # Grouping parameter. - group, param_name = param_name.split("|") - index = group.rfind("_") - group_name = group[:index] - group_index = int(group[index + 1:]) - - return set_value(param_dict, group_name, group_index, param_name, param_value) - - # Set parameters based tool's trackster config. - params_set = {} - for action in tool.trackster_conf.actions: - success = False - for joda in original_job.output_datasets: - if joda.name == action.output_name: - set_param_value(tool_params, action.name, joda.dataset) - params_set[action.name] = True - success = True - break - - if not success: - return trans.app.model.Dataset.conversion_messages.ERROR - - # - # Set input datasets for tool. If running on regions, extract and use subset - # when possible. - # - if run_on_regions: - regions_str = ",".join([str(r) for r in regions]) - for jida in original_job.input_datasets: - # If param set previously by config actions, do nothing. - if jida.name in params_set: - continue - - input_dataset = jida.dataset - if input_dataset is None: # optional dataset and dataset wasn't selected - tool_params[jida.name] = None - elif run_on_regions and 'data' in input_dataset.datatype.data_sources: - # Dataset is indexed and hence a subset can be extracted and used - # as input. - - # Look for subset. - subset_dataset_association = trans.sa_session.query(trans.app.model.HistoryDatasetAssociationSubset) \ - .filter_by(hda=input_dataset, location=regions_str) \ - .first() - if subset_dataset_association: - # Data subset exists. - subset_dataset = subset_dataset_association.subset - else: - # Need to create subset. - data_source = input_dataset.datatype.data_sources['data'] - input_dataset.get_converted_dataset(trans, data_source) - input_dataset.get_converted_dataset_deps(trans, data_source) - - # Create new HDA for input dataset's subset. - new_dataset = trans.app.model.HistoryDatasetAssociation(extension=input_dataset.ext, - dbkey=input_dataset.dbkey, - create_dataset=True, - sa_session=trans.sa_session, - name="Subset [%s] of data %i" % - (regions_str, input_dataset.hid), - visible=False) - target_history.add_dataset(new_dataset) - trans.sa_session.add(new_dataset) - trans.app.security_agent.set_all_dataset_permissions(new_dataset.dataset, hda_permissions) - - # Write subset of data to new dataset - data_provider = data_provider_registry.get_data_provider(trans, original_dataset=input_dataset, source='data') - trans.app.object_store.create(new_dataset.dataset) - data_provider.write_data_to_file(regions, new_dataset.file_name) - - # TODO: (a) size not working; (b) need to set peek. - new_dataset.set_size() - new_dataset.info = "Data subset for trackster" - new_dataset.set_dataset_state(trans.app.model.Dataset.states.OK) - - # Set metadata. - # TODO: set meta internally if dataset is small enough? - trans.app.datatypes_registry.set_external_metadata_tool.tool_action.execute(trans.app.datatypes_registry.set_external_metadata_tool, - trans, incoming={'input1': new_dataset}, - overwrite=False, job_params={"source": "trackster"}) - # Add HDA subset association. - subset_association = trans.app.model.HistoryDatasetAssociationSubset(hda=input_dataset, subset=new_dataset, location=regions_str) - trans.sa_session.add(subset_association) - - subset_dataset = new_dataset - - trans.sa_session.flush() - - # Add dataset to tool's parameters. - if not set_param_value(tool_params, jida.name, subset_dataset): - return {"error": True, "message": "error setting parameter %s" % jida.name} - - # - # Execute tool and handle outputs. - # - try: - subset_job, subset_job_outputs = tool.execute(trans, incoming=tool_params, - history=target_history, - job_params={"source": "trackster"}) - except Exception as e: - # Lots of things can go wrong when trying to execute tool. - return {"error": True, "message": e.__class__.__name__ + ": " + str(e)} - if run_on_regions: - for output in subset_job_outputs.values(): - output.visible = False - trans.sa_session.flush() - - # - # Return new track that corresponds to the original dataset. - # - output_name = None - for joda in original_job.output_datasets: - if joda.dataset == original_dataset: - output_name = joda.name - break - for joda in subset_job.output_datasets: - if joda.name == output_name: - output_dataset = joda.dataset - - dataset_dict = output_dataset.to_dict() - dataset_dict['id'] = trans.security.encode_id(dataset_dict['id']) - dataset_dict['track_config'] = self.get_new_track_config(trans, output_dataset) - return dataset_dict diff --git a/lib/galaxy/webapps/galaxy/api/toolshed.py b/lib/galaxy/webapps/galaxy/api/toolshed.py index 1710ea9b2cb..7bf669c3b99 100644 --- a/lib/galaxy/webapps/galaxy/api/toolshed.py +++ b/lib/galaxy/webapps/galaxy/api/toolshed.py @@ -187,10 +187,16 @@ class ToolShedController(BaseAPIController): :param tool_shed_url: the url of the toolshed to get repositories from :param category_id: the category to get repositories from + :param sort_key: the field by which the repositories should be sorted + :param sort_order: ascending or descending sort + :param page: the page number to return """ + sort_order = kwd.get('sort_order', 'asc') + sort_key = kwd.get('sort_key', 'name') + page = kwd.get('page', 1) tool_shed_url = urlunquote(kwd.get('tool_shed_url', '')) category_id = kwd.get('category_id', '') - params = dict(installable=True) + params = dict(installable=True, sort_order=sort_order, sort_key=sort_key, page=page) tool_shed_url = common_util.get_tool_shed_url_from_tool_shed_registry(trans.app, tool_shed_url) url = util.build_url(tool_shed_url, pathspec=['api', 'categories', category_id, 'repositories'], params=params) repositories = [] @@ -241,11 +247,11 @@ class ToolShedController(BaseAPIController): if len(tool_shed_url) == 0: # By design, this list should always be from the same toolshed. If # this is ever not the case, this code will need to be updated. - tool_shed_url = common_util.get_tool_shed_url_from_tool_shed_registry(self.app, tool_ids[0].split('/')[0]) + tool_shed_url = common_util.get_tool_shed_url_from_tool_shed_registry(self.app, tool_ids[0].split('/repos')[0]) found_repository = json.loads(util.url_get(tool_shed_url, params=dict(tool_ids=','.join(tool_ids)), pathspec=['api', 'repositories'])) - fr_first_key = next(iter(found_repository.keys())) - repository_id = found_repository[fr_first_key]['repository_id'] - repository_data['current_changeset'] = found_repository['current_changeset'] + current_changeset = found_repository['current_changeset'] + repository_id = found_repository[current_changeset]['repository_id'] + repository_data['current_changeset'] = current_changeset repository_data['repository'] = json.loads(util.url_get(tool_shed_url, pathspec=['api', 'repositories', repository_id])) del found_repository['current_changeset'] repository_data['tool_shed_url'] = tool_shed_url diff --git a/lib/galaxy/webapps/galaxy/api/users.py b/lib/galaxy/webapps/galaxy/api/users.py index c78b369020c..2c9e058fa8d 100644 --- a/lib/galaxy/webapps/galaxy/api/users.py +++ b/lib/galaxy/webapps/galaxy/api/users.py @@ -6,7 +6,6 @@ import logging import re import six -import yaml from markupsafe import escape from sqlalchemy import ( false, @@ -19,10 +18,7 @@ from galaxy import ( util, web ) -from galaxy.exceptions import ( - MessageException, - ObjectInvalid -) +from galaxy.exceptions import ObjectInvalid from galaxy.managers import users from galaxy.security.validate_user_input import ( validate_email, @@ -272,15 +268,7 @@ class UserAPIController(BaseAPIController, UsesTagsMixin, CreatesApiKeysMixin, B Reads the file user_preferences_extra_conf.yml to display admin defined user informations """ - path = trans.app.config.user_preferences_extra_config_file - try: - with open(path, 'r') as stream: - config = yaml.safe_load(stream) - except Exception: - log.warning('Config file (%s) could not be found or is malformed.' % path) - return {} - - return config['preferences'] if config else {} + return trans.app.config.user_preferences_extra['preferences'] def _build_extra_user_pref_inputs(self, preferences, user): """ @@ -408,7 +396,7 @@ class UserAPIController(BaseAPIController, UsesTagsMixin, CreatesApiKeysMixin, B email = payload.get('email') message = self._validate_email(email) or validate_email(trans, email, user) if message: - raise MessageException(message) + raise exceptions.RequestParameterInvalidException(message) if user.email != email: # Update user email and user's private role name which must match private_role = trans.app.security_agent.get_private_user_role(user) @@ -427,13 +415,13 @@ class UserAPIController(BaseAPIController, UsesTagsMixin, CreatesApiKeysMixin, B message = 'Unable to send activation email, please contact your local Galaxy administrator.' if trans.app.config.error_email_to is not None: message += ' Contact: %s' % trans.app.config.error_email_to - raise MessageException(message) + raise exceptions.InternalServerError(message) # Update public name if 'username' in payload: username = payload.get('username') message = self._validate_publicname(username) or validate_publicname(trans, username, user) if message: - raise MessageException(message) + raise exceptions.RequestParameterInvalidException(message) if user.username != username: user.username = username # Update user custom form @@ -464,7 +452,7 @@ class UserAPIController(BaseAPIController, UsesTagsMixin, CreatesApiKeysMixin, B section = extra_pref_keys[keys[0]] for input in section['inputs']: if input['name'] == keys[1] and input['required']: - raise MessageException("Please fill the required field") + raise exceptions.ObjectAttributeMissingException("Please fill the required field") extra_user_pref_data[item] = payload[item] user.preferences["extra_user_preferences"] = json.dumps(extra_user_pref_data) @@ -487,13 +475,13 @@ class UserAPIController(BaseAPIController, UsesTagsMixin, CreatesApiKeysMixin, B try: user_address = trans.sa_session.query(trans.app.model.UserAddress).get(trans.security.decode_id(d['id'])) except Exception as e: - raise MessageException('Failed to access user address (%s). %s' % (d['id'], e)) + raise exceptions.ObjectNotFound('Failed to access user address (%s). %s' % (d['id'], e)) else: user_address = trans.model.UserAddress() trans.log_event('User address added') for field in AddressField.fields(): if str(field[2]).lower() == 'required' and not d.get(field[0]): - raise MessageException('Address %s: %s (%s) required.' % (index + 1, field[1], field[0])) + raise exceptions.ObjectAttributeMissingException('Address %s: %s (%s) required.' % (index + 1, field[1], field[0])) setattr(user_address, field[0], str(d.get(field[0], ''))) user_address.user = user user.addresses.append(user_address) @@ -503,6 +491,72 @@ class UserAPIController(BaseAPIController, UsesTagsMixin, CreatesApiKeysMixin, B trans.log_event('User information added') return {'message': 'User information has been saved.'} + @expose_api + def set_favorite(self, trans, id, object_type, payload={}, **kwd): + """Add the object to user's favorites + PUT /api/users/{id}/favorites/{object_type} + + :param id: the encoded id of the user + :type id: str + :param object_type: the object type that users wants to favorite + :type object_type: str + :param object_id: the id of an object that users wants to favorite + :type object_id: str + """ + self._validate_favorite_object_type(object_type) + user = self._get_user(trans, id) + favorites = json.loads(user.preferences['favorites']) if 'favorites' in user.preferences else {} + if object_type == 'tools': + tool_id = payload.get('object_id') + tool = self.app.toolbox.get_tool(tool_id) + if not tool: + raise exceptions.ObjectNotFound("Could not find tool with id '%s'." % tool_id) + if not tool.allow_user_access(user): + raise exceptions.AuthenticationFailed("Access denied for tool with id '%s'." % tool_id) + if 'tools' in favorites: + favorite_tools = favorites['tools'] + else: + favorite_tools = [] + if tool_id not in favorite_tools: + favorite_tools.append(tool_id) + favorites['tools'] = favorite_tools + user.preferences['favorites'] = json.dumps(favorites) + trans.sa_session.flush() + return favorites + + @expose_api + def remove_favorite(self, trans, id, object_type, object_id, payload={}, **kwd): + """Remove the object from user's favorites + DELETE /api/users/{id}/favorites/{object_type}/{object_id:.*?} + + :param id: the encoded id of the user + :type id: str + :param object_type: the object type that users wants to favorite + :type object_type: str + :param object_id: the id of an object that users wants to remove from favorites + :type object_id: str + """ + self._validate_favorite_object_type(object_type) + user = self._get_user(trans, id) + favorites = json.loads(user.preferences['favorites']) if 'favorites' in user.preferences else {} + if object_type == 'tools': + if 'tools' in favorites: + favorite_tools = favorites['tools'] + if object_id in favorite_tools: + del favorite_tools[favorite_tools.index(object_id)] + favorites['tools'] = favorite_tools + user.preferences['favorites'] = json.dumps(favorites) + trans.sa_session.flush() + else: + raise exceptions.ObjectNotFound('Given object is not in the list of favorites') + return favorites + + def _validate_favorite_object_type(self, object_type): + if object_type in ['tools']: + pass + else: + raise exceptions.ObjectAttributeInvalidException("This type is not supported. Given object_type: %s" % object_type) + def _validate_email(self, email): ''' Validate email and username using regex ''' if email == '' or not isinstance(email, six.string_types): @@ -532,7 +586,7 @@ class UserAPIController(BaseAPIController, UsesTagsMixin, CreatesApiKeysMixin, B """ user, message = self.user_manager.change_password(trans, id=id, **payload) if user is None: - raise MessageException(message) + raise exceptions.AuthenticationRequired(message) return {"message": "Password has been changed."} @expose_api @@ -752,11 +806,11 @@ class UserAPIController(BaseAPIController, UsesTagsMixin, CreatesApiKeysMixin, B len_type = payload.get('len|type') len_value = payload.get('len|value') if len_type not in ['file', 'fasta', 'text'] or not len_value: - raise MessageException('Please specify a valid data source type.') + raise exceptions.RequestParameterInvalidException('Please specify a valid data source type.') if not name or not key: - raise MessageException('You must specify values for all the fields.') + raise exceptions.RequestParameterMissingException('You must specify values for all the fields.') elif key in dbkeys: - raise MessageException('There is already a custom build with that key. Delete it first if you want to replace it.') + raise exceptions.DuplicatedIdentifierException('There is already a custom build with that key. Delete it first if you want to replace it.') else: # Have everything needed; create new build. build_dict = {'name': name} @@ -771,7 +825,7 @@ class UserAPIController(BaseAPIController, UsesTagsMixin, CreatesApiKeysMixin, B try: trans.app.object_store.create(new_len.dataset) except ObjectInvalid: - raise MessageException('Unable to create output dataset: object store is full.') + raise exceptions.InternalServerError('Unable to create output dataset: object store is full.') trans.sa_session.flush() counter = 0 lines_skipped = 0 @@ -808,7 +862,7 @@ class UserAPIController(BaseAPIController, UsesTagsMixin, CreatesApiKeysMixin, B build_dict['len'] = new_len.id build_dict['linecount'] = new_linecount.id except Exception: - raise MessageException('Failed to convert dataset.') + raise exceptions.ToolExecutionError('Failed to convert dataset.') dbkeys[key] = build_dict user.preferences['dbkeys'] = json.dumps(dbkeys) trans.sa_session.flush() @@ -834,12 +888,12 @@ class UserAPIController(BaseAPIController, UsesTagsMixin, CreatesApiKeysMixin, B trans.sa_session.flush() return {'message': 'Deleted %s.' % key} else: - raise MessageException('Could not find and delete build (%s).' % key) + raise exceptions.ObjectNotFound('Could not find and delete build (%s).' % key) def _get_user(self, trans, id): user = self.get_user(trans, id) if not user: - raise MessageException('Invalid user (%s).' % id) + raise exceptions.RequestParameterInvalidException('Invalid user (%s).' % id) if user != trans.user and not trans.user_is_admin: - raise MessageException('Access denied.') + raise exceptions.InsufficientPermissionsException('Access denied.') return user diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index 761ccdb692b..5a7a457a647 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -3,6 +3,7 @@ API operations for Workflows """ from __future__ import absolute_import +import io import json import logging import os @@ -23,6 +24,7 @@ from galaxy.managers import ( ) from galaxy.model.item_attrs import UsesAnnotations from galaxy.tools.parameters import populate_state +from galaxy.tools.parameters.basic import workflow_building_modes from galaxy.util.sanitize_html import sanitize_html from galaxy.web import _future_expose_api as expose_api from galaxy.web import _future_expose_api_anonymous_and_sessionless as expose_api_anonymous_and_sessionless @@ -298,12 +300,13 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn raise exceptions.RequestParameterInvalidException(message) if 'installed_repository_file' in payload: + if not trans.user_is_admin: + raise exceptions.AdminRequiredException() installed_repository_file = payload.get('installed_repository_file', '') if not os.path.exists(installed_repository_file): - raise exceptions.MessageException("Repository file '%s' not found.") + raise exceptions.RequestParameterInvalidException("Workflow file '%s' not found" % installed_repository_file) elif os.path.getsize(os.path.abspath(installed_repository_file)) > 0: - workflow_data = None - with open(installed_repository_file, 'rb') as f: + with io.open(installed_repository_file, encoding='utf-8') as f: workflow_data = f.read() return self.__api_import_from_archive(trans, workflow_data) else: @@ -417,7 +420,16 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn def workflow_dict(self, trans, workflow_id, **kwd): """ GET /api/workflows/{encoded_workflow_id}/download + Returns a selected workflow as a json dictionary. + + :type style: str + :param style: Style of export. The default is 'export', which is the meant to be used + with workflow import endpoints. Other formats such as 'instance', 'editor', + 'run' are more tied to the GUI and should not be considered stable APIs. + By default the 'export' format in 19.05 is "ga" files, in 19.09 this will + become 'format2'. Style can be specified as either 'ga' or 'format2' directly + to be explicit about which format to download. """ stored_workflow = self.__get_stored_accessible_workflow(trans, workflow_id) @@ -428,7 +440,11 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn if download_format == 'json-download': sname = stored_workflow.name sname = ''.join(c in util.FILENAME_VALID_CHARS and c or '_' for c in sname)[0:150] - trans.response.headers["Content-Disposition"] = 'attachment; filename="Galaxy-Workflow-%s.ga"' % (sname) + if ret_dict.get("format-version", None) == "0.1": + extension = "ga" + else: + extension = "gxwf.json" + trans.response.headers["Content-Disposition"] = 'attachment; filename="Galaxy-Workflow-%s.%s"' % (sname, extension) trans.response.set_content_type('application/galaxy-archive') return ret_dict @@ -561,6 +577,7 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn Builds module models for the workflow editor. """ inputs = payload.get('inputs', {}) + trans.workflow_building_mode = workflow_building_modes.ENABLED module = module_factory.from_dict(trans, payload) if 'tool_state' not in payload: module_state = {} @@ -584,7 +601,10 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn try: data = json.loads(archive_data) except Exception: - raise exceptions.MessageException("The data content does not appear to be a valid workflow.") + if "GalaxyWorkflow" in archive_data: + data = {"yaml_content": archive_data} + else: + raise exceptions.MessageException("The data content does not appear to be a valid workflow.") if not data: raise exceptions.MessageException("The data content is missing.") raw_workflow_description = self.__normalize_workflow(trans, data) @@ -777,9 +797,11 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn else: history_id = None - if stored_workflow_id is None and encoded_history_id is None: + if not trans.user_is_admin: + # We restrict the query to the current users' invocations user_id = trans.user.id else: + # Get all invocation if user is admin user_id = None invocations = self.workflow_manager.build_invocations_query( diff --git a/lib/galaxy/webapps/galaxy/buildapp.py b/lib/galaxy/webapps/galaxy/buildapp.py index dcefc37580b..b10c23002f4 100644 --- a/lib/galaxy/webapps/galaxy/buildapp.py +++ b/lib/galaxy/webapps/galaxy/buildapp.py @@ -104,6 +104,7 @@ def app_factory(global_conf, load_app_kwds={}, **kwargs): webapp.add_client_route('/admin/data_manager{path_info:.*}', 'admin') webapp.add_client_route('/admin/error_stack', 'admin') webapp.add_client_route('/admin/users', 'admin') + webapp.add_client_route('/admin/users/create', 'admin') webapp.add_client_route('/admin/display_applications', 'admin') webapp.add_client_route('/admin/roles', 'admin') webapp.add_client_route('/admin/forms', 'admin') @@ -215,6 +216,11 @@ def populate_api_routes(webapp, app): name_prefix='history_', path_prefix='/api/histories/{history_id}', parent_resources=dict(member_name='history', collection_name='histories')) + webapp.mapper.connect("history_contents_batch_update", + "/api/histories/{history_id}/contents", + controller="history_contents", + action="update_batch", + conditions=dict(method=["PUT"])) webapp.mapper.connect("history_contents_display", "/api/histories/{history_id}/contents/{history_content_id}/display", controller="datasets", @@ -308,6 +314,18 @@ def populate_api_routes(webapp, app): controller='cloudauthz', conditions=dict(method=["POST"])) + webapp.mapper.connect('delete_cloudauthz_item', + '/api/cloud/authz/{encoded_authz_id}', + action='delete', + controller='cloudauthz', + conditions=dict(method=["DELETE"])) + + webapp.mapper.connect('upload_cloudauthz_item', + '/api/cloud/authz/{encoded_authz_id}', + action='update', + controller="cloudauthz", + conditions=dict(method=["PUT"])) + webapp.mapper.connect('get_custom_builds_metadata', '/api/histories/{id}/custom_builds_metadata', controller='histories', @@ -337,6 +355,7 @@ def populate_api_routes(webapp, app): webapp.mapper.connect('/api/tools/{id:.+?}/build_dependency_cache', action='build_dependency_cache', controller="tools", conditions=dict(method=["POST"])) webapp.mapper.connect('/api/tools/{id:.+?}', action='show', controller="tools") webapp.mapper.resource('tool', 'tools', path_prefix='/api') + webapp.mapper.resource('dynamic_tools', 'dynamic_tools', path_prefix='/api') webapp.mapper.connect('/api/dependency_resolvers/clean', action="clean", controller="tool_dependencies", conditions=dict(method=["POST"])) webapp.mapper.connect('/api/dependency_resolvers/dependency', action="manager_dependency", controller="tool_dependencies", conditions=dict(method=["GET"])) @@ -694,6 +713,18 @@ def populate_api_routes(webapp, app): action='delete_custom_builds', conditions=dict(method=["DELETE"])) + webapp.mapper.connect('set_favorite_tool', + '/api/users/{id}/favorites/{object_type}', + controller='users', + action='set_favorite', + conditions=dict(method=["PUT"])) + + webapp.mapper.connect('remove_favorite_tool', + '/api/users/{id}/favorites/{object_type}/{object_id:.*?}', + controller='users', + action='remove_favorite', + conditions=dict(method=["DELETE"])) + # ======================== # ===== WEBHOOKS API ===== # ======================== @@ -881,6 +912,17 @@ def populate_api_routes(webapp, app): repository_id=None, image_file=None) + # Do the same but without a repository id + webapp.add_route('/shed_tool_static/{shed}/{owner}/{repo}/{tool}/{version}/{image_file:.+?}', + controller='shed_tool_static', + action='index', + shed=None, + owner=None, + repo=None, + tool=None, + version=None, + image_file=None) + webapp.mapper.connect('tool_shed_contents', '/api/tool_shed/contents', controller='toolshed', diff --git a/lib/galaxy/webapps/galaxy/config_schema.yml b/lib/galaxy/webapps/galaxy/config_schema.yml index 846fe21e6f0..d41f7d68ef8 100644 --- a/lib/galaxy/webapps/galaxy/config_schema.yml +++ b/lib/galaxy/webapps/galaxy/config_schema.yml @@ -25,33 +25,16 @@ mapping: required: true mapping: - filter-with: - type: str - default: gzip - required: false - desc: | - If not running behind a proxy server, you may want to enable gzip compression - to decrease the size of data transferred over the network. If using a proxy - server, please enable gzip compression there instead. - - filter-with: - type: str - default: proxy-prefix - required: false - desc: | - If running behind a proxy server and Galaxy is served from a subdirectory, - enable the proxy-prefix filter and set the prefix in the - [filter:proxy-prefix] section above. - cookie_path: type: str default: '' required: false desc: | - If proxy-prefix is enabled and you're running more than one Galaxy instance - behind one hostname, you will want to set this to the same path as the prefix - in the filter above. This value becomes the "path" attribute set in the - cookie so the cookies from each instance will not clobber each other. + When running multiple Galaxy instances under separate URL prefixes on a + single hostname, you will want to set this to the same path as the prefix set + in the uWSGI "mount" configuration option above. This value becomes the "path" + attribute set in the cookie so the cookies from one instance will not clobber + those from another. database_connection: type: str @@ -161,14 +144,16 @@ mapping: default: database/files required: false desc: | - Dataset files are stored in this directory. + Where dataset files are stored. It must accessible at the same path on any cluster + nodes that will run Galaxy jobs, unless using Pulsar. new_file_path: type: str default: database/tmp required: false desc: | - Temporary files are stored in this directory. + Where temporary files are stored. It must accessible at the same path on any cluster + nodes that will run Galaxy jobs, unless using Pulsar. tool_config_file: type: str @@ -182,16 +167,14 @@ mapping: check_migrate_tools: type: bool - default: true + default: false required: false desc: | Enable / disable checking if any tools defined in the above non-shed tool_config_files (i.e., tool_conf.xml) have been migrated from the Galaxy - code distribution to the Tool Shed. This setting should generally be set to - False only for development Galaxy environments that are often rebuilt from - scratch where migrated tools do not need to be available in the Galaxy tool - panel. If the following setting remains commented, the default setting will - be True. + code distribution to the Tool Shed. This functionality is largely untested + in modern Galaxy releases and has serious issues such as #7273 and the possibility + of slowing down Galaxy startup, so the default and recommended value is False. migrated_tools_config: type: str @@ -953,6 +936,13 @@ mapping: - $iso8601 (complete format string as specified by ISO 8601 international standard). + user_preferences_extra_conf_path: + type: str + default: 'config/user_preferences_extra_conf.yml' + required: false + desc: | + Location of the configuration file containing extra user preferences. + default_locale: type: str default: auto @@ -1028,41 +1018,6 @@ mapping: desc: | The URL linked by the "Support" link in the "Help" menu. - biostar_url: - type: str - default: '' - required: false - desc: | - Enable integration with a custom Biostar instance. - - biostar_key_name: - type: str - default: '' - required: false - desc: | - Enable integration with a custom Biostar instance. - - biostar_key: - type: str - default: '' - required: false - desc: | - Enable integration with a custom Biostar instance. - - biostar_enable_bug_reports: - type: bool - default: true - required: false - desc: | - Enable integration with a custom Biostar instance. - - biostar_never_authenticate: - type: bool - default: false - required: false - desc: | - Enable integration with a custom Biostar instance. - citation_url: type: str default: https://galaxyproject.org/citing-galaxy @@ -2095,25 +2050,6 @@ mapping: desc: | Enable the new container interface for Interactive Environments - tool_submission_burst_threads: - type: int - default: 1 - required: false - desc: | - Set the following to a number of threads greater than 1 to spawn - a Python task queue for dealing with large tool submissions (either - through the tool form or as part of an individual workflow step across - large collection). This affects workflow scheduling and web processes, - not job handlers. This is a beta option and should not be used in production. - - tool_submission_burst_at: - type: int - default: 10 - required: false - desc: | - If tool_submission_burst_threads is set to a number greater than 1, this - is the number of jobs to schedule at which the task queue will be created. - enable_beta_workflow_modules: type: bool default: false @@ -2122,12 +2058,13 @@ mapping: Enable beta workflow modules that should not yet be considered part of Galaxy's stable API. - enable_beta_workflow_format: + enable_beta_export_format2_default: type: bool default: false required: false desc: | - Enable import and export of workflows as Galaxy Format 2 workflows. + Export of workflows as Galaxy Format 2 instead of GA workflows by default. The + default value of this configuration variable will switch to True in 19.09. force_beta_workflow_scheduled_min_steps: type: int @@ -2250,6 +2187,20 @@ mapping: need to bootstrap Galaxy, you probably do not want to set this on public servers. + enable_openid: + type: bool + default: false + required: false + desc: | + Enable access to post-authentication options via OpenID. + + openid_consumer_cache_path: + type: str + default: database/openid_consumer_cache + required: false + desc: | + If OpenID is enabled, consumer cache directory to use. + enable_tool_tags: type: bool default: false diff --git a/lib/galaxy/webapps/galaxy/config_watchers.py b/lib/galaxy/webapps/galaxy/config_watchers.py index ce8729398f2..d1cd4a2ba78 100644 --- a/lib/galaxy/webapps/galaxy/config_watchers.py +++ b/lib/galaxy/webapps/galaxy/config_watchers.py @@ -20,7 +20,7 @@ from galaxy.util.watcher import ( class ConfigWatchers(object): """Contains ToolConfWatcher, ToolWatcher and ToolDataWatcher objects.""" - def __init__(self, app): + def __init__(self, app, start_thread=True): self.app = app # ToolConfWatcher objects will watch the tool_cache if the tool_cache is passed into get_tool_conf_watcher. # Watching the tool_cache means removing outdated items from the tool_cache. @@ -37,7 +37,8 @@ class ConfigWatchers(object): self.job_rule_watcher = get_watcher(app.config, 'watch_job_rules', monitor_what_str='job rules') else: self.job_rule_watcher = get_watcher(app.config, '__invalid__') - self.start() + if start_thread: + self.start() def start(self): [self.tool_config_watcher.watch_file(config) for config in self.tool_config_paths] diff --git a/lib/galaxy/util/create_history_template.py b/lib/galaxy/webapps/galaxy/controllers/_create_history_template.py similarity index 100% rename from lib/galaxy/util/create_history_template.py rename to lib/galaxy/webapps/galaxy/controllers/_create_history_template.py diff --git a/lib/galaxy/webapps/galaxy/controllers/admin.py b/lib/galaxy/webapps/galaxy/controllers/admin.py index b06bfa4841e..e9ed9df0913 100644 --- a/lib/galaxy/webapps/galaxy/controllers/admin.py +++ b/lib/galaxy/webapps/galaxy/controllers/admin.py @@ -138,7 +138,7 @@ class UserListGrid(grids.Grid): visible=False, filterable="standard")) global_actions = [ - grids.GridAction("Create new user", url_args=dict(webapp="galaxy", action="create_new_user")) + grids.GridAction("Create new user", url_args=dict(action="users/create")) ] operations = [ grids.GridOperation("Manage Information", @@ -537,14 +537,22 @@ class AdminGalaxy(controller.JSAppLauncher, AdminActions, UsesQuotaMixin, QuotaP @web.expose @web.require_admin def index(self, trans, **kwd): + return self.client(trans, **kwd) + + @web.expose + @web.require_admin + def client(self, trans, **kwd): + """ + Endpoint for admin clientside routes. + """ message = escape(kwd.get('message', '')) status = kwd.get('status', 'done') settings = { - 'is_repo_installed' : trans.install_model.context.query(trans.install_model.ToolShedRepository).first() is not None, - 'installing_repository_ids' : repository_util.get_ids_of_tool_shed_repositories_being_installed(trans.app, as_string=True), - 'is_tool_shed_installed' : bool(trans.app.tool_shed_registry and trans.app.tool_shed_registry.tool_sheds) + 'is_repo_installed': trans.install_model.context.query(trans.install_model.ToolShedRepository).first() is not None, + 'installing_repository_ids': repository_util.get_ids_of_tool_shed_repositories_being_installed(trans.app, as_string=True), + 'is_tool_shed_installed': bool(trans.app.tool_shed_registry and trans.app.tool_shed_registry.tool_sheds) } - return self.template(trans, 'admin', settings=settings, message=message, status=status) + return self._bootstrapped_client(trans, app_name='admin', settings=settings, message=message, status=status) @web.expose @web.json @@ -876,23 +884,17 @@ class AdminGalaxy(controller.JSAppLauncher, AdminActions, UsesQuotaMixin, QuotaP message = escape(util.restore_text(kwd.get('message', ''))) status = util.restore_text(kwd.get('status', 'done')) migration_stages_dict = odict() - migration_modules = [] migration_scripts_dir = os.path.abspath(os.path.join(trans.app.config.root, 'lib', 'tool_shed', 'galaxy_install', 'migrate', 'versions')) - migration_scripts_dir_contents = os.listdir(migration_scripts_dir) - for item in migration_scripts_dir_contents: - if os.path.isfile(os.path.join(migration_scripts_dir, item)) and item.endswith('.py'): - module = item.replace('.py', '') - migration_modules.append(module) - if migration_modules: - migration_modules.sort() - # Remove the 0001_tools.py script since it is the seed. - migration_modules = migration_modules[1:] - # Reverse the list so viewing will be newest to oldest. - migration_modules.reverse() - for migration_module in migration_modules: - migration_stage = int(migration_module.replace('_tools', '')) + modules = os.listdir(migration_scripts_dir) + modules.sort() + modules.reverse() + for item in modules: + if not item.endswith('.py') or item.startswith('0001_tools'): + continue + module = item.replace('.py', '') + migration_stage = int(module.replace('_tools', '')) repo_name_dependency_tups = self.check_for_tool_dependencies(trans, migration_stage) - open_file_obj, file_name, description = imp.find_module(migration_module, [migration_scripts_dir]) + open_file_obj, file_name, description = imp.find_module(module, [migration_scripts_dir]) imported_module = imp.load_module('upgrade', open_file_obj, file_name, description) migration_info = imported_module.__doc__ open_file_obj.close() @@ -1615,7 +1617,7 @@ class AdminGalaxy(controller.JSAppLauncher, AdminActions, UsesQuotaMixin, QuotaP % (stop_msg, self.app.config.get("support_url", "https://galaxyproject.org/support/")) if trans.app.config.track_jobs_in_database: job = trans.sa_session.query(trans.app.model.Job).get(job_id) - job.stderr = error_msg + job.job_stderr = error_msg job.set_state(trans.app.model.Job.states.DELETED_NEW) trans.sa_session.add(job) else: @@ -1705,7 +1707,7 @@ class AdminGalaxy(controller.JSAppLauncher, AdminActions, UsesQuotaMixin, QuotaP selected_tool_ids = [] if not selected_environments_to_uninstall: selected_environments_to_uninstall = [] - tools_by_id = trans.app.toolbox.tools_by_id + tools_by_id = trans.app.toolbox.tools_by_id.copy() view = six.next(six.itervalues(trans.app.toolbox.tools_by_id))._view if selected_tool_ids: # install the dependencies for the tools in the selected_tool_ids list diff --git a/lib/galaxy/webapps/galaxy/controllers/admin_toolshed.py b/lib/galaxy/webapps/galaxy/controllers/admin_toolshed.py index da50198f54f..5d5b86f20b1 100644 --- a/lib/galaxy/webapps/galaxy/controllers/admin_toolshed.py +++ b/lib/galaxy/webapps/galaxy/controllers/admin_toolshed.py @@ -276,7 +276,7 @@ class AdminToolshed(AdminGalaxy): mimetype = trans.app.datatypes_registry.get_mimetype_by_extension(extension) if mimetype: trans.response.set_content_type(mimetype) - return open(path_to_file, 'r') + return open(path_to_file, 'rb') return None @web.expose @@ -587,6 +587,11 @@ class AdminToolshed(AdminGalaxy): repository_id = kwd.get('id', None) if repository_id is None: return trans.show_error_message('Missing required encoded repository id.') + if repository_id and isinstance(repository_id, list): + # FIXME: This is a hack that avoids unhandled and duplicate url parameters leaking in. + # This should be handled somewhere in the grids system, but given the legacy status + # this should be OK. + repository_id = [r for r in repository_id if '=' not in r][0] # This method only work for a single repo id operation = kwd.get('operation', None) repository = repository_util.get_installed_tool_shed_repository(trans.app, repository_id) if repository is None: diff --git a/lib/galaxy/webapps/galaxy/controllers/authnz.py b/lib/galaxy/webapps/galaxy/controllers/authnz.py index d992a0a0a8c..0a07dbbba3a 100644 --- a/lib/galaxy/webapps/galaxy/controllers/authnz.py +++ b/lib/galaxy/webapps/galaxy/controllers/authnz.py @@ -4,23 +4,24 @@ OAuth 2.0 and OpenID Connect Authentication and Authorization Controller. from __future__ import absolute_import -import json import logging +from galaxy import exceptions from galaxy import web from galaxy.web import url_for -from galaxy.web.base.controller import BaseUIController +from galaxy.web.base.controller import JSAppLauncher log = logging.getLogger(__name__) -class OIDC(BaseUIController): +class OIDC(JSAppLauncher): + @web.json @web.expose @web.require_login("list third-party identities") def index(self, trans, **kwargs): """ - GET /api/authnz/ + GET /authnz/ returns a list of third-party identities associated with the user. :type trans: galaxy.web.framework.webapp.GalaxyWebTransaction @@ -36,6 +37,7 @@ class OIDC(BaseUIController): rtv.append({'id': trans.app.security.encode_id(authnz.id), 'provider': authnz.provider}) return rtv + @web.json @web.expose def login(self, trans, provider): if not trans.app.config.enable_oidc: @@ -43,7 +45,10 @@ class OIDC(BaseUIController): log.debug(msg) return trans.show_error_message(msg) success, message, redirect_uri = trans.app.authnz_manager.authenticate(provider, trans) - return json.dumps({"redirect_uri": redirect_uri}) + if success: + return {"redirect_uri": redirect_uri} + else: + raise exceptions.AuthenticationFailed(message) @web.expose def callback(self, trans, provider, **kwargs): @@ -74,17 +79,7 @@ class OIDC(BaseUIController): "identity provider. Please try again, and if the problem persists, " "contact the Galaxy instance admin.".format(provider)) trans.handle_user_login(user) - return trans.fill_template('/user/login.mako', - login=user.username, - header="", - use_panels=False, - redirect_url=redirect_url, - redirect=redirect_url, - refresh_frames='refresh_frames', - message="You are now logged in as `{}.`".format(user.username), - status='done', - form_input_auto_focus=True, - active_view="user") + return self.client(trans) @web.expose @web.require_login("authenticate against the selected identity provider") diff --git a/lib/galaxy/webapps/galaxy/controllers/biostar.py b/lib/galaxy/webapps/galaxy/controllers/biostar.py deleted file mode 100644 index dbad55edb9e..00000000000 --- a/lib/galaxy/webapps/galaxy/controllers/biostar.py +++ /dev/null @@ -1,106 +0,0 @@ -""" -Controller for integration with the Biostar application -""" -from galaxy.util import biostar -from galaxy.web.base.controller import ( - BaseUIController, - error, - web -) - - -class BiostarController(BaseUIController): - """ - Provides integration with Biostar through external authentication, see: http://liondb.com/help/x/ - """ - - @web.expose - def biostar_redirect(self, trans, payload=None, biostar_action=None): - """ - Generate a redirect to a Biostar site using external authentication to - pass Galaxy user information and optional information about a specific tool. - """ - try: - url, payload = biostar.get_biostar_url(trans.app, payload=payload, biostar_action=biostar_action) - except Exception as e: - return error(str(e)) - # Only create/log in biostar user if is registered Galaxy user - if trans.user: - biostar.create_cookie(trans, trans.app.config.biostar_key_name, trans.app.config.biostar_key, trans.user.email) - if payload: - return trans.fill_template("biostar/post_redirect.mako", post_url=url, form_inputs=payload) - return trans.response.send_redirect(url) - - @web.expose - def biostar_tool_tag_redirect(self, trans, tool_id=None): - """ - Generate a redirect to a Biostar site using tag for tool. - """ - # tool_id is required - if tool_id is None: - return error("No tool_id provided") - # Load the tool - tool_version_select_field, tools, tool = \ - self.app.toolbox.get_tool_components(tool_id, tool_version=None, get_loaded_tools_by_lineage=False, set_selected=True) - # No matching tool, unlikely - if not tool: - return error("No tool found matching '%s'" % tool_id) - # Tool specific information for payload - payload = biostar.populate_tag_payload(tool=tool) - # Pass on to standard redirect method - return self.biostar_redirect(trans, payload=payload, biostar_action='show_tags') - - @web.expose - def biostar_question_redirect(self, trans, payload=None): - """ - Generate a redirect to a Biostar site using external authentication to - pass Galaxy user information and information about a specific tool. - """ - # Pass on to standard redirect method - return self.biostar_redirect(trans, payload=payload, biostar_action='new_post') - - @web.expose - def biostar_tool_question_redirect(self, trans, tool_id=None): - """ - Generate a redirect to a Biostar site using external authentication to - pass Galaxy user information and information about a specific tool. - """ - # tool_id is required - if tool_id is None: - return error("No tool_id provided") - # Load the tool - tool_version_select_field, tools, tool = \ - self.app.toolbox.get_tool_components(tool_id, tool_version=None, get_loaded_tools_by_lineage=False, set_selected=True) - # No matching tool, unlikely - if not tool: - return error("No tool found matching '%s'" % tool_id) - # Tool specific information for payload - payload = biostar.populate_tool_payload(tool=tool) - # Pass on to regular question method - return self.biostar_question_redirect(trans, payload) - - @web.expose - def biostar_tool_bug_report(self, trans, hda=None, email=None, message=None): - """ - Generate a redirect to a Biostar site using external authentication to - pass Galaxy user information and information about a specific tool error. - """ - try: - error_reporter = biostar.BiostarErrorReporter(hda, trans.app) - payload = error_reporter.send_report(trans.user, email=email, message=message) - except Exception as e: - return error(str(e)) - return self.biostar_redirect(trans, payload=payload, biostar_action='new_post') - - @web.expose - def biostar_logout(self, trans): - """ - Log out of biostar - """ - try: - url = biostar.biostar_log_out(trans) - except Exception as e: - return error(str(e)) - if url: - return trans.response.send_redirect(url) - return error("Could not determine Biostar logout URL.") diff --git a/lib/galaxy/webapps/galaxy/controllers/dataset.py b/lib/galaxy/webapps/galaxy/controllers/dataset.py index 11cdd2f9b43..14daace1ac4 100644 --- a/lib/galaxy/webapps/galaxy/controllers/dataset.py +++ b/lib/galaxy/webapps/galaxy/controllers/dataset.py @@ -186,7 +186,7 @@ class DatasetInterface(BaseUIController, UsesAnnotations, UsesItemRatings, UsesE file_ext = data.metadata.spec.get(metadata_name).get("file_ext", metadata_name) trans.response.headers["Content-Type"] = "application/octet-stream" trans.response.headers["Content-Disposition"] = 'attachment; filename="Galaxy%s-[%s].%s"' % (data.hid, fname, file_ext) - return open(data.metadata.get(metadata_name).file_name) + return open(data.metadata.get(metadata_name).file_name, 'rb') def _check_dataset(self, trans, hda_id): # DEPRECATION: We still support unencoded ids for backward compatibility @@ -253,27 +253,11 @@ class DatasetInterface(BaseUIController, UsesAnnotations, UsesItemRatings, UsesE @web.expose_api_anonymous def get_edit(self, trans, dataset_id=None, **kwd): """Produces the input definitions available to modify dataset attributes""" - message = None status = None - if dataset_id is not None: - id = self.decode_id(dataset_id) - data = trans.sa_session.query(self.app.model.HistoryDatasetAssociation).get(id) - else: - trans.log_event("dataset_id is None, cannot load a dataset to edit.") - return self.message_exception(trans, 'You must provide a dataset id to edit attributes.') - if data is None: - trans.log_event("Problem retrieving dataset id (%s)." % dataset_id) - return self.message_exception(trans, 'The dataset id is invalid.') - if dataset_id is not None and data.history.user is not None and data.history.user != trans.user: - trans.log_event("User attempted to edit a dataset they do not own (encoded: %s, decoded: %s)." % (dataset_id, id)) - return self.message_exception(trans, 'The dataset id is invalid.') - if data.history.user and not data.dataset.has_manage_permissions_roles(trans): - # Permission setting related to DATASET_MANAGE_PERMISSIONS was broken for a period of time, - # so it is possible that some Datasets have no roles associated with the DATASET_MANAGE_PERMISSIONS - # permission. In this case, we'll reset this permission to the hda user's private role. - manage_permissions_action = trans.app.security_agent.get_action(trans.app.security_agent.permitted_actions.DATASET_MANAGE_PERMISSIONS.action) - permissions = {manage_permissions_action : [trans.app.security_agent.get_private_user_role(data.history.user)]} - trans.app.security_agent.set_dataset_permission(data.dataset, permissions) + data, message = self._get_dataset_for_edit(trans, dataset_id) + if message: + return message + if self._can_access_dataset(trans, data): if data.state == trans.model.Dataset.states.UPLOAD: return self.message_exception(trans, 'Please wait until this dataset finishes uploading before attempting to edit its metadata.') @@ -418,13 +402,13 @@ class DatasetInterface(BaseUIController, UsesAnnotations, UsesItemRatings, UsesE return False return True - message = None status = 'success' - dataset_id = payload.get('dataset_id') operation = payload.get('operation') - if dataset_id is not None: - id = self.decode_id(dataset_id) - data = trans.sa_session.query(self.app.model.HistoryDatasetAssociation).get(id) + dataset_id = payload.get('dataset_id') + data, message = self._get_dataset_for_edit(trans, dataset_id) + if message: + return message + if operation == 'attributes': # The user clicked the Save button on the 'Edit Attributes' form data.name = payload.get('name') @@ -530,6 +514,28 @@ class DatasetInterface(BaseUIController, UsesAnnotations, UsesItemRatings, UsesE return self.message_exception(trans, 'Invalid operation identifier (%s).' % operation) return {'status': status, 'message': sanitize_text(message)} + def _get_dataset_for_edit(self, trans, dataset_id): + if dataset_id is not None: + id = self.decode_id(dataset_id) + data = trans.sa_session.query(self.app.model.HistoryDatasetAssociation).get(id) + else: + trans.log_event("dataset_id is None, cannot load a dataset to edit.") + return None, self.message_exception(trans, 'You must provide a dataset id to edit attributes.') + if data is None: + trans.log_event("Problem retrieving dataset id (%s)." % dataset_id) + return None, self.message_exception(trans, 'The dataset id is invalid.') + if dataset_id is not None and data.history.user is not None and data.history.user != trans.user: + trans.log_event("User attempted to edit a dataset they do not own (encoded: %s, decoded: %s)." % (dataset_id, id)) + return None, self.message_exception(trans, 'The dataset id is invalid.') + if data.history.user and not data.dataset.has_manage_permissions_roles(trans): + # Permission setting related to DATASET_MANAGE_PERMISSIONS was broken for a period of time, + # so it is possible that some Datasets have no roles associated with the DATASET_MANAGE_PERMISSIONS + # permission. In this case, we'll reset this permission to the hda user's private role. + manage_permissions_action = trans.app.security_agent.get_action(trans.app.security_agent.permitted_actions.DATASET_MANAGE_PERMISSIONS.action) + permissions = {manage_permissions_action : [trans.app.security_agent.get_private_user_role(data.history.user)]} + trans.app.security_agent.set_dataset_permission(data.dataset, permissions) + return data, None + @web.expose @web.json @web.require_login("see all available datasets") @@ -677,7 +683,7 @@ class DatasetInterface(BaseUIController, UsesAnnotations, UsesItemRatings, UsesE # TODO: figure out a way to display images in display template. if isinstance(dataset.datatype, datatypes.binary.Binary) or isinstance(dataset.datatype, datatypes.images.Image) or isinstance(dataset.datatype, datatypes.text.Html): trans.response.set_content_type(dataset.get_mime()) - return open(dataset.file_name) + return open(dataset.file_name, 'rb') else: # Get rating data. user_item_rating = 0 diff --git a/lib/galaxy/webapps/galaxy/controllers/history.py b/lib/galaxy/webapps/galaxy/controllers/history.py index 64a766a38f0..cb7274adf0b 100644 --- a/lib/galaxy/webapps/galaxy/controllers/history.py +++ b/lib/galaxy/webapps/galaxy/controllers/history.py @@ -15,7 +15,6 @@ from galaxy.model.item_attrs import ( UsesItemRatings ) from galaxy.util import listify, Params, parse_int, sanitize_text -from galaxy.util.create_history_template import render_item from galaxy.util.odict import odict from galaxy.web import url_for from galaxy.web.base.controller import ( @@ -29,6 +28,7 @@ from galaxy.web.base.controller import ( WARNING, ) from galaxy.web.framework.helpers import grids, iff, time_ago +from ._create_history_template import render_item log = logging.getLogger(__name__) @@ -98,6 +98,9 @@ class HistoryListGrid(grids.Grid): cols_to_filter=[columns[0], columns[3]], key="free-text-search", visible=False, filterable="standard") ) + global_actions = [ + grids.GridAction("Import from file", dict(controller="", action="histories/import")) + ] operations = [ grids.GridOperation("Switch", allow_multiple=False, condition=(lambda item: not item.deleted), async_compatible=True), grids.GridOperation("View", allow_multiple=False, url_args=dict(controller="", action="histories/view")), diff --git a/lib/galaxy/webapps/galaxy/controllers/openid.py b/lib/galaxy/webapps/galaxy/controllers/openid.py new file mode 100644 index 00000000000..6cf4e3eb0d2 --- /dev/null +++ b/lib/galaxy/webapps/galaxy/controllers/openid.py @@ -0,0 +1,72 @@ +""" +Contains the OpenID interface in the Universe class +""" + +import logging + +from galaxy import web +from galaxy.openid.openid_manager import OpenIDManager +from galaxy.openid.providers import OpenIDProviders +from galaxy.web import url_for +from galaxy.web.base.controller import BaseUIController + +log = logging.getLogger(__name__) + + +class OpenID(BaseUIController): + + def __init__(self, app): + super(OpenID, self).__init__(app) + if app.config.enable_openid: + self.openid_manager = OpenIDManager(app.config.openid_consumer_cache_path) + self.openid_providers = OpenIDProviders.from_file('lib/galaxy/openid/openid_conf.xml') + + @web.expose + def openid_auth(self, trans, **kwd): + '''Handles user request to access an OpenID provider''' + if not trans.app.config.enable_openid: + return trans.show_error_message("OpenID authentication is not enabled in this instance of Galaxy.") + consumer = self.openid_manager.get_consumer(trans) + openid_provider = kwd.get('openid_provider') + if openid_provider: + openid_provider_obj = self.openid_providers.get(openid_provider) + else: + return trans.show_error_message("An OpenID provider was not specified.") + if not openid_provider_obj: + return trans.show_error_message("An OpenID provider is invalid.") + process_url = trans.request.base.rstrip('/') + url_for(controller='openid', action='openid_process', openid_provider=openid_provider) + request = None + try: + request = consumer.begin(openid_provider_obj.op_endpoint_url) + if request is None: + return trans.show_error_message("No OpenID services are available at %s." % openid_provider_obj.op_endpoint_url) + except Exception as e: + return trans.show_error_message("Failed to begin OpenID authentication: %s." % str(e)) + if request is not None: + self.openid_manager.add_sreg(trans, request, required=openid_provider_obj.sreg_required, optional=openid_provider_obj.sreg_optional) + if request.shouldSendRedirect(): + redirect_url = request.redirectURL( + trans.request.base, process_url) + self.openid_manager.persist_session(trans, consumer) + return trans.response.send_redirect(redirect_url) + else: + form = request.htmlMarkup(trans.request.base, process_url, form_tag_attrs={'id': 'openid_message', 'target': '_top'}) + self.openid_manager.persist_session(trans, consumer) + return form + return trans.show_error_message("OpenID request failed.") + + @web.expose + def openid_process(self, trans, **kwd): + '''Handle's response from OpenID Providers''' + return_link = "Click here to return." % url_for("/") + if not trans.app.config.enable_openid: + return trans.show_error_message("OpenID authentication is not enabled in this instance of Galaxy. %s" % return_link) + consumer = self.openid_manager.get_consumer(trans) + info = consumer.complete(kwd, trans.request.url) + openid_provider = kwd.get('openid_provider', None) + if info.status == self.openid_manager.SUCCESS: + openid_provider_obj = self.openid_providers.get(openid_provider) + openid_provider_obj.post_authentication(trans, self.openid_manager, info) + return trans.show_message("Processed OpenID authentication. %s" % return_link) + else: + return trans.show_error_message("Authentication via OpenID failed: %s. %s" % (info.message, return_link)) diff --git a/lib/galaxy/webapps/galaxy/controllers/root.py b/lib/galaxy/webapps/galaxy/controllers/root.py index 5851f879cfd..d486d5e439a 100644 --- a/lib/galaxy/webapps/galaxy/controllers/root.py +++ b/lib/galaxy/webapps/galaxy/controllers/root.py @@ -15,7 +15,6 @@ from webob.exc import ( from galaxy import ( managers, - util, web ) from galaxy.model.item_attrs import UsesAnnotations @@ -47,31 +46,6 @@ class RootController(controller.JSAppLauncher, UsesAnnotations): """ raise HTTPNotFound('This link may not be followed from within Galaxy.') - def _get_extended_config(self, trans): - app = trans.app - config = { - 'active_view' : 'analysis', - 'enable_cloud_launch' : app.config.get_bool('enable_cloud_launch', False), - 'enable_webhooks' : True if app.webhooks_registry.webhooks else False, - # TODO: next two should be redundant - why can't we build one from the other? - 'toolbox' : app.toolbox.to_dict(trans, in_panel=False), - 'toolbox_in_panel' : app.toolbox.to_dict(trans), - 'message_box_visible' : app.config.message_box_visible, - 'show_inactivity_warning' : app.config.user_activation_on and trans.user and not trans.user.active - } - - # TODO: move to user - stored_workflow_menu_entries = config['stored_workflow_menu_entries'] = [] - for menu_item in getattr(trans.user, 'stored_workflow_menu_entries', []): - stored_workflow_menu_entries.append({ - 'encoded_stored_workflow_id': trans.security.encode_id(menu_item.stored_workflow_id), - 'stored_workflow': { - 'name': util.unicodify(menu_item.stored_workflow.name) - } - }) - - return config - @web.expose def index(self, trans, tool_id=None, workflow_id=None, history_id=None, m_c=None, m_a=None, **kwd): """ @@ -91,11 +65,8 @@ class RootController(controller.JSAppLauncher, UsesAnnotations): If m_c and m_a are present, the center panel will be loaded using the controller and action as a url: (e.g. 'user/dbkeys'). """ - if trans.app.config.require_login and self.user_manager.is_anonymous(trans.user): - # TODO: this doesn't properly redirect when login is done - # (see webapp __ensure_logged_in_user for the initial redirect - not sure why it doesn't redirect to login?) - login_url = web.url_for(controller="root", action="login") - trans.response.send_redirect(login_url) + + self._check_require_login(trans) # if a history_id was sent, attempt to switch to that history history = trans.history @@ -104,11 +75,7 @@ class RootController(controller.JSAppLauncher, UsesAnnotations): history = self.history_manager.get_owned(unencoded_id, trans.user) trans.set_history(history) - # index/analysis needs an extended configuration - js_options = self._get_js_options(trans) - config = js_options['config'] - config.update(self._get_extended_config(trans)) - return self.template(trans, 'analysis', options=js_options) + return self._bootstrapped_client(trans) @web.expose def login(self, trans, redirect=None, **kwd): @@ -216,7 +183,7 @@ class RootController(controller.JSAppLauncher, UsesAnnotations): trans.response.headers["Content-Disposition"] = 'attachment; filename="GalaxyHistoryItem-%s-[%s]%s"' % (data.hid, fname, toext) trans.log_event("Display dataset id: %s" % str(id)) try: - return open(data.file_name) + return open(data.file_name, 'rb') except Exception: return "This dataset contains no content" else: diff --git a/lib/galaxy/webapps/galaxy/controllers/shed_tool_static.py b/lib/galaxy/webapps/galaxy/controllers/shed_tool_static.py new file mode 100644 index 00000000000..258eaed3a21 --- /dev/null +++ b/lib/galaxy/webapps/galaxy/controllers/shed_tool_static.py @@ -0,0 +1,42 @@ +import logging +import os + +from galaxy import web +from galaxy.exceptions import RequestParameterInvalidException +from galaxy.util.path import join, safe_contains +from galaxy.web.base.controller import BaseUIController + +log = logging.getLogger(__name__) + + +class ShedToolStatic(BaseUIController): + + @web.expose + def index(self, trans, shed, owner, repo, tool, version, image_file): + """ + Open an image file that is contained in an installed tool shed repository or that is referenced by a URL for display. The + image can be defined in either a README.rst file contained in the repository or the help section of a Galaxy tool config that + is contained in the repository. The following image definitions are all supported. The former $PATH_TO_IMAGES is no longer + required, and is now ignored. + .. image:: https://raw.github.com/galaxy/some_image.png + .. image:: $PATH_TO_IMAGES/some_image.png + .. image:: /static/images/some_image.gif + .. image:: some_image.jpg + .. image:: /deep/some_image.png + """ + guid = '/'.join([shed, 'repos', owner, repo, tool, version]) + tool = trans.app.toolbox.get_tool(guid) + repo_path = tool._repository_dir + if 'static/images' not in image_file: + path = join(repo_path, 'static', 'images', image_file) + else: + path = join(repo_path, image_file) + if not safe_contains(os.path.abspath(repo_path), os.path.abspath(path)): + raise RequestParameterInvalidException() + ext = os.path.splitext(image_file)[-1].lstrip('.') + if ext: + mime = trans.app.datatypes_registry.get_mimetype_by_extension(ext) + if mime: + trans.response.set_content_type(mime) + if os.path.exists(path): + return open(path, 'rb') diff --git a/lib/galaxy/webapps/galaxy/controllers/tag.py b/lib/galaxy/webapps/galaxy/controllers/tag.py index bc3e9dfe6ae..25e74fdf576 100644 --- a/lib/galaxy/webapps/galaxy/controllers/tag.py +++ b/lib/galaxy/webapps/galaxy/controllers/tag.py @@ -30,8 +30,6 @@ class TagsController(BaseUIController, UsesTagsMixin): user=trans.user, tagged_item=item, elt_context=elt_context, - in_form=False, - input_size="22", tag_click_fn="default_tag_click_fn", use_toggle_link=False) diff --git a/lib/galaxy/webapps/galaxy/controllers/user.py b/lib/galaxy/webapps/galaxy/controllers/user.py index 05e908f4f32..574c8313769 100644 --- a/lib/galaxy/webapps/galaxy/controllers/user.py +++ b/lib/galaxy/webapps/galaxy/controllers/user.py @@ -17,12 +17,10 @@ from galaxy import ( from galaxy.managers import users from galaxy.queue_worker import send_local_control_task from galaxy.security.validate_user_input import ( - transform_publicname, validate_email, validate_password, validate_publicname ) -from galaxy.util import biostar from galaxy.web import _future_expose_api_anonymous_and_sessionless as expose_api_anonymous_and_sessionless from galaxy.web import url_for from galaxy.web.base.controller import ( @@ -30,7 +28,6 @@ from galaxy.web.base.controller import ( CreatesApiKeysMixin, UsesFormDefinitionsMixin ) -from galaxy.web.form_builder import CheckboxField log = logging.getLogger(__name__) @@ -118,9 +115,14 @@ class User(BaseUIController, UsesFormDefinitionsMixin, CreatesApiKeysMixin): def __validate_login(self, trans, payload={}, **kwd): '''Handle Galaxy Log in''' - login = kwd.get("login", payload.get("login")) - password = kwd.get("password", payload.get("password")) - redirect = kwd.get("redirect", payload.get("redirect")) + if not payload: + payload = kwd + message = trans.check_csrf_token(payload) + if message: + return self.message_exception(trans, message) + login = payload.get("login") + password = payload.get("password") + redirect = payload.get("redirect") status = None if not login or not password: return self.message_exception(trans, "Please specify a username and password.") @@ -148,7 +150,7 @@ class User(BaseUIController, UsesFormDefinitionsMixin, CreatesApiKeysMixin): elif trans.app.config.user_activation_on and not user.active: # activation is ON and the user is INACTIVE if (trans.app.config.activation_grace_period != 0): # grace period is ON if self.is_outside_grace_period(trans, user.create_time): # User is outside the grace period. Login is disabled and he will have the activation email resent. - message, status = self.resend_activation_email(trans, user.email, user.username, unescaped=True) + message, status = self.resend_activation_email(trans, user.email, user.username) return self.message_exception(trans, message, sanitize=False) else: # User is within the grace period, let him log in. trans.handle_user_login(user) @@ -211,154 +213,34 @@ class User(BaseUIController, UsesFormDefinitionsMixin, CreatesApiKeysMixin): @web.expose def logout(self, trans, logout_all=False, **kwd): - if trans.webapp.name == 'galaxy': - csrf_check = trans.check_csrf_token() - if csrf_check: - return csrf_check - - if trans.app.config.require_login: - refresh_frames = ['masthead', 'history', 'tools'] - else: - refresh_frames = ['masthead', 'history'] - if trans.user: - # Queue a quota recalculation (async) task -- this takes a - # while sometimes, so we don't want to block on logout. - send_local_control_task(trans.app, - 'recalculate_user_disk_usage', - {'user_id': trans.security.encode_id(trans.user.id)}) - # Since logging an event requires a session, we'll log prior to ending the session - trans.log_event("User logged out") - else: - refresh_frames = ['masthead'] + message = trans.check_csrf_token(kwd) + if message: + return self.message_exception(trans, message) + if trans.user: + # Queue a quota recalculation (async) task -- this takes a + # while sometimes, so we don't want to block on logout. + send_local_control_task(trans.app, + "recalculate_user_disk_usage", + {"user_id": trans.security.encode_id(trans.user.id)}) + # Since logging an event requires a session, we'll log prior to ending the session + trans.log_event("User logged out") trans.handle_user_logout(logout_all=logout_all) - message = 'You have been logged out.
    To log in again go to the home page.' % \ - (url_for('/')) - if biostar.biostar_logged_in(trans): - biostar_url = biostar.biostar_logout(trans) - if biostar_url: - # TODO: It would be better if we automatically logged this user out of biostar - message += '
    To logout of Biostar, please click here.' % (biostar_url) - if trans.app.config.use_remote_user and trans.app.config.remote_user_logout_href: - trans.response.send_redirect(trans.app.config.remote_user_logout_href) - else: - return trans.fill_template('/user/logout.mako', - refresh_frames=refresh_frames, - message=message, - status='done', - active_view="user") - @web.expose - def create(self, trans, cntrller='user', redirect_url='', refresh_frames=[], **kwd): - params = util.Params(kwd) - # If the honeypot field is not empty we are dealing with a bot. - honeypot_field = params.get('bear_field', '') - if honeypot_field != '': - return trans.show_error_message("You've been flagged as a possible bot. If you are not, please try registering again and fill the form out carefully. Go to the home page.") % url_for('/') - - message = util.restore_text(params.get('message', '')) - status = params.get('status', 'done') - use_panels = util.string_as_bool(kwd.get('use_panels', True)) - email = util.restore_text(params.get('email', '')) - # Do not sanitize passwords, so take from kwd - # instead of params ( which were sanitized ) - password = kwd.get('password', '') - confirm = kwd.get('confirm', '') - username = util.restore_text(params.get('username', '')) - subscribe = params.get('subscribe', '') - subscribe_checked = CheckboxField.is_checked(subscribe) - referer = trans.request.referer or '' - redirect = kwd.get('redirect', referer).strip() - is_admin = trans.user_is_admin - success = False - show_user_prepopulate_form = False - if not trans.app.config.allow_user_creation and not trans.user_is_admin: - message = 'User registration is disabled. Please contact your local Galaxy administrator for an account.' - if trans.app.config.error_email_to is not None: - message += ' Contact: %s' % trans.app.config.error_email_to - status = 'error' - else: - # check user is allowed to register - message, status = trans.app.auth_manager.check_registration_allowed(email, username, password) - if not message: - # Create the user, save all the user info and login to Galaxy - if params.get('create_user_button', False): - # Check email and password validity - message = self.__validate(trans, email, password, confirm, username) - if not message: - # All the values are valid - message, status, user, success = self.__register(trans, subscribe_checked=subscribe_checked, **kwd) - if success and not is_admin: - # The handle_user_login() method has a call to the history_set_default_permissions() method - # (needed when logging in with a history), user needs to have default permissions set before logging in - trans.handle_user_login(user) - trans.log_event("User created a new account") - trans.log_event("User logged in") - else: - status = 'error' - registration_warning_message = trans.app.config.registration_warning_message - if success: - if is_admin: - redirect_url = web.url_for('/admin/users?status=success&message=Created new user account.') - else: - redirect_url = web.url_for('/') - return trans.fill_template('/user/register.mako', - cntrller=cntrller, - email=email, - username=transform_publicname(trans, username), - subscribe_checked=subscribe_checked, - show_user_prepopulate_form=show_user_prepopulate_form, - use_panels=use_panels, - redirect=redirect, - redirect_url=redirect_url, - refresh_frames=refresh_frames, - registration_warning_message=registration_warning_message, - message=message, - status=status) - - def __register(self, trans, email=None, username=None, password=None, subscribe_checked=False, **kwd): - """Registers a new user.""" - email = util.restore_text(email) - username = util.restore_text(username) - status = None - message = None - is_admin = trans.user_is_admin - user = self.user_manager.create(email=email, username=username, password=password) - if subscribe_checked: - # subscribe user to email list - if trans.app.config.smtp_server is None: - status = "error" - message = "Now logged in as " + user.email + ". However, subscribing to the mailing list has failed because mail is not configured for this Galaxy instance.
    Please contact your local Galaxy administrator." - else: - body = 'Join Mailing list.\n' - to = trans.app.config.mailing_join_addr - frm = email - subject = 'Join Mailing List' - try: - util.send_mail(frm, to, subject, body, trans.app.config) - except Exception: - log.exception('Subscribing to the mailing list has failed.') - status = "warning" - message = "Now logged in as " + user.email + ". However, subscribing to the mailing list has failed." - if status != "error": - if not is_admin: - # The handle_user_login() method has a call to the history_set_default_permissions() method - # (needed when logging in with a history), user needs to have default permissions set before logging in - trans.handle_user_login(user) - trans.log_event("User created a new account") - trans.log_event("User logged in") - if trans.app.config.user_activation_on: - is_activation_sent = self.user_manager.send_activation_email(trans, email, username) - if is_activation_sent: - message = 'Now logged in as %s.
    Verification email has been sent to your email address. Please verify it by clicking the activation link in the email.
    Please check your spam/trash folder in case you cannot find the message.
    Return to the home page.' % (escape(user.email), url_for('/')) - else: - status = "error" - message = 'Unable to send activation email, please contact your local Galaxy administrator.' - if trans.app.config.error_email_to is not None: - message += ' Contact: %s' % trans.app.config.error_email_to - else: - # User activation is OFF, proceed without sending the activation email. - message = 'Now logged in as %s.
    Return to the home page.' % (escape(user.email), url_for('/')) - return message, status, user, status is None + @expose_api_anonymous_and_sessionless + def create(self, trans, payload={}, **kwd): + if not payload: + payload = kwd + message = trans.check_csrf_token(payload) + if message: + return self.message_exception(trans, message) + user, message = self.user_manager.register(trans, **payload) + if message: + return self.message_exception(trans, message, sanitize=False) + elif user and not trans.user_is_admin: + trans.handle_user_login(user) + trans.log_event("User created a new account") + trans.log_event("User logged in") + return {"message": "Success."} @web.expose def activate(self, trans, **kwd): diff --git a/lib/galaxy/webapps/galaxy/controllers/workflow.py b/lib/galaxy/webapps/galaxy/controllers/workflow.py index fdcce648d47..63283caadad 100644 --- a/lib/galaxy/webapps/galaxy/controllers/workflow.py +++ b/lib/galaxy/webapps/galaxy/controllers/workflow.py @@ -601,7 +601,7 @@ class WorkflowController(BaseUIController, SharableMixin, UsesStoredWorkflowMixi session.flush() try: - workflow, errors = workflow_contents_manager.update_workflow_from_dict( + workflow, errors = workflow_contents_manager.update_workflow_from_raw_description( trans, stored_workflow, workflow_data, @@ -771,45 +771,6 @@ class WorkflowController(BaseUIController, SharableMixin, UsesStoredWorkflowMixi trans.response.set_content_type('application/galaxy-archive') return stored_dict - @web.expose - @web.json - def import_workflow(self, trans, **kwd): - """ - Import a workflow through 3rd party services, e.g. myExperiment. - """ - message = "" - status = "done" - workflow_text = kwd.get('workflow_text') - workflow_source = kwd.get('workflow_source') - if workflow_text: - # Convert incoming workflow data from json - try: - data = json.loads(workflow_text) - except Exception: - data = None - message = "The data content does not appear to be a Galaxy workflow." - status = "error" - log.exception("Error importing workflow.") - if data: - # Create workflow if possible. If a required tool is not available in the local - # Galaxy instance, the tool information will be available in the step_dict. - workflow, missing_tool_tups = self._workflow_from_dict(trans, data, source=workflow_source) - workflow = workflow.latest_workflow - # Provide user feedback and show workflow list. - if workflow.has_errors: - message += "Imported, but some steps in this workflow have validation errors. " - status = "error" - if workflow.has_cycles: - message += "Imported, but this workflow contains cycles. " - status = "error" - else: - message += "Workflow %s imported successfully. " % escape(workflow.name) - else: - status = "error" - message = "Import request requires 'workflow_text'." - redirect_url = url_for('/') + 'workflows/list?status=' + status + '&message=%s' % escape(message) - return trans.response.send_redirect(redirect_url) - @web.expose def build_from_current_history(self, trans, job_ids=None, dataset_ids=None, dataset_collection_ids=None, workflow_name=None, dataset_names=None, dataset_collection_names=None): user = trans.get_user() diff --git a/lib/galaxy/webapps/reports/app.py b/lib/galaxy/webapps/reports/app.py index 27318ae99cd..25d12eb9688 100644 --- a/lib/galaxy/webapps/reports/app.py +++ b/lib/galaxy/webapps/reports/app.py @@ -3,7 +3,7 @@ import sys import time import galaxy.model -from galaxy.web import security +from galaxy.security import idencoding from galaxy.web.stack import application_stack_instance from . import config @@ -36,7 +36,7 @@ class UniverseApplication(object): else: self.targets_mysql = 'mysql' in self.config.database_connection # Security helper - self.security = security.SecurityHelper(id_secret=self.config.id_secret) + self.security = idencoding.IdEncodingHelper(id_secret=self.config.id_secret) # used for cachebusting -- refactor this into a *SINGLE* UniverseApplication base. self.server_starttime = int(time.time()) diff --git a/lib/galaxy/webapps/reports/config_schema.yml b/lib/galaxy/webapps/reports/config_schema.yml index b123a17c8f0..57f8d7b4f46 100644 --- a/lib/galaxy/webapps/reports/config_schema.yml +++ b/lib/galaxy/webapps/reports/config_schema.yml @@ -7,21 +7,14 @@ mapping: desc: | Galaxy Reports configuration options. mapping: - filter-with: - type: str - default: proxy-prefix - desc: | - If running behind a proxy server and Galaxy is served from a subdirectory, - enable the proxy-prefix filter and set the prefix in the - [filter:proxy-prefix] section above. - cookie_path: type: str desc: | - If proxy-prefix is enabled and you're running more than one Galaxy instance - behind one hostname, you will want to set this to the same path as the prefix - in the filter above. This value becomes the "path" attribute set in the - cookie so the cookies from each instance will not clobber each other. + When running multiple Galaxy Reports instances under separate URL prefixes on a + single hostname, you will want to set this to the same path as the prefix set + in the uWSGI "mount" configuration option above. This value becomes the "path" + attribute set in the cookie so the cookies from one instance will not clobber + those from another. log_level: type: str @@ -34,8 +27,8 @@ mapping: type: str default: sqlite:///./database/universe.sqlite?isolation_level=IMMEDIATE desc: | - Database connection - Galaxy reports are intended for production Galaxy instances, so sqlite (and the default value + Database connection. + Galaxy Reports are intended for production Galaxy instances, so sqlite (and the default value below) is not supported. An SQLAlchemy connection string should be used specify an external database. @@ -43,15 +36,13 @@ mapping: type: str default: database/files desc: | - Where dataset files are saved - Temporary storage for additional datasets, this should be shared through the cluster + Where dataset files are stored. new_file_path: type: str default: database/tmp desc: | - Where dataset files are saved - Temporary storage for additional datasets, this should be shared through the cluster + Where temporary files are stored. template_cache_path: type: str diff --git a/lib/galaxy/webapps/reports/framework/grids.py b/lib/galaxy/webapps/reports/framework/grids.py index 1cd9c69a135..677afa43847 100644 --- a/lib/galaxy/webapps/reports/framework/grids.py +++ b/lib/galaxy/webapps/reports/framework/grids.py @@ -6,7 +6,7 @@ from markupsafe import escape from six import string_types, text_type from sqlalchemy.sql.expression import and_, false, func, null, or_, true -from galaxy.model.item_attrs import RuntimeException, UsesAnnotations, UsesItemRatings +from galaxy.model.item_attrs import get_foreign_key, UsesAnnotations, UsesItemRatings from galaxy.util import sanitize_text, unicodify from galaxy.util.odict import odict from galaxy.web.framework import decorators, url_for @@ -503,16 +503,6 @@ class CommunityRatingColumn(GridColumn, UsesItemRatings): item_id=trans.security.encode_id(item.id)) def sort(self, trans, query, ascending, column_name=None): - def get_foreign_key(source_class, target_class): - """ Returns foreign key in source class that references target class. """ - target_fk = None - for fk in source_class.table.foreign_keys: - if fk.references(target_class.table): - target_fk = fk - break - if not target_fk: - raise RuntimeException("No foreign key found between objects: %s, %s" % source_class.table, target_class.table) - return target_fk # Get the columns that connect item's table and item's rating association table. item_rating_assoc_class = getattr(trans.model, '%sRatingAssociation' % self.model_class.__name__) foreign_key = get_foreign_key(item_rating_assoc_class, self.model_class) @@ -573,7 +563,7 @@ class CommunityTagsColumn(TextColumn): def get_value(self, trans, grid, item): return trans.fill_template("/tagging_common.mako", tag_type="community", trans=trans, user=trans.get_user(), tagged_item=item, elt_context=self.grid_name, - in_form=True, input_size="20", tag_click_fn="add_tag_to_grid_filter", use_toggle_link=True) + tag_click_fn="add_tag_to_grid_filter", use_toggle_link=True) def filter(self, trans, user, query, column_filter): """ Modify query to filter model_class by tag. Multiple filters are ANDed. """ @@ -609,8 +599,6 @@ class IndividualTagsColumn(CommunityTagsColumn): user=trans.user, tagged_item=item, elt_context=self.grid_name, - in_form=True, - input_size="20", tag_click_fn="add_tag_to_grid_filter", use_toggle_link=True) diff --git a/lib/galaxy/webapps/tool_shed/api/categories.py b/lib/galaxy/webapps/tool_shed/api/categories.py index c083cab0e16..ced76ffd0a6 100644 --- a/lib/galaxy/webapps/tool_shed/api/categories.py +++ b/lib/galaxy/webapps/tool_shed/api/categories.py @@ -73,10 +73,16 @@ class CategoriesController(BaseAPIController): Return information about the provided category and the repositories in that category. :param id: the encoded id of the Category object + :param sort_key: the field by which the repositories should be sorted + :param sort_order: ascending or descending sort + :param page: the page number to return Example: GET localhost:9009/api/categories/f9cad7b01a472135/repositories """ installable = util.asbool(kwd.get('installable', 'false')) + sort_key = kwd.get('sort_key', 'name') + sort_order = kwd.get('sort_order', 'asc') + page = kwd.get('page', None) category = suc.get_category(self.app, category_id) if category is None: category_dict = dict(message='Unable to locate category record for id %s.' % (str(id)), @@ -84,10 +90,16 @@ class CategoriesController(BaseAPIController): return category_dict category_dict = category.to_dict(view='element', value_mapper=self.__get_value_mapper(trans)) + category_dict['repository_count'] = suc.count_repositories_in_category(self.app, category_id) category_dict['url'] = web.url_for(controller='categories', action='show', id=trans.security.encode_id(category.id)) - repositories = repository_util.get_repositories_by_category(self.app, category.id, installable=installable) + repositories = repository_util.get_repositories_by_category(self.app, + category.id, + installable=installable, + sort_order=sort_order, + sort_key=sort_key, + page=page) category_dict['repositories'] = repositories return category_dict diff --git a/lib/galaxy/webapps/tool_shed/api/tools.py b/lib/galaxy/webapps/tool_shed/api/tools.py index 5e493ca2f83..0892575b455 100644 --- a/lib/galaxy/webapps/tool_shed/api/tools.py +++ b/lib/galaxy/webapps/tool_shed/api/tools.py @@ -178,7 +178,6 @@ class ToolsController(BaseAPIController): tool_dict.update({ 'help' : tool_help, 'citations' : bool(tool.citations), - 'biostar_url' : trans.app.config.biostar_url, 'requirements' : [{'name' : r.name, 'version' : r.version} for r in tool.requirements], 'state_inputs' : params_to_strings(tool.inputs, {}, trans.app), 'display' : tool.display_interface, diff --git a/lib/galaxy/webapps/tool_shed/app.py b/lib/galaxy/webapps/tool_shed/app.py index 587b86965f8..0fdd8ecec6d 100644 --- a/lib/galaxy/webapps/tool_shed/app.py +++ b/lib/galaxy/webapps/tool_shed/app.py @@ -10,9 +10,9 @@ import tool_shed.repository_registry import tool_shed.repository_types.registry from galaxy import tools from galaxy.config import configure_logging -from galaxy.managers.tags import CommunityTagManager +from galaxy.model.tags import CommunityTagHandler +from galaxy.security import idencoding from galaxy.util.dbkeys import GenomeBuilds -from galaxy.web import security from galaxy.web.stack import application_stack_instance from tool_shed.grids.repository_grid_filter_manager import RepositoryGridFilterManager from . import config @@ -51,10 +51,9 @@ class UniverseApplication(object): self.model = mapping.init(self.config.file_path, db_url, self.config.database_engine_options) - # Initialize the Tool Shed security helper. - self.security = security.SecurityHelper(id_secret=self.config.id_secret) + self.security = idencoding.IdEncodingHelper(id_secret=self.config.id_secret) # initialize the Tool Shed tag handler. - self.tag_handler = CommunityTagManager(self) + self.tag_handler = CommunityTagHandler(self) # Initialize the Tool Shed tool data tables. Never pass a configuration file here # because the Tool Shed should always have an empty dictionary! self.tool_data_tables = galaxy.tools.data.ToolDataTableManager(self.config.tool_data_path) diff --git a/lib/galaxy/webapps/tool_shed/config.py b/lib/galaxy/webapps/tool_shed/config.py index d1d5e6ead52..8fef0ca1944 100644 --- a/lib/galaxy/webapps/tool_shed/config.py +++ b/lib/galaxy/webapps/tool_shed/config.py @@ -116,7 +116,6 @@ class Configuration(object): self.support_url = kwargs.get('support_url', 'https://galaxyproject.org/support') self.wiki_url = kwargs.get('wiki_url', 'https://galaxyproject.org/') self.blog_url = kwargs.get('blog_url', None) - self.biostar_url = kwargs.get('biostar_url', None) self.screencasts_url = kwargs.get('screencasts_url', None) self.log_events = False self.cloud_controller_instance = False diff --git a/lib/galaxy/webapps/tool_shed/config_schema.yml b/lib/galaxy/webapps/tool_shed/config_schema.yml index dceb83e0c7b..430764a6f26 100644 --- a/lib/galaxy/webapps/tool_shed/config_schema.yml +++ b/lib/galaxy/webapps/tool_shed/config_schema.yml @@ -37,15 +37,14 @@ mapping: default: database/community_files required: false desc: | - Where tool shed repositories are stored. + Where Tool Shed repositories are stored. new_file_path: type: str default: database/tmp required: false desc: | - Temporary storage for additional datasets, - this should be shared through the cluster + Where temporary files are stored. builds_file_path: type: str @@ -293,14 +292,14 @@ mapping: default: smtp.your_tool_shed_server required: false desc: | - For use by email messages sent from the tool shed + For use by email messages sent from the Tool Shed. email_from: type: str default: your_tool_shed_email@server required: false desc: | - For use by email messages sent from the tool shed + For use by email messages sent from the Tool Shed. smtp_username: type: str @@ -522,10 +521,11 @@ mapping: default: null required: false desc: | - If proxy-prefix is enabled and you're running more than one Galaxy instance - behind one hostname, you will want to set this to the same path as the prefix - in the filter above. This value becomes the "path" attribute set in the - cookie so the cookies from each instance will not clobber each other. + When running multiple Tool Shed instances under separate URL prefixes on a + single hostname, you will want to set this to the same path as the prefix set + in the uWSGI "mount" configuration option above. This value becomes the "path" + attribute set in the cookie so the cookies from one instance will not clobber + those from another. log_actions: type: bool diff --git a/lib/galaxy/webapps/tool_shed/controllers/repository.py b/lib/galaxy/webapps/tool_shed/controllers/repository.py index 0935c15ca5f..63841ad1d78 100644 --- a/lib/galaxy/webapps/tool_shed/controllers/repository.py +++ b/lib/galaxy/webapps/tool_shed/controllers/repository.py @@ -870,7 +870,7 @@ class RepositoryController(BaseUIController, ratings_util.ItemRatings): mimetype = trans.app.datatypes_registry.get_mimetype_by_extension(extension) if mimetype: trans.response.set_content_type(mimetype) - return open(path_to_file, 'r') + return open(path_to_file, 'rb') return None @web.expose @@ -1721,6 +1721,7 @@ class RepositoryController(BaseUIController, ratings_util.ItemRatings): user_id = kwd.get('user_id', None) repository_id = kwd.get('repository_id', None) changeset_revision = kwd.get('changeset_revision', None) + self.validate_changeset_revision(trans, changeset_revision, repository_id) return trans.fill_template('/webapps/tool_shed/index.mako', repository_metadata=repository_metadata, can_administer_repositories=can_administer_repositories, @@ -2167,6 +2168,7 @@ class RepositoryController(BaseUIController, ratings_util.ItemRatings): status = kwd.get('status', 'done') repository = repository_util.get_repository_in_tool_shed(trans.app, repository_id) changeset_revision = kwd.get('changeset_revision', repository.tip(trans.app)) + self.validate_changeset_revision(trans, changeset_revision, repository_id) repository_metadata = metadata_util.get_repository_metadata_by_changeset_revision(trans.app, repository_id, changeset_revision) if repository_metadata: repository_metadata_id = trans.security.encode_id(repository_metadata.id), @@ -2801,13 +2803,7 @@ class RepositoryController(BaseUIController, ratings_util.ItemRatings): repo = hg_util.get_repo_for_repository(trans.app, repository=repository) avg_rating, num_ratings = self.get_ave_item_rating_data(trans.sa_session, repository, webapp_model=trans.model) changeset_revision = kwd.get('changeset_revision', repository.tip(trans.app)) - if not hg_util.get_changectx_for_changeset(repo, changeset_revision): - message = 'Invalid changeset revision' - return trans.response.send_redirect(web.url_for(controller='repository', - action='index', - repository_id=id, - message=message, - status='error')) + self.validate_changeset_revision(trans, changeset_revision, id) repository.share_url = repository_util.generate_sharable_link_for_repository_in_tool_shed(repository, changeset_revision=changeset_revision) repository.clone_url = common_util.generate_clone_url_for_repository_in_tool_shed(trans.user, repository) display_reviews = kwd.get('display_reviews', False) @@ -2902,6 +2898,7 @@ class RepositoryController(BaseUIController, ratings_util.ItemRatings): tool_lineage = [] tool = None guid = None + self.validate_changeset_revision(trans, changeset_revision, repository_id) revision_label = hg_util.get_revision_label(trans.app, repository, changeset_revision, include_date=False) repository_metadata = metadata_util.get_repository_metadata_by_changeset_revision(trans.app, repository_id, changeset_revision) if repository_metadata: @@ -2989,3 +2986,16 @@ class RepositoryController(BaseUIController, ratings_util.ItemRatings): metadata=metadata, message=message, status=status) + + def validate_changeset_revision(self, trans, changeset_revision, repository_id): + """In case changeset revision is invalid send them to the repository page""" + if changeset_revision: + repository = repository_util.get_repository_in_tool_shed(trans.app, repository_id) + repo = hg_util.get_repo_for_repository(trans.app, repository=repository) + if not hg_util.get_changectx_for_changeset(repo, changeset_revision): + message = 'Invalid changeset revision' + return trans.response.send_redirect(web.url_for(controller='repository', + action='index', + repository_id=repository_id, + message=message, + status='error')) diff --git a/lib/galaxy/webapps/tool_shed/controllers/user.py b/lib/galaxy/webapps/tool_shed/controllers/user.py index 48c7b25164f..12d04e0e840 100644 --- a/lib/galaxy/webapps/tool_shed/controllers/user.py +++ b/lib/galaxy/webapps/tool_shed/controllers/user.py @@ -9,10 +9,12 @@ from galaxy import ( web ) from galaxy.security.validate_user_input import ( + transform_publicname, validate_email, validate_publicname ) from galaxy.web import url_for +from galaxy.web.form_builder import CheckboxField from galaxy.webapps.galaxy.controllers.user import User as BaseUser log = logging.getLogger(__name__) @@ -62,9 +64,6 @@ class User(BaseUser): message = 'You are already logged in.' status = 'info' elif kwd.get('login_button', False): - csrf_check = trans.check_csrf_token() - if csrf_check: - return csrf_check response = self.__validate_login(trans, **kwd) if trans.response.status == 400: trans.response.status = 200 @@ -97,6 +96,119 @@ class User(BaseUser): form_input_auto_focus=True, active_view="user") + @web.expose + def create(self, trans, cntrller='user', redirect_url='', refresh_frames=[], **kwd): + params = util.Params(kwd) + # If the honeypot field is not empty we are dealing with a bot. + honeypot_field = params.get('bear_field', '') + if honeypot_field != '': + return trans.show_error_message("You've been flagged as a possible bot. If you are not, please try registering again and fill the form out carefully. Go to the home page.") % url_for('/') + + message = util.restore_text(params.get('message', '')) + status = params.get('status', 'done') + use_panels = util.string_as_bool(kwd.get('use_panels', True)) + email = util.restore_text(params.get('email', '')) + # Do not sanitize passwords, so take from kwd + # instead of params ( which were sanitized ) + password = kwd.get('password', '') + confirm = kwd.get('confirm', '') + username = util.restore_text(params.get('username', '')) + subscribe = params.get('subscribe', '') + subscribe_checked = CheckboxField.is_checked(subscribe) + referer = trans.request.referer or '' + redirect = kwd.get('redirect', referer).strip() + is_admin = trans.user_is_admin + success = False + show_user_prepopulate_form = False + if not trans.app.config.allow_user_creation and not trans.user_is_admin: + message = 'User registration is disabled. Please contact your local Galaxy administrator for an account.' + if trans.app.config.error_email_to is not None: + message += ' Contact: %s' % trans.app.config.error_email_to + status = 'error' + else: + # check user is allowed to register + message, status = trans.app.auth_manager.check_registration_allowed(email, username, password) + if not message: + # Create the user, save all the user info and login to Galaxy + if params.get('create_user_button', False): + # Check email and password validity + message = self.__validate(trans, email, password, confirm, username) + if not message: + # All the values are valid + message, status, user, success = self.__register(trans, subscribe_checked=subscribe_checked, **kwd) + if success and not is_admin: + # The handle_user_login() method has a call to the history_set_default_permissions() method + # (needed when logging in with a history), user needs to have default permissions set before logging in + trans.handle_user_login(user) + trans.log_event("User created a new account") + trans.log_event("User logged in") + else: + status = 'error' + registration_warning_message = trans.app.config.registration_warning_message + if success: + if is_admin: + redirect_url = web.url_for('/admin/users?status=success&message=Created new user account.') + else: + redirect_url = web.url_for('/') + return trans.fill_template('/webapps/tool_shed/user/register.mako', + cntrller=cntrller, + email=email, + username=transform_publicname(trans, username), + subscribe_checked=subscribe_checked, + show_user_prepopulate_form=show_user_prepopulate_form, + use_panels=use_panels, + redirect=redirect, + redirect_url=redirect_url, + refresh_frames=refresh_frames, + registration_warning_message=registration_warning_message, + message=message, + status=status) + + def __register(self, trans, email=None, username=None, password=None, subscribe_checked=False, **kwd): + """Registers a new user.""" + email = util.restore_text(email) + username = util.restore_text(username) + status = None + message = None + is_admin = trans.user_is_admin + user = self.user_manager.create(email=email, username=username, password=password) + if subscribe_checked: + # subscribe user to email list + if trans.app.config.smtp_server is None: + status = "error" + message = "Now logged in as " + user.email + ". However, subscribing to the mailing list has failed because mail is not configured for this Galaxy instance.
    Please contact your local Galaxy administrator." + else: + body = 'Join Mailing list.\n' + to = trans.app.config.mailing_join_addr + frm = email + subject = 'Join Mailing List' + try: + util.send_mail(frm, to, subject, body, trans.app.config) + except Exception: + log.exception('Subscribing to the mailing list has failed.') + status = "warning" + message = "Now logged in as " + user.email + ". However, subscribing to the mailing list has failed." + if status != "error": + if not is_admin: + # The handle_user_login() method has a call to the history_set_default_permissions() method + # (needed when logging in with a history), user needs to have default permissions set before logging in + trans.handle_user_login(user) + trans.log_event("User created a new account") + trans.log_event("User logged in") + if trans.app.config.user_activation_on: + is_activation_sent = self.user_manager.send_activation_email(trans, email, username) + if is_activation_sent: + message = 'Now logged in as %s.
    Verification email has been sent to your email address. Please verify it by clicking the activation link in the email.
    Please check your spam/trash folder in case you cannot find the message.
    Return to the home page.' % (escape(user.email), url_for('/')) + else: + status = "error" + message = 'Unable to send activation email, please contact your local Galaxy administrator.' + if trans.app.config.error_email_to is not None: + message += ' Contact: %s' % trans.app.config.error_email_to + else: + # User activation is OFF, proceed without sending the activation email. + message = 'Now logged in as %s.
    Return to the home page.' % (escape(user.email), url_for('/')) + return message, status, user, status is None + @web.expose def reset_password(self, trans, email=None, **kwd): """Reset the user's password. Send an email with token that allows a password change.""" @@ -327,6 +439,20 @@ class User(BaseUser): return trans.show_ok_message('The password has been changed and any other existing Galaxy sessions have been logged out (but jobs in histories in those sessions will not be interrupted).') return trans.fill_template('/webapps/tool_shed/user/change_password.mako', token=token, id=id) + @web.expose + def logout(self, trans, logout_all=False, **kwd): + trans.handle_user_logout(logout_all=logout_all) + message = 'You have been logged out.
    To log in again go to the home page.' % \ + (url_for('/')) + if trans.app.config.use_remote_user and trans.app.config.remote_user_logout_href: + trans.response.send_redirect(trans.app.config.remote_user_logout_href) + else: + return trans.fill_template('/webapps/tool_shed/user/logout.mako', + refresh_frames=['masthead'], + message=message, + status='done', + active_view="user") + def __validate(self, trans, email, password, confirm, username): # If coming from the tool shed webapp, we'll require a public user name if not username: diff --git a/lib/galaxy/webapps/tool_shed/model/__init__.py b/lib/galaxy/webapps/tool_shed/model/__init__.py index bf3ec3b30db..052fc043669 100644 --- a/lib/galaxy/webapps/tool_shed/model/__init__.py +++ b/lib/galaxy/webapps/tool_shed/model/__init__.py @@ -304,19 +304,20 @@ class Repository(Dictifiable): class RepositoryMetadata(Dictifiable): - dict_collection_visible_keys = ['id', 'repository_id', 'changeset_revision', 'malicious', 'downloadable', 'missing_test_components', + dict_collection_visible_keys = ['id', 'repository_id', 'numeric_revision', 'changeset_revision', 'malicious', 'downloadable', 'missing_test_components', 'has_repository_dependencies', 'includes_datatypes', 'includes_tools', 'includes_tool_dependencies', 'includes_tools_for_display_in_tool_panel', 'includes_workflows'] - dict_element_visible_keys = ['id', 'repository_id', 'changeset_revision', 'malicious', 'downloadable', 'missing_test_components', + dict_element_visible_keys = ['id', 'repository_id', 'numeric_revision', 'changeset_revision', 'malicious', 'downloadable', 'missing_test_components', 'has_repository_dependencies', 'includes_datatypes', 'includes_tools', 'includes_tool_dependencies', 'includes_tools_for_display_in_tool_panel', 'includes_workflows', 'repository_dependencies'] - def __init__(self, id=None, repository_id=None, changeset_revision=None, metadata=None, tool_versions=None, malicious=False, + def __init__(self, id=None, repository_id=None, numeric_revision=None, changeset_revision=None, metadata=None, tool_versions=None, malicious=False, downloadable=False, missing_test_components=None, tools_functionally_correct=False, test_install_error=False, has_repository_dependencies=False, includes_datatypes=False, includes_tools=False, includes_tool_dependencies=False, includes_workflows=False): self.id = id self.repository_id = repository_id + self.numeric_revision = numeric_revision self.changeset_revision = changeset_revision self.metadata = metadata self.tool_versions = tool_versions diff --git a/lib/galaxy/webapps/tool_shed/model/mapping.py b/lib/galaxy/webapps/tool_shed/model/mapping.py index 5728da9d3ce..a461e69b249 100644 --- a/lib/galaxy/webapps/tool_shed/model/mapping.py +++ b/lib/galaxy/webapps/tool_shed/model/mapping.py @@ -129,6 +129,7 @@ RepositoryMetadata.table = Table("repository_metadata", metadata, Column("update_time", DateTime, default=now, onupdate=now), Column("repository_id", Integer, ForeignKey("repository.id"), index=True), Column("changeset_revision", TrimmedString(255), index=True), + Column("numeric_revision", Integer, index=True), Column("metadata", JSONType, nullable=True), Column("tool_versions", JSONType, nullable=True), Column("malicious", Boolean, default=False), diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0026_add_numeric_revision_column.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0026_add_numeric_revision_column.py new file mode 100644 index 00000000000..f9839b9ae66 --- /dev/null +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0026_add_numeric_revision_column.py @@ -0,0 +1,45 @@ +"""Migration script to add the numeric_revision column to the repository metadata table.""" +from __future__ import print_function + +import logging +import sys + +from sqlalchemy import Column, Integer, MetaData, Table + +log = logging.getLogger(__name__) +log.setLevel(logging.DEBUG) +handler = logging.StreamHandler(sys.stdout) +format = "%(name)s %(levelname)s %(asctime)s %(message)s" +formatter = logging.Formatter(format) +handler.setFormatter(formatter) +log.addHandler(handler) + +metadata = MetaData() + + +def upgrade(migrate_engine): + print(__doc__) + metadata.bind = migrate_engine + metadata.reflect() + RepositoryMetadata_table = Table("repository_metadata", metadata, autoload=True) + c = Column("numeric_revision", Integer, index=True) + try: + # Create + c.create(RepositoryMetadata_table, index_name="ix_numeric_revision") + assert c is RepositoryMetadata_table.c.numeric_revision + except Exception: + log.exception("Adding numeric_revision column to the repository table failed.") + # Update the numeric_revision column to have the default undefined value. + cmd = "UPDATE repository_metadata SET numeric_revision = -1" + migrate_engine.execute(cmd) + + +def downgrade(migrate_engine): + metadata.bind = migrate_engine + metadata.reflect() + # Drop numeric_revision column from repository table. + RepositoryMetadata_table = Table("repository_metadata", metadata, autoload=True) + try: + RepositoryMetadata_table.c.numeric_revision.drop() + except Exception: + log.exception("Dropping column numeric_revision from the repository table failed.") diff --git a/lib/galaxy/workflow/modules.py b/lib/galaxy/workflow/modules.py index 707695844fb..d3a2c16617a 100644 --- a/lib/galaxy/workflow/modules.py +++ b/lib/galaxy/workflow/modules.py @@ -14,14 +14,16 @@ from galaxy import ( model, web ) -from galaxy.dataset_collections import matching from galaxy.exceptions import ToolMissingException from galaxy.jobs.actions.post import ActionBox from galaxy.model import PostJobAction +from galaxy.model.dataset_collections import matching from galaxy.tools import ( + DatabaseOperationTool, DefaultToolState, ToolInputsNotReadyException ) +from galaxy.tools.actions import filter_output from galaxy.tools.execute import execute, MappingParameters, PartialJobExecution from galaxy.tools.parameters import ( check_param, @@ -33,6 +35,7 @@ from galaxy.tools.parameters.basic import ( ConnectedValue, DataCollectionToolParameter, DataToolParameter, + HiddenToolParameter, is_runtime_value, parameter_types, runtime_to_json, @@ -42,6 +45,7 @@ from galaxy.tools.parameters.basic import ( ) from galaxy.tools.parameters.history_query import HistoryQuery from galaxy.tools.parameters.wrapped import make_dict_copy +from galaxy.tools.parser.output_objects import ToolExpressionOutput from galaxy.util.bunch import Bunch from galaxy.util.json import safe_loads from galaxy.util.odict import odict @@ -144,7 +148,7 @@ class WorkflowModule(object): else: self.state.inputs = safe_loads(state) or {} - def get_errors(self): + def get_errors(self, **kwargs): """ This returns a step related error message as string or None """ return None @@ -314,13 +318,10 @@ class WorkflowModule(object): def _find_collections_to_match(self, progress, step, all_inputs): collections_to_match = matching.CollectionsToMatch() + dataset_collection_type_descriptions = self.trans.app.dataset_collections_service.collection_type_descriptions for input_dict in all_inputs: name = input_dict["name"] - multiple = input_dict["multiple"] - if multiple: - continue - data = progress.replacement_for_input(step, input_dict) can_map_over = hasattr(data, "collection") # and data.collection.allow_implicit_mapping @@ -329,13 +330,23 @@ class WorkflowModule(object): is_data_param = input_dict["input_type"] == "dataset" if is_data_param: - collections_to_match.add(name, data) + multiple = input_dict["multiple"] + if multiple: + # multiple="true" data input, acts like "list" collection_type. + # just need to figure out subcollection_type_description + history_query = HistoryQuery.from_collection_types( + ['list'], + dataset_collection_type_descriptions, + ) + subcollection_type_description = history_query.can_map_over(data) + if subcollection_type_description: + collections_to_match.add(name, data, subcollection_type=subcollection_type_description) + else: + collections_to_match.add(name, data) continue is_data_collection_param = input_dict["input_type"] == "dataset_collection" if is_data_collection_param: - dataset_collection_type_descriptions = self.trans.app.dataset_collections_service.collection_type_descriptions - history_query = HistoryQuery.from_collection_types( input_dict.get("collection_types", None), dataset_collection_type_descriptions, @@ -411,9 +422,21 @@ class SubWorkflowModule(WorkflowModule): extensions=["data"], input_type=step_to_input_type[step_type], ) + if step.type == 'data_collection_input': + input['collection_type'] = step.tool_inputs.get('collection_type') if step.tool_inputs else None inputs.append(input) return inputs + def get_modules(self): + return [module_factory.from_workflow_step(self.trans, step) for step in self.subworkflow.steps] + + def get_errors(self, **kwargs): + errors = (module.get_errors(include_tool_id=True) for module in self.get_modules()) + errors = [e for e in errors if e] + if any(errors): + return errors + return None + def get_all_outputs(self, data_only=False): outputs = [] if hasattr(self.subworkflow, 'workflow_outputs'): @@ -422,23 +445,29 @@ class SubWorkflowModule(WorkflowModule): subworkflow_dict = workflow_contents_manager._workflow_to_dict_editor(trans=self.trans, stored=self.subworkflow.stored_workflow, workflow=self.subworkflow, - tooltip=False) + tooltip=False, + is_subworkflow=True) for order_index in sorted(subworkflow_dict['steps']): step = subworkflow_dict['steps'][order_index] - data_outputs = subworkflow_dict['steps'][order_index]['outputs'] + data_outputs = step['outputs'] for workflow_output in step['workflow_outputs']: label = workflow_output['label'] if not label: label = "%s:%s" % (order_index, workflow_output['output_name']) + workflow_output_uuid = workflow_output.get('uuid') or object() for data_output in data_outputs: - if data_output['name'] == workflow_output['output_name']: + data_output_uuid = data_output.get('uuid') or object() + if data_output['name'] == workflow_output['output_name'] or data_output_uuid == workflow_output_uuid: data_output['label'] = label data_output['name'] = label # That's the right data_output break else: - # This hopefully can't happen, but let's be clear - raise Exception("Workflow output '%s' defined, but not listed among data outputs" % workflow_output['output_name']) + # This can happen when importing workflows with missing tools. + # We can't raise an exception here, as that would prevent loading + # the workflow. + log.error("Workflow output '%s' defined, but not listed among data outputs" % workflow_output['output_name']) + continue outputs.append(data_output) return outputs @@ -746,11 +775,12 @@ class ToolModule(WorkflowModule): type = "tool" name = "Tool" - def __init__(self, trans, tool_id, tool_version=None, exact_tools=True, **kwds): + def __init__(self, trans, tool_id, tool_version=None, exact_tools=True, tool_uuid=None, **kwds): super(ToolModule, self).__init__(trans, content_id=tool_id, **kwds) self.tool_id = tool_id self.tool_version = tool_version - self.tool = trans.app.toolbox.get_tool(tool_id, tool_version=tool_version, exact=exact_tools) + self.tool_uuid = tool_uuid + self.tool = trans.app.toolbox.get_tool(tool_id, tool_version=tool_version, exact=exact_tools, tool_uuid=tool_uuid) if self.tool and tool_version and exact_tools and str(self.tool.version) != str(tool_version): log.info("Exact tool specified during workflow module creation for [%s] but couldn't find correct version [%s]." % (tool_id, tool_version)) self.tool = None @@ -764,12 +794,25 @@ class ToolModule(WorkflowModule): @classmethod def from_dict(Class, trans, d, **kwds): tool_id = d.get('content_id') or d.get('tool_id') - if tool_id is None: - raise exceptions.RequestParameterInvalidException("No tool id could be located for step [%s]." % d) tool_version = d.get('tool_version') if tool_version: tool_version = str(tool_version) - module = super(ToolModule, Class).from_dict(trans, d, tool_id=tool_id, tool_version=tool_version, **kwds) + tool_uuid = d.get('tool_uuid', None) + if tool_id is None and tool_uuid is None: + tool_representation = d.get("tool_representation") + if tool_representation: + create_request = { + "representation": tool_representation, + } + if not trans.user_is_admin: + raise exceptions.AdminRequiredException("Only admin users can create tools dynamically.") + dynamic_tool = trans.app.dynamic_tool_manager.create_tool( + create_request, allow_load=False + ) + tool_uuid = dynamic_tool.uuid + if tool_id is None and tool_uuid is None: + raise exceptions.RequestParameterInvalidException("No content id could be located for for step [%s]" % d) + module = super(ToolModule, Class).from_dict(trans, d, tool_id=tool_id, tool_version=tool_version, tool_uuid=tool_uuid, **kwds) module.post_job_actions = d.get('post_job_actions', {}) module.workflow_outputs = d.get('workflow_outputs', []) if module.tool: @@ -785,16 +828,20 @@ class ToolModule(WorkflowModule): @classmethod def from_workflow_step(Class, trans, step, **kwds): - tool_id = trans.app.toolbox.get_tool_id(step.tool_id) or step.tool_id + if step.tool_id is not None: + tool_id = trans.app.toolbox.get_tool_id(step.tool_id) or step.tool_id + else: + tool_id = None tool_version = step.tool_version - module = super(ToolModule, Class).from_workflow_step(trans, step, tool_id=tool_id, tool_version=tool_version, **kwds) + tool_uuid = step.tool_uuid + module = super(ToolModule, Class).from_workflow_step(trans, step, tool_id=tool_id, tool_version=tool_version, tool_uuid=tool_uuid, **kwds) module.workflow_outputs = step.workflow_outputs module.post_job_actions = {} for pja in step.post_job_actions: module.post_job_actions[pja.action_type] = pja if module.tool: message = "" - if step.tool_id != module.tool_id: # This means the exact version of the tool is not installed. We inform the user. + if step.tool_id and step.tool_id != module.tool_id: # This means the exact version of the tool is not installed. We inform the user. old_tool_shed = step.tool_id.split("/repos/")[0] if old_tool_shed not in tool_id: # Only display the following warning if the tool comes from a different tool shed old_tool_shed_url = common_util.get_tool_shed_url_from_tool_shed_registry(trans.app, old_tool_shed) @@ -819,7 +866,13 @@ class ToolModule(WorkflowModule): def save_to_step(self, step): super(ToolModule, self).save_to_step(step) step.tool_id = self.tool_id - step.tool_version = self.get_version() + if self.tool: + step.tool_version = self.get_version() + else: + step.tool_version = self.tool_version + tool_uuid = getattr(self, "tool_uuid", None) + if tool_uuid: + step.dynamic_tool = self.trans.app.dynamic_tool_manager.get_tool_by_uuid(tool_uuid) for k, v in self.post_job_actions.items(): pja = self.__to_pja(k, v, step) self.trans.sa_session.add(pja) @@ -841,8 +894,12 @@ class ToolModule(WorkflowModule): # ---- Configuration time ----------------------------------------------- - def get_errors(self): - return None if self.tool else "Tool is not installed." + def get_errors(self, include_tool_id=False, **kwargs): + if not self.tool: + if include_tool_id: + return "%s is not installed" % self.tool_id + else: + return "Tool is not installed" def get_inputs(self): return self.tool.inputs if self.tool else {} @@ -863,6 +920,8 @@ class ToolModule(WorkflowModule): skip = not visible or not is_data elif connectable_only: skip = not visible or not (is_data or is_connectable) + elif isinstance(input, HiddenToolParameter): + skip = False else: skip = not visible if not skip: @@ -900,7 +959,11 @@ class ToolModule(WorkflowModule): data_outputs = [] if self.tool: for name, tool_output in self.tool.outputs.items(): + if filter_output(tool_output, self.state.inputs): + continue extra_kwds = {} + if isinstance(tool_output, ToolExpressionOutput): + extra_kwds['parameter'] = True if tool_output.collection: extra_kwds["collection"] = True collection_type = tool_output.structure.collection_type @@ -937,6 +1000,7 @@ class ToolModule(WorkflowModule): dict( name=name, extensions=formats, + type=tool_output.output_type, **extra_kwds ) ) @@ -982,7 +1046,8 @@ class ToolModule(WorkflowModule): return ConnectedValue() visit_input_values(self.tool.inputs, self.state.inputs, callback) else: - raise ToolMissingException("Tool %s missing. Cannot add dummy datasets." % self.tool_id) + raise ToolMissingException("Tool %s missing. Cannot add dummy datasets." % self.tool_id, + tool_id=self.tool_id) def get_post_job_actions(self, incoming): return ActionBox.handle_incoming(incoming) @@ -1082,7 +1147,8 @@ class ToolModule(WorkflowModule): state.inputs[RUNTIME_STEP_META_STATE_KEY] = step_metadata_runtime_state return state, step_errors else: - raise ToolMissingException("Tool %s missing. Cannot compute runtime state." % self.tool_id) + raise ToolMissingException("Tool %s missing. Cannot compute runtime state." % self.tool_id, + tool_id=self.tool_id) def decode_runtime_state(self, runtime_state): """ Take runtime state from persisted invocation and convert it @@ -1094,12 +1160,13 @@ class ToolModule(WorkflowModule): self.__restore_step_meta_runtime_state(loads(runtime_state[RUNTIME_STEP_META_STATE_KEY])) return state else: - raise ToolMissingException("Tool %s missing. Cannot recover runtime state." % self.tool_id) + raise ToolMissingException("Tool %s missing. Cannot recover runtime state." % self.tool_id, + tool_id=self.tool_id) def execute(self, trans, progress, invocation_step, use_cached_job=False): invocation = invocation_step.workflow_invocation step = invocation_step.workflow_step - tool = trans.app.toolbox.get_tool(step.tool_id, tool_version=step.tool_version) + tool = trans.app.toolbox.get_tool(step.tool_id, tool_version=step.tool_version, tool_uuid=step.tool_uuid) if not tool.is_workflow_compatible: message = "Specified tool [%s] in workflow is not workflow-compatible." % tool.id raise Exception(message) @@ -1136,8 +1203,9 @@ class ToolModule(WorkflowModule): replacement = NO_REPLACEMENT if iteration_elements and prefixed_name in iteration_elements: - if isinstance(input, DataToolParameter): - # Pull out dataset instance from element. + if isinstance(input, DataToolParameter) and hasattr(iteration_elements[prefixed_name], 'dataset_instance'): + # Pull out dataset instance (=HDA) from element and set a temporary element_identifier attribute + # See https://github.com/galaxyproject/galaxy/pull/1693 for context. replacement = iteration_elements[prefixed_name].dataset_instance if hasattr(iteration_elements[prefixed_name], u'element_identifier') and iteration_elements[prefixed_name].element_identifier: replacement.element_identifier = iteration_elements[prefixed_name].element_identifier @@ -1256,7 +1324,7 @@ class ToolModule(WorkflowModule): flush_required = False effective_post_job_actions = self._effective_post_job_actions(step) for pja in effective_post_job_actions: - if pja.action_type in ActionBox.immediate_actions: + if pja.action_type in ActionBox.immediate_actions or isinstance(self.tool, DatabaseOperationTool): ActionBox.execute(self.trans.app, self.trans.sa_session, pja, job, replacement_dict) else: pjaa = model.PostJobActionAssociation(pja, job_id=job.id) diff --git a/lib/galaxy/workflow/run.py b/lib/galaxy/workflow/run.py index b75bee7ecac..961a673e9f4 100644 --- a/lib/galaxy/workflow/run.py +++ b/lib/galaxy/workflow/run.py @@ -1,3 +1,4 @@ +import json import logging import uuid @@ -367,6 +368,17 @@ class WorkflowProgress(object): delayed_why = "dependent collection [%s] not yet populated with datasets" % replacement.id raise modules.DelayedWorkflowEvaluation(why=delayed_why) + + is_hda = isinstance(replacement, model.HistoryDatasetAssociation) + if not is_data and is_hda: + if replacement.is_ok: + with open(replacement.file_name, 'r') as f: + replacement = json.load(f) + elif replacement.is_pending: + raise modules.DelayedWorkflowEvaluation() + else: + raise modules.CancelWorkflowEvaluation() + return replacement def get_replacement_workflow_output(self, workflow_output): diff --git a/lib/galaxy/workflow/scheduling_manager.py b/lib/galaxy/workflow/scheduling_manager.py index 968747fbcc1..f88e9b23e78 100644 --- a/lib/galaxy/workflow/scheduling_manager.py +++ b/lib/galaxy/workflow/scheduling_manager.py @@ -7,8 +7,8 @@ import galaxy.workflow.schedulers from galaxy import model from galaxy.exceptions import HandlerAssignmentError from galaxy.util import plugin_config -from galaxy.util.handlers import ConfiguresHandlers, HANDLER_ASSIGNMENT_METHODS from galaxy.util.monitors import Monitors +from galaxy.web.stack.handlers import ConfiguresHandlers, HANDLER_ASSIGNMENT_METHODS from galaxy.web.stack.message import WorkflowSchedulingMessage log = logging.getLogger(__name__) diff --git a/lib/galaxy_ext/expressions/__init__.py b/lib/galaxy_ext/expressions/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/galaxy_ext/expressions/handle_job.py b/lib/galaxy_ext/expressions/handle_job.py new file mode 100644 index 00000000000..680e16fad20 --- /dev/null +++ b/lib/galaxy_ext/expressions/handle_job.py @@ -0,0 +1,45 @@ +""" +Execute an external process to evaluate expressions for Galaxy jobs. + +Galaxy should be importable on sys.path . +""" +import json +import logging +import os +import sys + +# insert *this* galaxy before all others on sys.path +sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))) + +try: + from cwltool import expression +except ImportError: + expression = None + +from galaxy.tools.expressions import evaluate + +logging.basicConfig() +log = logging.getLogger(__name__) + + +def run(environment_path=None): + if expression is None: + raise Exception("Python library cwltool must be available to evaluate expressions.") + + if environment_path is None: + environment_path = os.environ.get("GALAXY_EXPRESSION_INPUTS") + with open(environment_path, "r") as f: + raw_inputs = json.load(f) + + outputs = raw_inputs["outputs"] + inputs = raw_inputs.copy() + del inputs["outputs"] + + result = evaluate(None, inputs) + + for output in outputs: + path = output["path"] + from_expression = "$(" + output["from_expression"] + ")" + output_value = expression.interpolate(from_expression, result) + with open(path, "w") as f: + json.dump(output_value, f) diff --git a/lib/galaxy_ext/metadata/_provided_metadata.py b/lib/galaxy_ext/metadata/_provided_metadata.py new file mode 120000 index 00000000000..6e81f9dc9f3 --- /dev/null +++ b/lib/galaxy_ext/metadata/_provided_metadata.py @@ -0,0 +1 @@ +../../galaxy/tools/provided_metadata.py \ No newline at end of file diff --git a/lib/galaxy_ext/metadata/set_metadata.py b/lib/galaxy_ext/metadata/set_metadata.py index d0d1e6e1972..a3f7ec9b844 100644 --- a/lib/galaxy_ext/metadata/set_metadata.py +++ b/lib/galaxy_ext/metadata/set_metadata.py @@ -24,6 +24,7 @@ from sqlalchemy.orm import clear_mappers import galaxy.model.mapping # need to load this before we unpickle, in order to setup properties assigned by the mappers from galaxy.model.custom_types import total_size from galaxy.util import stringify_dictionary_keys +from ._provided_metadata import parse_tool_provided_metadata # ensure supported version assert sys.version_info[:2] >= (2, 7), 'Python version must be at least 2.7, this is: %s' % sys.version @@ -52,9 +53,7 @@ def set_meta_with_tool_provided(dataset_instance, file_dict, set_meta_kwds, data # Set special metadata property that will reload this on server side. setattr(dataset_instance.metadata, "__extension__", extension) except Exception: - # TODO: log this when metadata can log stuff... - # https://trello.com/c/Nrwodu9d - pass + log.exception("Problem sniffing datatype.") for metadata_name, metadata_value in file_dict.get('metadata', {}).items(): setattr(dataset_instance.metadata, metadata_name, metadata_value) @@ -70,8 +69,69 @@ def set_meta_with_tool_provided(dataset_instance, file_dict, set_meta_kwds, data def set_metadata(): - # locate galaxy_root for loading datatypes - galaxy_root = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir)) + if len(sys.argv) == 1: + set_metadata_portable() + else: + set_metadata_legacy() + + +def set_metadata_portable(): + import galaxy.model + galaxy.model.metadata.MetadataTempFile.tmp_dir = tool_job_working_directory = os.path.abspath(os.getcwd()) + + metadata_params_path = os.path.join("metadata", "params.json") + try: + with open(metadata_params_path, "r") as f: + metadata_params = json.load(f) + except IOError: + raise Exception("Failed to find metadata/params.json from cwd [%s]" % tool_job_working_directory) + datatypes_config = metadata_params["datatypes_config"] + job_metadata = metadata_params["job_metadata"] + max_metadata_value_size = metadata_params.get("max_metadata_value_size") or 0 + outputs = metadata_params["outputs"] + + datatypes_registry = validate_and_load_datatypes_config(datatypes_config) + tool_provided_metadata = load_job_metadata(job_metadata) + + def set_meta(new_dataset_instance, file_dict): + set_meta_with_tool_provided(new_dataset_instance, file_dict, set_meta_kwds, datatypes_registry, max_metadata_value_size) + + for output_name, output_dict in outputs.items(): + filename_in = os.path.join("metadata/metadata_in_%s" % output_name) + filename_kwds = os.path.join("metadata/metadata_kwds_%s" % output_name) + filename_out = os.path.join("metadata/metadata_out_%s" % output_name) + filename_results_code = os.path.join("metadata/metadata_results_%s" % output_name) + override_metadata = os.path.join("metadata/metadata_override_%s" % output_name) + dataset_filename_override = output_dict["filename_override"] + + # Same block as below... + set_meta_kwds = stringify_dictionary_keys(json.load(open(filename_kwds))) # load kwds; need to ensure our keywords are not unicode + try: + dataset = cPickle.load(open(filename_in, 'rb')) # load DatasetInstance + dataset.dataset.external_filename = dataset_filename_override + store_by = metadata_params.get("object_store_store_by", "id") + extra_files_dir_name = "dataset_%s_files" % getattr(dataset.dataset, store_by) + files_path = os.path.abspath(os.path.join(tool_job_working_directory, extra_files_dir_name)) + dataset.dataset.external_extra_files_path = files_path + file_dict = tool_provided_metadata.get_dataset_meta(output_name, dataset.dataset.id) + if 'ext' in file_dict: + dataset.extension = file_dict['ext'] + # Metadata FileParameter types may not be writable on a cluster node, and are therefore temporarily substituted with MetadataTempFiles + override_metadata = json.load(open(override_metadata)) + for metadata_name, metadata_file_override in override_metadata: + if galaxy.datatypes.metadata.MetadataTempFile.is_JSONified_value(metadata_file_override): + metadata_file_override = galaxy.datatypes.metadata.MetadataTempFile.from_JSON(metadata_file_override) + setattr(dataset.metadata, metadata_name, metadata_file_override) + set_meta(dataset, file_dict) + dataset.metadata.to_JSON_dict(filename_out) # write out results of set_meta + json.dump((True, 'Metadata has been set successfully'), open(filename_results_code, 'wt+')) # setting metadata has succeeded + except Exception as e: + json.dump((False, str(e)), open(filename_results_code, 'wt+')) # setting metadata has failed somehow + + write_job_metadata(tool_job_working_directory, job_metadata, set_meta, tool_provided_metadata) + + +def set_metadata_legacy(): import galaxy.model galaxy.model.metadata.MetadataTempFile.tmp_dir = tool_job_working_directory = os.path.abspath(os.getcwd()) @@ -87,27 +147,13 @@ def set_metadata(): # Set up datatypes registry datatypes_config = sys.argv.pop(1) - if not os.path.exists(datatypes_config): - print("Metadata setting failed because registry.xml could not be found. You may retry setting metadata.") - sys.exit(1) - import galaxy.datatypes.registry - datatypes_registry = galaxy.datatypes.registry.Registry() - datatypes_registry.load_datatypes(root_dir=galaxy_root, config=datatypes_config) - galaxy.model.set_datatypes_registry(datatypes_registry) + datatypes_registry = validate_and_load_datatypes_config(datatypes_config) job_metadata = sys.argv.pop(1) - existing_job_metadata_dict = {} - new_job_metadata_dict = {} - if job_metadata != "None" and os.path.exists(job_metadata): - for line in open(job_metadata, 'r'): - try: - line = stringify_dictionary_keys(json.loads(line)) - if line['type'] == 'dataset': - existing_job_metadata_dict[int(line['dataset_id'])] = line - elif line['type'] == 'new_primary_dataset': - new_job_metadata_dict[line['filename']] = line - except Exception: - continue + tool_provided_metadata = load_job_metadata(job_metadata) + + def set_meta(new_dataset_instance, file_dict): + set_meta_with_tool_provided(new_dataset_instance, file_dict, set_meta_kwds, datatypes_registry, max_metadata_value_size) for filenames in sys.argv[1:]: fields = filenames.split(',') @@ -123,7 +169,7 @@ def set_metadata(): dataset.dataset.external_filename = dataset_filename_override files_path = os.path.abspath(os.path.join(tool_job_working_directory, "dataset_%s_files" % (dataset.dataset.id))) dataset.dataset.external_extra_files_path = files_path - file_dict = existing_job_metadata_dict.get(dataset.dataset.id, {}) + file_dict = tool_provided_metadata.get_dataset_meta(None, dataset.dataset.id) if 'ext' in file_dict: dataset.extension = file_dict['ext'] # Metadata FileParameter types may not be writable on a cluster node, and are therefore temporarily substituted with MetadataTempFiles @@ -132,25 +178,44 @@ def set_metadata(): if galaxy.datatypes.metadata.MetadataTempFile.is_JSONified_value(metadata_file_override): metadata_file_override = galaxy.datatypes.metadata.MetadataTempFile.from_JSON(metadata_file_override) setattr(dataset.metadata, metadata_name, metadata_file_override) - set_meta_with_tool_provided(dataset, file_dict, set_meta_kwds, datatypes_registry, max_metadata_value_size) + set_meta(dataset, file_dict) dataset.metadata.to_JSON_dict(filename_out) # write out results of set_meta json.dump((True, 'Metadata has been set successfully'), open(filename_results_code, 'wt+')) # setting metadata has succeeded except Exception as e: json.dump((False, str(e)), open(filename_results_code, 'wt+')) # setting metadata has failed somehow - for i, (filename, file_dict) in enumerate(new_job_metadata_dict.items(), start=1): - new_dataset_filename = os.path.join(tool_job_working_directory, "working", file_dict['filename']) + write_job_metadata(tool_job_working_directory, job_metadata, set_meta, tool_provided_metadata) + + +def validate_and_load_datatypes_config(datatypes_config): + galaxy_root = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir)) + + if not os.path.exists(datatypes_config): + print("Metadata setting failed because registry.xml could not be found. You may retry setting metadata.") + sys.exit(1) + import galaxy.datatypes.registry + datatypes_registry = galaxy.datatypes.registry.Registry() + datatypes_registry.load_datatypes(root_dir=galaxy_root, config=datatypes_config) + galaxy.model.set_datatypes_registry(datatypes_registry) + return datatypes_registry + + +def load_job_metadata(job_metadata): + return parse_tool_provided_metadata(job_metadata) + + +def write_job_metadata(tool_job_working_directory, job_metadata, set_meta, tool_provided_metadata): + for i, file_dict in enumerate(tool_provided_metadata.get_new_datasets_for_metadata_collection(), start=1): + filename = file_dict["filename"] + new_dataset_filename = os.path.join(tool_job_working_directory, "working", filename) new_dataset = galaxy.model.Dataset(id=-i, external_filename=new_dataset_filename) extra_files = file_dict.get('extra_files', None) if extra_files is not None: new_dataset._extra_files_path = os.path.join(tool_job_working_directory, "working", extra_files) new_dataset.state = new_dataset.states.OK new_dataset_instance = galaxy.model.HistoryDatasetAssociation(id=-i, dataset=new_dataset, extension=file_dict.get('ext', 'data')) - set_meta_with_tool_provided(new_dataset_instance, file_dict, set_meta_kwds, datatypes_registry, max_metadata_value_size) + set_meta(new_dataset_instance, file_dict) file_dict['metadata'] = json.loads(new_dataset_instance.metadata.to_JSON_dict()) # storing metadata in external form, need to turn back into dict, then later jsonify - if existing_job_metadata_dict or new_job_metadata_dict: - with open(job_metadata, 'wt') as job_metadata_fh: - for value in list(existing_job_metadata_dict.values()) + list(new_job_metadata_dict.values()): - job_metadata_fh.write("%s\n" % (json.dumps(value))) + tool_provided_metadata.rewrite() clear_mappers() diff --git a/lib/tool_shed/galaxy_install/tools/tool_panel_manager.py b/lib/tool_shed/galaxy_install/tools/tool_panel_manager.py index 2f99c2c0098..9c1c76a004a 100644 --- a/lib/tool_shed/galaxy_install/tools/tool_panel_manager.py +++ b/lib/tool_shed/galaxy_install/tools/tool_panel_manager.py @@ -372,7 +372,7 @@ class ToolPanelManager(object): new_tool_panel_section_label=new_tool_panel_section_label) return tool_section, tool_panel_section_key - def remove_from_shed_tool_config(self, shed_tool_conf_dict, guids_to_remove): + def remove_from_shed_tool_config(self, shed_tool_conf_dict, metadata): """ A tool shed repository is being uninstalled so change the shed_tool_conf file. Parse the config file to generate the entire list @@ -380,6 +380,13 @@ class ToolPanelManager(object): subset of the entire list if one or more repositories have been deactivated. """ + if 'tools' not in metadata: + return + # We need to use the tool path to uniquely identify the tools to remove, + # since multiple installable revisions of a repository can provide the + # same version of a tool (i.e. there may be another tool with the same + # guid that we should not remove from shed_tool_conf). + guid_paths_to_remove = [(_['guid'], _['tool_config']) for _ in metadata['tools']] shed_tool_conf = shed_tool_conf_dict['config_filename'] tool_path = shed_tool_conf_dict['tool_path'] config_elems = [] @@ -393,7 +400,7 @@ class ToolPanelManager(object): if config_elem.tag == 'section': tool_elems_to_remove = [] for tool_elem in config_elem: - if tool_elem.get('guid') in guids_to_remove: + if (tool_elem.get('guid'), tool_elem.get('file')) in guid_paths_to_remove: tool_elems_to_remove.append(tool_elem) for tool_elem in tool_elems_to_remove: # Remove all of the appropriate tool sub-elements from the section element. @@ -402,7 +409,7 @@ class ToolPanelManager(object): # Keep a list of all empty section elements so they can be removed. config_elems_to_remove.append(config_elem) elif config_elem.tag == 'tool': - if config_elem.get('guid') in guids_to_remove: + if (tool_elem.get('guid'), tool_elem.get('file')) in guid_paths_to_remove: config_elems_to_remove.append(config_elem) for config_elem in config_elems_to_remove: config_elems.remove(config_elem) @@ -424,18 +431,15 @@ class ToolPanelManager(object): # Create a list of guids for all tools that will be removed from the in-memory tool panel # and config file on disk. guids_to_remove = list(tool_panel_dict.keys()) - self.remove_guids(guids_to_remove, shed_tool_conf, uninstall) - - def remove_guids(self, guids_to_remove, shed_tool_conf, uninstall): toolbox = self.app.toolbox # Remove the tools from the toolbox's tools_by_id dictionary. for guid_to_remove in guids_to_remove: # remove_from_tool_panel to false, will handling that logic below. toolbox.remove_tool_by_id(guid_to_remove, remove_from_panel=False) shed_tool_conf_dict = self.get_shed_tool_conf_dict(shed_tool_conf) - # Always remove from the shed_tool_conf file on disk. Used to test for uninstall, not sure there is a legitimate use for this?! if uninstall: - self.remove_from_shed_tool_config(shed_tool_conf_dict, guids_to_remove) + # Remove from the shed_tool_conf file on disk. + self.remove_from_shed_tool_config(shed_tool_conf_dict, repository.metadata) def update_tool_panel_dict(self, tool_panel_dict, tool_panel_section_mapping, repository_tools_tups): for tool_guid in tool_panel_dict: diff --git a/lib/tool_shed/tools/tool_validator.py b/lib/tool_shed/tools/tool_validator.py index 8a6296cbec2..609186438d4 100644 --- a/lib/tool_shed/tools/tool_validator.py +++ b/lib/tool_shed/tools/tool_validator.py @@ -297,10 +297,12 @@ class ToolValidator(object): error_message = 'This file requires an entry for "%s" in the tool_data_table_conf.xml file. Upload a file ' % str(e) error_message += 'named tool_data_table_conf.xml.sample to the repository that includes the required entry to correct ' error_message += 'this error. ' + log.exception(error_message) except Exception as e: tool = None valid = False error_message = str(e) + log.exception('Caught exception loading tool from %s:', full_path) return tool, valid, error_message def load_tool_from_tmp_config(self, repo, repository_id, ctx, ctx_file, work_dir): diff --git a/lib/tool_shed/util/common_util.py b/lib/tool_shed/util/common_util.py index 538cceedb5e..8f5686197a2 100644 --- a/lib/tool_shed/util/common_util.py +++ b/lib/tool_shed/util/common_util.py @@ -2,6 +2,8 @@ import json import logging import os +from six.moves.urllib.parse import urljoin + from galaxy import util from galaxy.util.odict import odict from galaxy.web import url_for @@ -242,6 +244,16 @@ def get_tool_shed_url_from_tool_shed_registry(app, tool_shed): return None +def get_tool_shed_repository_url(app, tool_shed, owner, name): + tool_shed_url = get_tool_shed_url_from_tool_shed_registry(app, tool_shed) + if tool_shed_url: + # Append a slash to the tool shed URL, because urlparse.urljoin will eliminate + # the last part of a URL if it does not end with a forward slash. + tool_shed_url = '%s/' % tool_shed_url + return urljoin(tool_shed_url, 'view/%s/%s' % (owner, name)) + return tool_shed_url + + def get_user_by_username(app, username): """Get a user from the database by username.""" sa_session = app.model.context.current diff --git a/lib/tool_shed/util/metadata_util.py b/lib/tool_shed/util/metadata_util.py index bd1988c41d7..99339e6fcea 100644 --- a/lib/tool_shed/util/metadata_util.py +++ b/lib/tool_shed/util/metadata_util.py @@ -113,6 +113,7 @@ def get_metadata_revisions(app, repository, sort_revisions=True, reverse=False, """ Return a list of changesets for the provided repository. """ + sa_session = app.model.context.current if downloadable: metadata_revisions = repository.downloadable_revisions else: @@ -120,10 +121,16 @@ def get_metadata_revisions(app, repository, sort_revisions=True, reverse=False, repo_path = repository.repo_path(app) changeset_tups = [] for repository_metadata in metadata_revisions: - try: - rev = hg_util.changeset2rev(repo_path, repository_metadata.changeset_revision) - except Exception: - rev = -1 + if repository_metadata.numeric_revision == -1 or repository_metadata.numeric_revision is None: + try: + rev = hg_util.changeset2rev(repo_path, repository_metadata.changeset_revision) + repository_metadata.numeric_revision = rev + sa_session.add(repository_metadata) + sa_session.flush() + except Exception: + rev = -1 + else: + rev = repository_metadata.numeric_revision changeset_tups.append((rev, repository_metadata.changeset_revision)) if sort_revisions: changeset_tups.sort(key=itemgetter(0), reverse=reverse) @@ -218,7 +225,6 @@ def get_repository_metadata_by_changeset_revision(app, id, changeset_revision): all_metadata_records = sa_session.query(app.model.RepositoryMetadata) \ .filter(and_(app.model.RepositoryMetadata.table.c.repository_id == app.security.decode_id(id), app.model.RepositoryMetadata.table.c.changeset_revision == changeset_revision)) \ - .order_by(app.model.RepositoryMetadata.table.c.update_time.desc()) \ .all() if len(all_metadata_records) > 1: # Delete all records older than the last one updated. diff --git a/lib/tool_shed/util/readme_util.py b/lib/tool_shed/util/readme_util.py index bf22f22a8be..d3624168b2a 100644 --- a/lib/tool_shed/util/readme_util.py +++ b/lib/tool_shed/util/readme_util.py @@ -1,7 +1,6 @@ import json import logging import os -import threading from mako.template import Template @@ -50,16 +49,12 @@ def build_readme_files_dict(app, repository, changeset_revision, metadata, tool_ text_of_reasonable_length = basic_util.size_string(text) if text_of_reasonable_length.find('.. image:: ') >= 0: # Handle image display for README files that are contained in repositories in the tool shed or installed into Galaxy. - lock = threading.Lock() - lock.acquire(True) try: text_of_reasonable_length = suc.set_image_paths(app, - app.security.encode_id(repository.id), - text_of_reasonable_length) + text_of_reasonable_length, + encoded_repository_id=app.security.encode_id(repository.id)) except Exception: log.exception("Exception in build_readme_files_dict, so images may not be properly displayed") - finally: - lock.release() if readme_file_name.endswith('.rst'): text_of_reasonable_length = Template(rst_to_html(text_of_reasonable_length), input_encoding='utf-8', diff --git a/lib/tool_shed/util/repository_util.py b/lib/tool_shed/util/repository_util.py index ef9dd0bc019..b51abff9f9c 100644 --- a/lib/tool_shed/util/repository_util.py +++ b/lib/tool_shed/util/repository_util.py @@ -6,6 +6,7 @@ import shutil from six.moves import configparser from six.moves.urllib.error import HTTPError from sqlalchemy import and_, false, or_ +from sqlalchemy.sql import select import tool_shed.dependencies.repository import tool_shed.util.metadata_util as metadata_util @@ -494,20 +495,36 @@ def get_repo_info_tuple_contents(repo_info_tuple): return description, repository_clone_url, changeset_revision, ctx_rev, repository_owner, repository_dependencies, tool_dependencies -def get_repositories_by_category(app, category_id, installable=False): +def get_repositories_by_category(app, category_id, installable=False, sort_order='asc', sort_key='name', page=None, per_page=25): sa_session = app.model.context.current - resultset = sa_session.query(app.model.Category).get(category_id) + query = sa_session.query(app.model.Repository) \ + .join(app.model.RepositoryCategoryAssociation, app.model.Repository.id == app.model.RepositoryCategoryAssociation.repository_id) \ + .join(app.model.User, app.model.User.id == app.model.Repository.user_id) \ + .filter(app.model.RepositoryCategoryAssociation.category_id == category_id) + if installable: + subquery = select([app.model.RepositoryMetadata.table.c.repository_id]) + query = query.filter(app.model.Repository.id.in_(subquery)) + if sort_key == 'owner': + query = query.order_by(app.model.User.username) if sort_order == 'asc' else query.order_by(app.model.User.username.desc()) + else: + query = query.order_by(app.model.Repository.name) if sort_order == 'asc' else query.order_by(app.model.Repository.name.desc()) + if page is not None: + page = int(page) + query = query.limit(per_page) + if page > 1: + query = query.offset((page - 1) * per_page) + resultset = query.all() repositories = [] - default_value_mapper = {'id': app.security.encode_id, 'user_id': app.security.encode_id} - for row in resultset.repositories: - repository_dict = row.repository.to_dict(value_mapper=default_value_mapper) + for repository in resultset: + default_value_mapper = {'id': app.security.encode_id, 'user_id': app.security.encode_id, 'repository_id': app.security.encode_id} + repository_dict = repository.to_dict(value_mapper=default_value_mapper) repository_dict['metadata'] = {} - for changeset, changehash in row.repository.installable_revisions(app): - encoded_id = app.security.encode_id(row.repository.id) + for changeset, changehash in repository.installable_revisions(app): + encoded_id = app.security.encode_id(repository.id) metadata = metadata_util.get_repository_metadata_by_changeset_revision(app, encoded_id, changehash) repository_dict['metadata']['%s:%s' % (changeset, changehash)] = metadata.to_dict(value_mapper=default_value_mapper) if installable: - if len(row.repository.installable_revisions(app)): + if len(repository.installable_revisions(app)): repositories.append(repository_dict) else: repositories.append(repository_dict) diff --git a/lib/tool_shed/util/shed_util_common.py b/lib/tool_shed/util/shed_util_common.py index 6b52b5ecb2e..a2bb858a632 100644 --- a/lib/tool_shed/util/shed_util_common.py +++ b/lib/tool_shed/util/shed_util_common.py @@ -149,6 +149,13 @@ def clean_dependency_relationships(trans, metadata_dict, tool_shed_repository, t trans.install_model.context.flush() +def count_repositories_in_category(app, category_id): + sa_session = app.model.context.current + return sa_session.query(app.model.RepositoryCategoryAssociation) \ + .filter(app.model.RepositoryCategoryAssociation.table.c.category_id == app.security.decode_id(category_id)) \ + .count() + + def generate_tool_guid(repository_clone_url, tool): """ Generate a guid for the installed tool. It is critical that this guid matches the guid for @@ -601,7 +608,7 @@ def open_repository_files_folder(app, folder_path, repository_id, is_admin=False return folder_contents -def set_image_paths(app, encoded_repository_id, text): +def set_image_paths(app, text, encoded_repository_id=None, tool_shed_repository=None, tool_id=None, tool_version=None): """ Handle tool help image display for tools that are contained in repositories in the tool shed or installed into Galaxy as well as image display in repository @@ -609,11 +616,21 @@ def set_image_paths(app, encoded_repository_id, text): return the path to it that will enable the caller to open the file. """ if text: - if repository_util.is_tool_shed_client(app): + if repository_util.is_tool_shed_client(app) and encoded_repository_id: route_to_images = 'admin_toolshed/static/images/%s' % encoded_repository_id - else: + elif encoded_repository_id: # We're in the tool shed. route_to_images = '/repository/static/images/%s' % encoded_repository_id + elif tool_shed_repository and tool_id and tool_version: + route_to_images = 'shed_tool_static/{shed}/{owner}/{repo}/{tool}/{version}'.format( + shed=tool_shed_repository.tool_shed, + owner=tool_shed_repository.owner, + repo=tool_shed_repository.name, + tool=tool_id, + version=tool_version, + ) + else: + raise Exception("encoded_repository_id or tool_shed_repository and tool_id and tool_version must be provided") # We used to require $PATH_TO_IMAGES and ${static_path}, but # we now eliminate it if it's used. text = text.replace('$PATH_TO_IMAGES', '') diff --git a/lib/tool_shed/util/xml_util.py b/lib/tool_shed/util/xml_util.py index 014dc8b8a98..5e7e9db05e4 100644 --- a/lib/tool_shed/util/xml_util.py +++ b/lib/tool_shed/util/xml_util.py @@ -1,7 +1,6 @@ import io import logging import os -import sys import tempfile from xml.etree import ElementTree as XmlET @@ -10,11 +9,13 @@ from galaxy.util import ( ) log = logging.getLogger(__name__) -using_python_27 = sys.version_info[:2] >= (2, 7) class Py27CommentedTreeBuilder(XmlET.TreeBuilder): - # Python 2.7 uses ElementTree 1.3.x. + + def doctype(*args): + # handle deprecation warning for XMLParsing a file with DOCTYPE + pass def comment(self, data): self.start(XmlET.Comment, {}) @@ -82,18 +83,10 @@ def parse_xml(file_name): return None, "File does not exist %s" % str(file_name) with open(file_name, 'r') as fobj: - if using_python_27: - try: - tree = XmlET.parse(fobj, parser=XmlET.XMLParser(target=Py27CommentedTreeBuilder())) - except Exception as e: - error_message = "Exception attempting to parse %s: %s" % (str(file_name), str(e)) - log.exception(error_message) - return None, error_message - else: - try: - tree = XmlET.parse(fobj) - except Exception as e: - error_message = "Exception attempting to parse %s: %s" % (str(file_name), str(e)) - log.exception(error_message) - return None, error_message + try: + tree = XmlET.parse(fobj, parser=XmlET.XMLParser(target=Py27CommentedTreeBuilder())) + except Exception as e: + error_message = "Exception attempting to parse %s: %s" % (str(file_name), str(e)) + log.exception(error_message) + return None, error_message return tree, error_message diff --git a/manage_db.sh b/manage_db.sh index 192bc3d1515..b5a185ab573 100755 --- a/manage_db.sh +++ b/manage_db.sh @@ -11,4 +11,5 @@ cd `dirname $0` setup_python +find lib/galaxy/model/migrate/versions -name '*.pyc' -delete python ./scripts/manage_db.py $@ diff --git a/openid/launchpad.xml b/openid/launchpad.xml deleted file mode 100644 index 27b59c69e9c..00000000000 --- a/openid/launchpad.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - http://login.launchpad.net - diff --git a/packages/data/HISTORY.rst b/packages/data/HISTORY.rst new file mode 100644 index 00000000000..c49739afaf3 --- /dev/null +++ b/packages/data/HISTORY.rst @@ -0,0 +1,12 @@ +.. :changelog: + +History +------- + +.. to_doc + +--------------------- +19.5.0.dev0 +--------------------- + +* Initial import from dev branch of Galaxy during 19.05 release cycle. diff --git a/packages/data/LICENSE b/packages/data/LICENSE new file mode 120000 index 00000000000..1ef648f64b3 --- /dev/null +++ b/packages/data/LICENSE @@ -0,0 +1 @@ +../../LICENSE.txt \ No newline at end of file diff --git a/packages/data/MANIFEST.in b/packages/data/MANIFEST.in new file mode 100644 index 00000000000..2ef21241a5b --- /dev/null +++ b/packages/data/MANIFEST.in @@ -0,0 +1,3 @@ +include *.rst LICENSE +include galaxy/datatypes/test/* + diff --git a/packages/data/README.rst b/packages/data/README.rst new file mode 100644 index 00000000000..962fa16e2ea --- /dev/null +++ b/packages/data/README.rst @@ -0,0 +1,14 @@ + +.. image:: https://badge.fury.io/py/galaxy-datatypes.svg + :target: https://pypi.python.org/pypi/galaxy-datatypes/ + + +Overview +-------- + +The Galaxy_ models, datatype framework, and datatype implementations. + +* Free software: Academic Free License version 3.0 +* Code: https://github.com/galaxyproject/galaxy + +.. _Galaxy: http://galaxyproject.org/ diff --git a/packages/data/galaxy/__init__.py b/packages/data/galaxy/__init__.py new file mode 100644 index 00000000000..69e3be50dac --- /dev/null +++ b/packages/data/galaxy/__init__.py @@ -0,0 +1 @@ +__path__ = __import__('pkgutil').extend_path(__path__, __name__) diff --git a/packages/data/galaxy/datatypes b/packages/data/galaxy/datatypes new file mode 120000 index 00000000000..66d6a8bf663 --- /dev/null +++ b/packages/data/galaxy/datatypes @@ -0,0 +1 @@ +../../../lib/galaxy/datatypes \ No newline at end of file diff --git a/packages/data/galaxy/model b/packages/data/galaxy/model new file mode 120000 index 00000000000..531c6de0dd1 --- /dev/null +++ b/packages/data/galaxy/model @@ -0,0 +1 @@ +../../../lib/galaxy/model \ No newline at end of file diff --git a/packages/data/galaxy/project_galaxy_data.py b/packages/data/galaxy/project_galaxy_data.py new file mode 100644 index 00000000000..2dc2d71d64d --- /dev/null +++ b/packages/data/galaxy/project_galaxy_data.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- + +__version__ = '19.5.0.dev0' + +PROJECT_NAME = "galaxy-data" +PROJECT_OWNER = PROJECT_USERAME = "galaxyproject" +PROJECT_URL = "https://github.com/galaxyproject/galaxy" +PROJECT_AUTHOR = 'Galaxy Project and Community' +PROJECT_EMAIL = 'jmchilton@gmail.com' +RAW_CONTENT_URL = "https://raw.github.com/%s/%s/master/" % ( + PROJECT_USERAME, PROJECT_NAME +) diff --git a/packages/data/galaxy/security b/packages/data/galaxy/security new file mode 120000 index 00000000000..c9ba3e1d73d --- /dev/null +++ b/packages/data/galaxy/security @@ -0,0 +1 @@ +../../../lib/galaxy/security \ No newline at end of file diff --git a/packages/data/requirements.txt b/packages/data/requirements.txt new file mode 100644 index 00000000000..67a3b0d0a59 --- /dev/null +++ b/packages/data/requirements.txt @@ -0,0 +1,12 @@ +galaxy-objectstore +galaxy-util +bx +h5py +isa-rwval +numpy +pycryptodome +pysam +social_auth_core +SQLAlchemy==1.2 +sqlalchemy-migrate +WebOb diff --git a/packages/data/setup.cfg b/packages/data/setup.cfg new file mode 120000 index 00000000000..eb7cf09393f --- /dev/null +++ b/packages/data/setup.cfg @@ -0,0 +1 @@ +../../setup.cfg \ No newline at end of file diff --git a/packages/data/setup.py b/packages/data/setup.py new file mode 100644 index 00000000000..e9fe1fea3bb --- /dev/null +++ b/packages/data/setup.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import ast +import os +import re +try: + from setuptools import setup +except ImportError: + from distutils.core import setup + +SOURCE_DIR = "galaxy" + +_version_re = re.compile(r'__version__\s+=\s+(.*)') + +with open('%s/project_galaxy_data.py' % SOURCE_DIR, 'rb') as f: + init_contents = f.read().decode('utf-8') + + def get_var(var_name): + pattern = re.compile(r'%s\s+=\s+(.*)' % var_name) + match = pattern.search(init_contents).group(1) + return str(ast.literal_eval(match)) + + version = get_var("__version__") + PROJECT_NAME = get_var("PROJECT_NAME") + PROJECT_URL = get_var("PROJECT_URL") + PROJECT_AUTHOR = get_var("PROJECT_AUTHOR") + PROJECT_EMAIL = get_var("PROJECT_EMAIL") + +TEST_DIR = 'tests' +PROJECT_DESCRIPTION = 'Galaxy Datatype Framework and Datatypes' +PACKAGES = [ + 'galaxy', + 'galaxy.datatypes', + 'galaxy.datatypes.dataproviders', + 'galaxy.datatypes.util', + 'galaxy.datatypes.test', + 'galaxy.model', + 'galaxy.model.dataset_collections', + 'galaxy.model.migrate', + 'galaxy.model.orm', + 'galaxy.model.tool_shed_install', + 'galaxy.security', +] +ENTRY_POINTS = ''' + [console_scripts] +''' +PACKAGE_DATA = { + # Be sure to update MANIFEST.in for source dist. + 'galaxy': [ + 'datatypes/test/*', + ], +} +PACKAGE_DIR = { + SOURCE_DIR: SOURCE_DIR, +} + +readme = open('README.rst').read() +history = open('HISTORY.rst').read().replace('.. :changelog:', '') + +if os.path.exists("requirements.txt"): + requirements = open("requirements.txt").read().split("\n") +else: + # In tox, it will cover them anyway. + requirements = [] + + +test_requirements = [ + # TODO: put package test requirements here +] + + +setup( + name=PROJECT_NAME, + version=version, + description=PROJECT_DESCRIPTION, + long_description=readme + '\n\n' + history, + author=PROJECT_AUTHOR, + author_email=PROJECT_EMAIL, + url=PROJECT_URL, + packages=PACKAGES, + entry_points=ENTRY_POINTS, + package_data=PACKAGE_DATA, + package_dir=PACKAGE_DIR, + include_package_data=True, + install_requires=requirements, + license="AFL", + zip_safe=False, + keywords='galaxy', + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Environment :: Console', + 'License :: OSI Approved :: Academic Free License (AFL)', + 'Operating System :: POSIX', + 'Topic :: Software Development', + 'Topic :: Software Development :: Code Generators', + 'Topic :: Software Development :: Testing', + 'Natural Language :: English', + "Programming Language :: Python :: 2", + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + ], + test_suite=TEST_DIR, + tests_require=test_requirements +) diff --git a/packages/data/tests/__init__.py b/packages/data/tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/data/tests/datatypes b/packages/data/tests/datatypes new file mode 120000 index 00000000000..d01dc2e3776 --- /dev/null +++ b/packages/data/tests/datatypes @@ -0,0 +1 @@ +../../../test/unit/datatypes \ No newline at end of file diff --git a/packages/data/tests/test_galaxy_mapping.py b/packages/data/tests/test_galaxy_mapping.py new file mode 120000 index 00000000000..0cc477d90ee --- /dev/null +++ b/packages/data/tests/test_galaxy_mapping.py @@ -0,0 +1 @@ +../../../test/unit/test_galaxy_mapping.py \ No newline at end of file diff --git a/packages/data/tests/unittest_utils b/packages/data/tests/unittest_utils new file mode 120000 index 00000000000..6b6233367e6 --- /dev/null +++ b/packages/data/tests/unittest_utils @@ -0,0 +1 @@ +../../../test/unit/unittest_utils \ No newline at end of file diff --git a/packages/objectstore/HISTORY.rst b/packages/objectstore/HISTORY.rst new file mode 100644 index 00000000000..c49739afaf3 --- /dev/null +++ b/packages/objectstore/HISTORY.rst @@ -0,0 +1,12 @@ +.. :changelog: + +History +------- + +.. to_doc + +--------------------- +19.5.0.dev0 +--------------------- + +* Initial import from dev branch of Galaxy during 19.05 release cycle. diff --git a/packages/objectstore/LICENSE b/packages/objectstore/LICENSE new file mode 120000 index 00000000000..1ef648f64b3 --- /dev/null +++ b/packages/objectstore/LICENSE @@ -0,0 +1 @@ +../../LICENSE.txt \ No newline at end of file diff --git a/packages/objectstore/MANIFEST.in b/packages/objectstore/MANIFEST.in new file mode 100644 index 00000000000..2bf148a5764 --- /dev/null +++ b/packages/objectstore/MANIFEST.in @@ -0,0 +1,2 @@ +include *.rst LICENSE + diff --git a/packages/objectstore/README.rst b/packages/objectstore/README.rst new file mode 100644 index 00000000000..fa243ffe29b --- /dev/null +++ b/packages/objectstore/README.rst @@ -0,0 +1,14 @@ + +.. image:: https://badge.fury.io/py/galaxy-objectstore.svg + :target: https://pypi.python.org/pypi/galaxy-objectstore/ + + +Overview +-------- + +The Galaxy_ object store framework and default implementations. + +* Free software: Academic Free License version 3.0 +* Code: https://github.com/galaxyproject/galaxy + +.. _Galaxy: http://galaxyproject.org/ diff --git a/packages/objectstore/galaxy/__init__.py b/packages/objectstore/galaxy/__init__.py new file mode 100644 index 00000000000..69e3be50dac --- /dev/null +++ b/packages/objectstore/galaxy/__init__.py @@ -0,0 +1 @@ +__path__ = __import__('pkgutil').extend_path(__path__, __name__) diff --git a/packages/objectstore/galaxy/objectstore b/packages/objectstore/galaxy/objectstore new file mode 120000 index 00000000000..dd20803f056 --- /dev/null +++ b/packages/objectstore/galaxy/objectstore @@ -0,0 +1 @@ +../../../lib/galaxy/objectstore \ No newline at end of file diff --git a/packages/objectstore/galaxy/project_galaxy_objectstore.py b/packages/objectstore/galaxy/project_galaxy_objectstore.py new file mode 100644 index 00000000000..f64be773ba9 --- /dev/null +++ b/packages/objectstore/galaxy/project_galaxy_objectstore.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- + +__version__ = '19.5.0.dev0' + +PROJECT_NAME = "galaxy-objectstore" +PROJECT_OWNER = PROJECT_USERAME = "galaxyproject" +PROJECT_URL = "https://github.com/galaxyproject/galaxy" +PROJECT_AUTHOR = 'Galaxy Project and Community' +PROJECT_EMAIL = 'jmchilton@gmail.com' +RAW_CONTENT_URL = "https://raw.github.com/%s/%s/master/" % ( + PROJECT_USERAME, PROJECT_NAME +) diff --git a/packages/objectstore/requirements.txt b/packages/objectstore/requirements.txt new file mode 100644 index 00000000000..623c4906bb2 --- /dev/null +++ b/packages/objectstore/requirements.txt @@ -0,0 +1 @@ +galaxy-util diff --git a/packages/objectstore/setup.cfg b/packages/objectstore/setup.cfg new file mode 120000 index 00000000000..eb7cf09393f --- /dev/null +++ b/packages/objectstore/setup.cfg @@ -0,0 +1 @@ +../../setup.cfg \ No newline at end of file diff --git a/packages/objectstore/setup.py b/packages/objectstore/setup.py new file mode 100644 index 00000000000..3b3d519ec0b --- /dev/null +++ b/packages/objectstore/setup.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import ast +import os +import re +try: + from setuptools import setup +except ImportError: + from distutils.core import setup + +SOURCE_DIR = "galaxy" + +_version_re = re.compile(r'__version__\s+=\s+(.*)') + +with open('%s/project_galaxy_objectstore.py' % SOURCE_DIR, 'rb') as f: + init_contents = f.read().decode('utf-8') + + def get_var(var_name): + pattern = re.compile(r'%s\s+=\s+(.*)' % var_name) + match = pattern.search(init_contents).group(1) + return str(ast.literal_eval(match)) + + version = get_var("__version__") + PROJECT_NAME = get_var("PROJECT_NAME") + PROJECT_URL = get_var("PROJECT_URL") + PROJECT_AUTHOR = get_var("PROJECT_AUTHOR") + PROJECT_EMAIL = get_var("PROJECT_EMAIL") + +TEST_DIR = 'tests' +PROJECT_DESCRIPTION = 'Galaxy Datatype Framework and Datatypes' +PACKAGES = [ + 'galaxy', + 'galaxy.objectstore', +] +ENTRY_POINTS = ''' + [console_scripts] +''' +PACKAGE_DATA = { + # Be sure to update MANIFEST.in for source dist. + 'galaxy': [ + ], +} +PACKAGE_DIR = { + SOURCE_DIR: SOURCE_DIR, +} + +readme = open('README.rst').read() +history = open('HISTORY.rst').read().replace('.. :changelog:', '') + +if os.path.exists("requirements.txt"): + requirements = open("requirements.txt").read().split("\n") +else: + # In tox, it will cover them anyway. + requirements = [] + + +test_requirements = [ + # TODO: put package test requirements here +] + + +setup( + name=PROJECT_NAME, + version=version, + description=PROJECT_DESCRIPTION, + long_description=readme + '\n\n' + history, + author=PROJECT_AUTHOR, + author_email=PROJECT_EMAIL, + url=PROJECT_URL, + packages=PACKAGES, + entry_points=ENTRY_POINTS, + package_data=PACKAGE_DATA, + package_dir=PACKAGE_DIR, + include_package_data=True, + install_requires=requirements, + license="AFL", + zip_safe=False, + keywords='galaxy', + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Environment :: Console', + 'License :: OSI Approved :: Academic Free License (AFL)', + 'Operating System :: POSIX', + 'Topic :: Software Development', + 'Topic :: Software Development :: Code Generators', + 'Topic :: Software Development :: Testing', + 'Natural Language :: English', + "Programming Language :: Python :: 2", + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + ], + test_suite=TEST_DIR, + tests_require=test_requirements +) diff --git a/packages/objectstore/tests/test_objectstore.py b/packages/objectstore/tests/test_objectstore.py new file mode 120000 index 00000000000..0ef5a715802 --- /dev/null +++ b/packages/objectstore/tests/test_objectstore.py @@ -0,0 +1 @@ +../../../test/unit/test_objectstore.py \ No newline at end of file diff --git a/packages/util/HISTORY.rst b/packages/util/HISTORY.rst new file mode 100644 index 00000000000..c49739afaf3 --- /dev/null +++ b/packages/util/HISTORY.rst @@ -0,0 +1,12 @@ +.. :changelog: + +History +------- + +.. to_doc + +--------------------- +19.5.0.dev0 +--------------------- + +* Initial import from dev branch of Galaxy during 19.05 release cycle. diff --git a/packages/util/LICENSE b/packages/util/LICENSE new file mode 120000 index 00000000000..1ef648f64b3 --- /dev/null +++ b/packages/util/LICENSE @@ -0,0 +1 @@ +../../LICENSE.txt \ No newline at end of file diff --git a/packages/util/MANIFEST.in b/packages/util/MANIFEST.in new file mode 100644 index 00000000000..a3dd4013351 --- /dev/null +++ b/packages/util/MANIFEST.in @@ -0,0 +1,3 @@ +include *.rst LICENSE +include galaxy/util/docutils_template.txt +include galaxy/exceptions/error_codes.json diff --git a/packages/util/README.rst b/packages/util/README.rst new file mode 100644 index 00000000000..778c380a853 --- /dev/null +++ b/packages/util/README.rst @@ -0,0 +1,14 @@ + +.. image:: https://badge.fury.io/py/galaxy-util.svg + :target: https://pypi.python.org/pypi/galaxy-util/ + + +Overview +-------- + +The Galaxy_ utilities module. + +* Free software: Academic Free License version 3.0 +* Code: https://github.com/galaxyproject/galaxy + +.. _Galaxy: http://galaxyproject.org/ diff --git a/packages/util/galaxy/__init__.py b/packages/util/galaxy/__init__.py new file mode 100644 index 00000000000..69e3be50dac --- /dev/null +++ b/packages/util/galaxy/__init__.py @@ -0,0 +1 @@ +__path__ = __import__('pkgutil').extend_path(__path__, __name__) diff --git a/packages/util/galaxy/exceptions b/packages/util/galaxy/exceptions new file mode 120000 index 00000000000..594b036185a --- /dev/null +++ b/packages/util/galaxy/exceptions @@ -0,0 +1 @@ +../../../lib/galaxy/exceptions \ No newline at end of file diff --git a/packages/util/galaxy/project_galaxy_util.py b/packages/util/galaxy/project_galaxy_util.py new file mode 100644 index 00000000000..770df42ba3d --- /dev/null +++ b/packages/util/galaxy/project_galaxy_util.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- + +__version__ = '19.5.0.dev0' + +PROJECT_NAME = "galaxy-util" +PROJECT_OWNER = PROJECT_USERAME = "galaxyproject" +PROJECT_URL = "https://github.com/galaxyproject/galaxy" +PROJECT_AUTHOR = 'Galaxy Project and Community' +PROJECT_EMAIL = 'jmchilton@gmail.com' +RAW_CONTENT_URL = "https://raw.github.com/%s/%s/master/" % ( + PROJECT_USERAME, PROJECT_NAME +) diff --git a/packages/util/galaxy/util b/packages/util/galaxy/util new file mode 120000 index 00000000000..b4acbee88f7 --- /dev/null +++ b/packages/util/galaxy/util @@ -0,0 +1 @@ +../../../lib/galaxy/util \ No newline at end of file diff --git a/packages/util/requirements.txt b/packages/util/requirements.txt new file mode 100644 index 00000000000..3290e4ad496 --- /dev/null +++ b/packages/util/requirements.txt @@ -0,0 +1,9 @@ +bleach +boltons +bz2file; python_version < '3.3' +Cheetah3 +docutils +markupsafe +packaging +pyyaml +six>=1.9.0 diff --git a/packages/util/setup.cfg b/packages/util/setup.cfg new file mode 120000 index 00000000000..eb7cf09393f --- /dev/null +++ b/packages/util/setup.cfg @@ -0,0 +1 @@ +../../setup.cfg \ No newline at end of file diff --git a/packages/util/setup.py b/packages/util/setup.py new file mode 100644 index 00000000000..212c29cfda4 --- /dev/null +++ b/packages/util/setup.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import ast +import os +import re +try: + from setuptools import setup +except ImportError: + from distutils.core import setup + +SOURCE_DIR = "galaxy" + +_version_re = re.compile(r'__version__\s+=\s+(.*)') + +with open('%s/project_galaxy_util.py' % SOURCE_DIR, 'rb') as f: + init_contents = f.read().decode('utf-8') + + def get_var(var_name): + pattern = re.compile(r'%s\s+=\s+(.*)' % var_name) + match = pattern.search(init_contents).group(1) + return str(ast.literal_eval(match)) + + version = get_var("__version__") + PROJECT_NAME = get_var("PROJECT_NAME") + PROJECT_URL = get_var("PROJECT_URL") + PROJECT_AUTHOR = get_var("PROJECT_AUTHOR") + PROJECT_EMAIL = get_var("PROJECT_EMAIL") + +TEST_DIR = 'tests' +PROJECT_DESCRIPTION = 'Galaxy Datatype Framework and Datatypes' +PACKAGES = [ + 'galaxy', + 'galaxy.util', + 'galaxy.util.logging', + 'galaxy.util.path', +] +ENTRY_POINTS = ''' + [console_scripts] +''' +PACKAGE_DATA = { + # Be sure to update MANIFEST.in for source dist. + 'galaxy': [ + 'util/docutils_template.txt', + 'exceptions/error_codes.json', + ], +} +PACKAGE_DIR = { + SOURCE_DIR: SOURCE_DIR, +} + +readme = open('README.rst').read() +history = open('HISTORY.rst').read().replace('.. :changelog:', '') + +if os.path.exists("requirements.txt"): + requirements = open("requirements.txt").read().split("\n") +else: + # In tox, it will cover them anyway. + requirements = [] + + +test_requirements = [ + # TODO: put package test requirements here +] + + +setup( + name=PROJECT_NAME, + version=version, + description=PROJECT_DESCRIPTION, + long_description=readme + '\n\n' + history, + author=PROJECT_AUTHOR, + author_email=PROJECT_EMAIL, + url=PROJECT_URL, + packages=PACKAGES, + entry_points=ENTRY_POINTS, + package_data=PACKAGE_DATA, + package_dir=PACKAGE_DIR, + include_package_data=True, + install_requires=requirements, + license="AFL", + zip_safe=False, + keywords='galaxy', + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Environment :: Console', + 'License :: OSI Approved :: Academic Free License (AFL)', + 'Operating System :: POSIX', + 'Topic :: Software Development', + 'Topic :: Software Development :: Code Generators', + 'Topic :: Software Development :: Testing', + 'Natural Language :: English', + "Programming Language :: Python :: 2", + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + ], + test_suite=TEST_DIR, + tests_require=test_requirements +) diff --git a/packages/util/tests/test_utils.py b/packages/util/tests/test_utils.py new file mode 120000 index 00000000000..d81c3aa4ae7 --- /dev/null +++ b/packages/util/tests/test_utils.py @@ -0,0 +1 @@ +../../../test/unit/test_utils.py \ No newline at end of file diff --git a/run.sh b/run.sh index a172ed50d8f..4d1422dec49 100755 --- a/run.sh +++ b/run.sh @@ -38,7 +38,6 @@ if [ ! -z "$GALAXY_RUN_WITH_TEST_TOOLS" ]; then export GALAXY_CONFIG_OVERRIDE_TOOL_CONFIG_FILE="test/functional/tools/samples_tool_conf.xml" export GALAXY_CONFIG_ENABLE_BETA_WORKFLOW_MODULES="true" - export GALAXY_CONFIG_ENABLE_BETA_WORKFLOW_FORMAT="true" export GALAXY_CONFIG_OVERRIDE_ENABLE_BETA_TOOL_FORMATS="true" export GALAXY_CONFIG_OVERRIDE_WEBHOOKS_DIR="test/functional/webhooks" fi @@ -81,7 +80,7 @@ if [ "$run_server" = "python" -a -n "$GALAXY_RUN_ALL" ]; then continue fi # Search for all pids in the logs and tail for the last one - latest_pid=$(grep '^Starting server in PID [0-9]\+\.$' "$server.log" | sed 's/^Starting server in PID \([0-9]\+\).$/\1/' | tail -n 1) + latest_pid=$(grep '^Starting server in PID [0-9]\+\.$' "$server.log" | sed 's/^Starting server in PID \([0-9]\{1,\}\).$/\1/' | tail -n 1) # If they're equivalent, then the current pid file agrees with our logs # and we've succesfully started [ -n "$latest_pid" ] && [ "$latest_pid" -eq "$current_pid_in_file" ] && break diff --git a/run_tests.sh b/run_tests.sh index 3ad62945d46..5634c1d7793 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -270,7 +270,7 @@ exists() { type "$1" >/dev/null 2>/dev/null } -DOCKER_DEFAULT_IMAGE='galaxy/testing-base:19.01.0' +DOCKER_DEFAULT_IMAGE='galaxy/testing-base:19.05.0' test_script="./scripts/functional_tests.py" report_file="run_functional_tests.html" @@ -285,20 +285,31 @@ then DOCKER_EXTRA_ARGS=${DOCKER_ARGS:-""} DOCKER_RUN_EXTRA_ARGS=${DOCKER_RUN_EXTRA_ARGS:-""} DOCKER_IMAGE=${DOCKER_IMAGE:-${DOCKER_DEFAULT_IMAGE}} - if [ "$1" = "--python3" ]; then - DOCKER_RUN_EXTRA_ARGS="-e GALAXY_VIRTUAL_ENV=/galaxy_venv3 $DOCKER_RUN_EXTRA_ARGS" - shift 1 - fi - if [ "$1" = "--db" ]; then - db_type=$2 - shift 2 - else - db_type="sqlite" - fi + db_type="sqlite" + while [ $# -gt 0 ]; do + case "$1" in + --python3) + DOCKER_RUN_EXTRA_ARGS="${DOCKER_RUN_EXTRA_ARGS} -e GALAXY_VIRTUAL_ENV=/galaxy_venv3" + shift 1 + ;; + --db) + db_type=$2 + shift 2 + ;; + *) + break + ;; + esac + done + # Skip client build process in the Docker container for all tests except Selenium + GALAXY_SKIP_CLIENT_BUILD=1 + case "$*" in + *-selenium*) + GALAXY_SKIP_CLIENT_BUILD=0 + ;; + esac MY_UID=$(id -u) - # Skip client build process in the Docker container for all tests, the Jenkins task builds the client - # locally before testing - you will need to do this also if using this script for Selenium testing. - DOCKER_RUN_EXTRA_ARGS="-e GALAXY_TEST_UID=${MY_UID} -e GALAXY_SKIP_CLIENT_BUILD=1 ${DOCKER_RUN_EXTRA_ARGS}" + DOCKER_RUN_EXTRA_ARGS="${DOCKER_RUN_EXTRA_ARGS} -e GALAXY_TEST_UID=${MY_UID} -e GALAXY_SKIP_CLIENT_BUILD=${GALAXY_SKIP_CLIENT_BUILD}" echo "Docker version:" docker --version echo "Launching docker container for testing with extra args ${DOCKER_RUN_EXTRA_ARGS}..." diff --git a/scripts/bootstrap_history.py b/scripts/bootstrap_history.py index 3c5a57bc06a..901c50aeb66 100644 --- a/scripts/bootstrap_history.py +++ b/scripts/bootstrap_history.py @@ -41,10 +41,10 @@ DEVTEAM = [ "VJalili" ] -TEMPLATE = """ +TEMPLATE = string.Template(""" .. to_doc -%s +${release} =============================== .. announce_start @@ -74,9 +74,9 @@ Fixes .. bug -.. github_links +.. include:: ${release}_prs.rst -""" +""") ANNOUNCE_TEMPLATE = string.Template(""" =========================================================== @@ -123,6 +123,45 @@ Release Notes .. include:: _thanks.rst """) +ANNOUNCE_USER_TEMPLATE = string.Template(""" +=========================================================== +${month_name} 20${year} Galaxy Release (v ${release}) +=========================================================== + +.. include:: _header.rst + +Highlights +=========================================================== + +**Feature1** + Feature description. + +**Feature2** + Feature description. + +**Feature3** + Feature description. + + +New Visualisations +=========================================================== + +New Datatypes +=========================================================== + +Builtin Tool Updates +=========================================================== + +Release Notes +=========================================================== + +Please see the `full release notes <${release}_announce.html>`_ for more details. + +.. include:: ${release}_prs.rst + +.. include:: _thanks.rst +""") + NEXT_TEMPLATE = string.Template(""" =========================================================== ${month_name} 20${year} Galaxy Release (v ${version}) @@ -223,12 +262,12 @@ RELEASE_ISSUE_TEMPLATE = string.Template(""" - [ ] Verify release included in https://docs.galaxyproject.org/en/master/releases/index.html - [ ] Review announcement in https://github.com/galaxyproject/galaxy/blob/dev/doc/source/releases/${version}_announce.rst - - [ ] Stage announcement content (Hub, Biostars, etc.) on announce date to capture date tags. Note: all final content does not need to be completed to do this. - - [ ] Create hub *highlights* and post as a new "news" content item. [An Example](https://galaxyproject.org/news/2018-9-galaxy-release/). - - [ ] Tweet docs news *highlights* link as @galaxyproject on twitter. [An Example](https://twitter.com/galaxyproject/status/973646125633695744). - - [ ] Post *highlights* as type 'News' to Galaxy Biostars https://biostar.usegalaxy.org. [An Example](https://biostar.usegalaxy.org/p/27118/). - - [ ] Email *highlights* to [galaxy-dev](http://dev.list.galaxyproject.org/) and [galaxy-announce](http://announce.list.galaxyproject.org/) @lists.galaxyproject.org. [An Example](http://dev.list.galaxyproject.org/The-Galaxy-release-16-04-is-out-tp4669419.html) - - [ ] Adjust http://getgalaxy.org text and links to match current master branch by opening a PR for https://github.com/galaxyproject/galaxy-hub/ + - [ ] Stage announcement content (Hub, Galaxy Help, etc.) on announce date to capture date tags. Note: all final content does not need to be completed to do this. + - [ ] Create hub *highlights* and post as a new "news" content item. [An example](https://galaxyproject.org/news/2018-9-galaxy-release/). + - [ ] Tweet docs news *highlights* link as @galaxyproject on twitter. [An example](https://twitter.com/galaxyproject/status/973646125633695744). + - [ ] Post *highlights* with tags `news` and `release` to [Galaxy Help](https://help.galaxyproject.org/). [An example](https://help.galaxyproject.org/t/galaxy-release-19-01/712). + - [ ] Email *highlights* to [galaxy-dev](http://dev.list.galaxyproject.org/) and [galaxy-announce](http://announce.list.galaxyproject.org/) @lists.galaxyproject.org. [An example](http://dev.list.galaxyproject.org/The-Galaxy-release-16-04-is-out-tp4669419.html) + - [ ] Adjust http://getgalaxy.org text and links to match current master branch by opening a PR at https://github.com/galaxyproject/galaxy-hub/ - [ ] **Prepare for next release** @@ -286,7 +325,7 @@ def release_issue(argv): def do_release(argv): release_name = argv[2] release_file = _release_file(release_name + ".rst") - release_info = TEMPLATE % release_name + release_info = TEMPLATE.safe_substitute(release=release_name) open(release_file, "w").write(release_info.encode("utf-8")) month = int(release_name.split(".")[1]) month_name = calendar.month_name[month] @@ -300,6 +339,14 @@ def do_release(argv): announce_file = _release_file(release_name + "_announce.rst") open(announce_file, "w").write(announce_info.encode("utf-8")) + announce_user_info = ANNOUNCE_USER_TEMPLATE.substitute( + month_name=month_name, + year=year, + release=release_name + ) + announce_user_file = _release_file(release_name + "_announce_user.rst") + open(announce_user_file, "w").write(announce_user_info.encode("utf-8")) + next_version_params = _next_version_params(release_name) next_version = next_version_params["version"] next_release_file = _release_file(next_version + "_announce.rst") @@ -319,7 +366,7 @@ def do_release(argv): "number": pr.number, "head": pr.head, } - main([argv[0], "--release_file", "%s.rst" % release_name, "--request", as_dict, "pr" + str(pr.number)]) + main([argv[0], "--release_file", "%s_prs.rst" % release_name, "--request", as_dict, "pr" + str(pr.number)]) def check_release(argv): diff --git a/scripts/check_python.py b/scripts/check_python.py index 7fb265ad2ff..2a5175e29e6 100644 --- a/scripts/check_python.py +++ b/scripts/check_python.py @@ -4,33 +4,21 @@ message to stderr and returns 1 """ from __future__ import print_function -import os import sys version_string = '.'.join(str(_) for _ in sys.version_info[:3]) msg = """ERROR: Your Python version is: %s -Galaxy is currently supported on Python 2.7 only. To run Galaxy, please -install a supported Python version. If a supported version is already -installed but is not your default, https://galaxyproject.org/admin/python/ -contains instructions on how to force Galaxy to use a different version.""" % version_string - -msg_beta = """WARNING: Your Python version is: %s -Galaxy is currently supported on Python 2.7 only, support for Python >= 3.4 -is still in beta stage. Since you are using a virtual environment, we assume -you are developing or testing Galaxy under Python 3. If not, +Galaxy is currently supported on Python 2.7 and >=3.5 . To run Galaxy, please install a supported Python version. If a supported version is already installed but is not your default, https://galaxyproject.org/admin/python/ contains instructions on how to force Galaxy to use a different version.""" % version_string def check_python(): - venv = os.getenv('VIRTUAL_ENV') - if sys.version_info[:2] == (2, 7): + if sys.version_info[:2] == (2, 7) or sys.version_info[:2] >= (3, 5): # supported return - elif sys.version_info[:2] >= (3, 4) and venv: - print(msg_beta, file=sys.stderr) else: print(msg, file=sys.stderr) raise Exception(msg) diff --git a/scripts/common_startup.sh b/scripts/common_startup.sh index 74859ab602f..25d421bb795 100755 --- a/scripts/common_startup.sh +++ b/scripts/common_startup.sh @@ -18,7 +18,7 @@ CREATE_VENV=1 REPLACE_PIP=$SET_VENV COPY_SAMPLE_FILES=1 SKIP_CLIENT_BUILD=${GALAXY_SKIP_CLIENT_BUILD:-0} -NODE_VERSION="10.13.0" +NODE_VERSION=${GALAXY_NODE_VERSION:-"$(cat client/.node_version)"} for arg in "$@"; do [ "$arg" = "--skip-eggs" ] && FETCH_WHEELS=0 @@ -100,7 +100,6 @@ if command -v git >/dev/null && [ -d .git ]; then esac else GIT_BRANCH=0 - DEV_WHEELS=1 fi : ${GALAXY_VIRTUAL_ENV:=.venv} @@ -108,13 +107,12 @@ fi if [ $SET_VENV -eq 1 -a $CREATE_VENV -eq 1 ]; then if [ ! -d "$GALAXY_VIRTUAL_ENV" ]; then - # Locate `conda` and set $CONDA_EXE (if needed). If `python` is Conda Python and $GALAXY_VIRTUAL_ENV does not - # exist, virtualenv will not be used. setup_python calls this as well but in this case we need it done - # beforehand. + # Locate `conda` and set $CONDA_EXE (if needed). setup_python calls this + # as well, but in this case we need it done beforehand. set_conda_exe if [ -n "$CONDA_EXE" ]; then echo "Found Conda, will set up a virtualenv using conda." - echo "To use a virtualenv instead, create one with a non-Conda Python 2.7 at $GALAXY_VIRTUAL_ENV" + echo "To use a virtualenv instead, create one with a non-Conda Python at $GALAXY_VIRTUAL_ENV" : ${GALAXY_CONDA_ENV:="_galaxy_"} if [ "$CONDA_DEFAULT_ENV" != "$GALAXY_CONDA_ENV" ]; then if ! check_conda_env "$GALAXY_CONDA_ENV"; then @@ -155,7 +153,7 @@ if [ $SET_VENV -eq 1 -a $CREATE_VENV -eq 1 ]; then from urllib import urlretrieve except: from urllib.request import urlretrieve -urllib.urlretrieve('$vurl', '$vsrc')" +urlretrieve('$vurl', '$vsrc')" fi echo "Verifying $vsrc checksum is $vsha" python -c "import hashlib; assert hashlib.sha256(open('$vsrc', 'rb').read()).hexdigest() == '$vsha', '$vsrc: invalid checksum'" @@ -211,10 +209,12 @@ if [ $SKIP_CLIENT_BUILD -eq 0 ]; then # If git is not used and static/client_build_hash.txt is present, next # client rebuilds must be done manually by the admin if [ "$GIT_BRANCH" = "0" ]; then + echo "Skipping Galaxy client build because git is not in use and the client build state cannot be compared against local changes. If you have made local modifications, then manual client builds will be required." SKIP_CLIENT_BUILD=1 else # Check if anything has changed in client/ since the last build if git diff --quiet $(cat static/client_build_hash.txt) -- client/; then + echo "The Galaxy client build is up to date and will not be rebuilt at this time." SKIP_CLIENT_BUILD=1 else echo "The Galaxy client is out of date and will be built now." @@ -223,16 +223,27 @@ if [ $SKIP_CLIENT_BUILD -eq 0 ]; then else echo "The Galaxy client has not yet been built and will be built now." fi +else + echo "The Galaxy client build is being skipped due to the SKIP_CLIENT_BUILD environment variable." +fi + +# Install node if not installed +if [ -n "$VIRTUAL_ENV" ]; then + if ! in_venv "$(command -v node)" || [ "$(node --version)" != "v$NODE_VERSION" ]; then + echo "Installing node into $VIRTUAL_ENV with nodeenv." + nodeenv -n $NODE_VERSION -p + fi +elif [ -n "$CONDA_DEFAULT_ENV" -a -n "$CONDA_EXE" ]; then + if ! in_conda_env "$(command -v node)"; then + echo "Installing node into '$CONDA_DEFAULT_ENV' Conda environment with conda." + $CONDA_EXE install --yes --override-channels --channel conda-forge --channel defaults --name $CONDA_DEFAULT_ENV node=$NODE_VERSION + fi fi # Build client if necessary. if [ $SKIP_CLIENT_BUILD -eq 0 ]; then # Ensure dependencies are installed if [ -n "$VIRTUAL_ENV" ]; then - if ! in_venv "$(command -v node)" || [ "$(node --version)" != "v$NODE_VERSION" ]; then - echo "Installing node into $VIRTUAL_ENV with nodeenv." - nodeenv -n $NODE_VERSION -p - fi if ! in_venv "$(command -v yarn)"; then echo "Installing yarn into $VIRTUAL_ENV with npm." npm install --global yarn @@ -247,7 +258,7 @@ if [ $SKIP_CLIENT_BUILD -eq 0 ]; then fi # Build client cd client - if yarn install --network-timeout 120000 --check-files; then + if yarn install --network-timeout 300000 --check-files; then if ! yarn run build-production-maps; then echo "ERROR: Galaxy client build failed. See ./client/README.md for more information, including how to get help." exit 1 @@ -257,7 +268,4 @@ if [ $SKIP_CLIENT_BUILD -eq 0 ]; then exit 1 fi cd - -else - echo "Regenerating static plugin directories." - python ./scripts/plugin_staging.py fi diff --git a/scripts/communication/communication_server.py b/scripts/communication/communication_server.py index 1909bfcb447..3288aa459de 100755 --- a/scripts/communication/communication_server.py +++ b/scripts/communication/communication_server.py @@ -50,9 +50,9 @@ from flask_socketio import ( sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 'lib'))) import galaxy.config +from galaxy.security.idencoding import IdEncodingHelper from galaxy.util.sanitize_html import sanitize_html from galaxy.util.script import app_properties_from_args, populate_config_args -from galaxy.web.security import SecurityHelper logging.basicConfig() log = logging.getLogger(__name__) @@ -77,7 +77,7 @@ if "id_secret" not in app_properties: id_secret = app_properties.get('id_secret', 'dangerous_default') -security_helper = SecurityHelper(id_secret=id_secret) +security_helper = IdEncodingHelper(id_secret=id_secret) # And get access to the models # Login manager to manage current_user functionality login_manager = flask_login.LoginManager() diff --git a/scripts/galaxy-main b/scripts/galaxy-main index a1daa5d0731..5943cc709ab 100755 --- a/scripts/galaxy-main +++ b/scripts/galaxy-main @@ -103,6 +103,7 @@ def load_galaxy_app( attach_to_pools=attach_to_pools, **kwds ) + app.database_heartbeat.start() app.control_worker.bind_and_start() app.application_stack.log_startup() return app diff --git a/scripts/get_uwsgi_args.py b/scripts/get_uwsgi_args.py index 0a8fc9be662..638c3d4ea31 100644 --- a/scripts/get_uwsgi_args.py +++ b/scripts/get_uwsgi_args.py @@ -93,9 +93,7 @@ def _get_uwsgi_args(cliargs, kwargs): 'static-map': ('/static/style={here}/static/style/blue'.format(here=os.getcwd()), '/static={here}/static'.format(here=os.getcwd()), '/favicon.ico={here}/static/favicon.ico'.format(here=os.getcwd())), - 'static-safe': ('{here}/config/plugins/visualizations'.format(here=os.getcwd()), - '{here}/client/galaxy/images'.format(here=os.getcwd()), - '{here}/config/plugins/interactive_environments'.format(here=os.getcwd())), + 'static-safe': ('{here}/client/galaxy/images'.format(here=os.getcwd())), 'die-on-term': True, 'enable-threads': True, 'hook-master-start': ('unix_signal:2 gracefully_kill_them_all', diff --git a/scripts/helper.py b/scripts/helper.py index 1bf36fb9e4e..7ed11afaec9 100644 --- a/scripts/helper.py +++ b/scripts/helper.py @@ -13,8 +13,8 @@ import sys sys.path.insert(1, os.path.join(os.path.dirname(__file__), os.pardir, 'lib')) import galaxy.config +from galaxy.security import idencoding from galaxy.util.script import app_properties_from_args, populate_config_args -from galaxy.web import security parser = argparse.ArgumentParser() populate_config_args(parser) @@ -26,7 +26,7 @@ args = parser.parse_args() app_properties = app_properties_from_args(args) config = galaxy.config.Configuration(**app_properties) -helper = security.SecurityHelper(id_secret=app_properties.get('id_secret')) +helper = idencoding.IdEncodingHelper(id_secret=app_properties.get('id_secret')) model = galaxy.config.init_models_from_config(config) if args.encode_id: diff --git a/scripts/pages_identifier_conversion.py b/scripts/pages_identifier_conversion.py index b7139e8e77d..ce914938f21 100644 --- a/scripts/pages_identifier_conversion.py +++ b/scripts/pages_identifier_conversion.py @@ -10,10 +10,10 @@ import galaxy import galaxy.app import galaxy.config from galaxy.objectstore import build_object_store_from_config +from galaxy.security.idencoding import IdEncodingHelper from galaxy.util import unicodify from galaxy.util.bunch import Bunch from galaxy.util.script import app_properties_from_args, populate_config_args -from galaxy.web.security import SecurityHelper from galaxy.webapps.galaxy.controllers.page import _PageContentProcessor, _placeholderRenderForSave @@ -26,7 +26,7 @@ def main(argv): properties = app_properties_from_args(args) config = galaxy.config.Configuration(**properties) secret = args.secret_key or config.id_secret - security_helper = SecurityHelper(id_secret=secret) + security_helper = IdEncodingHelper(id_secret=secret) object_store = build_object_store_from_config(config) if not config.database_connection: print("The database connection is empty. If you are using the default value, please uncomment that in your galaxy.yml") diff --git a/scripts/plugin_staging.py b/scripts/plugin_staging.py index bd4986801ec..908ae6d5599 100644 --- a/scripts/plugin_staging.py +++ b/scripts/plugin_staging.py @@ -7,7 +7,7 @@ import shutil GXY_ROOT = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir)) -def link_up_static(f): +def stage_static(f): src = os.path.join(GXY_ROOT, 'config/plugins', f) dest = os.path.join(GXY_ROOT, 'static/plugins', f) dest_parent = os.path.abspath(os.path.join(dest, os.pardir)) @@ -18,12 +18,14 @@ def link_up_static(f): else: shutil.rmtree(dest) elif not os.path.exists(dest_parent): - # Create parent dir structure to symlink directly to static. os.makedirs(dest_parent) try: - os.symlink(src, dest) + if os.path.isdir(src): + shutil.copytree(src, dest) + else: + shutil.copy(src, dest) except Exception: - print("Error symlinking '%s' to '%s'" % (src, dest)) + print("Error copying '%s' to '%s'" % (src, dest)) raise @@ -31,4 +33,4 @@ if __name__ == "__main__": # This is not awesome, but it's temporary, and it supports two-tier plugin static. for f in glob.glob(os.path.join(GXY_ROOT, 'config/plugins/*/*/static')) + glob.glob(os.path.join(GXY_ROOT, 'config/plugins/*/*/*/static')): f = os.path.relpath(f, os.path.join(GXY_ROOT, 'config/plugins')) - link_up_static(f) + stage_static(f) diff --git a/scripts/secret_decoder_ring.py b/scripts/secret_decoder_ring.py index 9ccbcba5ff7..a70dd711b66 100644 --- a/scripts/secret_decoder_ring.py +++ b/scripts/secret_decoder_ring.py @@ -9,9 +9,9 @@ import sys sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, 'lib'))) +from galaxy.security.idencoding import IdEncodingHelper from galaxy.util import unicodify from galaxy.util.script import app_properties_from_args, populate_config_args -from galaxy.web.security import SecurityHelper logging.basicConfig() log = logging.getLogger(__name__) @@ -35,7 +35,7 @@ if "id_secret" not in app_properties: id_secret = app_properties.get('id_secret', 'dangerous_default') -security_helper = SecurityHelper(id_secret=id_secret) +security_helper = IdEncodingHelper(id_secret=id_secret) # And get access to the models # Login manager to manage current_user functionality diff --git a/scripts/tool_shed/bootstrap_tool_shed/create_user_with_api_key.py b/scripts/tool_shed/bootstrap_tool_shed/create_user_with_api_key.py index 13cb8165710..f91cdd356f8 100644 --- a/scripts/tool_shed/bootstrap_tool_shed/create_user_with_api_key.py +++ b/scripts/tool_shed/bootstrap_tool_shed/create_user_with_api_key.py @@ -13,7 +13,7 @@ sys.path.insert(1, os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, sys.path.insert(1, os.path.join(os.path.dirname(__file__))) import galaxy.webapps.tool_shed.config as tool_shed_config -from galaxy.web import security +from galaxy.security.idencoding import IdEncodingHelper from galaxy.webapps.tool_shed.model import mapping from bootstrap_util import admin_user_info # noqa: I100,I201 @@ -40,7 +40,7 @@ class BootstrapApplication(object): self.config.database_connection, engine_options={}, create_tables=False) - self.security = security.SecurityHelper(id_secret=self.config.id_secret) + self.security = IdEncodingHelper(id_secret=self.config.id_secret) self.hgweb_config_manager = self.model.hgweb_config_manager self.hgweb_config_manager.hgweb_config_dir = self.config.hgweb_config_dir print('Using hgweb.config file: ', self.hgweb_config_manager.hgweb_config) diff --git a/templates/display_common.mako b/templates/display_common.mako index b84cde8a393..937da544531 100644 --- a/templates/display_common.mako +++ b/templates/display_common.mako @@ -82,8 +82,6 @@ class_plural = "Datasets" elif a_class == model.FormDefinitionCurrent: class_plural = "Forms" - elif a_class == model.UserOpenID: - class_plural = "OpenIDs" else: class_plural = "items" return class_plural diff --git a/templates/message.mako b/templates/message.mako index 69c36790243..c22c338da2c 100644 --- a/templates/message.mako +++ b/templates/message.mako @@ -1,4 +1,6 @@ <%! + import bleach + def inherit(context): if context.get('use_panels'): if context.get('webapp'): @@ -53,7 +55,13 @@ ## Render a message <%def name="render_msg( msg, status='done' )"> - <% status = "success" if status == "done" else status %> - <% status = "danger" if status == "error" else status %> -
    ${_(msg)}
    + <% + if status == "done": + status = "success" + elif status == "error": + status = "danger" + if status not in ("danger", "info", "success", "warning"): + status = "info" + %> +
    ${_(bleach.clean(msg))}
    diff --git a/templates/show_params.mako b/templates/show_params.mako index 6e60bcf3c5e..daeeb837d84 100644 --- a/templates/show_params.mako +++ b/templates/show_params.mako @@ -173,6 +173,13 @@
%if job: + %if job.job_messages: + + %endif %endif
OwnerNameSynopsisOwnerNameSynopsisType
@@ -320,7 +319,6 @@ export default Backbone.View.extend({
-
-
+
${this.model.get("owner") === Galaxy.user.attributes.username ? "You" : this.model.get("owner")} @@ -139,13 +138,13 @@ const WorkflowItemView = Backbone.View.extend({ }, renderTagEditor: function() { - const TagEditor = new TAGS.TagsEditor({ + let el = $(this.el).find(".tags-display")[0]; + let propsData = { model: this.model, - el: $.find(`.${this.model.id}.tags-display`), - workflow_mode: true - }); - TagEditor.toggle(true); - TagEditor.render(); + disabled: false, + context: "workflow" + }; + return mountModelTags(propsData, el); }, /** Template for user actions for workflows */ @@ -195,13 +194,14 @@ const WorkflowListView = Backbone.View.extend({ }, events: { - dragleave: "unhighlightDropZone", - drop: "drop", - dragover: function(ev) { - $(".hidden_description_layer").addClass("dragover"); - $(".menubutton").addClass("background-none"); - ev.preventDefault(); - } + dragover: "highlightDropZone", + dragleave: "unhighlightDropZone" + }, + + highlightDropZone: function(ev) { + $(".hidden_description_layer").addClass("dragover"); + $(".menubutton").addClass("background-none"); + ev.preventDefault(); }, unhighlightDropZone: function() { @@ -226,7 +226,7 @@ const WorkflowListView = Backbone.View.extend({ try { wf_json = JSON.parse(reader.result); } catch (e) { - mod_toastr.error(`Could not read file '${f.name}'. Verify it is a valid Galaxy workflow`); + Toast.error(`Could not read file '${f.name}'. Verify it is a valid Galaxy workflow`); wf_json = null; } if (wf_json) { @@ -234,10 +234,10 @@ const WorkflowListView = Backbone.View.extend({ at: 0, wait: true, success: function() { - mod_toastr.success(`Successfully imported workflow '${wf_json.name}'`); + Toast.success(`Successfully imported workflow '${wf_json.name}'`); }, error: function(model, resp, options) { - mod_toastr.error(options.errorThrown); + Toast.error(options.errorThrown); } }); } @@ -250,9 +250,9 @@ const WorkflowListView = Backbone.View.extend({ const msg_text = QueryStringParsing.get("message"); const msg_status = QueryStringParsing.get("status"); if (msg_status === "error") { - mod_toastr.error(_.escape(msg_text || "Unknown Error, please report this to an administrator.")); + Toast.error(_.escape(msg_text || "Unknown Error, please report this to an administrator.")); } else if (msg_text) { - mod_toastr.info(_.escape(msg_text)); + Toast.info(_.escape(msg_text)); } }), @@ -272,6 +272,9 @@ const WorkflowListView = Backbone.View.extend({ this.searchWorkflow(this.$(".search-wf"), this.$(".workflow-search tr"), minQueryLength); this.adjustActiondropdown(); this._showArgErrors(); + this.$(".hidden_description_layer") + .get(0) + .addEventListener("drop", _.bind(this.drop, this)); return this; }, diff --git a/client/galaxy/scripts/onload/initQueue.js b/client/galaxy/scripts/onload/initQueue.js index fa9572f95b2..7a32f59c91d 100644 --- a/client/galaxy/scripts/onload/initQueue.js +++ b/client/galaxy/scripts/onload/initQueue.js @@ -17,7 +17,7 @@ import { BehaviorSubject } from "rxjs"; import { filter } from "rxjs/operators"; -const queue = (window.top._initQueue = new BehaviorSubject([])); +const queue = new BehaviorSubject([]); // don't emit unless there are things to initialize export const initializations$ = queue.pipe(filter(list => list.length > 0)); diff --git a/client/galaxy/scripts/qunit/tests/masthead_tests.js b/client/galaxy/scripts/qunit/tests/masthead_tests.js index 18cf41cda37..5718b9ab2ce 100644 --- a/client/galaxy/scripts/qunit/tests/masthead_tests.js +++ b/client/galaxy/scripts/qunit/tests/masthead_tests.js @@ -13,8 +13,6 @@ QUnit.module("Masthead test", { use_remote_user: "use_remote_user", remote_user_logout_href: "remote_user_logout_href", lims_doc_url: "lims_doc_url", - biostar_url: "biostar_url", - biostar_url_redirect: "biostar_url_redirect", support_url: "support_url", search_url: "search_url", mailing_lists: "mailing_lists", diff --git a/client/galaxy/scripts/qunit/tests/ui_tests.js b/client/galaxy/scripts/qunit/tests/ui_tests.js index 35c7d8618db..e60164f7986 100644 --- a/client/galaxy/scripts/qunit/tests/ui_tests.js +++ b/client/galaxy/scripts/qunit/tests/ui_tests.js @@ -726,15 +726,18 @@ QUnit.test("select-content", function(assert) { "Contains " + options.totalmultiple + " multiselect fields" ); assert.ok( - select.$el.children(".ui-options").find(".ui-option").length === - (options.selectfields > 1 ? options.selectfields : 0), - "Radio button count" + select.$el.find(".ui-options:first .ui-option").length === options.selectfields, + "Radio button count, expected " + options.selectfields ); assert.ok(select.$(".ui-select:first").css("display") == "block", "Check select visibility"); assert.ok( select.$(".ui-select:last").css("display") == (options.selectfields == 1 ? "block" : "none"), "Last select visibility" ); + /*assert.ok( + (select.button_dialog.$el.css("display") != "none") === options.showdialog, + "Data dialog button visible" + );*/ _testSelect("first", options); _testSelect("last", options); }; @@ -759,7 +762,8 @@ QUnit.test("select-content", function(assert) { lastvalue: "id2", lastlabel: "hid2: name2", lastlength: 3, - lastmultiple: false + lastmultiple: false, + showdialog: true }; _test(initial); @@ -775,7 +779,8 @@ QUnit.test("select-content", function(assert) { lastvalue: "id2", lastlabel: "hid2: name2", lastlength: 3, - lastmultiple: true + lastmultiple: true, + showdialog: true }); select.model.set("multiple", false); @@ -790,7 +795,8 @@ QUnit.test("select-content", function(assert) { lastvalue: "id2", lastlabel: "hid2: name2", lastlength: 3, - lastmultiple: false + lastmultiple: false, + showdialog: false }); select.model.set("type", "module_data_collection"); @@ -804,7 +810,8 @@ QUnit.test("select-content", function(assert) { lastvalue: "id2", lastlabel: "hid2: name2", lastlength: 3, - lastmultiple: true + lastmultiple: true, + showdialog: false }); select.model.set("type", "module_data"); @@ -818,7 +825,8 @@ QUnit.test("select-content", function(assert) { lastvalue: "id0", lastlabel: "hid0: name0", lastlength: 2, - lastmultiple: true + lastmultiple: true, + showdialog: true }); select.model.set("type", "data"); diff --git a/client/galaxy/scripts/qunit/tests/workflow_editor_tests.js b/client/galaxy/scripts/qunit/tests/workflow_editor_tests.js index 2836d353f27..c02570236ff 100644 --- a/client/galaxy/scripts/qunit/tests/workflow_editor_tests.js +++ b/client/galaxy/scripts/qunit/tests/workflow_editor_tests.js @@ -72,7 +72,7 @@ QUnit.module("Input terminal model test", { return Terminals.NULL_COLLECTION_TYPE_DESCRIPTION; }; } - return this.input_terminal.canAccept(other); + return this.input_terminal.canAccept(other).canAccept; }, pja_change_datatype_node: function(output_name, newtype) { var pja = { @@ -319,7 +319,7 @@ QUnit.test("Collection output can connect to same collection input type", functi }); outputTerminal.node = {}; assert.ok( - inputTerminal.canAccept(outputTerminal), + inputTerminal.canAccept(outputTerminal).canAccept, "Input terminal " + inputTerminal + " can not accept " + outputTerminal ); }); @@ -332,7 +332,7 @@ QUnit.test("Collection output cannot connect to different collection input type" collection_type: "paired" }); outputTerminal.node = {}; - assert.ok(!inputTerminal.canAccept(outputTerminal)); + assert.ok(!inputTerminal.canAccept(outputTerminal).canAccept); }); QUnit.module("Node unit test", { @@ -545,6 +545,8 @@ QUnit.module("Node view ", { input_terminals: {}, output_terminals: {}, markChanged: function() {}, + hasConnectedOutputTerminals: function() {}, + connectedMappedInputTerminals: function() {}, terminalMapping: { disableMapOver: function() {} } }); }, @@ -987,7 +989,7 @@ QUnit.module("terminal mapping logic", { outputTerminal = output; } - assert.ok(!inputTerminal.attachable(outputTerminal)); + assert.ok(!inputTerminal.attachable(outputTerminal).canAccept); }, verifyAttachable: function(assert, inputTerminal, output) { var outputTerminal; @@ -998,12 +1000,15 @@ QUnit.module("terminal mapping logic", { outputTerminal = output; } - assert.ok(inputTerminal.attachable(outputTerminal), "Cannot attach " + outputTerminal + " to " + inputTerminal); + assert.ok( + inputTerminal.attachable(outputTerminal).canAccept, + "Cannot attach " + outputTerminal + " to " + inputTerminal + ); // Go further... make sure datatypes are being enforced inputTerminal.datatypes = ["bam"]; outputTerminal.datatypes = ["txt"]; - assert.ok(!inputTerminal.attachable(outputTerminal)); + assert.ok(!inputTerminal.attachable(outputTerminal).canAccept); }, verifyMappedOver: function(assert, terminal) { assert.ok(terminal.terminalMapping.mapOver.isCollection); @@ -1174,7 +1179,14 @@ QUnit.test("multiple input attachable by collections", function(assert) { this.verifyAttachable(assert, this.inputTerminal1, "list"); }); -QUnit.test("unconnected multiple inputs cannot be connected to rank > 1 collections (yet...)", function(assert) { +QUnit.test("multiple input attachable by nested collections", function(assert) { + this.inputTerminal1 = this.newInputTerminal(null, { multiple: true }); + var connectedInput1 = this.addConnectedInput(this.inputTerminal1); + this.addConnectedOutput(connectedInput1); + this.verifyAttachable(assert, this.inputTerminal1, "list:list"); +}); + +QUnit.test("Multiple inputs cannot be connected to pairs", function(assert) { this.inputTerminal1 = this.newInputTerminal(null, { multiple: true }); this.verifyNotAttachable(assert, this.inputTerminal1, "list:paired"); }); diff --git a/client/galaxy/scripts/reports/run_stats.js b/client/galaxy/scripts/reports/run_stats.js index 4c248b39997..0c30bae7971 100644 --- a/client/galaxy/scripts/reports/run_stats.js +++ b/client/galaxy/scripts/reports/run_stats.js @@ -1,6 +1,6 @@ import $ from "jquery"; -import * as d3 from "../libs/d3"; -import { event as currentEvent } from "../libs/d3"; +import * as d3 from "d3"; +import { event as currentEvent } from "d3"; function date_by_subtracting_days(date, days) { return new Date( diff --git a/client/galaxy/scripts/sample.test.js b/client/galaxy/scripts/sample.test.js deleted file mode 100644 index de7697b1a7b..00000000000 --- a/client/galaxy/scripts/sample.test.js +++ /dev/null @@ -1,12 +0,0 @@ -describe("Sample Test", () => { - it("hey look, tests run", () => { - assert.equal(1, 1); - }); - - it("prove I can use a Proxy", () => { - let target = {}; - let thing = new Proxy(target, {}); - thing.foo = 232; - assert.equal(target.foo, thing.foo); - }); -}); diff --git a/client/galaxy/scripts/store/gridSearchStore.js b/client/galaxy/scripts/store/gridSearchStore.js new file mode 100644 index 00000000000..e67ddaaf92d --- /dev/null +++ b/client/galaxy/scripts/store/gridSearchStore.js @@ -0,0 +1,33 @@ +/** + * Vuex store module used for managing search parameters in the grid. Currently + * only manages the tags that access this store via the new tagging components, + * but presumably the entire grid search filter criteria will live here one day. + */ + +export const gridSearchStore = { + state: { + searchTags: new Set() + }, + mutations: { + // TODO: we could write an equivalence comparator here for searchTag + // Sets and not register a change if the new set is equivalent + setSearchTags(state, tags) { + state.searchTags = new Set(tags); + } + }, + actions: { + toggleSearchTag({ state, commit }, { text }) { + let tags = new Set(state.searchTags); + tags.has(text) ? tags.delete(text) : tags.add(text); + commit("setSearchTags", tags); + }, + removeSearchTag({ state, commit }, { text }) { + let tags = new Set(state.searchTags); + tags.delete(text); + commit("setSearchTags", tags); + }, + clearSearchTags({ state, commit }) { + commit("setSearchTags", new Set()); + } + } +}; diff --git a/client/galaxy/scripts/store/gridSearchStore.test.js b/client/galaxy/scripts/store/gridSearchStore.test.js new file mode 100644 index 00000000000..7e841c66286 --- /dev/null +++ b/client/galaxy/scripts/store/gridSearchStore.test.js @@ -0,0 +1,8 @@ +import store from "./index"; + +describe("store/gridSearchStore.js", () => { + it("the searchTags in the store should be a Set object", () => { + let searchTags = store.state.gridSearch.searchTags; // this is a Set() + assert(searchTags instanceof Set, "searchTags wrong variable type, should be Set()"); + }); +}); diff --git a/client/galaxy/scripts/store/index.js b/client/galaxy/scripts/store/index.js new file mode 100644 index 00000000000..3ab28141579 --- /dev/null +++ b/client/galaxy/scripts/store/index.js @@ -0,0 +1,17 @@ +/** + * Central Vuex store + */ + +import Vue from "vue"; +import Vuex from "vuex"; +import { gridSearchStore } from "./gridSearchStore"; +import { tagStore } from "./tagStore"; + +Vue.use(Vuex); + +export default new Vuex.Store({ + modules: { + gridSearch: gridSearchStore, + tags: tagStore + } +}); diff --git a/client/galaxy/scripts/store/tagStore.js b/client/galaxy/scripts/store/tagStore.js new file mode 100644 index 00000000000..20dc8cee3c7 --- /dev/null +++ b/client/galaxy/scripts/store/tagStore.js @@ -0,0 +1,43 @@ +export const state = { + userTagList: [], // List of recent user tags + modelTagCache: new Map() // Maps model id to list of tags +}; + +export const getters = { + getTagsById: state => key => { + if (state.modelTagCache.has(key)) { + let tagSet = state.modelTagCache.get(key); //.sort(); + return Array.from(tagSet); + } + return []; + } +}; + +export const actions = { + updateTags({ commit }, { key, tags }) { + commit("setTags", { key, tags }); + }, + initializeTags({ dispatch, state }, { key, tags }) { + if (!state.modelTagCache.has(key)) { + dispatch("updateTags", { key, tags }); + } + } +}; + +export const mutations = { + setTags(state, { key, tags }) { + state.modelTagCache = new Map(state.modelTagCache); + state.modelTagCache.set(key, new Set(tags)); + }, + reset(state) { + state.userTagList = []; + state.modelTagCache = new Map(); + } +}; + +export const tagStore = { + state, + getters, + actions, + mutations +}; diff --git a/client/galaxy/scripts/store/tagStore.test.js b/client/galaxy/scripts/store/tagStore.test.js new file mode 100644 index 00000000000..26cb42fe1ca --- /dev/null +++ b/client/galaxy/scripts/store/tagStore.test.js @@ -0,0 +1,61 @@ +import { tagStore } from "./tagStore"; + +describe("store/tagStore.js", () => { + const state = tagStore.state; + const { reset } = tagStore.mutations; + + afterEach(() => { + reset(state); + }); + + describe("mutations/setTags", () => { + const { setTags } = tagStore.mutations; + const testKey = "foo"; + const testTags = ["a", "b", "c", "b"]; + + let stateTags; + + beforeEach(() => { + setTags(state, { key: testKey, tags: testTags }); + stateTags = state.modelTagCache.get(testKey); + }); + + it("should update the state Map and store a Set", () => { + assert(stateTags instanceof Set, "Stored list should be a Set"); + testTags.forEach(t => { + assert(stateTags.has(t), `Missing tag: ${t}`); + }); + }); + + it("that set should contain all the passed tags", () => { + testTags.forEach(t => { + assert(stateTags.has(t), `Missing tag: ${t}`); + }); + }); + + it("should store a list of unique values", () => { + assert(stateTags.size == 3, "Stored list should only consist of unique items"); + }); + }); + + describe("getters/getTagsById", () => { + const { getTagsById } = tagStore.getters; + const { setTags } = tagStore.mutations; + const testKey = "foo"; + const testTags = ["a", "b", "c", "b"]; + + let thisGetter; + + beforeEach(() => { + setTags(state, { key: testKey, tags: testTags }); + // getter functions are compound functions, need to build the getter first + thisGetter = getTagsById(state); + }); + + it("should update the state Map and store a Set", () => { + let tags = thisGetter(testKey); + assert(tags instanceof Array, "returned result should be a simple array"); + assert((tags.length = 3)); + }); + }); +}); diff --git a/client/galaxy/scripts/toolshed/groups/group-detail-view.js b/client/galaxy/scripts/toolshed/groups/group-detail-view.js index 73358436f27..6c3ed575b16 100644 --- a/client/galaxy/scripts/toolshed/groups/group-detail-view.js +++ b/client/galaxy/scripts/toolshed/groups/group-detail-view.js @@ -1,7 +1,7 @@ import $ from "jquery"; import _ from "underscore"; import Backbone from "backbone"; -import * as mod_toastr from "libs/toastr"; +import { Toast } from "ui/toast"; import mod_group_model from "toolshed/groups/group-model"; // toolshed group detail view @@ -34,9 +34,9 @@ const GroupDetailView = Backbone.View.extend({ }, error: function(model, response) { if (typeof response.responseJSON !== "undefined") { - mod_toastr.error(response.responseJSON.err_msg); + Toast.error(response.responseJSON.err_msg); } else { - mod_toastr.error("An error occurred."); + Toast.error("An error occurred."); } } }); @@ -98,7 +98,7 @@ const GroupDetailView = Backbone.View.extend({ '<% _.each(group.get("repositories"), function(repo) { %>', "
", - '<%= _.escape(repo.name) %>', + '<%= _.escape(repo.name) %>', "", "<%= _.escape(repo.description) %>", diff --git a/client/galaxy/scripts/toolshed/groups/group-list-view.js b/client/galaxy/scripts/toolshed/groups/group-list-view.js index fafd76eec73..9556048d14f 100644 --- a/client/galaxy/scripts/toolshed/groups/group-list-view.js +++ b/client/galaxy/scripts/toolshed/groups/group-list-view.js @@ -1,7 +1,7 @@ import $ from "jquery"; import _ from "underscore"; import Backbone from "backbone"; -import * as mod_toastr from "libs/toastr"; +import { Toast } from "ui/toast"; import mod_group_model from "toolshed/groups/group-model"; import mod_group_row from "toolshed/groups/group-listrow-view"; const GroupListView = Backbone.View.extend({ @@ -23,9 +23,9 @@ const GroupListView = Backbone.View.extend({ }, error: function(model, response) { if (typeof response.responseJSON !== "undefined") { - mod_toastr.error(response.responseJSON.err_msg); + Toast.error(response.responseJSON.err_msg); } else { - mod_toastr.error("An error occurred."); + Toast.error("An error occurred."); } } }); diff --git a/client/galaxy/scripts/ui/autocom_tagging.js b/client/galaxy/scripts/ui/autocom_tagging.js deleted file mode 100644 index 6420484002b..00000000000 --- a/client/galaxy/scripts/ui/autocom_tagging.js +++ /dev/null @@ -1,369 +0,0 @@ -import $ from "jquery"; -import _ from "underscore"; - -// ============================================================================ -/** - * JQuery extension for tagging with autocomplete. - * @author: Jeremy Goecks - * @require: jquery.autocomplete plugin - */ -// -// Initialize "tag click functions" for tags. -// -export function init_tag_click_function(tag_elt, click_func) { - $(tag_elt) - .find(".tag-name") - .each(function() { - $(this).click(function() { - var tag_str = $(this).text(); - var tag_name_and_value = tag_str.split(":"); - click_func(tag_name_and_value[0], tag_name_and_value[1]); - return true; - }); - }); -} - -$.fn.autocomplete_tagging = function(options) { - var defaults = { - get_toggle_link_text_fn: function(tags) { - var text = ""; - var num_tags = _.size(tags); - if (num_tags > 0) { - text = num_tags + (num_tags > 1 ? " Tags" : " Tag"); - } else { - text = "Add tags"; - } - return text; - }, - tag_click_fn: function(name, value) {}, - editable: true, - input_size: 20, - in_form: false, - tags: {}, - use_toggle_link: true, - item_id: "", - add_tag_img: "", - add_tag_img_rollover: "", - delete_tag_img: "", - ajax_autocomplete_tag_url: "", - ajax_retag_url: "", - ajax_delete_tag_url: "", - ajax_add_tag_url: "" - }; - - var settings = $.extend(defaults, options); - - // - // Initalize object's elements. - // - - // Get elements for this object. For this_obj, assume the last element with the id is the "this"; this is somewhat of a hack to address the problem - // that there may be two tagging elements for a single item if there are both community and individual tags for an element. - var this_obj = $(this); - var tag_area = this_obj.find(".tag-area"); - var toggle_link = this_obj.find(".toggle-link"); - var tag_input_field = this_obj.find(".tag-input"); - var add_tag_button = this_obj.find(".add-tag-button"); - - // Initialize toggle link. - toggle_link.click(function() { - // Take special actions depending on whether toggle is showing or hiding link. - var after_toggle_fn; - if (tag_area.is(":hidden")) { - after_toggle_fn = function() { - // If there are no tags, go right to editing mode by generating a click on the area. - var num_tags = $(this).find(".tag-button").length; - if (num_tags === 0) { - tag_area.click(); - } - }; - } else { - after_toggle_fn = () => { - tag_area.blur(); - }; - } - tag_area.slideToggle("fast", after_toggle_fn); - return $(this); - }); - - // Initialize tag input field. - if (settings.editable) { - tag_input_field.hide(); - } - tag_input_field.keyup(function(e) { - if (e.keyCode === 27) { - // Escape key - $(this).trigger("blur"); - } else if ( - e.keyCode === 13 || // Return Key - e.keyCode === 188 || // Comma - e.keyCode === 32 // Space - ) { - // - // Check input. - // - - var new_value = this.value; - - // Suppress space after a ":" - if (new_value.indexOf(": ", new_value.length - 2) !== -1) { - this.value = new_value.substring(0, new_value.length - 1); - return false; - } - - // Remove trigger keys from input. - if (e.keyCode === 188 || e.keyCode === 32) { - new_value = new_value.substring(0, new_value.length - 1); - } - - // Trim whitespace. - new_value = $.trim(new_value); - - // Too short? - if (new_value.length < 2) { - return false; - } - - // - // New tag OK - apply it. - // - - this.value = ""; // Reset text field now that tag is being added - - // Add button for tag after all other tag buttons. - var new_tag_button = build_tag_button(new_value); - var tag_buttons = tag_area.children(".tag-button"); - if (tag_buttons.length !== 0) { - var last_tag_button = tag_buttons.slice(tag_buttons.length - 1); - last_tag_button.after(new_tag_button); - } else { - tag_area.prepend(new_tag_button); - } - - // Add tag to internal list. - var tag_name_and_value = new_value.split(":"); - settings.tags[tag_name_and_value[0]] = tag_name_and_value[1]; - - // Update toggle link text. - var new_text = settings.get_toggle_link_text_fn(settings.tags); - toggle_link.text(new_text); - - // Commit tag to server. - var zz = $(this); - $.ajax({ - url: settings.ajax_add_tag_url, - data: { new_tag: new_value }, - error: function() { - // Failed. Roll back changes and show alert. - new_tag_button.remove(); - delete settings.tags[tag_name_and_value[0]]; - var new_text = settings.get_toggle_link_text_fn(settings.tags); - toggle_link.text(new_text); - alert("Add tag failed"); - }, - success: function() { - // Flush autocomplete cache because it's not out of date. - // TODO: in the future, we could remove the particular item - // that was chosen from the cache rather than flush it. - zz.data("autocompleter").cacheFlush(); - } - }); - - return false; - } - }); - - // Add autocomplete to input. - var format_item_func = (key, row_position, num_rows, value, search_term) => { - var tag_name_and_value = value.split(":"); - return tag_name_and_value.length === 1 ? tag_name_and_value[0] : tag_name_and_value[1]; - }; - var autocomplete_options = { - selectFirst: false, - formatItem: format_item_func, - autoFill: false, - highlight: false - }; - tag_input_field.autocomplete_verheul(settings.ajax_autocomplete_tag_url, autocomplete_options); - - // Initialize delete tag images for current tags. - this_obj.find(".delete-tag-img").each(function() { - init_delete_tag_image($(this)); - }); - - // Initialize tag click function. - init_tag_click_function($(this), settings.tag_click_fn); - - // Initialize "add tag" button. - add_tag_button.click(function() { - $(this).hide(); - - // Clicking on button is the same as clicking on the tag area. - tag_area.click(); - return false; - }); - - // - // Set up tag area interactions; these are needed only if tags are editable. - // - if (settings.editable) { - // When the tag area blurs, go to "view tag" mode. - tag_area.bind("blur", e => { - if (_.size(settings.tags) > 0) { - add_tag_button.show(); - tag_input_field.hide(); - tag_area.removeClass("active-tag-area"); - // tag_area.addClass("tooltip"); - } else { - // No tags, so do nothing to ensure that input is still visible. - } - }); - - // On click, enable user to add tags. - tag_area.click(function(e) { - var is_active = $(this).hasClass("active-tag-area"); - - // If a "delete image" object was pressed and area is inactive, do nothing. - if ($(e.target).hasClass("delete-tag-img") && !is_active) { - return false; - } - - // If a "tag name" object was pressed and area is inactive, do nothing. - if ($(e.target).hasClass("tag-name") && !is_active) { - return false; - } - - // Remove tooltip. - // $(this).removeClass("tooltip"); - - // Hide add tag button, show tag_input field. Change background to show - // area is active. - $(this).addClass("active-tag-area"); - add_tag_button.hide(); - tag_input_field.show(); - tag_input_field.focus(); - - // Add handler to document that will call blur when the tag area is blurred; - // a tag area is blurred when a user clicks on an element outside the area. - var handle_document_click = e => { - var check_click = function(tag_area, target) { - // Blur the tag area if the element clicked on is not in the tag area. - if (target !== tag_area) { - tag_area.blur(); - $(window).unbind("click.tagging_blur"); - $(this).addClass("tooltip"); - } - }; - check_click(tag_area, $(e.target)); - }; - // TODO: we should attach the click handler to all frames in order to capture - // clicks outside the frame that this element is in. - //window.parent.document.onclick = handle_document_click; - //var temp = $(window.parent.document.body).contents().find("iframe").html(); - //alert(temp); - //$(document).parent().click(handle_document_click); - $(window).bind("click.tagging_blur", handle_document_click); - - return false; - }); - } - - // If using toggle link, hide the tag area. Otherwise, show the tag area. - if (settings.use_toggle_link) { - tag_area.hide(); - } - - // - // Helper functions. - // - - // Initialize a "delete tag image": when click, delete tag from UI and send delete request to server. - function init_delete_tag_image(delete_img) { - $(delete_img).mouseenter(function() { - $(this).attr("src", settings.delete_tag_img_rollover); - }); - $(delete_img).mouseleave(function() { - $(this).attr("src", settings.delete_tag_img); - }); - $(delete_img).click(function() { - // Tag button is image's parent. - var tag_button = $(this).parent(); - - // Get tag name, value. - var tag_name_elt = tag_button.find(".tag-name").eq(0); - var tag_str = tag_name_elt.text(); - var tag_name_and_value = tag_str.split(":"); - var tag_name = tag_name_and_value[0]; - var tag_value = tag_name_and_value[1]; - - var prev_button = tag_button.prev(); - tag_button.remove(); - - // Remove tag from local list for consistency. - delete settings.tags[tag_name]; - - // Update toggle link text. - var new_text = settings.get_toggle_link_text_fn(settings.tags); - toggle_link.text(new_text); - - // Delete tag. - $.ajax({ - url: settings.ajax_delete_tag_url, - data: { tag_name: tag_name }, - error: function() { - // Failed. Roll back changes and show alert. - settings.tags[tag_name] = tag_value; - if (prev_button.hasClass("tag-button")) { - prev_button.after(tag_button); - } else { - tag_area.prepend(tag_button); - } - alert("Remove tag failed"); - - toggle_link.text(settings.get_toggle_link_text_fn(settings.tags)); - - // TODO: no idea why it's necessary to set this up again. - delete_img.mouseenter(function() { - $(this).attr("src", settings.delete_tag_img_rollover); - }); - delete_img.mouseleave(function() { - $(this).attr("src", settings.delete_tag_img); - }); - }, - success: function() {} - }); - - return true; - }); - } - - // - // Function that builds a tag button. - // - function build_tag_button(tag_str) { - // Build "delete tag" image. - var delete_img = $("") - .attr("src", settings.delete_tag_img) - .addClass("delete-tag-img"); - init_delete_tag_image(delete_img); - - // Build tag button. - var tag_name_elt = $("") - .text(tag_str) - .addClass("tag-name"); - tag_name_elt.click(() => { - var tag_name_and_value = tag_str.split(":"); - settings.tag_click_fn(tag_name_and_value[0], tag_name_and_value[1]); - return true; - }); - - var tag_button = $("").addClass("tag-button"); - tag_button.append(tag_name_elt); - // Allow delete only if element is editable. - if (settings.editable) { - tag_button.append(delete_img); - } - - return tag_button; - } -}; diff --git a/client/galaxy/scripts/ui/popupmenu.js b/client/galaxy/scripts/ui/popupmenu.js index e620c9e3a3e..68b68d51c29 100644 --- a/client/galaxy/scripts/ui/popupmenu.js +++ b/client/galaxy/scripts/ui/popupmenu.js @@ -98,67 +98,59 @@ export function make_popupmenu(button_element, initial_options) { * - div 2 becomes the 'face' of the popupmenu * * NOTE: make_popup_menus finds and operates on all divs with a popupmenu attr (no need to point it at something) - * but (since that selector searches the dom on the page), you can send a parent in * NOTE: make_popup_menus, and make_popupmenu are horrible names */ -export function make_popup_menus(parent) { - console.log("make_popup_menus"); +export function make_popup_menus() { + $("div[popupmenu]").each(function() { + var options = {}; + var menu = $(this); - // find all popupmenu menu divs (divs that contains anchors to be converted to menu options) - // either in the parent or the document if no parent passed - parent = parent || document; - $(parent) - .find("div[popupmenu]") - .each(function() { - var options = {}; - var menu = $(this); + // find each anchor in the menu, convert them into an options map: { a.text : click_function } + menu.find("a").each(function() { + var link = $(this); + var link_dom = link.get(0); + var confirmtext = link_dom.getAttribute("confirm"); + var href = link_dom.getAttribute("href"); + var target = link_dom.getAttribute("target"); - // find each anchor in the menu, convert them into an options map: { a.text : click_function } - menu.find("a").each(function() { - var link = $(this); - var link_dom = link.get(0); - var confirmtext = link_dom.getAttribute("confirm"); - var href = link_dom.getAttribute("href"); - var target = link_dom.getAttribute("target"); - - // no href - no function (gen. a label) - if (!href) { - options[link.text()] = null; - } else { - options[link.text()] = { - url: href, - action: function(event) { - // if theres confirm text, send the dialog - if (!confirmtext || confirm(confirmtext)) { - // link.click() doesn't use target for some reason, - // so manually do it here. - if (target) { - window.open(href, target); - return false; - } else { - // For all other links, do the default action. - link.click(); - } + // no href - no function (gen. a label) + if (!href) { + options[link.text()] = null; + } else { + options[link.text()] = { + url: href, + action: function(event) { + // if theres confirm text, send the dialog + if (!confirmtext || confirm(confirmtext)) { + // link.click() doesn't use target for some reason, + // so manually do it here. + if (target) { + window.open(href, target); + return false; } else { - event.preventDefault(); + // For all other links, do the default action. + link.click(); } + } else { + event.preventDefault(); } - }; - } - }); - // locate the element with the id corresponding to the menu's popupmenu attr - var box = $(parent).find(`#${menu.attr("popupmenu")}`); - - // For menus with clickable link text, make clicking on the link go through instead - // of activating the popup menu - box.find("a").bind("click", e => { - e.stopPropagation(); // Stop bubbling so clicking on the link goes through - return true; - }); - - // attach the click events and menu box building to the box element - make_popupmenu(box, options); - box.addClass("popup"); - menu.remove(); + } + }; + } }); + // locate the element with the id corresponding to the menu's popupmenu attr + var box = $(`#${menu.attr("popupmenu")}`); + + // For menus with clickable link text, make clicking on the link go through instead + // of activating the popup menu + box.find("a").bind("click", e => { + e.stopPropagation(); // Stop bubbling so clicking on the link goes through + return true; + }); + + // attach the click events and menu box building to the box element + make_popupmenu(box, options); + box.addClass("popup"); + menu.remove(); + }); } diff --git a/client/galaxy/scripts/ui/toast.js b/client/galaxy/scripts/ui/toast.js new file mode 100644 index 00000000000..2c85a48aa4a --- /dev/null +++ b/client/galaxy/scripts/ui/toast.js @@ -0,0 +1,7 @@ +import Toastr from "toastr"; + +// Simple passthrough for now, but this gives us an extension point to work +// against when converting over to either boostrap-specific or vue-based +// toasts. + +export const Toast = Toastr; diff --git a/client/galaxy/scripts/unitTestBundle.js b/client/galaxy/scripts/unitTestBundle.js deleted file mode 100644 index 7de155c4784..00000000000 --- a/client/galaxy/scripts/unitTestBundle.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * A combination of all the mocha test files so that we can run them as a unit, - * which is much faster during deployment. - * - * Note, this is non-intuitive, but the parameters of require.context must be - * literals, can't even be stored in local variables or webpack can't do static - * dependency analysis. - * - * https://webpack.js.org/guides/dependency-management/ - */ -// eslint-disable-next-line no-undef -let testContext = require.context("./", true, /\.test\.js$/); -testContext.keys().forEach(testContext); diff --git a/client/galaxy/scripts/utils/color.js b/client/galaxy/scripts/utils/color.js new file mode 100644 index 00000000000..6dc89e51ab9 --- /dev/null +++ b/client/galaxy/scripts/utils/color.js @@ -0,0 +1,71 @@ +import { hashFnv32a } from "utils/utils"; + +/** + * Implement W3C contrasting color algorithm + * http://www.w3.org/TR/AERT#color-contrast + * + * @param {number} r Red + * @param {number} g Green + * @param {number} b Blue + * @return {string} Either 'white' or 'black' + * + * Assumes r, g, b are in the set [0, 1] + */ +export function contrastingColor(r, g, b) { + var o = (r * 255 * 299 + g * 255 * 587 + b * 255 * 114) / 1000; + return o > 125 ? "black" : "white"; +} + +/** + * Converts an HSL color value to RGB. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSL_color_space. + * Assumes h, s, and l are contained in the set [0, 1] and + * returns r, g, and b in the set [0, 1]. + * + * @param {number} h The hue + * @param {number} s The saturation + * @param {number} l The lightness + * @return {Array} The RGB representation + */ +export function hslToRgb(h, s, l) { + var r, g, b; + + if (s == 0) { + r = g = b = l; // achromatic + } else { + var hue2rgb = function hue2rgb(p, q, t) { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + }; + + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + + return [r, g, b]; +} + +/** + * Simple 3-color keyed color scheme generated + * from a string key + */ +export function keyedColorScheme(strKey) { + let hash = hashFnv32a(strKey); + let hue = Math.abs((hash >> 4) % 360); + let lightnessOffset = 75; + let lightness = lightnessOffset + (hash & 0xf); + + let primary = `hsl(${hue}, 100%, ${lightness}%)`; + let darker = `hsl(${hue}, 100%, ${lightness - 40}%)`; + let [r, g, b] = hslToRgb(hue, 1.0, lightness / 100); + let contrasting = contrastingColor(r, g, b); + + return { primary, darker, contrasting }; +} diff --git a/client/galaxy/scripts/utils/mock.js b/client/galaxy/scripts/utils/mock.js index 571b511c18c..6761196706c 100644 --- a/client/galaxy/scripts/utils/mock.js +++ b/client/galaxy/scripts/utils/mock.js @@ -1,4 +1,4 @@ -// console.log api but does nothing +// Applies noop to all methods for mock creation const doNothing = () => null; diff --git a/client/galaxy/scripts/utils/mountVueComponent.js b/client/galaxy/scripts/utils/mountVueComponent.js new file mode 100644 index 00000000000..ab13a882cb3 --- /dev/null +++ b/client/galaxy/scripts/utils/mountVueComponent.js @@ -0,0 +1,14 @@ +// Generic Vue component mount for use in transitional +// mount functions + +import Vue from "vue"; +import store from "../store"; +import _l from "utils/localization"; + +// make localization filter available to all components +Vue.filter("localize", value => _l(value)); + +export const mountVueComponent = ComponentDefinition => (propsData, el) => { + let component = Vue.extend(ComponentDefinition); + return new component({ store, propsData, el }); +}; diff --git a/client/galaxy/scripts/utils/redirect.js b/client/galaxy/scripts/utils/redirect.js new file mode 100644 index 00000000000..e4fb137d8b2 --- /dev/null +++ b/client/galaxy/scripts/utils/redirect.js @@ -0,0 +1,5 @@ +// This file exists purely to make unit testing easier + +export function redirectToUrl(url) { + window.location = url; +} diff --git a/client/galaxy/scripts/utils/utils.js b/client/galaxy/scripts/utils/utils.js index 5bbd35f2601..fda27e2bf6d 100644 --- a/client/galaxy/scripts/utils/utils.js +++ b/client/galaxy/scripts/utils/utils.js @@ -356,7 +356,7 @@ export function setWindowTitle(title) { * @param {string} str the input value * @returns {integer} */ -function hashFnv32a(str) { +export function hashFnv32a(str) { var i, l, hval = 0x811c9dc5; @@ -368,72 +368,6 @@ function hashFnv32a(str) { return hval >>> 0; } -/** - * Implement W3C contrasting color algorithm - * http://www.w3.org/TR/AERT#color-contrast - * - * @param {number} r Red - * @param {number} g Green - * @param {number} b Blue - * @return {string} Either 'white' or 'black' - * - * Assumes r, g, b are in the set [0, 1] - */ -function contrastingColor(r, g, b) { - var o = (r * 255 * 299 + g * 255 * 587 + b * 255 * 114) / 1000; - return o > 125 ? "black" : "white"; -} - -/** - * Converts an HSL color value to RGB. Conversion formula - * adapted from http://en.wikipedia.org/wiki/HSL_color_space. - * Assumes h, s, and l are contained in the set [0, 1] and - * returns r, g, and b in the set [0, 1]. - * - * @param {number} h The hue - * @param {number} s The saturation - * @param {number} l The lightness - * @return {Array} The RGB representation - */ -function hslToRgb(h, s, l) { - var r, g, b; - - if (s == 0) { - r = g = b = l; // achromatic - } else { - var hue2rgb = function hue2rgb(p, q, t) { - if (t < 0) t += 1; - if (t > 1) t -= 1; - if (t < 1 / 6) return p + (q - p) * 6 * t; - if (t < 1 / 2) return q; - if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; - return p; - }; - - var q = l < 0.5 ? l * (1 + s) : l + s - l * s; - var p = 2 * l - q; - r = hue2rgb(p, q, h + 1 / 3); - g = hue2rgb(p, q, h); - b = hue2rgb(p, q, h - 1 / 3); - } - - return [r, g, b]; -} - -export function generateTagStyle(tag) { - var hash = hashFnv32a(tag); - var hue = Math.abs((hash >> 4) % 360); - var lightnessOffset = 75; - var lightness = lightnessOffset + (hash & 0xf); - var bgColor = `hsl(${hue}, 100%, ${lightness}%)`; - var brColor = `hsl(${hue}, 100%, ${lightness - 40}%)`; - - var [r, g, b] = hslToRgb(hue, 1.0, lightness / 100); - var fgColor = contrastingColor(r, g, b); - - return `background-color: ${bgColor}; color: ${fgColor}; border: 1px solid ${brColor}`; -} - export default { cssLoadFile: cssLoadFile, cssGetAttribute: cssGetAttribute, @@ -453,6 +387,5 @@ export default { linkify: linkify, appendScriptStyle: appendScriptStyle, getQueryString: getQueryString, - setWindowTitle: setWindowTitle, - generateTagStyle: generateTagStyle + setWindowTitle: setWindowTitle }; diff --git a/client/galaxy/scripts/viz/phyloviz.js b/client/galaxy/scripts/viz/phyloviz.js index 27f90973a28..762f74734cb 100644 --- a/client/galaxy/scripts/viz/phyloviz.js +++ b/client/galaxy/scripts/viz/phyloviz.js @@ -1,7 +1,7 @@ import $ from "jquery"; import Backbone from "backbone"; import _l from "utils/localization"; -import * as d3 from "libs/d3"; +import * as d3 from "d3"; import visualization_mod from "viz/visualization"; import { Dataset } from "mvc/dataset/data"; import mod_icon_btn from "mvc/ui/icon-button"; diff --git a/client/galaxy/scripts/viz/sweepster.js b/client/galaxy/scripts/viz/sweepster.js index 5303840e733..64906b0964e 100644 --- a/client/galaxy/scripts/viz/sweepster.js +++ b/client/galaxy/scripts/viz/sweepster.js @@ -8,7 +8,7 @@ import $ from "jquery"; import Backbone from "backbone"; import { getAppRoot } from "onload/loadConfig"; import _l from "utils/localization"; -import * as d3 from "libs/d3"; +import * as d3 from "d3"; import visualization from "viz/visualization"; import tracks from "viz/trackster/tracks"; import tools from "mvc/tool/tools"; diff --git a/client/galaxy/style/scss/base.scss b/client/galaxy/style/scss/base.scss index bdc70014e91..d72195cb283 100644 --- a/client/galaxy/style/scss/base.scss +++ b/client/galaxy/style/scss/base.scss @@ -1,7 +1,7 @@ // Bootstrap variables and core framework -@import "../../../node_modules/bootstrap/scss/_functions.scss"; +@import "~bootstrap/scss/_functions.scss"; @import "theme/blue.scss"; -@import "../../../node_modules/bootstrap/scss/bootstrap.scss"; +@import "~bootstrap/scss/bootstrap.scss"; // Bootstrap-related style overrides @import "overrides.scss"; @@ -30,7 +30,6 @@ $fa-font-path: "../../../node_modules/font-awesome/fonts/"; @import "ui.scss"; @import "library.scss"; @import "trackster.scss"; -@import "autocomplete_tagging.scss"; @import "toastr.scss"; @import "jstree.scss"; @import "tour.scss"; @@ -233,28 +232,14 @@ body { .panel-header-buttons { order: 9999; float: right; - } - .panel-header-button { - text-decoration: none; - display: inline-block; - cursor: pointer; - width: 1.2rem; - text-align: center; - padding: 0px; - &:not(:last-child) { - margin-right: 2px; - } - &:hover { - color: $brand-info; - } - // Bootstrap style span caret needs positioning - .caret { - margin-top: 7px; - } - // Another way to get a caret - &.popup { - padding-right: 1.75em; - background: url(../../images/dropdownarrow.png) no-repeat right 7px; + .panel-header-button { + text-align: center; + &:not(:last-child) { + @extend .mr-2; + } + &:hover { + color: $brand-info; + } } } } @@ -608,7 +593,7 @@ div.toolFormRow { @extend .py-1; @extend .px-2; cursor: move; - background: $panel-bg-color; + background: $brand-secondary; } .toolFormBody { @extend .card-body; @@ -807,9 +792,9 @@ button { .warningmessagelarge, .donemessagelarge, .infomessagelarge, -.ui-form-help .error, -.ui-form-help .warning, -.ui-form-help .note { +.form-help .error, +.form-help .warning, +.form-help .note { @extend .alert; min-height: 36px; padding-left: 52px; @@ -851,9 +836,9 @@ button { .warningmessagesmall, .donemessagesmall, .infomessagesmall, -.ui-form-help .error, -.ui-form-help .warning, -.ui-form-help .note { +.form-help .error, +.form-help .warning, +.form-help .note { @extend .alert; padding: 5px; padding-left: 25px; @@ -867,13 +852,13 @@ button { .errormessage, .errormessagesmall, -.ui-form-help .error { +.form-help .error { @extend .alert-danger; } .warningmessage, .warningmessagesmall, -.ui-form-help .warning { +.form-help .warning { @extend .alert-warning; background-image: url(../../images/warn_small.png); } @@ -886,7 +871,7 @@ button { .infomessage, .infomessagesmall, -.ui-form-help .note { +.form-help .note { @extend .alert-info; background-image: url(../../images/info_small.png); } @@ -1517,84 +1502,57 @@ div.permissionContainer { @import "ui/error-modal.scss"; // ==== Tool menu styles - .toolMenuContainer { color: $panel-text-color; a { color: $panel-text-color; - // text-decoration: none; } background: $panel-bg-color; min-height: 100%; @extend .m-3; } -div.toolSectionPad { - margin: 0; - padding: 0; - height: 5px; - font-size: 0px; -} - div.toolSectionWrapper { - margin-bottom: 5px; -} - -div.toolSectionDetailsInner { - margin-left: 5px; - margin-right: 5px; + @extend .mb-1; } div.toolSectionTitle { font-weight: 500; - font-size: 1rem; + font-size: $h4-font-size; } div.toolPanelLabel { - padding-top: 10px; - padding-bottom: 5px; + @extend .m-2; font-weight: bold; - color: gray; + color: $gray-600; text-transform: uppercase; } div.toolTitle { - padding-top: 5px; - padding-bottom: 5px; - margin-left: 10px; - margin-right: 10px; + @extend .m-2; display: block; .labels { float: right; - margin-left: 2px; } } div a.tool-link { text-decoration: none; display: block; - span.tool-old-link { text-decoration: underline; } -} - -div.toolSectionBody div.toolPanelLabel { - padding-top: 5px; - padding-bottom: 5px; - margin-left: 16px; - margin-right: 10px; - display: list-item; - list-style: none outside; + &:hover { + background: darken($panel-bg-color, 5%); + } } div.toolTitleNoSection { - padding-bottom: 5px; + @extend .pb-1; font-weight: bold; } // Dataset Display Styles - #loading_indicator { position: fixed; right: 10px; @@ -1780,11 +1738,6 @@ div.toolTitleNoSection { padding: 2px; } -.communication-iframe { - width: 100%; - height: 100%; -} - .close-modal { float: right; cursor: pointer; @@ -1860,19 +1813,6 @@ div.toolTitleNoSection { opacity: 1; } -/* Temporary tag editor display; will be moved to a scoped Vue SFC upon refactoring */ -.tags-display { - @extend %vertical-spacing; - .select2-container { - max-height: 80px; - overflow: auto; - min-width: 0px; - .select2-choices { - border-radius: 3px; - } - } -} - /* TEMPORARY, REFACTOR THIS -- This is only image-related styles from the * workflow editor; everything should be moved out of the mako and into its own * file */ @@ -1912,9 +1852,12 @@ div.toolTitleNoSection { color: $brand-success; } } -.input-terminal-active > icon { +.input-terminal-active.can-accept > icon { color: $brand-success; } +.input-terminal-active.cannot-accept > icon { + color: $brand-warning; +} .drag-terminal { @extend .fa; @extend .fa-circle; @@ -1969,7 +1912,7 @@ div.toolTitleNoSection { } /* Toolshed, reports custom styles from mako templates */ -body.toolshed { +body.toolshed, body.reports { margin: 0; padding: 0; overflow: hidden; @@ -1984,15 +1927,6 @@ body.toolshed { } } -body.reports { - margin: 0; - padding: 0; - overflow: hidden; - #left { - background: #c1c9e5 url(../../images/menu_bg.png) top repeat-x; - } -} - /* Used in Galaxy IE for spinner loading TODO: Remove in IE entrypoint refactoring */ #ie-loading-spinner { position: absolute; diff --git a/client/galaxy/style/scss/dataset.scss b/client/galaxy/style/scss/dataset.scss index e08b3d82cfe..28a28b04dfa 100644 --- a/client/galaxy/style/scss/dataset.scss +++ b/client/galaxy/style/scss/dataset.scss @@ -304,10 +304,6 @@ } } - .tags-display { - // Hidden by default; the interface toggles this. - display: none; - } .annotation-display { display: none; @extend %vertical-spacing; diff --git a/client/galaxy/style/scss/history.scss b/client/galaxy/style/scss/history.scss index 848a5a31cd3..2a569d750e7 100644 --- a/client/galaxy/style/scss/history.scss +++ b/client/galaxy/style/scss/history.scss @@ -1,3 +1,5 @@ +@import "scss/mixins"; + // all histories .history-panel { @extend .flex-vertical-container; @@ -34,7 +36,6 @@ } //TODO: move these out - .tags-display, .annotation-display { display: none; margin-bottom: 8px; @@ -194,15 +195,6 @@ } } } - div.nametags { - max-height: 26px; - overflow-y: auto; - span.badge { - display: inline-block; - margin-right: 2px; - text-decoration: none; - } - } } } @@ -218,7 +210,6 @@ .subtitle, .history-size, //TODO: move these out - .tags-display .prompt, .annotation-display .prompt { display: none; } @@ -629,3 +620,9 @@ } } } + +// History panel tags editor +.history-panel .tags-display { + padding: 1px; + @include shutterFade(250px); +} diff --git a/client/galaxy/style/scss/list-item.scss b/client/galaxy/style/scss/list-item.scss index 96c1d0a58a6..d5fe3b18b02 100644 --- a/client/galaxy/style/scss/list-item.scss +++ b/client/galaxy/style/scss/list-item.scss @@ -80,11 +80,13 @@ $vertical-gap: 8px; .title { display: inline; font-weight: bold; - text-decoration: underline; word-wrap: break-word; word-break: break-all; line-height: 16px; } + .title:hover { + text-decoration: underline; + } .subtitle { color: #777; font-size: 90%; diff --git a/client/galaxy/style/scss/mixins.scss b/client/galaxy/style/scss/mixins.scss new file mode 100644 index 00000000000..6cdf41fd46f --- /dev/null +++ b/client/galaxy/style/scss/mixins.scss @@ -0,0 +1,31 @@ +// Utility mixin expands to container edges +@mixin fill() { + position: relative; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +// animates max-height and visibility hidden by default, add .active to show +// boxHeight must be larger than the expected maximum height of the element +// or it will be clipped + +@mixin shutterFade($boxHeight: 100px) { + max-height: 0px; + visibility: hidden; + opacity: 0; + transition-duration: 0.35s; + transition-property: visibility, opacity, max-height; + transition-timing-function: ease; + + &.active { + max-height: $boxHeight; + visibility: visible; + opacity: 1; + } + + &::-webkit-scrollbar { + display: none; + } +} diff --git a/client/galaxy/style/scss/overrides.scss b/client/galaxy/style/scss/overrides.scss index f87c27bd64d..f45f259ea75 100644 --- a/client/galaxy/style/scss/overrides.scss +++ b/client/galaxy/style/scss/overrides.scss @@ -46,13 +46,6 @@ input[type="radio"] { float: left; } -// Override badge's few default CSS -.badge-tags { - background-color: #3189a3; - padding: 0.2em 0.6em 0.3em; - border-radius: 0.15rem; -} - // Modal -- wider by default, scroll like Trello .modal-dialog { diff --git a/client/galaxy/style/scss/reports.scss b/client/galaxy/style/scss/reports.scss index 92a9082877a..bd3cf8ae891 100644 --- a/client/galaxy/style/scss/reports.scss +++ b/client/galaxy/style/scss/reports.scss @@ -1,21 +1,5 @@ // ############################################################# Reports Webapp -// ======================================================== Left ============== -.reports-panel-container { - position: absolute; - /*From base.scss, top should be $panel_header_height*/ - top: 30px; - bottom: 10px; - overflow: scroll; - width: 100%; - padding: 10px; -} -#reports_home { - position: relative; - top: -7px; - float: right; -} - // ======================================================== SparkLines ======== #spark_time_select { display: inline-block; diff --git a/client/galaxy/style/scss/ui.scss b/client/galaxy/style/scss/ui.scss index a7ee926a32b..5d19dd67279 100644 --- a/client/galaxy/style/scss/ui.scss +++ b/client/galaxy/style/scss/ui.scss @@ -195,7 +195,6 @@ $ui-margin-horizontal-large: $margin-v * 2; .ui-portlet { @extend .card; border: none; - height: 100%; .portlet-header:first-child { @extend .card-header; @extend .rounded; @@ -422,6 +421,9 @@ $ui-margin-horizontal-large: $margin-v * 2; -webkit-appearance: none; -moz-border-radius: $border-radius-base; line-height: 1.5rem; + .select2-chosen { + white-space: normal; + } .select2-arrow { display: none; } @@ -450,12 +452,6 @@ $ui-margin-horizontal-large: $margin-v * 2; } } -.ui-select-content { - .ui-options { - @extend .float-left; - } -} - .ui-dragover { border-radius: 3px; border: 2px solid $table-border-color; diff --git a/client/galaxy/style/scss/upload.scss b/client/galaxy/style/scss/upload.scss index d3581b96046..01712bbcf37 100644 --- a/client/galaxy/style/scss/upload.scss +++ b/client/galaxy/style/scss/upload.scss @@ -1,14 +1,22 @@ .upload-button { - position: relative; - width: 31px; - height: 21px; + @extend .ml-2; + float: right; + width: 32px; + height: 24px; + font-size: inherit; cursor: pointer; -moz-border-radius: $border-radius-base; border-radius: $border-radius-base; + .upload-button-link { + @extend .panel-header-button; + position: absolute; + width: inherit; + } .progress { - height: 21px; - margin: 0px; - font-size: 0.75rem; + position: absolute; + height: inherit; + width: inherit; + font-size: inherit; background-color: $panel-bg-color; } .progress-bar-danger { @@ -27,13 +35,6 @@ -o-transition: none; transition: none; } - .panel-header-button { - position: absolute; - width: 31px !important; - text-align: center; - font-size: 1.2em; - padding: 0px; - } } .upload-input { diff --git a/client/gulpfile.js b/client/gulpfile.js index ed8f1161654..e2dc3cba831 100644 --- a/client/gulpfile.js +++ b/client/gulpfile.js @@ -1,28 +1,20 @@ -var path = require("path"); -var fs = require("fs"); -var del = require("del"); -var _ = require("underscore"); +const path = require("path"); +const fs = require("fs"); +const del = require("del"); +const { src, dest, series, parallel } = require("gulp"); -var gulp = require("gulp"); -var uglify = require("gulp-uglify"); -var babel = require("gulp-babel"); - -var paths = { +const paths = { node_modules: "./node_modules", - scripts: [ - "galaxy/scripts/**/*.js", - "!galaxy/scripts/qunit/**/*", - "!galaxy/scripts/entry/**/*", - "!galaxy/scripts/libs/**/*" + plugin_dirs: [ + "../config/plugins/{visualizations,interactive_environments}/*/static/**/*", + "../config/plugins/{visualizations,interactive_environments}/*/*/static/**/*" ], - plugin_dirs: ["../config/plugins/**/static/**/*", "!../config/plugins/**/node_modules{,/**}"], lib_locs: { // This is a stepping stone towards having all this staged // automatically. Eventually, this dictionary and staging step will // not be necessary. backbone: ["backbone.js", "backbone.js"], "bootstrap-tour": ["build/js/bootstrap-tour.js", "bootstrap-tour.js"], - d3: ["d3.js", "d3.js"], "bibtex-parse-js": ["bibtexParse.js", "bibtexParse.js"], jquery: ["dist/jquery.js", "jquery/jquery.js"], "jquery.complexify": ["jquery.complexify.js", "jquery/jquery.complexify.js"], @@ -36,8 +28,8 @@ var paths = { libs: ["galaxy/scripts/libs/**/*.js"] }; -gulp.task("stage-libs", function(callback) { - _.each(_.keys(paths.lib_locs), function(lib) { +function stageLibs(callback) { + Object.keys(paths.lib_locs).forEach(lib => { var p1 = path.resolve(path.join(paths.node_modules, lib, paths.lib_locs[lib][0])); var p2 = path.resolve(path.join("galaxy", "scripts", "libs", paths.lib_locs[lib][1])); if (fs.existsSync(p1)) { @@ -50,44 +42,25 @@ gulp.task("stage-libs", function(callback) { ); } }); -}); + return callback(); +} -gulp.task("fonts", function() { - return gulp - .src(path.resolve(path.join(paths.node_modules, "font-awesome/fonts/**/*"))) - .pipe(gulp.dest("../static/images/fonts")); -}); +function fonts() { + return src(path.resolve(path.join(paths.node_modules, "font-awesome/fonts/**/*"))).pipe( + dest("../static/images/fonts") + ); +} -// TODO: Remove script and lib tasks (for 19.05) once we are sure there are no -// external accessors (via require or explicit inclusion in templates) -gulp.task("scripts", function() { - return gulp - .src(paths.scripts) - .pipe( - babel({ - plugins: ["transform-es2015-modules-amd"] - }) - ) - .pipe(uglify()) - .pipe(gulp.dest("../static/scripts/")); -}); +function stagePlugins() { + return src(paths.plugin_dirs).pipe(dest("../static/plugins/")); +} -gulp.task("libs", function() { - return gulp - .src(paths.libs) - .pipe(uglify()) - .pipe(gulp.dest("../static/scripts/libs/")); -}); +function cleanPlugins() { + return del(["../static/plugins/{visualizations,interactive_environments}/*"], { force: true }); +} -gulp.task("plugins", function() { - return gulp.src(paths.plugin_dirs).pipe(gulp.dest("../static/plugins/")); -}); - -gulp.task("clean", function() { - //Wipe out all scripts that aren't handled by webpack - return del(["../static/scripts/**/*.js", "!../static/scripts/bundled/**.*.js"], { force: true }); -}); - -gulp.task("staging", ["stage-libs", "fonts"]); - -gulp.task("default", ["libs", "scripts"]); +module.exports.fonts = fonts; +module.exports.stageLibs = stageLibs; +module.exports.cleanPlugins = cleanPlugins; +module.exports.plugins = series(cleanPlugins, stagePlugins); +module.exports.default = parallel(stageLibs, fonts, module.exports.plugins); diff --git a/client/karma/karma.config.mocha.js b/client/karma/karma.config.mocha.js index 058e1587095..61135bbfca2 100644 --- a/client/karma/karma.config.mocha.js +++ b/client/karma/karma.config.mocha.js @@ -1,41 +1,69 @@ /** * Runs mocha tests + * + * Individual files can be run by passing in a comma-delimited list + * of globs for the karma config like this: + * + * npm run test-watch watch-only="Tags.test.js,something.js,doodads.js" */ const baseKarmaConfig = require("./karma.config.base"); -const single_pack = (process.env.GALAXY_TEST_AS_SINGLE_PACK == "true"); - -const testBundles = [ - "**/unitTestBundle.js", - "**/mocha/test.js" -]; - -const separateTests = [ +// Complete list of unit tests +const defaultFiles = [ + // component/module tests "**/*.test.js", + // pre-existing rules definition tests "**/mocha/tests/*_tests.js" ]; +function getTestFiles() { + // check for user-supplied list + let userPatterns = getUserTestGlobs(); + let patterns = userPatterns.length ? userPatterns : defaultFiles; + return patterns.map(pattern => ({ pattern, watched: true})); +} + +// command line arg "watch-only" can be a list of file globs +// for karma to watch +function getUserTestGlobs() { + let userGlobs = process.argv.find(s => s.startsWith("watch-only")); + return userGlobs ? processUserGlobs(userGlobs) : []; +} + +// split command line arg into an array +function processUserGlobs(val) { + let result = []; + let fileListString = val.split("=")[1]; + if (fileListString) { + result = fileListString.split(",") + .map(s => s.trim()) + .map(checkGlobPrefix); + } + return result; +} + +// prefixes user-supplied glob with directory wildcard +function checkGlobPrefix(glob) { + return glob.startsWith("**/") ? glob : `**/${glob}`; +} + + module.exports = function (config) { - console.log("single_pack?", single_pack); - - // pick all separate tests or the dynamic test-bundles - let files = single_pack - ? testBundles - : separateTests; - - let preprocessors = files.reduce((result, path) => { - result[path] = ["webpack"]; - return result; - }, {}); + let files = [ + "../../node_modules/@babel/polyfill/dist/polyfill.js", + ...getTestFiles() + ]; let settings = Object.assign({}, baseKarmaConfig, { - files: files, + files, + preprocessors: { + "**/*.js": ["webpack"] + }, exclude: ["**/qunit/*"], - preprocessors: preprocessors, reporters: ["mocha"], - frameworks: ["polyfill", "mocha", "chai"] + frameworks: ["mocha", "chai"] }); config.set(settings); diff --git a/client/karma/karma.config.qunit.js b/client/karma/karma.config.qunit.js index 743b0eb86c7..54e585a4dc2 100644 --- a/client/karma/karma.config.qunit.js +++ b/client/karma/karma.config.qunit.js @@ -29,7 +29,8 @@ module.exports = function (config) { let settings = Object.assign({}, baseKarmaConfig, { files: testFiles.concat(assets), preprocessors: preprocessors, - frameworks: ["polyfill", "qunit"] + frameworks: ["polyfill", "qunit"], + singleRun: true }); config.set(settings); diff --git a/client/karma/webpack.config.unittest.js b/client/karma/webpack.config.unittest.js index 77483b5b2a3..c3d98dfec4f 100644 --- a/client/karma/webpack.config.unittest.js +++ b/client/karma/webpack.config.unittest.js @@ -3,28 +3,45 @@ * the ignore-loader for speedier testing. */ -let merge = require("webpack-merge"); -let wpConfig = require("../webpack.config"); +const merge = require("webpack-merge"); +const wpConfig = require("../webpack.config"); wpConfig.mode = "development"; wpConfig.entry = () => ({}); -// Don't need assets for unit testing, override those rules -module.exports = merge.smart(wpConfig, { - module: { - rules: [ - { - test: /\.(png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)(\?.*$|$)/, - loader: "ignore-loader" - }, - { - test: /\.css$/, - loader: "ignore-loader" - }, - { - test: /\.scss$/, - loader: "ignore-loader" - } - ] + +// Don't need any assets for unit testing + +let ignoreAssetLoaders = { + rules:[ + { + test: /\.(png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)(\?.*$|$)/, + loader: "ignore-loader" + }, + { + test: /\.css$/, + loader: "ignore-loader" + }, + { + test: /\.scss$/, + loader: "ignore-loader" + } + ] +}; + +wpConfig.module = merge.smart(wpConfig.module, ignoreAssetLoaders); + + +// Using babel-plugin-rewire to handle dependency mocking since webpack 4 +// exports immutable bindings for ES modules but we still need a way to +// overwrite dependencies during unit-testing. + +wpConfig.module.rules = wpConfig.module.rules.map(rule => { + if (rule.loader == "babel-loader") { + rule.options.plugins.push("rewire"); } + return rule; }); + + +module.exports = wpConfig; diff --git a/client/package.json b/client/package.json index cae03da10ba..4aeb15dd09e 100644 --- a/client/package.json +++ b/client/package.json @@ -11,23 +11,19 @@ }, "license": "AFL-3.0", "dependencies": { - "@babel/core": "^7.1.2", - "@babel/plugin-syntax-dynamic-import": "^7.0.0", - "@babel/polyfill": "^7.0.0", - "@babel/preset-env": "^7.1.0", + "@babel/polyfill": "^7.4.0", "@handsontable/vue": "^2.0.0-beta1", - "amdi18n-loader": "^0.8.0", + "@johmun/vue-tags-input": "^2.0.1", + "@vue/test-utils": "1.0.0-beta.29", "axios": "^0.18.0", - "backbone": "1.3", + "backbone": "1.4.0", "bibtex-parse-js": "^0.0.24", - "bootstrap": "^4.1.3", - "bootstrap-tour": "https://github.com/sorich87/bootstrap-tour.git#6a1028fb562f9aa68c451f0901f8cfeb43cad140", - "bootstrap-vue": "^2.0.0-rc.11", + "bootstrap": "4.3.1", + "bootstrap-tour": "https://github.com/sorich87/bootstrap-tour.git#65a49742e131d19f41e3f5bf63588995f7b8a9e0", + "bootstrap-vue": "^2.0.0-rc.16", "d3": "3", "decode-uri-component": "^0.2.0", "font-awesome": "^4.7.0", - "gulp-babel": "^8.0.0", - "gulp-uglify": "^3.0.1", "handsontable": "^2.0.0", "imports-loader": "^0.8.0", "jquery": "2", @@ -38,29 +34,30 @@ "jquery.cookie": "^1.4.1", "latex-parser": "^0.6.2", "latex-to-unicode-converter": "^0.5.2", - "mini-css-extract-plugin": "^0.4.4", - "popper.js": "^1.14.4", + "popper.js": "^1.14.7", "pyre-to-regexp": "^0.0.5", "raven-js": "^3.27.0", "requirejs": "2.3.6", - "rxjs": "^6.3.3", + "rxjs": "^6.4.0", + "slugify": "^1.3.4", + "toastr": "^2.1.4", "underscore": "^1.9.1", - "vue": "2.5.17", - "vue-router": "3.0.1" + "vue": "^2.6.10", + "vue-router": "^3.0.2", + "vue-rx": "^6.1.0", + "vuex": "^3.1.0" }, "scripts": { - "watch": "gulp staging && gulp clean && gulp && yarn run save-build-hash && yarn run webpack-watch", - "build": "NODE_ENV=development gulp staging && concurrently -r \"yarn run webpack\" \"yarn run gulp clean && yarn run gulp\" && yarn run save-build-hash", - "build-production": "NODE_ENV=production gulp staging && concurrently -r \"yarn run webpack-production\" \"yarn run gulp clean && yarn run gulp-production\" && yarn run save-build-hash", - "build-production-maps": "NODE_ENV=production gulp staging && concurrently -r \"yarn run webpack-production-maps\" \"yarn run gulp clean && yarn run gulp-production-maps\" && yarn run save-build-hash", + "watch": "gulp && yarn run save-build-hash && yarn run webpack-watch", + "build": "NODE_ENV=development gulp && yarn run 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", "build-stats": "webpack --profile --json > webpack-stats.json", "webpack": "webpack -d", "webpack-watch": "webpack -d --watch", "webpack-production": "webpack -p", "webpack-production-maps": "GXY_BUILD_SOURCEMAPS=1 webpack -p", "gulp": "gulp", - "gulp-production": "NODE_ENV=production gulp", - "gulp-production-maps": "GXY_BUILD_SOURCEMAPS=1 NODE_ENV=production gulp", "save-build-hash": "(git rev-parse HEAD 2>/dev/null || echo '') >../static/client_build_hash.txt", "build-charts": "webpack -p --config ../config/plugins/visualizations/charts/webpack.config.js", "build-scatterplot": "NODE_PATH=./node_modules webpack -p --config ../config/plugins/visualizations/scatterplot/webpack.config.js", @@ -69,33 +66,33 @@ "styleguide:build": "vue-styleguidist build", "test": "npm run test-mocha && npm run test-qunit", "test-watch": "npm run test-mocha -- --no-single-run", - "test-qunit": "GALAXY_TEST_AS_SINGLE_PACK=true karma start karma/karma.config.qunit.js", - "test-mocha": "GALAXY_TEST_AS_SINGLE_PACK=true karma start karma/karma.config.mocha.js", - "jshint": "jshint --exclude='galaxy/scripts/libs/**' galaxy/scripts/**/*.js", + "test-qunit": "karma start karma/karma.config.qunit.js", + "test-mocha": "karma start karma/karma.config.mocha.js", "eslint": "eslint -c .eslintrc.js galaxy/scripts --ext .js,.vue" }, "devDependencies": { - "babel-loader": "^8.0.4", + "@babel/core": "^7.4.0", + "@babel/plugin-syntax-dynamic-import": "^7.2.0", + "@babel/preset-env": "^7.4.2", + "amdi18n-loader": "^0.9.2", + "babel-loader": "^8.0.5", + "babel-plugin-rewire": "^1.2.0", "babel-plugin-transform-inline-environment-variables": "^0.4.3", "babel-plugin-transform-vue-template": "^0.4.2", "chai": "^4.2.0", - "concurrently": "^4.0.1", - "css-loader": "^1.0.0", - "del": "^3.0.0", + "css-loader": "^2.1.1", + "del": "^4.0.0", "duplicate-package-checker-webpack-plugin": "^3.0.0", - "eslint": "^5.12.0", - "eslint-config-airbnb-base": "^13.1.0", - "eslint-config-prettier": "^3.3.0", - "eslint-plugin-html": "^5.0.0", - "eslint-plugin-import": "^2.14.0", + "eslint": "^5.15.3", + "eslint-plugin-html": "^5.0.3", + "eslint-plugin-import": "^2.16.0", "eslint-plugin-prettier": "^3.0.1", - "eslint-plugin-vue": "^5.1.0", + "eslint-plugin-vue": "^5.2.2", "expose-loader": "^0.7.5", - "file-loader": "^2.0.0", - "gulp": "^3.9.1", - "gulp-sourcemaps": "^2.6.4", + "file-loader": "^3.0.1", + "gulp": "^4.0.0", "ignore-loader": "^0.1.2", - "jshint": "^2.9.6", + "json-loader": "^0.5.7", "karma": "^1.7.1", "karma-chai": "^0.1.0", "karma-chrome-launcher": "^2.2.0", @@ -105,24 +102,25 @@ "karma-polyfill": "^1.0.0", "karma-qunit": "^1.2.1", "karma-webpack": "^4.0.0-rc.2", + "mini-css-extract-plugin": "^0.5.0", "mocha": "^5.2.0", - "node-sass": "^4.9.4", + "node-sass": "^4.11.0", "optimize-css-assets-webpack-plugin": "^5.0.1", "phantomjs-polyfill": "0.0.2", "phantomjs-prebuilt": "^2.1.7", - "prettier": "^1.15.3", + "prettier": "^1.16.4", "qunitjs": "^2.4.1", + "raw-loader": "^2.0.0", "sass-loader": "^7.1.0", "sinon": "^4.1.2", "store": "^2.0.12", "style-loader": "^0.23.1", - "vue-loader": "^15.4.2", - "vue-style-loader": "^4.1.2", - "vue-styleguidist": "^1.8.9", - "vue-template-compiler": "2.5.17", - "webpack": "^4.23.0", - "webpack-cli": "^3.1.2", - "webpack-merge": "^4.1.4", + "vue-loader": "^15.7.0", + "vue-styleguidist": "^3.11.4", + "vue-template-compiler": "^2.6.10", + "webpack": "^4.29.6", + "webpack-cli": "^3.3.0", + "webpack-merge": "^4.2.1", "yaml-loader": "^0.5.0" } } diff --git a/client/styleguide.config.js b/client/styleguide.config.js index c1f395d2fe8..57aa4f09065 100644 --- a/client/styleguide.config.js +++ b/client/styleguide.config.js @@ -1,10 +1,18 @@ -let path = require("path"); -let glob = require("glob"); +const path = require("path"); +const glob = require("glob"); +const merge = require("webpack-merge"); let webpackConfig = require("./webpack.config.js"); -// We don't use webpack for our sass files in the main app, but use it here -// so we get rebuilds +const fileLoaderTest = /\.(png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)(\?.*$|$)/; + +// Clear existing .scss rule(s) out of webpack config, we need special handling +// here. +// TODO: reimplement using smart merge w/ replacement strategy via webpack-merge +webpackConfig.module.rules = webpackConfig.module.rules.filter(rule => { + return rule.test.toString() != /\.scss$/.toString() && rule.test.toString() != fileLoaderTest.toString(); +}); + webpackConfig.module.rules.push({ test: /\.scss$/, use: [ @@ -12,13 +20,7 @@ webpackConfig.module.rules.push({ loader: "style-loader" }, { - loader: "css-loader", - options: { - alias: { - "../images": path.resolve(__dirname, "../static/images"), - ".": path.resolve(__dirname, "../static/style/blue") - } - } + loader: "css-loader" }, { loader: "sass-loader", @@ -29,28 +31,32 @@ webpackConfig.module.rules.push({ ] }); -webpackConfig.module.rules.push({ test: /\.(png|jpg|gif|eot|ttf|woff|woff2|svg)$/, use: ["file-loader"] }); +const fileLoaderConfigRule = { rules: [{ test: fileLoaderTest, use: ["file-loader"] }] }; -sections = [] +webpackConfig.module = merge.smart(webpackConfig.module, fileLoaderConfigRule); +webpackConfig.output.publicPath = ""; + +const sections = []; glob("./galaxy/docs/galaxy-*.md", (err, files) => { - files.forEach( file => { - name = file.match( /galaxy-(\w+).md/ )[1] - sections.push({ name: "Galaxy " + name, content: file }) + files.forEach(file => { + const name = file.match(/galaxy-(\w+).md/)[1]; + sections.push({ name: "Galaxy " + name, content: file }); }); }), - -sections.push( ...[ - { - name: "Basic Bootstrap Styles", - content: "./galaxy/docs/bootstrap.md" - }, - // This will require additional configuration - // { - // name: 'Components', - // components: './galaxy/scripts/components/*.vue' - // } -]) + sections.push( + ...[ + { + name: "Basic Bootstrap Styles", + content: "./galaxy/docs/bootstrap.md" + } + // This will require additional configuration + // { + // name: 'Components', + // components: './galaxy/scripts/components/*.vue' + // } + ] + ); module.exports = { webpackConfig, diff --git a/client/webpack.config.js b/client/webpack.config.js index 88d306b6e06..18973b9d67b 100644 --- a/client/webpack.config.js +++ b/client/webpack.config.js @@ -20,6 +20,7 @@ let buildconfig = { }, output: { path: path.join(__dirname, "../", "static/scripts/bundled"), + publicPath: "/static/scripts/bundled/", filename: "[name].bundled.js", chunkFilename: "[name].chunk.js" }, @@ -29,7 +30,7 @@ let buildconfig = { alias: { jquery$: `${libsBase}/jquery.custom.js`, jqueryVendor$: `${libsBase}/jquery/jquery.js`, - store$: "store/dist/store.modern.js" + storemodern$: "store/dist/store.modern.js" } }, optimization: { @@ -43,7 +44,7 @@ let buildconfig = { }, libs: { name: "libs", - test: /(node_modules|galaxy\/scripts\/(?!entry)).*\.(vue|js)$/, // .*\.(vue|js)$ + test: /node_modules[\\/](?!(handsontable|pikaday|moment)[\\/])|galaxy\/scripts\/libs/, chunks: "all", priority: -10 } @@ -64,7 +65,22 @@ let buildconfig = { libsBase ], loader: "babel-loader", - options: { babelrc: true } + options: { + cacheDirectory: true, + cacheCompression: false, + presets: [ + ["@babel/preset-env", { modules: false }] + ], + plugins: [ + "transform-vue-template", + "@babel/plugin-syntax-dynamic-import" + ], + ignore: [ + "i18n.js", + "utils/localization.js", + "nls/*" + ] + } }, { test: `${libsBase}/jquery.custom.js`, @@ -98,7 +114,7 @@ let buildconfig = { loader: "file-loader", options: { outputPath: "assets", - publicPath: "/static/scripts/bundled/assets/" + publicPath: "../scripts/bundled/assets/" } } }, @@ -144,12 +160,15 @@ let buildconfig = { }, { loader: "sass-loader", - options: { sourceMap: true } + options: { + sourceMap: true, + includePaths: ["galaxy/style/scss"] + } } ] }, { - test: /\.tmpl$/, + test: /\.(txt|tmpl)$/, loader: "raw-loader" } ] diff --git a/client/yarn.lock b/client/yarn.lock index 23886d562b7..3f407c345fc 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -2,6 +2,13 @@ # yarn lockfile v1 +"@babel/code-frame@7.0.0", "@babel/code-frame@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" + integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA== + dependencies: + "@babel/highlight" "^7.0.0" + "@babel/code-frame@7.0.0-beta.44": version "7.0.0-beta.44" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.44.tgz#2a02643368de80916162be70865c97774f3adbd9" @@ -9,29 +16,22 @@ dependencies: "@babel/highlight" "7.0.0-beta.44" -"@babel/code-frame@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" - integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA== - dependencies: - "@babel/highlight" "^7.0.0" - -"@babel/core@^7.0.1", "@babel/core@^7.1.2": - version "7.1.2" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.1.2.tgz#f8d2a9ceb6832887329a7b60f9d035791400ba4e" - integrity sha512-IFeSSnjXdhDaoysIlev//UzHZbdEmm7D0EIH2qtse9xK7mXEZQpYjs2P00XlP1qYsYvid79p+Zgg6tz1mp6iVw== +"@babel/core@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.4.0.tgz#248fd6874b7d755010bfe61f557461d4f446d9e9" + integrity sha512-Dzl7U0/T69DFOTwqz/FJdnOSWS57NpjNfCwMKHABr589Lg8uX1RrlBIJ7L5Dubt/xkLsx0xH5EBFzlBVes1ayA== dependencies: "@babel/code-frame" "^7.0.0" - "@babel/generator" "^7.1.2" - "@babel/helpers" "^7.1.2" - "@babel/parser" "^7.1.2" - "@babel/template" "^7.1.2" - "@babel/traverse" "^7.1.0" - "@babel/types" "^7.1.2" + "@babel/generator" "^7.4.0" + "@babel/helpers" "^7.4.0" + "@babel/parser" "^7.4.0" + "@babel/template" "^7.4.0" + "@babel/traverse" "^7.4.0" + "@babel/types" "^7.4.0" convert-source-map "^1.1.0" - debug "^3.1.0" - json5 "^0.5.0" - lodash "^4.17.10" + debug "^4.1.0" + json5 "^2.1.0" + lodash "^4.17.11" resolve "^1.3.2" semver "^5.4.1" source-map "^0.5.0" @@ -47,7 +47,7 @@ source-map "^0.5.0" trim-right "^1.0.1" -"@babel/generator@^7.1.2", "@babel/generator@^7.1.3": +"@babel/generator@^7.1.3": version "7.1.3" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.1.3.tgz#2103ec9c42d9bdad9190a6ad5ff2d456fd7b8673" integrity sha512-ZoCZGcfIJFJuZBqxcY9OjC1KW2lWK64qrX1o4UYL3yshVhwKFYgzpWZ0vvtGMNJdTlvkw0W+HR1VnYN8q3QPFQ== @@ -58,6 +58,17 @@ source-map "^0.5.0" trim-right "^1.0.1" +"@babel/generator@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.4.0.tgz#c230e79589ae7a729fd4631b9ded4dc220418196" + integrity sha512-/v5I+a1jhGSKLgZDcmAUZ4K/VePi43eRkUs3yePW1HB1iANOD5tqJXwGSG4BZhSksP8J9ejSlwGeTiiOFZOrXQ== + dependencies: + "@babel/types" "^7.4.0" + jsesc "^2.5.1" + lodash "^4.17.11" + source-map "^0.5.0" + trim-right "^1.0.1" + "@babel/helper-annotate-as-pure@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32" @@ -73,23 +84,23 @@ "@babel/helper-explode-assignable-expression" "^7.1.0" "@babel/types" "^7.0.0" -"@babel/helper-call-delegate@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.1.0.tgz#6a957f105f37755e8645343d3038a22e1449cc4a" - integrity sha512-YEtYZrw3GUK6emQHKthltKNZwszBcHK58Ygcis+gVUrF4/FmTVr5CCqQNSfmvg2y+YDEANyYoaLz/SHsnusCwQ== +"@babel/helper-call-delegate@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.4.0.tgz#f308eabe0d44f451217853aedf4dea5f6fe3294f" + integrity sha512-SdqDfbVdNQCBp3WhK2mNdDvHd3BD6qbmIc43CAyjnsfCmgHMeqgDcM3BzY2lchi7HBJGJ2CVdynLWbezaE4mmQ== dependencies: - "@babel/helper-hoist-variables" "^7.0.0" - "@babel/traverse" "^7.1.0" - "@babel/types" "^7.0.0" + "@babel/helper-hoist-variables" "^7.4.0" + "@babel/traverse" "^7.4.0" + "@babel/types" "^7.4.0" -"@babel/helper-define-map@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz#3b74caec329b3c80c116290887c0dd9ae468c20c" - integrity sha512-yPPcW8dc3gZLN+U1mhYV91QU3n5uTbx7DUdf8NnPbjS0RMwBuHi9Xt2MUgppmNz7CJxTBWsGczTiEp1CSOTPRg== +"@babel/helper-define-map@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.4.0.tgz#cbfd8c1b2f12708e262c26f600cd16ed6a3bc6c9" + integrity sha512-wAhQ9HdnLIywERVcSvX40CEJwKdAa1ID4neI9NXQPDOHwwA+57DqwLiPEVy2AIyWzAk0CQ8qx4awO0VUURwLtA== dependencies: "@babel/helper-function-name" "^7.1.0" - "@babel/types" "^7.0.0" - lodash "^4.17.10" + "@babel/types" "^7.4.0" + lodash "^4.17.11" "@babel/helper-explode-assignable-expression@^7.1.0": version "7.1.0" @@ -131,12 +142,12 @@ dependencies: "@babel/types" "^7.0.0" -"@babel/helper-hoist-variables@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0.tgz#46adc4c5e758645ae7a45deb92bab0918c23bb88" - integrity sha512-Ggv5sldXUeSKsuzLkddtyhyHe2YantsxWKNi7A+7LeD12ExRDWTRk29JCXpaHPAbMaIPZSil7n+lq78WY2VY7w== +"@babel/helper-hoist-variables@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.0.tgz#25b621399ae229869329730a62015bbeb0a6fbd6" + integrity sha512-/NErCuoe/et17IlAQFKWM24qtyYYie7sFIrW/tIQXpck6vAu2hhtYYsKLBWQV+BQZMbcIYPU/QMYuTufrY4aQw== dependencies: - "@babel/types" "^7.0.0" + "@babel/types" "^7.4.0" "@babel/helper-member-expression-to-functions@^7.0.0": version "7.0.0" @@ -204,6 +215,16 @@ "@babel/traverse" "^7.1.0" "@babel/types" "^7.0.0" +"@babel/helper-replace-supers@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.4.0.tgz#4f56adb6aedcd449d2da9399c2dcf0545463b64c" + integrity sha512-PVwCVnWWAgnal+kJ+ZSAphzyl58XrFeSKSAJRiqg5QToTsjL+Xu1f9+RJ+d+Q0aPhPfBGaYfkox66k86thxNSg== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.0.0" + "@babel/helper-optimise-call-expression" "^7.0.0" + "@babel/traverse" "^7.4.0" + "@babel/types" "^7.4.0" + "@babel/helper-simple-access@^7.1.0": version "7.1.0" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz#65eeb954c8c245beaa4e859da6188f39d71e585c" @@ -226,6 +247,13 @@ dependencies: "@babel/types" "^7.0.0" +"@babel/helper-split-export-declaration@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.0.tgz#571bfd52701f492920d63b7f735030e9a3e10b55" + integrity sha512-7Cuc6JZiYShaZnybDmfwhY4UYHzI6rlqhWjaIqbsJGsIqPimEYy5uh3akSRLMg65LSdSEnJ8a8/bWQN6u2oMGw== + dependencies: + "@babel/types" "^7.4.0" + "@babel/helper-wrap-function@^7.1.0": version "7.1.0" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.1.0.tgz#8cf54e9190706067f016af8f75cb3df829cc8c66" @@ -236,14 +264,14 @@ "@babel/traverse" "^7.1.0" "@babel/types" "^7.0.0" -"@babel/helpers@^7.1.2": - version "7.1.2" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.1.2.tgz#ab752e8c35ef7d39987df4e8586c63b8846234b5" - integrity sha512-Myc3pUE8eswD73aWcartxB16K6CGmHDv9KxOmD2CeOs/FaEAQodr3VYGmlvOmog60vNQ2w8QbatuahepZwrHiA== +"@babel/helpers@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.4.0.tgz#03392e52c4ce7ad2e7b1cc07d1aba867a8ce2e32" + integrity sha512-2Lfcn74A2WSFUbYJ76ilYE1GnegCKUHTfXxp25EL2zPZHjV7OcDncqNjl295mUH0VnB65mNriXW4J5ROvxsgGg== dependencies: - "@babel/template" "^7.1.2" - "@babel/traverse" "^7.1.0" - "@babel/types" "^7.1.2" + "@babel/template" "^7.4.0" + "@babel/traverse" "^7.4.0" + "@babel/types" "^7.4.0" "@babel/highlight@7.0.0-beta.44": version "7.0.0-beta.44" @@ -268,393 +296,374 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.1.3.tgz#2c92469bac2b7fbff810b67fca07bd138b48af77" integrity sha512-gqmspPZOMW3MIRb9HlrnbZHXI1/KHTOroBwN1NcLL6pWxzqzEKGvRTq0W/PxS45OtQGbaFikSQpkS5zbnsQm2w== -"@babel/plugin-proposal-async-generator-functions@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.1.0.tgz#41c1a702e10081456e23a7b74d891922dd1bb6ce" - integrity sha512-Fq803F3Jcxo20MXUSDdmZZXrPe6BWyGcWBPPNB/M7WaUYESKDeKMOGIxEzQOjGSmW/NWb6UaPZrtTB2ekhB/ew== +"@babel/parser@^7.2.3": + version "7.4.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.2.tgz#b4521a400cb5a871eab3890787b4bc1326d38d91" + integrity sha512-9fJTDipQFvlfSVdD/JBtkiY0br9BtfvW2R8wo6CX/Ej2eMuV0gWPk1M67Mt3eggQvBqYW1FCEk8BN7WvGm/g5g== + +"@babel/parser@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.0.tgz#6de669e73ac3a32c754280d0fef8fca6aad2c416" + integrity sha512-ZmMhJfU/+SXXvy9ALjDZopa3T3EixQtQai89JRC48eM9OUwrxJjYjuM/0wmdl2AekytlzMVhPY8cYdLb13kpKQ== + +"@babel/plugin-proposal-async-generator-functions@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz#b289b306669dce4ad20b0252889a15768c9d417e" + integrity sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-remap-async-to-generator" "^7.1.0" - "@babel/plugin-syntax-async-generators" "^7.0.0" + "@babel/plugin-syntax-async-generators" "^7.2.0" -"@babel/plugin-proposal-json-strings@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.0.0.tgz#3b4d7b5cf51e1f2e70f52351d28d44fc2970d01e" - integrity sha512-kfVdUkIAGJIVmHmtS/40i/fg/AGnw/rsZBCaapY5yjeO5RA9m165Xbw9KMOu2nqXP5dTFjEjHdfNdoVcHv133Q== +"@babel/plugin-proposal-json-strings@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz#568ecc446c6148ae6b267f02551130891e29f317" + integrity sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg== dependencies: "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-json-strings" "^7.0.0" + "@babel/plugin-syntax-json-strings" "^7.2.0" -"@babel/plugin-proposal-object-rest-spread@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.0.0.tgz#9a17b547f64d0676b6c9cecd4edf74a82ab85e7e" - integrity sha512-14fhfoPcNu7itSen7Py1iGN0gEm87hX/B+8nZPqkdmANyyYWYMY2pjA3r8WXbWVKMzfnSNS0xY8GVS0IjXi/iw== +"@babel/plugin-proposal-object-rest-spread@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.4.0.tgz#e4960575205eadf2a1ab4e0c79f9504d5b82a97f" + integrity sha512-uTNi8pPYyUH2eWHyYWWSYJKwKg34hhgl4/dbejEjL+64OhbHjTX7wEVWMQl82tEmdDsGeu77+s8HHLS627h6OQ== dependencies: "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-object-rest-spread" "^7.0.0" + "@babel/plugin-syntax-object-rest-spread" "^7.2.0" -"@babel/plugin-proposal-optional-catch-binding@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.0.0.tgz#b610d928fe551ff7117d42c8bb410eec312a6425" - integrity sha512-JPqAvLG1s13B/AuoBjdBYvn38RqW6n1TzrQO839/sIpqLpbnXKacsAgpZHzLD83Sm8SDXMkkrAvEnJ25+0yIpw== +"@babel/plugin-proposal-optional-catch-binding@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz#135d81edb68a081e55e56ec48541ece8065c38f5" + integrity sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g== dependencies: "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-optional-catch-binding" "^7.0.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" -"@babel/plugin-proposal-unicode-property-regex@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.0.0.tgz#498b39cd72536cd7c4b26177d030226eba08cd33" - integrity sha512-tM3icA6GhC3ch2SkmSxv7J/hCWKISzwycub6eGsDrFDgukD4dZ/I+x81XgW0YslS6mzNuQ1Cbzh5osjIMgepPQ== +"@babel/plugin-proposal-unicode-property-regex@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.0.tgz#202d91ee977d760ef83f4f416b280d568be84623" + integrity sha512-h/KjEZ3nK9wv1P1FSNb9G079jXrNYR0Ko+7XkOx85+gM24iZbPn0rh4vCftk+5QKY7y1uByFataBTmX7irEF1w== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-regex" "^7.0.0" - regexpu-core "^4.2.0" + regexpu-core "^4.5.4" -"@babel/plugin-syntax-async-generators@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.0.0.tgz#bf0891dcdbf59558359d0c626fdc9490e20bc13c" - integrity sha512-im7ged00ddGKAjcZgewXmp1vxSZQQywuQXe2B1A7kajjZmDeY/ekMPmWr9zJgveSaQH0k7BcGrojQhcK06l0zA== +"@babel/plugin-syntax-async-generators@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz#69e1f0db34c6f5a0cf7e2b3323bf159a76c8cb7f" + integrity sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-syntax-dynamic-import@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.0.0.tgz#6dfb7d8b6c3be14ce952962f658f3b7eb54c33ee" - integrity sha512-Gt9xNyRrCHCiyX/ZxDGOcBnlJl0I3IWicpZRC4CdC0P5a/I07Ya2OAMEBU+J7GmRFVmIetqEYRko6QYRuKOESw== +"@babel/plugin-syntax-dynamic-import@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz#69c159ffaf4998122161ad8ebc5e6d1f55df8612" + integrity sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-syntax-flow@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.0.0.tgz#70638aeaad9ee426bc532e51523cff8ff02f6f17" - integrity sha512-zGcuZWiWWDa5qTZ6iAnpG0fnX/GOu49pGR5PFvkQ9GmKNaSphXQnlNXh/LG20sqWtNrx/eB6krzfEzcwvUyeFA== +"@babel/plugin-syntax-json-strings@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz#72bd13f6ffe1d25938129d2a186b11fd62951470" + integrity sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-syntax-json-strings@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.0.0.tgz#0d259a68090e15b383ce3710e01d5b23f3770cbd" - integrity sha512-UlSfNydC+XLj4bw7ijpldc1uZ/HB84vw+U6BTuqMdIEmz/LDe63w/GHtpQMdXWdqQZFeAI9PjnHe/vDhwirhKA== +"@babel/plugin-syntax-object-rest-spread@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz#3b7a3e733510c57e820b9142a6579ac8b0dfad2e" + integrity sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-syntax-jsx@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.0.0.tgz#034d5e2b4e14ccaea2e4c137af7e4afb39375ffd" - integrity sha512-PdmL2AoPsCLWxhIr3kG2+F9v4WH06Q3z+NoGVpQgnUNGcagXHq5sB3OXxkSahKq9TLdNMN/AJzFYSOo8UKDMHg== +"@babel/plugin-syntax-optional-catch-binding@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz#a94013d6eda8908dfe6a477e7f9eda85656ecf5c" + integrity sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-syntax-object-rest-spread@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.0.0.tgz#37d8fbcaf216bd658ea1aebbeb8b75e88ebc549b" - integrity sha512-5A0n4p6bIiVe5OvQPxBnesezsgFJdHhSs3uFSvaPdMqtsovajLZ+G2vZyvNe10EzJBWWo3AcHGKhAFUxqwp2dw== +"@babel/plugin-transform-arrow-functions@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz#9aeafbe4d6ffc6563bf8f8372091628f00779550" + integrity sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-syntax-optional-catch-binding@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.0.0.tgz#886f72008b3a8b185977f7cb70713b45e51ee475" - integrity sha512-Wc+HVvwjcq5qBg1w5RG9o9RVzmCaAg/Vp0erHCKpAYV8La6I94o4GQAmFYNmkzoMO6gzoOSulpKeSSz6mPEoZw== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - -"@babel/plugin-transform-arrow-functions@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.0.0.tgz#a6c14875848c68a3b4b3163a486535ef25c7e749" - integrity sha512-2EZDBl1WIO/q4DIkIp4s86sdp4ZifL51MoIviLY/gG/mLSuOIEg7J8o6mhbxOTvUJkaN50n+8u41FVsr5KLy/w== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - -"@babel/plugin-transform-async-to-generator@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.1.0.tgz#109e036496c51dd65857e16acab3bafdf3c57811" - integrity sha512-rNmcmoQ78IrvNCIt/R9U+cixUHeYAzgusTFgIAv+wQb9HJU4szhpDD6e5GCACmj/JP5KxuCwM96bX3L9v4ZN/g== +"@babel/plugin-transform-async-to-generator@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.4.0.tgz#234fe3e458dce95865c0d152d256119b237834b0" + integrity sha512-EeaFdCeUULM+GPFEsf7pFcNSxM7hYjoj5fiYbyuiXobW4JhFnjAv9OWzNwHyHcKoPNpAfeRDuW6VyaXEDUBa7g== dependencies: "@babel/helper-module-imports" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-remap-async-to-generator" "^7.1.0" -"@babel/plugin-transform-block-scoped-functions@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.0.0.tgz#482b3f75103927e37288b3b67b65f848e2aa0d07" - integrity sha512-AOBiyUp7vYTqz2Jibe1UaAWL0Hl9JUXEgjFvvvcSc9MVDItv46ViXFw2F7SVt1B5k+KWjl44eeXOAk3UDEaJjQ== +"@babel/plugin-transform-block-scoped-functions@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz#5d3cc11e8d5ddd752aa64c9148d0db6cb79fd190" + integrity sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-block-scoping@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.0.0.tgz#1745075edffd7cdaf69fab2fb6f9694424b7e9bc" - integrity sha512-GWEMCrmHQcYWISilUrk9GDqH4enf3UmhOEbNbNrlNAX1ssH3MsS1xLOS6rdjRVPgA7XXVPn87tRkdTEoA/dxEg== +"@babel/plugin-transform-block-scoping@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.4.0.tgz#164df3bb41e3deb954c4ca32ffa9fcaa56d30bcb" + integrity sha512-AWyt3k+fBXQqt2qb9r97tn3iBwFpiv9xdAiG+Gr2HpAZpuayvbL55yWrsV3MyHvXk/4vmSiedhDRl1YI2Iy5nQ== dependencies: "@babel/helper-plugin-utils" "^7.0.0" - lodash "^4.17.10" + lodash "^4.17.11" -"@babel/plugin-transform-classes@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.1.0.tgz#ab3f8a564361800cbc8ab1ca6f21108038432249" - integrity sha512-rNaqoD+4OCBZjM7VaskladgqnZ1LO6o2UxuWSDzljzW21pN1KXkB7BstAVweZdxQkHAujps5QMNOTWesBciKFg== +"@babel/plugin-transform-classes@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.4.0.tgz#e3428d3c8a3d01f33b10c529b998ba1707043d4d" + integrity sha512-XGg1Mhbw4LDmrO9rSTNe+uI79tQPdGs0YASlxgweYRLZqo/EQktjaOV4tchL/UZbM0F+/94uOipmdNGoaGOEYg== dependencies: "@babel/helper-annotate-as-pure" "^7.0.0" - "@babel/helper-define-map" "^7.1.0" + "@babel/helper-define-map" "^7.4.0" "@babel/helper-function-name" "^7.1.0" "@babel/helper-optimise-call-expression" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-replace-supers" "^7.1.0" - "@babel/helper-split-export-declaration" "^7.0.0" + "@babel/helper-replace-supers" "^7.4.0" + "@babel/helper-split-export-declaration" "^7.4.0" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.0.0.tgz#2fbb8900cd3e8258f2a2ede909b90e7556185e31" - integrity sha512-ubouZdChNAv4AAWAgU7QKbB93NU5sHwInEWfp+/OzJKA02E6Woh9RVoX4sZrbRwtybky/d7baTUqwFx+HgbvMA== +"@babel/plugin-transform-computed-properties@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz#83a7df6a658865b1c8f641d510c6f3af220216da" + integrity sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-destructuring@^7.0.0": - version "7.1.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.1.3.tgz#e69ff50ca01fac6cb72863c544e516c2b193012f" - integrity sha512-Mb9M4DGIOspH1ExHOUnn2UUXFOyVTiX84fXCd+6B5iWrQg/QMeeRmSwpZ9lnjYLSXtZwiw80ytVMr3zue0ucYw== +"@babel/plugin-transform-destructuring@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.4.0.tgz#acbb9b2418d290107db333f4d6cd8aa6aea00343" + integrity sha512-HySkoatyYTY3ZwLI8GGvkRWCFrjAGXUHur5sMecmCIdIharnlcWWivOqDJI76vvmVZfzwb6G08NREsrY96RhGQ== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-dotall-regex@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.0.0.tgz#73a24da69bc3c370251f43a3d048198546115e58" - integrity sha512-00THs8eJxOJUFVx1w8i1MBF4XH4PsAjKjQ1eqN/uCH3YKwP21GCKfrn6YZFZswbOk9+0cw1zGQPHVc1KBlSxig== +"@babel/plugin-transform-dotall-regex@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.2.0.tgz#f0aabb93d120a8ac61e925ea0ba440812dbe0e49" + integrity sha512-sKxnyHfizweTgKZf7XsXu/CNupKhzijptfTM+bozonIuyVrLWVUvYjE2bhuSBML8VQeMxq4Mm63Q9qvcvUcciQ== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-regex" "^7.0.0" regexpu-core "^4.1.3" -"@babel/plugin-transform-duplicate-keys@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.0.0.tgz#a0601e580991e7cace080e4cf919cfd58da74e86" - integrity sha512-w2vfPkMqRkdxx+C71ATLJG30PpwtTpW7DDdLqYt2acXU7YjztzeWW2Jk1T6hKqCLYCcEA5UQM/+xTAm+QCSnuQ== +"@babel/plugin-transform-duplicate-keys@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.2.0.tgz#d952c4930f312a4dbfff18f0b2914e60c35530b3" + integrity sha512-q+yuxW4DsTjNceUiTzK0L+AfQ0zD9rWaTLiUqHA8p0gxx7lu1EylenfzjeIWNkPy6e/0VG/Wjw9uf9LueQwLOw== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-exponentiation-operator@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.1.0.tgz#9c34c2ee7fd77e02779cfa37e403a2e1003ccc73" - integrity sha512-uZt9kD1Pp/JubkukOGQml9tqAeI8NkE98oZnHZ2qHRElmeKCodbTZgOEUtujSCSLhHSBWbzNiFSDIMC4/RBTLQ== +"@babel/plugin-transform-exponentiation-operator@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz#a63868289e5b4007f7054d46491af51435766008" + integrity sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A== dependencies: "@babel/helper-builder-binary-assignment-operator-visitor" "^7.1.0" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-flow-strip-types@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.0.0.tgz#c40ced34c2783985d90d9f9ac77a13e6fb396a01" - integrity sha512-WhXUNb4It5a19RsgKKbQPrjmy4yWOY1KynpEbNw7bnd1QTcrT/EIl3MJvnGgpgvrKyKbqX7nUNOJfkpLOnoDKA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-flow" "^7.0.0" - -"@babel/plugin-transform-for-of@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.0.0.tgz#f2ba4eadb83bd17dc3c7e9b30f4707365e1c3e39" - integrity sha512-TlxKecN20X2tt2UEr2LNE6aqA0oPeMT1Y3cgz8k4Dn1j5ObT8M3nl9aA37LLklx0PBZKETC9ZAf9n/6SujTuXA== +"@babel/plugin-transform-for-of@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.0.tgz#56c8c36677f5d4a16b80b12f7b768de064aaeb5f" + integrity sha512-vWdfCEYLlYSxbsKj5lGtzA49K3KANtb8qCPQ1em07txJzsBwY+cKJzBHizj5fl3CCx7vt+WPdgDLTHmydkbQSQ== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-function-name@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.1.0.tgz#29c5550d5c46208e7f730516d41eeddd4affadbb" - integrity sha512-VxOa1TMlFMtqPW2IDYZQaHsFrq/dDoIjgN098NowhexhZcz3UGlvPgZXuE1jEvNygyWyxRacqDpCZt+par1FNg== +"@babel/plugin-transform-function-name@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.2.0.tgz#f7930362829ff99a3174c39f0afcc024ef59731a" + integrity sha512-kWgksow9lHdvBC2Z4mxTsvc7YdY7w/V6B2vy9cTIPtLEE9NhwoWivaxdNM/S37elu5bqlLP/qOY906LukO9lkQ== dependencies: "@babel/helper-function-name" "^7.1.0" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-literals@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.0.0.tgz#2aec1d29cdd24c407359c930cdd89e914ee8ff86" - integrity sha512-1NTDBWkeNXgpUcyoVFxbr9hS57EpZYXpje92zv0SUzjdu3enaRwF/l3cmyRnXLtIdyJASyiS6PtybK+CgKf7jA== +"@babel/plugin-transform-literals@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz#690353e81f9267dad4fd8cfd77eafa86aba53ea1" + integrity sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-modules-amd@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.1.0.tgz#f9e0a7072c12e296079b5a59f408ff5b97bf86a8" - integrity sha512-wt8P+xQ85rrnGNr2x1iV3DW32W8zrB6ctuBkYBbf5/ZzJY99Ob4MFgsZDFgczNU76iy9PWsy4EuxOliDjdKw6A== +"@babel/plugin-transform-modules-amd@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.2.0.tgz#82a9bce45b95441f617a24011dc89d12da7f4ee6" + integrity sha512-mK2A8ucqz1qhrdqjS9VMIDfIvvT2thrEsIQzbaTdc5QFzhDjQv2CkJJ5f6BXIkgbmaoax3zBr2RyvV/8zeoUZw== dependencies: "@babel/helper-module-transforms" "^7.1.0" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-modules-commonjs@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.1.0.tgz#0a9d86451cbbfb29bd15186306897c67f6f9a05c" - integrity sha512-wtNwtMjn1XGwM0AXPspQgvmE6msSJP15CX2RVfpTSTNPLhKhaOjaIfBaVfj4iUZ/VrFSodcFedwtPg/NxwQlPA== +"@babel/plugin-transform-modules-commonjs@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.4.0.tgz#3b8ec61714d3b75d20c5ccfa157f2c2e087fd4ca" + integrity sha512-iWKAooAkipG7g1IY0eah7SumzfnIT3WNhT4uYB2kIsvHnNSB6MDYVa5qyICSwaTBDBY2c4SnJ3JtEa6ltJd6Jw== dependencies: "@babel/helper-module-transforms" "^7.1.0" "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-simple-access" "^7.1.0" -"@babel/plugin-transform-modules-systemjs@^7.0.0": - version "7.1.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.1.3.tgz#2119a3e3db612fd74a19d88652efbfe9613a5db0" - integrity sha512-PvTxgjxQAq4pvVUZF3mD5gEtVDuId8NtWkJsZLEJZMZAW3TvgQl1pmydLLN1bM8huHFVVU43lf0uvjQj9FRkKw== +"@babel/plugin-transform-modules-systemjs@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.4.0.tgz#c2495e55528135797bc816f5d50f851698c586a1" + integrity sha512-gjPdHmqiNhVoBqus5qK60mWPp1CmYWp/tkh11mvb0rrys01HycEGD7NvvSoKXlWEfSM9TcL36CpsK8ElsADptQ== dependencies: - "@babel/helper-hoist-variables" "^7.0.0" + "@babel/helper-hoist-variables" "^7.4.0" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-modules-umd@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.1.0.tgz#a29a7d85d6f28c3561c33964442257cc6a21f2a8" - integrity sha512-enrRtn5TfRhMmbRwm7F8qOj0qEYByqUvTttPEGimcBH4CJHphjyK1Vg7sdU7JjeEmgSpM890IT/efS2nMHwYig== +"@babel/plugin-transform-modules-umd@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz#7678ce75169f0877b8eb2235538c074268dd01ae" + integrity sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw== dependencies: "@babel/helper-module-transforms" "^7.1.0" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-new-target@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.0.0.tgz#ae8fbd89517fa7892d20e6564e641e8770c3aa4a" - integrity sha512-yin069FYjah+LbqfGeTfzIBODex/e++Yfa0rH0fpfam9uTbuEeEOx5GLGr210ggOV77mVRNoeqSYqeuaqSzVSw== +"@babel/plugin-transform-named-capturing-groups-regex@^7.4.2": + version "7.4.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.4.2.tgz#800391136d6cbcc80728dbdba3c1c6e46f86c12e" + integrity sha512-NsAuliSwkL3WO2dzWTOL1oZJHm0TM8ZY8ZSxk2ANyKkt5SQlToGA4pzctmq1BEjoacurdwZ3xp2dCQWJkME0gQ== + dependencies: + regexp-tree "^0.1.0" + +"@babel/plugin-transform-new-target@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.0.tgz#67658a1d944edb53c8d4fa3004473a0dd7838150" + integrity sha512-6ZKNgMQmQmrEX/ncuCwnnw1yVGoaOW5KpxNhoWI7pCQdA0uZ0HqHGqenCUIENAnxRjy2WwNQ30gfGdIgqJXXqw== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-object-super@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.1.0.tgz#b1ae194a054b826d8d4ba7ca91486d4ada0f91bb" - integrity sha512-/O02Je1CRTSk2SSJaq0xjwQ8hG4zhZGNjE8psTsSNPXyLRCODv7/PBozqT5AmQMzp7MI3ndvMhGdqp9c96tTEw== +"@babel/plugin-transform-object-super@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.2.0.tgz#b35d4c10f56bab5d650047dad0f1d8e8814b6598" + integrity sha512-VMyhPYZISFZAqAPVkiYb7dUe2AsVi2/wCT5+wZdsNO31FojQJa9ns40hzZ6U9f50Jlq4w6qwzdBB2uwqZ00ebg== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-replace-supers" "^7.1.0" -"@babel/plugin-transform-parameters@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.1.0.tgz#44f492f9d618c9124026e62301c296bf606a7aed" - integrity sha512-vHV7oxkEJ8IHxTfRr3hNGzV446GAb+0hgbA7o/0Jd76s+YzccdWuTU296FOCOl/xweU4t/Ya4g41yWz80RFCRw== +"@babel/plugin-transform-parameters@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.0.tgz#a1309426fac4eecd2a9439a4c8c35124a11a48a9" + integrity sha512-Xqv6d1X+doyiuCGDoVJFtlZx0onAX0tnc3dY8w71pv/O0dODAbusVv2Ale3cGOwfiyi895ivOBhYa9DhAM8dUA== dependencies: - "@babel/helper-call-delegate" "^7.1.0" + "@babel/helper-call-delegate" "^7.4.0" "@babel/helper-get-function-arity" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-regenerator@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0.tgz#5b41686b4ed40bef874d7ed6a84bdd849c13e0c1" - integrity sha512-sj2qzsEx8KDVv1QuJc/dEfilkg3RRPvPYx/VnKLtItVQRWt1Wqf5eVCOLZm29CiGFfYYsA3VPjfizTCV0S0Dlw== +"@babel/plugin-transform-regenerator@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.0.tgz#0780e27ee458cc3fdbad18294d703e972ae1f6d1" + integrity sha512-SZ+CgL4F0wm4npojPU6swo/cK4FcbLgxLd4cWpHaNXY/NJ2dpahODCqBbAwb2rDmVszVb3SSjnk9/vik3AYdBw== dependencies: - regenerator-transform "^0.13.3" + regenerator-transform "^0.13.4" -"@babel/plugin-transform-runtime@^7.0.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.1.0.tgz#9f76920d42551bb577e2dc594df229b5f7624b63" - integrity sha512-WFLMgzu5DLQEah0lKTJzYb14vd6UiES7PTnXcvrPZ1VrwFeJ+mTbvr65fFAsXYMt2bIoOoC0jk76zY1S7HZjUg== - dependencies: - "@babel/helper-module-imports" "^7.0.0" - "@babel/helper-plugin-utils" "^7.0.0" - resolve "^1.8.1" - semver "^5.5.1" - -"@babel/plugin-transform-shorthand-properties@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.0.0.tgz#85f8af592dcc07647541a0350e8c95c7bf419d15" - integrity sha512-g/99LI4vm5iOf5r1Gdxq5Xmu91zvjhEG5+yZDJW268AZELAu4J1EiFLnkSG3yuUsZyOipVOVUKoGPYwfsTymhw== +"@babel/plugin-transform-shorthand-properties@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz#6333aee2f8d6ee7e28615457298934a3b46198f0" + integrity sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-spread@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.0.0.tgz#93583ce48dd8c85e53f3a46056c856e4af30b49b" - integrity sha512-L702YFy2EvirrR4shTj0g2xQp7aNwZoWNCkNu2mcoU0uyzMl0XRwDSwzB/xp6DSUFiBmEXuyAyEN16LsgVqGGQ== +"@babel/plugin-transform-spread@^7.2.0": + version "7.2.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz#3103a9abe22f742b6d406ecd3cd49b774919b406" + integrity sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-sticky-regex@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.0.0.tgz#30a9d64ac2ab46eec087b8530535becd90e73366" - integrity sha512-LFUToxiyS/WD+XEWpkx/XJBrUXKewSZpzX68s+yEOtIbdnsRjpryDw9U06gYc6klYEij/+KQVRnD3nz3AoKmjw== +"@babel/plugin-transform-sticky-regex@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz#a1e454b5995560a9c1e0d537dfc15061fd2687e1" + integrity sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-regex" "^7.0.0" -"@babel/plugin-transform-template-literals@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.0.0.tgz#084f1952efe5b153ddae69eb8945f882c7a97c65" - integrity sha512-vA6rkTCabRZu7Nbl9DfLZE1imj4tzdWcg5vtdQGvj+OH9itNNB6hxuRMHuIY8SGnEt1T9g5foqs9LnrHzsqEFg== +"@babel/plugin-transform-template-literals@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.2.0.tgz#d87ed01b8eaac7a92473f608c97c089de2ba1e5b" + integrity sha512-FkPix00J9A/XWXv4VoKJBMeSkyY9x/TqIh76wzcdfl57RJJcf8CehQ08uwfhCDNtRQYtHQKBTwKZDEyjE13Lwg== dependencies: "@babel/helper-annotate-as-pure" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-typeof-symbol@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.0.0.tgz#4dcf1e52e943e5267b7313bff347fdbe0f81cec9" - integrity sha512-1r1X5DO78WnaAIvs5uC48t41LLckxsYklJrZjNKcevyz83sF2l4RHbw29qrCPr/6ksFsdfRpT/ZgxNWHXRnffg== +"@babel/plugin-transform-typeof-symbol@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz#117d2bcec2fbf64b4b59d1f9819894682d29f2b2" + integrity sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-unicode-regex@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.0.0.tgz#c6780e5b1863a76fe792d90eded9fcd5b51d68fc" - integrity sha512-uJBrJhBOEa3D033P95nPHu3nbFwFE9ZgXsfEitzoIXIwqAZWk7uXcg06yFKXz9FSxBH5ucgU/cYdX0IV8ldHKw== +"@babel/plugin-transform-unicode-regex@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.2.0.tgz#4eb8db16f972f8abb5062c161b8b115546ade08b" + integrity sha512-m48Y0lMhrbXEJnVUaYly29jRXbQ3ksxPrS1Tg8t+MHqzXhtBYAvI51euOBaoAlZLPHsieY9XPVMf80a5x0cPcA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-regex" "^7.0.0" regexpu-core "^4.1.3" -"@babel/polyfill@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.0.0.tgz#c8ff65c9ec3be6a1ba10113ebd40e8750fb90bff" - integrity sha512-dnrMRkyyr74CRelJwvgnnSUDh2ge2NCTyHVwpOdvRMHtJUyxLtMAfhBN3s64pY41zdw0kgiLPh6S20eb1NcX6Q== +"@babel/polyfill@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.4.0.tgz#90f9d68ae34ac42ab4b4aa03151848f536960218" + integrity sha512-bVsjsrtsDflIHp5I6caaAa2V25Kzn50HKPL6g3X0P0ni1ks+58cPB8Mz6AOKVuRPgaVdq/OwEUc/1vKqX+Mo4A== dependencies: - core-js "^2.5.7" - regenerator-runtime "^0.11.1" + core-js "^2.6.5" + regenerator-runtime "^0.13.2" -"@babel/preset-env@^7.0.0", "@babel/preset-env@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.1.0.tgz#e67ea5b0441cfeab1d6f41e9b5c79798800e8d11" - integrity sha512-ZLVSynfAoDHB/34A17/JCZbyrzbQj59QC1Anyueb4Bwjh373nVPq5/HMph0z+tCmcDjXDe+DlKQq9ywQuvWrQg== +"@babel/preset-env@^7.4.2": + version "7.4.2" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.4.2.tgz#2f5ba1de2daefa9dcca653848f96c7ce2e406676" + integrity sha512-OEz6VOZaI9LW08CWVS3d9g/0jZA6YCn1gsKIy/fut7yZCJti5Lm1/Hi+uo/U+ODm7g4I6gULrCP+/+laT8xAsA== dependencies: "@babel/helper-module-imports" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-proposal-async-generator-functions" "^7.1.0" - "@babel/plugin-proposal-json-strings" "^7.0.0" - "@babel/plugin-proposal-object-rest-spread" "^7.0.0" - "@babel/plugin-proposal-optional-catch-binding" "^7.0.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.0.0" - "@babel/plugin-syntax-async-generators" "^7.0.0" - "@babel/plugin-syntax-object-rest-spread" "^7.0.0" - "@babel/plugin-syntax-optional-catch-binding" "^7.0.0" - "@babel/plugin-transform-arrow-functions" "^7.0.0" - "@babel/plugin-transform-async-to-generator" "^7.1.0" - "@babel/plugin-transform-block-scoped-functions" "^7.0.0" - "@babel/plugin-transform-block-scoping" "^7.0.0" - "@babel/plugin-transform-classes" "^7.1.0" - "@babel/plugin-transform-computed-properties" "^7.0.0" - "@babel/plugin-transform-destructuring" "^7.0.0" - "@babel/plugin-transform-dotall-regex" "^7.0.0" - "@babel/plugin-transform-duplicate-keys" "^7.0.0" - "@babel/plugin-transform-exponentiation-operator" "^7.1.0" - "@babel/plugin-transform-for-of" "^7.0.0" - "@babel/plugin-transform-function-name" "^7.1.0" - "@babel/plugin-transform-literals" "^7.0.0" - "@babel/plugin-transform-modules-amd" "^7.1.0" - "@babel/plugin-transform-modules-commonjs" "^7.1.0" - "@babel/plugin-transform-modules-systemjs" "^7.0.0" - "@babel/plugin-transform-modules-umd" "^7.1.0" - "@babel/plugin-transform-new-target" "^7.0.0" - "@babel/plugin-transform-object-super" "^7.1.0" - "@babel/plugin-transform-parameters" "^7.1.0" - "@babel/plugin-transform-regenerator" "^7.0.0" - "@babel/plugin-transform-shorthand-properties" "^7.0.0" - "@babel/plugin-transform-spread" "^7.0.0" - "@babel/plugin-transform-sticky-regex" "^7.0.0" - "@babel/plugin-transform-template-literals" "^7.0.0" - "@babel/plugin-transform-typeof-symbol" "^7.0.0" - "@babel/plugin-transform-unicode-regex" "^7.0.0" - browserslist "^4.1.0" + "@babel/plugin-proposal-async-generator-functions" "^7.2.0" + "@babel/plugin-proposal-json-strings" "^7.2.0" + "@babel/plugin-proposal-object-rest-spread" "^7.4.0" + "@babel/plugin-proposal-optional-catch-binding" "^7.2.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.4.0" + "@babel/plugin-syntax-async-generators" "^7.2.0" + "@babel/plugin-syntax-json-strings" "^7.2.0" + "@babel/plugin-syntax-object-rest-spread" "^7.2.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" + "@babel/plugin-transform-arrow-functions" "^7.2.0" + "@babel/plugin-transform-async-to-generator" "^7.4.0" + "@babel/plugin-transform-block-scoped-functions" "^7.2.0" + "@babel/plugin-transform-block-scoping" "^7.4.0" + "@babel/plugin-transform-classes" "^7.4.0" + "@babel/plugin-transform-computed-properties" "^7.2.0" + "@babel/plugin-transform-destructuring" "^7.4.0" + "@babel/plugin-transform-dotall-regex" "^7.2.0" + "@babel/plugin-transform-duplicate-keys" "^7.2.0" + "@babel/plugin-transform-exponentiation-operator" "^7.2.0" + "@babel/plugin-transform-for-of" "^7.4.0" + "@babel/plugin-transform-function-name" "^7.2.0" + "@babel/plugin-transform-literals" "^7.2.0" + "@babel/plugin-transform-modules-amd" "^7.2.0" + "@babel/plugin-transform-modules-commonjs" "^7.4.0" + "@babel/plugin-transform-modules-systemjs" "^7.4.0" + "@babel/plugin-transform-modules-umd" "^7.2.0" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.4.2" + "@babel/plugin-transform-new-target" "^7.4.0" + "@babel/plugin-transform-object-super" "^7.2.0" + "@babel/plugin-transform-parameters" "^7.4.0" + "@babel/plugin-transform-regenerator" "^7.4.0" + "@babel/plugin-transform-shorthand-properties" "^7.2.0" + "@babel/plugin-transform-spread" "^7.2.0" + "@babel/plugin-transform-sticky-regex" "^7.2.0" + "@babel/plugin-transform-template-literals" "^7.2.0" + "@babel/plugin-transform-typeof-symbol" "^7.2.0" + "@babel/plugin-transform-unicode-regex" "^7.2.0" + "@babel/types" "^7.4.0" + browserslist "^4.4.2" + core-js-compat "^3.0.0" invariant "^2.2.2" js-levenshtein "^1.1.3" semver "^5.3.0" -"@babel/preset-flow@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.0.0.tgz#afd764835d9535ec63d8c7d4caf1c06457263da2" - integrity sha512-bJOHrYOPqJZCkPVbG1Lot2r5OSsB+iUOaxiHdlOeB1yPWS6evswVHwvkDLZ54WTaTRIk89ds0iHmGZSnxlPejQ== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-transform-flow-strip-types" "^7.0.0" - "@babel/runtime@^7.0.0": version "7.1.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.1.2.tgz#81c89935f4647706fc54541145e6b4ecfef4b8e3" @@ -672,7 +681,7 @@ babylon "7.0.0-beta.44" lodash "^4.2.0" -"@babel/template@^7.0.0-beta.46", "@babel/template@^7.1.0", "@babel/template@^7.1.2": +"@babel/template@^7.0.0-beta.46", "@babel/template@^7.1.0": version "7.1.2" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.2.tgz#090484a574fef5a2d2d7726a674eceda5c5b5644" integrity sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag== @@ -681,6 +690,15 @@ "@babel/parser" "^7.1.2" "@babel/types" "^7.1.2" +"@babel/template@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.0.tgz#12474e9c077bae585c5d835a95c0b0b790c25c8b" + integrity sha512-SOWwxxClTTh5NdbbYZ0BmaBVzxzTh2tO/TeLTbF6MO6EzVhHTnff8CdBXx3mEtazFBoysmEM6GU/wF+SuSx4Fw== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.4.0" + "@babel/types" "^7.4.0" + "@babel/traverse@7.0.0-beta.44": version "7.0.0-beta.44" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.0.0-beta.44.tgz#a970a2c45477ad18017e2e465a0606feee0d2966" @@ -712,6 +730,21 @@ globals "^11.1.0" lodash "^4.17.10" +"@babel/traverse@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.4.0.tgz#14006967dd1d2b3494cdd650c686db9daf0ddada" + integrity sha512-/DtIHKfyg2bBKnIN+BItaIlEg5pjAnzHOIQe5w+rHAw/rg9g0V7T4rqPX8BJPfW11kt3koyjAnTNwCzb28Y1PA== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/generator" "^7.4.0" + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-split-export-declaration" "^7.4.0" + "@babel/parser" "^7.4.0" + "@babel/types" "^7.4.0" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.11" + "@babel/types@7.0.0-beta.44": version "7.0.0-beta.44" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.0.0-beta.44.tgz#6b1b164591f77dec0a0342aca995f2d046b3a757" @@ -730,30 +763,49 @@ lodash "^4.17.10" to-fast-properties "^2.0.0" -"@gulp-sourcemaps/identity-map@1.X": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@gulp-sourcemaps/identity-map/-/identity-map-1.0.2.tgz#1e6fe5d8027b1f285dc0d31762f566bccd73d5a9" - integrity sha512-ciiioYMLdo16ShmfHBXJBOFm3xPC4AuwO4xeRpFeHz7WK9PYsWCmigagG2XyzZpubK4a3qNKoUBDhbzHfa50LQ== +"@babel/types@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.4.0.tgz#670724f77d24cce6cc7d8cf64599d511d164894c" + integrity sha512-aPvkXyU2SPOnztlgo8n9cEiXW755mgyvueUPcpStqdzoSPm0fjO0vQBjLkt3JKJW7ufikfcnMTTPsN1xaTsBPA== dependencies: - acorn "^5.0.3" - css "^2.2.1" - normalize-path "^2.1.1" - source-map "^0.6.0" - through2 "^2.0.3" - -"@gulp-sourcemaps/map-sources@1.X": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz#890ae7c5d8c877f6d384860215ace9d7ec945bda" - integrity sha1-iQrnxdjId/bThIYCFazp1+yUW9o= - dependencies: - normalize-path "^2.0.1" - through2 "^2.0.3" + esutils "^2.0.2" + lodash "^4.17.11" + to-fast-properties "^2.0.0" "@handsontable/vue@^2.0.0-beta1": version "2.0.0" resolved "https://registry.yarnpkg.com/@handsontable/vue/-/vue-2.0.0.tgz#28ae7d247a89738088abc32584562925dde18db0" integrity sha512-QVqJywrQWwJzoDCXibZkXrg+T91hNCTCN9qznmum4FSewL0MUtaHotv7Zc/D4scvEwpwWnKx5vpw4CY14V4OYw== +"@johmun/vue-tags-input@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@johmun/vue-tags-input/-/vue-tags-input-2.0.1.tgz#5ca22a573858c8df6c05788e3ffabe92e1baa1df" + integrity sha512-1LUsINr6iBROfG31C05qf+XpoM4jLstpLzTmfu+57fxErgP2gGnVB41oz1qVxjKe1gdpOaAZSByhoAm2h0cO3w== + dependencies: + vue "^2.5.16" + +"@mrmlnc/readdir-enhanced@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" + integrity sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g== + dependencies: + call-me-maybe "^1.0.1" + glob-to-regexp "^0.3.0" + +"@nodelib/fs.stat@^1.1.2": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" + integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== + +"@nuxt/opencollective@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@nuxt/opencollective/-/opencollective-0.2.1.tgz#8290f1220072637e575c3935733719a78ad2d056" + integrity sha512-NP2VSUKRFGutbhWeKgIU0MnY4fmpH8UWxxwTJNPurCQ5BeWhOxp+Gp5ltO39P/Et/J2GYGb3+ALNqZJ+5cGBBw== + dependencies: + chalk "^2.4.1" + consola "^2.3.0" + node-fetch "^2.3.0" + "@sinonjs/formatio@3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-3.0.0.tgz#9d282d81030a03a03fa0c5ce31fd8786a4da311a" @@ -787,20 +839,55 @@ dependencies: "@types/babel-types" "*" -"@vue/component-compiler-utils@^2.0.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-2.2.0.tgz#bbbb7ed38a9a8a7c93abe7ef2e54a90a04b631b4" - integrity sha512-pS4zlcdD7BvedyB+IfiTfrbi6C977UMIfulSk8r6uL0BU46ZE2+fUj/zbSNSfVxeaj9ElmnSni5OMwF9np+b+w== +"@types/node@*": + version "11.11.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.7.tgz#f1c35a906b82adae76ede5ab0d2088e58fa37843" + integrity sha512-bHbRcyD6XpXVLg42QYaQCjvDXaCFkvb3WbCIxSDmhGbJYVroxvYzekk9QGg1beeIawfvSLkdZpP0h7jxE4ihnA== + +"@types/unist@*", "@types/unist@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" + integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ== + +"@types/vfile-message@*": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/vfile-message/-/vfile-message-1.0.1.tgz#e1e9895cc6b36c462d4244e64e6d0b6eaf65355a" + integrity sha512-mlGER3Aqmq7bqR1tTTIVHq8KSAFFRyGbrxuM8C/H82g6k7r2fS+IMEkIu3D7JHzG10NvPdR8DNx0jr0pwpp4dA== + dependencies: + "@types/node" "*" + "@types/unist" "*" + +"@types/vfile@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/vfile/-/vfile-3.0.2.tgz#19c18cd232df11ce6fa6ad80259bc86c366b09b9" + integrity sha512-b3nLFGaGkJ9rzOcuXRfHkZMdjsawuDD0ENL9fzTophtBg8FJHSGbH7daXkEpcwy3v7Xol3pAvsmlYyFhR4pqJw== + dependencies: + "@types/node" "*" + "@types/unist" "*" + "@types/vfile-message" "*" + +"@vue/component-compiler-utils@^2.5.1": + version "2.5.2" + resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-2.5.2.tgz#a8d57e773354ab10e4742c7d6a8dd86184d4d7be" + integrity sha512-3exq9O89GXo9E+CGKzgURCbasG15FtFMs8QRrCUVWGaKue4Egpw41MHb3Avtikv1VykKfBq3FvAnf9Nx3sdVJg== dependencies: consolidate "^0.15.1" hash-sum "^1.0.2" lru-cache "^4.1.2" merge-source-map "^1.1.0" - postcss "^6.0.20" - postcss-selector-parser "^3.1.1" - prettier "1.13.7" - source-map "^0.5.6" - vue-template-es2015-compiler "^1.6.0" + postcss "^7.0.14" + postcss-selector-parser "^5.0.0" + prettier "1.16.3" + source-map "~0.6.1" + vue-template-es2015-compiler "^1.8.2" + +"@vue/test-utils@1.0.0-beta.29": + version "1.0.0-beta.29" + resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.0.0-beta.29.tgz#c942cf25e891cf081b6a03332b4ae1ef430726f0" + integrity sha512-yX4sxEIHh4M9yAbLA/ikpEnGKMNBCnoX98xE1RwxfhQVcn0MaXNSj1Qmac+ZydTj6VBSEVukchBogXBTwc+9iA== + dependencies: + dom-event-types "^1.0.0" + lodash "^4.17.4" "@vxna/mini-html-webpack-template@^0.1.7": version "0.1.7" @@ -809,158 +896,161 @@ dependencies: common-tags "^1.7.2" -"@webassemblyjs/ast@1.7.10": - version "1.7.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.7.10.tgz#0cfc61d61286240b72fc522cb755613699eea40a" - integrity sha512-wTUeaByYN2EA6qVqhbgavtGc7fLTOx0glG2IBsFlrFG51uXIGlYBTyIZMf4SPLo3v1bgV/7lBN3l7Z0R6Hswew== +"@webassemblyjs/ast@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359" + integrity sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ== dependencies: - "@webassemblyjs/helper-module-context" "1.7.10" - "@webassemblyjs/helper-wasm-bytecode" "1.7.10" - "@webassemblyjs/wast-parser" "1.7.10" + "@webassemblyjs/helper-module-context" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/wast-parser" "1.8.5" -"@webassemblyjs/floating-point-hex-parser@1.7.10": - version "1.7.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.7.10.tgz#ee63d729c6311a85863e369a473f9983f984e4d9" - integrity sha512-gMsGbI6I3p/P1xL2UxqhNh1ga2HCsx5VBB2i5VvJFAaqAjd2PBTRULc3BpTydabUQEGlaZCzEUQhLoLG7TvEYQ== +"@webassemblyjs/floating-point-hex-parser@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz#1ba926a2923613edce496fd5b02e8ce8a5f49721" + integrity sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ== -"@webassemblyjs/helper-api-error@1.7.10": - version "1.7.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.7.10.tgz#bfcb3bbe59775357475790a2ad7b289f09b2f198" - integrity sha512-DoYRlPWtuw3yd5BOr9XhtrmB6X1enYF0/54yNvQWGXZEPDF5PJVNI7zQ7gkcKfTESzp8bIBWailaFXEK/jjCsw== +"@webassemblyjs/helper-api-error@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz#c49dad22f645227c5edb610bdb9697f1aab721f7" + integrity sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA== -"@webassemblyjs/helper-buffer@1.7.10": - version "1.7.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.7.10.tgz#0a8c624c67ad0b214d2e003859921a1988cb151b" - integrity sha512-+RMU3dt/dPh4EpVX4u5jxsOlw22tp3zjqE0m3ftU2tsYxnPULb4cyHlgaNd2KoWuwasCQqn8Mhr+TTdbtj3LlA== +"@webassemblyjs/helper-buffer@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz#fea93e429863dd5e4338555f42292385a653f204" + integrity sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q== -"@webassemblyjs/helper-code-frame@1.7.10": - version "1.7.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.7.10.tgz#0ab7e22fad0241a173178c73976fc0edf50832ce" - integrity sha512-UiytbpKAULOEab2hUZK2ywXen4gWJVrgxtwY3Kn+eZaaSWaRM8z/7dAXRSoamhKFiBh1uaqxzE/XD9BLlug3gw== +"@webassemblyjs/helper-code-frame@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz#9a740ff48e3faa3022b1dff54423df9aa293c25e" + integrity sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ== dependencies: - "@webassemblyjs/wast-printer" "1.7.10" + "@webassemblyjs/wast-printer" "1.8.5" -"@webassemblyjs/helper-fsm@1.7.10": - version "1.7.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.7.10.tgz#0915e7713fbbb735620a9d3e4fa3d7951f97ac64" - integrity sha512-w2vDtUK9xeSRtt5+RnnlRCI7wHEvLjF0XdnxJpgx+LJOvklTZPqWkuy/NhwHSLP19sm9H8dWxKeReMR7sCkGZA== +"@webassemblyjs/helper-fsm@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz#ba0b7d3b3f7e4733da6059c9332275d860702452" + integrity sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow== -"@webassemblyjs/helper-module-context@1.7.10": - version "1.7.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.7.10.tgz#9beb83f72740f5ac8075313b5cac5e796510f755" - integrity sha512-yE5x/LzZ3XdPdREmJijxzfrf+BDRewvO0zl8kvORgSWmxpRrkqY39KZSq6TSgIWBxkK4SrzlS3BsMCv2s1FpsQ== - -"@webassemblyjs/helper-wasm-bytecode@1.7.10": - version "1.7.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.7.10.tgz#797b1e734bbcfdea8399669cdc58308ef1c7ffc0" - integrity sha512-u5qy4SJ/OrxKxZqJ9N3qH4ZQgHaAzsopsYwLvoWJY6Q33r8PhT3VPyNMaJ7ZFoqzBnZlCcS/0f4Sp8WBxylXfg== - -"@webassemblyjs/helper-wasm-section@1.7.10": - version "1.7.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.7.10.tgz#c0ea3703c615d7bc3e3507c3b7991c8767b2f20e" - integrity sha512-Ecvww6sCkcjatcyctUrn22neSJHLN/TTzolMGG/N7S9rpbsTZ8c6Bl98GpSpV77EvzNijiNRHBG0+JO99qKz6g== +"@webassemblyjs/helper-module-context@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz#def4b9927b0101dc8cbbd8d1edb5b7b9c82eb245" + integrity sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g== dependencies: - "@webassemblyjs/ast" "1.7.10" - "@webassemblyjs/helper-buffer" "1.7.10" - "@webassemblyjs/helper-wasm-bytecode" "1.7.10" - "@webassemblyjs/wasm-gen" "1.7.10" + "@webassemblyjs/ast" "1.8.5" + mamacro "^0.0.3" -"@webassemblyjs/ieee754@1.7.10": - version "1.7.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.7.10.tgz#62c1728b7ef0f66ef8221e2966a0afd75db430df" - integrity sha512-HRcWcY+YWt4+s/CvQn+vnSPfRaD4KkuzQFt5MNaELXXHSjelHlSEA8ZcqT69q0GTIuLWZ6JaoKar4yWHVpZHsQ== +"@webassemblyjs/helper-wasm-bytecode@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz#537a750eddf5c1e932f3744206551c91c1b93e61" + integrity sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ== + +"@webassemblyjs/helper-wasm-section@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz#74ca6a6bcbe19e50a3b6b462847e69503e6bfcbf" + integrity sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-buffer" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/wasm-gen" "1.8.5" + +"@webassemblyjs/ieee754@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz#712329dbef240f36bf57bd2f7b8fb9bf4154421e" + integrity sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g== dependencies: "@xtuc/ieee754" "^1.2.0" -"@webassemblyjs/leb128@1.7.10": - version "1.7.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.7.10.tgz#167e0bb4b06d7701585772a73fba9f4df85439f6" - integrity sha512-og8MciYlA8hvzCLR71hCuZKPbVBfLQeHv7ImKZ4nlyxrYbG7uJHYtHiHu6OV9SqrGuD03H/HtXC4Bgdjfm9FHw== +"@webassemblyjs/leb128@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.8.5.tgz#044edeb34ea679f3e04cd4fd9824d5e35767ae10" + integrity sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A== dependencies: - "@xtuc/long" "4.2.1" + "@xtuc/long" "4.2.2" -"@webassemblyjs/utf8@1.7.10": - version "1.7.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.7.10.tgz#b6728f5b6f50364abc155be029f9670e6685605a" - integrity sha512-Ng6Pxv6siyZp635xCSnH3mKmIFgqWPCcGdoo0GBYgyGdxu7cUj4agV7Uu1a8REP66UYUFXJLudeGgd4RvuJAnQ== +"@webassemblyjs/utf8@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.8.5.tgz#a8bf3b5d8ffe986c7c1e373ccbdc2a0915f0cedc" + integrity sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw== -"@webassemblyjs/wasm-edit@1.7.10": - version "1.7.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.7.10.tgz#83fe3140f5a58f5a30b914702be9f0e59a399092" - integrity sha512-e9RZFQlb+ZuYcKRcW9yl+mqX/Ycj9+3/+ppDI8nEE/NCY6FoK8f3dKBcfubYV/HZn44b+ND4hjh+4BYBt+sDnA== +"@webassemblyjs/wasm-edit@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz#962da12aa5acc1c131c81c4232991c82ce56e01a" + integrity sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q== dependencies: - "@webassemblyjs/ast" "1.7.10" - "@webassemblyjs/helper-buffer" "1.7.10" - "@webassemblyjs/helper-wasm-bytecode" "1.7.10" - "@webassemblyjs/helper-wasm-section" "1.7.10" - "@webassemblyjs/wasm-gen" "1.7.10" - "@webassemblyjs/wasm-opt" "1.7.10" - "@webassemblyjs/wasm-parser" "1.7.10" - "@webassemblyjs/wast-printer" "1.7.10" + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-buffer" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/helper-wasm-section" "1.8.5" + "@webassemblyjs/wasm-gen" "1.8.5" + "@webassemblyjs/wasm-opt" "1.8.5" + "@webassemblyjs/wasm-parser" "1.8.5" + "@webassemblyjs/wast-printer" "1.8.5" -"@webassemblyjs/wasm-gen@1.7.10": - version "1.7.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.7.10.tgz#4de003806ae29c97ab3707782469b53299570174" - integrity sha512-M0lb6cO2Y0PzDye/L39PqwV+jvO+2YxEG5ax+7dgq7EwXdAlpOMx1jxyXJTScQoeTpzOPIb+fLgX/IkLF8h2yw== +"@webassemblyjs/wasm-gen@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz#54840766c2c1002eb64ed1abe720aded714f98bc" + integrity sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg== dependencies: - "@webassemblyjs/ast" "1.7.10" - "@webassemblyjs/helper-wasm-bytecode" "1.7.10" - "@webassemblyjs/ieee754" "1.7.10" - "@webassemblyjs/leb128" "1.7.10" - "@webassemblyjs/utf8" "1.7.10" + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/ieee754" "1.8.5" + "@webassemblyjs/leb128" "1.8.5" + "@webassemblyjs/utf8" "1.8.5" -"@webassemblyjs/wasm-opt@1.7.10": - version "1.7.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.7.10.tgz#d151e31611934a556c82789fdeec41a814993c2a" - integrity sha512-R66IHGCdicgF5ZliN10yn5HaC7vwYAqrSVJGjtJJQp5+QNPBye6heWdVH/at40uh0uoaDN/UVUfXK0gvuUqtVg== +"@webassemblyjs/wasm-opt@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz#b24d9f6ba50394af1349f510afa8ffcb8a63d264" + integrity sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q== dependencies: - "@webassemblyjs/ast" "1.7.10" - "@webassemblyjs/helper-buffer" "1.7.10" - "@webassemblyjs/wasm-gen" "1.7.10" - "@webassemblyjs/wasm-parser" "1.7.10" + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-buffer" "1.8.5" + "@webassemblyjs/wasm-gen" "1.8.5" + "@webassemblyjs/wasm-parser" "1.8.5" -"@webassemblyjs/wasm-parser@1.7.10": - version "1.7.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.7.10.tgz#0367be7bf8f09e3e6abc95f8e483b9206487ec65" - integrity sha512-AEv8mkXVK63n/iDR3T693EzoGPnNAwKwT3iHmKJNBrrALAhhEjuPzo/lTE4U7LquEwyvg5nneSNdTdgrBaGJcA== +"@webassemblyjs/wasm-parser@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz#21576f0ec88b91427357b8536383668ef7c66b8d" + integrity sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw== dependencies: - "@webassemblyjs/ast" "1.7.10" - "@webassemblyjs/helper-api-error" "1.7.10" - "@webassemblyjs/helper-wasm-bytecode" "1.7.10" - "@webassemblyjs/ieee754" "1.7.10" - "@webassemblyjs/leb128" "1.7.10" - "@webassemblyjs/utf8" "1.7.10" + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-api-error" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/ieee754" "1.8.5" + "@webassemblyjs/leb128" "1.8.5" + "@webassemblyjs/utf8" "1.8.5" -"@webassemblyjs/wast-parser@1.7.10": - version "1.7.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.7.10.tgz#058f598b52f730b23fc874d4775b6286b6247264" - integrity sha512-YTPEtOBljkCL0VjDp4sHe22dAYSm3ZwdJ9+2NTGdtC7ayNvuip1wAhaAS8Zt9Q6SW9E5Jf5PX7YE3XWlrzR9cw== +"@webassemblyjs/wast-parser@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz#e10eecd542d0e7bd394f6827c49f3df6d4eefb8c" + integrity sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg== dependencies: - "@webassemblyjs/ast" "1.7.10" - "@webassemblyjs/floating-point-hex-parser" "1.7.10" - "@webassemblyjs/helper-api-error" "1.7.10" - "@webassemblyjs/helper-code-frame" "1.7.10" - "@webassemblyjs/helper-fsm" "1.7.10" - "@xtuc/long" "4.2.1" + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/floating-point-hex-parser" "1.8.5" + "@webassemblyjs/helper-api-error" "1.8.5" + "@webassemblyjs/helper-code-frame" "1.8.5" + "@webassemblyjs/helper-fsm" "1.8.5" + "@xtuc/long" "4.2.2" -"@webassemblyjs/wast-printer@1.7.10": - version "1.7.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.7.10.tgz#d817909d2450ae96c66b7607624d98a33b84223b" - integrity sha512-mJ3QKWtCchL1vhU/kZlJnLPuQZnlDOdZsyP0bbLWPGdYsQDnSBvyTLhzwBA3QAMlzEL9V4JHygEmK6/OTEyytA== +"@webassemblyjs/wast-printer@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz#114bbc481fd10ca0e23b3560fa812748b0bae5bc" + integrity sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg== dependencies: - "@webassemblyjs/ast" "1.7.10" - "@webassemblyjs/wast-parser" "1.7.10" - "@xtuc/long" "4.2.1" + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/wast-parser" "1.8.5" + "@xtuc/long" "4.2.2" "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== -"@xtuc/long@4.2.1": - version "4.2.1" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.1.tgz#5c85d662f76fa1d34575766c5dcd6615abcd30d8" - integrity sha512-FZdkNBDqBRHKQ2MEbSC17xnPFOhZxeJ2YGSfr2BKf3sujG49Qe3bB+rGCwQfIaA7WHnGeGkSijX4FuBCdrzW/g== +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== abbrev@1: version "1.1.1" @@ -1019,10 +1109,10 @@ acorn-jsx@^5.0.0: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.0.tgz#958584ddb60990c02c97c1bd9d521fce433bb101" integrity sha512-XkB50fn0MURDyww9+UYL3c1yLbOBz0ZFvrdYlGB8l+Ije1oSC75qAqrzSPjYQbdnQUzhlUGNKuesryAv0gxZOg== -acorn@5.X, acorn@^5.0.0, acorn@^5.0.3, acorn@^5.4.1, acorn@^5.6.2, acorn@^5.7.1: - version "5.7.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" - integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== +acorn-jsx@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.1.tgz#32a064fd925429216a09b141102bfdd185fae40e" + integrity sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg== acorn@^3.1.0: version "3.3.0" @@ -1034,11 +1124,21 @@ acorn@^4.0.4, acorn@~4.0.2: resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" integrity sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c= +acorn@^5.0.0, acorn@^5.0.3, acorn@^5.4.1: + version "5.7.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" + integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== + acorn@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.0.2.tgz#6a459041c320ab17592c6317abbfdf4bbaa98ca4" integrity sha512-GXmKIvbrN3TV7aVqAzVFaMW8F8wzVX7voEBRO3bDA64+EX37YSayggRJP5Xig6HYHBkWKpFg9W5gg6orklubhg== +acorn@^6.0.5, acorn@^6.0.7, acorn@^6.1.0, acorn@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f" + integrity sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA== + address@1.0.3, address@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/address/-/address-1.0.3.tgz#b5f50631f8d6cec8bd20c963963afb55e06cbce9" @@ -1059,7 +1159,7 @@ ajv-keywords@^3.1.0: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a" integrity sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo= -ajv@^5.0.0, ajv@^5.3.0: +ajv@^5.3.0: version "5.5.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU= @@ -1079,17 +1179,26 @@ ajv@^6.1.0: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^6.5.3, ajv@^6.6.1: - version "6.6.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.6.2.tgz#caceccf474bf3fc3ce3b147443711a24063cc30d" - integrity sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g== +ajv@^6.9.1: + version "6.10.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1" + integrity sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg== dependencies: fast-deep-equal "^2.0.1" fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.4.1" uri-js "^4.2.2" -alphanum-sort@^1.0.0, alphanum-sort@^1.0.1, alphanum-sort@^1.0.2: +align-text@^0.1.1, align-text@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" + integrity sha1-DNkKVhCT810KmSVsIrcGlDP60Rc= + dependencies: + kind-of "^3.0.2" + longest "^1.0.1" + repeat-string "^1.5.2" + +alphanum-sort@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= @@ -1099,10 +1208,10 @@ amdefine@>=0.0.4: resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU= -amdi18n-loader@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/amdi18n-loader/-/amdi18n-loader-0.8.0.tgz#1ff611951abbf0f708ad72d255bcf0077ed87958" - integrity sha512-NuyJ113uQv9YuWsxL6mt2ZZzZS+oojeEyat0SZ3e3IHBYeKKKFrE2Np6FD3UN2+5ZcLXvXjWK1gyW07XZri/qg== +amdi18n-loader@^0.9.2: + version "0.9.2" + resolved "https://registry.yarnpkg.com/amdi18n-loader/-/amdi18n-loader-0.9.2.tgz#92fd1d43a91468b9a83f469a18b807f6d570d40d" + integrity sha512-Qn3QVw3fHEyQCnrPHfXPiYbBWz325PtJbyeSJqPb8IvMmYLkQ1dG1RPfPrIphk9Ey2vsFD1YrXBi8pNO9PwPkw== dependencies: coffee-loader "^0.7.2" coffee-script "^1.12.2" @@ -1126,16 +1235,16 @@ ansi-colors@^3.0.0: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.1.0.tgz#dcfaacc90ef9187de413ec3ef8d5eb981a98808f" integrity sha512-hTv1qPdi+sVEk3jYsdjox5nQI0C9HTbjKShbCdYLKb1LOfNbb7wsF4d7OEKIZoxIHx02tSp3m94jcPW2EfMjmA== -ansi-escapes@^1.1.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" - integrity sha1-06ioOzGapneTZisT52HHkRQiMG4= - ansi-escapes@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30" integrity sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw== +ansi-escapes@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" + integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== + ansi-gray@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/ansi-gray/-/ansi-gray-0.1.1.tgz#2962cf54ec9792c48510a3deb524436861ef7251" @@ -1163,6 +1272,11 @@ ansi-regex@^4.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.0.0.tgz#70de791edf021404c3fd615aa89118ae0432e5a9" integrity sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w== +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" @@ -1201,6 +1315,13 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" +append-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/append-buffer/-/append-buffer-1.0.2.tgz#d8220cf466081525efea50614f3de6514dfa58f1" + integrity sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE= + dependencies: + buffer-equal "^1.0.0" + aproba@^1.0.3, aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -1243,36 +1364,36 @@ arr-exclude@^1.0.0: resolved "https://registry.yarnpkg.com/arr-exclude/-/arr-exclude-1.0.0.tgz#dfc7c2e552a270723ccda04cf3128c8cbfe5c631" integrity sha1-38fC5VKicHI8zaBM8xKMjL/lxjE= +arr-filter@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/arr-filter/-/arr-filter-1.1.2.tgz#43fdddd091e8ef11aa4c45d9cdc18e2dff1711ee" + integrity sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4= + dependencies: + make-iterator "^1.0.0" + arr-flatten@^1.0.1, arr-flatten@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== +arr-map@^2.0.0, arr-map@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/arr-map/-/arr-map-2.0.2.tgz#3a77345ffc1cf35e2a91825601f9e58f2e24cac4" + integrity sha1-Onc0X/wc814qkYJWAfnljy4kysQ= + dependencies: + make-iterator "^1.0.0" + arr-union@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= -array-back@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/array-back/-/array-back-1.0.4.tgz#644ba7f095f7ffcf7c43b5f0dc39d3c1f03c063b" - integrity sha1-ZEun8JX3/898Q7Xw3DnTwfA8Bjs= - dependencies: - typical "^2.6.0" - -array-back@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/array-back/-/array-back-2.0.0.tgz#6877471d51ecc9c9bfa6136fb6c7d5fe69748022" - integrity sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw== - dependencies: - typical "^2.6.1" - array-differ@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" integrity sha1-7/UuN1gknTO+QCuLuOVkuytdQDE= -array-each@^1.0.1: +array-each@^1.0.0, array-each@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" integrity sha1-p5SvDAWrF1KEbudTofIRoFugxE8= @@ -1302,13 +1423,20 @@ array-from@^2.1.1: resolved "https://registry.yarnpkg.com/array-from/-/array-from-2.1.1.tgz#cfe9d8c26628b9dc5aecc62a9f5d8f1f352c1195" integrity sha1-z+nYwmYoudxa7MYqn12PHzUsEZU= -array-includes@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d" - integrity sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0= +array-initial@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/array-initial/-/array-initial-1.1.0.tgz#2fa74b26739371c3947bd7a7adc73be334b3d795" + integrity sha1-L6dLJnOTccOUe9enrcc74zSz15U= dependencies: - define-properties "^1.1.2" - es-abstract "^1.7.0" + array-slice "^1.0.0" + is-number "^4.0.0" + +array-last@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/array-last/-/array-last-1.3.0.tgz#7aa77073fec565ddab2493f5f88185f404a9d336" + integrity sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg== + dependencies: + is-number "^4.0.0" array-map@~0.0.0: version "0.0.0" @@ -1330,6 +1458,15 @@ array-slice@^1.0.0: resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.1.0.tgz#e368ea15f89bc7069f7ffb89aec3a6c7d4ac22d4" integrity sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w== +array-sort@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-sort/-/array-sort-1.0.0.tgz#e4c05356453f56f53512a7d1d6123f2c54c0a88a" + integrity sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg== + dependencies: + default-compare "^1.0.0" + get-value "^2.0.6" + kind-of "^5.0.2" + array-union@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" @@ -1362,6 +1499,11 @@ arrify@^1.0.0, arrify@^1.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= +asap@~2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= + asn1.js@^4.0.0: version "4.10.1" resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" @@ -1400,20 +1542,20 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= -ast-types@0.10.2: - version "0.10.2" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.10.2.tgz#aef76a04fde54634976fc94defaad1a67e2eadb0" - integrity sha512-ufWX953VU1eIuWqxS0nRDMYlGyFH+yxln5CsmIHlpzEt3fdYqUnRtsFt0XAsQot8OaVCwFqxT1RiwvtzYjeYeg== - ast-types@0.11.5: version "0.11.5" resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.11.5.tgz#9890825d660c03c28339f315e9fa0a360e31ec28" integrity sha512-oJjo+5e7/vEc2FBK8gUalV0pba4L3VdBIs2EKhOLHLcOd2FgQIVQN9xb0eZ9IjEWyAL7vq6fGJxOvVvdCHNyMw== -ast-types@^0.11.5: - version "0.11.6" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.11.6.tgz#4e2266c2658829aef3b40cc33ad599c4e9eb89ef" - integrity sha512-nHiuV14upVGl7MWwFUYbzJ6YlfwWS084CU9EA8HajfYQjMSli5TQi3UTRygGF58LFWVkXxS1rbgRhROEqlQkXg== +ast-types@0.11.7: + version "0.11.7" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.11.7.tgz#f318bf44e339db6a320be0009ded64ec1471f46c" + integrity sha512-2mP3TwtkY/aTv5X3ZsMpNAbOnyoC/aMJwJSoaELPkHId0nSQgFcnU4dRW3isxiz7+zBexk0ym3WNVjMiQBnJSw== + +ast-types@0.12.2, ast-types@^0.12.2: + version "0.12.2" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.12.2.tgz#341656049ee328ac03fc805c156b49ebab1e4462" + integrity sha512-8c83xDLJM/dLDyXNLiR6afRRm4dPKN6KAnKqytRK3DBJul9lA+atxdQkNDkSVPdTqea5HiRq3lnnOIZ0MBpvdg== ast-types@^0.7.2: version "0.7.8" @@ -1425,6 +1567,16 @@ astral-regex@^1.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== +async-done@^1.2.0, async-done@^1.2.2: + version "1.3.1" + resolved "https://registry.yarnpkg.com/async-done/-/async-done-1.3.1.tgz#14b7b73667b864c8f02b5b253fc9c6eddb777f3e" + integrity sha512-R1BaUeJ4PMoLNJuk+0tLJgjmEqVsdN118+Z8O+alhnQDQgy0kmD5Mqi0DNEmMx2LM0Ed5yekKu+ZXYvIHceicg== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.2" + process-nextick-args "^1.0.7" + stream-exhaust "^1.0.1" + async-each@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" @@ -1435,23 +1587,25 @@ async-foreach@^0.1.3: resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI= +async-settle@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-settle/-/async-settle-1.0.0.tgz#1d0a914bb02575bec8a8f3a74e5080f72b2c0c6b" + integrity sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs= + dependencies: + async-done "^1.2.2" + async@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= -async@^2.0.0, async@^2.1.4, async@^2.4.1: +async@^2.0.0, async@^2.1.4: version "2.6.1" resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ== dependencies: lodash "^4.17.10" -async@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/async/-/async-1.0.0.tgz#f8fc04ca3a13784ade9e1641af98578cfbd647a9" - integrity sha1-+PwEyjoTeErenhZBr5hXjPvWR6k= - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -1462,18 +1616,6 @@ atob@^2.1.1: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== -autoprefixer@^6.3.1: - version "6.7.7" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014" - integrity sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ= - dependencies: - browserslist "^1.7.6" - caniuse-db "^1.0.30000634" - normalize-range "^0.1.2" - num2fraction "^1.2.2" - postcss "^5.2.16" - postcss-value-parser "^3.2.3" - ava-init@^0.1.0: version "0.1.6" resolved "https://registry.yarnpkg.com/ava-init/-/ava-init-0.1.6.tgz#ef19ed0b24b6bf359dad6fbadf1a05d836395c91" @@ -1583,7 +1725,7 @@ axios@^0.18.0: follow-redirects "^1.3.0" is-buffer "^1.1.5" -babel-code-frame@6.26.0, babel-code-frame@^6.26.0, babel-code-frame@^6.7.5: +babel-code-frame@^6.26.0, babel-code-frame@^6.7.5: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= @@ -1767,11 +1909,6 @@ babel-helper-replace-supers@^6.24.1: babel-traverse "^6.24.1" babel-types "^6.24.1" -babel-helper-vue-jsx-merge-props@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz#22aebd3b33902328e513293a8e4992b384f9f1b6" - integrity sha512-gsLiKK7Qrb7zYJNgiXKpXblxbV5ffSwR0f5whkPAaBAR4fhi6bwRZxX9wBlIc5M/v8CCkXUbXZL4N/nSE97cqg== - babel-helpers@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" @@ -1780,21 +1917,12 @@ babel-helpers@^6.24.1: babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-loader@^7.1.4: - version "7.1.5" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.5.tgz#e3ee0cd7394aa557e013b02d3e492bfd07aa6d68" - integrity sha512-iCHfbieL5d1LfOQeeVJEUyD9rTwBcP/fcEbRCfempxTDuqrKpu0AZjLAQHEQa3Yqyj9ORKe2iHfoj4rHLf7xpw== +babel-loader@^8.0.5: + version "8.0.5" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.5.tgz#225322d7509c2157655840bba52e46b6c2f2fe33" + integrity sha512-NTnHnVRd2JnRqPC0vW+iOQWU5pchDbYXsG2E6DMXEpMfUcQKclF9gmf3G3ZMhzG7IG9ji4coL0cm+FxeWxDpnw== dependencies: - find-cache-dir "^1.0.0" - loader-utils "^1.0.2" - mkdirp "^0.5.1" - -babel-loader@^8.0.4: - version "8.0.4" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.4.tgz#7bbf20cbe4560629e2e41534147692d3fecbdce6" - integrity sha512-fhBhNkUToJcW9nV46v8w87AJOwAJDz84c1CL57n3Stj73FANM/b9TbCUK4YhdOwEyZ+OxhYpdeZDNzSI29Firw== - dependencies: - find-cache-dir "^1.0.0" + find-cache-dir "^2.0.0" loader-utils "^1.0.2" mkdirp "^0.5.1" util.promisify "^1.0.0" @@ -1839,6 +1967,11 @@ babel-plugin-espower@^2.1.0: espurify "^1.6.0" estraverse "^4.1.1" +babel-plugin-rewire@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/babel-plugin-rewire/-/babel-plugin-rewire-1.2.0.tgz#822562d72ed2c84e47c0f95ee232c920853e9d89" + integrity sha512-JBZxczHw3tScS+djy6JPLMjblchGhLI89ep15H3SyjujIzlxo5nr6Yjo7AXotdeVczeBmWs0tF8PgJWDdgzAkQ== + babel-plugin-syntax-async-functions@^6.8.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" @@ -1869,11 +2002,6 @@ babel-plugin-syntax-exponentiation-operator@^6.8.0: resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" integrity sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4= -babel-plugin-syntax-jsx@^6.18.0: - version "6.18.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" - integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY= - babel-plugin-syntax-object-rest-spread@^6.8.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" @@ -1893,7 +2021,7 @@ babel-plugin-transform-async-generator-functions@^6.24.1: babel-plugin-syntax-async-generators "^6.5.0" babel-runtime "^6.22.0" -babel-plugin-transform-async-to-generator@^6.22.0, babel-plugin-transform-async-to-generator@^6.24.1: +babel-plugin-transform-async-to-generator@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761" integrity sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E= @@ -1937,7 +2065,7 @@ babel-plugin-transform-es2015-block-scoped-functions@^6.22.0: dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-block-scoping@^6.23.0, babel-plugin-transform-es2015-block-scoping@^6.24.1: +babel-plugin-transform-es2015-block-scoping@^6.24.1: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f" integrity sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8= @@ -1948,7 +2076,7 @@ babel-plugin-transform-es2015-block-scoping@^6.23.0, babel-plugin-transform-es20 babel-types "^6.26.0" lodash "^4.17.4" -babel-plugin-transform-es2015-classes@^6.23.0, babel-plugin-transform-es2015-classes@^6.24.1: +babel-plugin-transform-es2015-classes@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db" integrity sha1-WkxYpQyclGHlZLSyo7+ryXolhNs= @@ -1963,7 +2091,7 @@ babel-plugin-transform-es2015-classes@^6.23.0, babel-plugin-transform-es2015-cla babel-traverse "^6.24.1" babel-types "^6.24.1" -babel-plugin-transform-es2015-computed-properties@^6.22.0, babel-plugin-transform-es2015-computed-properties@^6.24.1: +babel-plugin-transform-es2015-computed-properties@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3" integrity sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM= @@ -1971,14 +2099,14 @@ babel-plugin-transform-es2015-computed-properties@^6.22.0, babel-plugin-transfor babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-plugin-transform-es2015-destructuring@^6.22.0, babel-plugin-transform-es2015-destructuring@^6.23.0: +babel-plugin-transform-es2015-destructuring@^6.22.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" integrity sha1-mXux8auWf2gtKwh2/jWNYOdlxW0= dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-duplicate-keys@^6.22.0, babel-plugin-transform-es2015-duplicate-keys@^6.24.1: +babel-plugin-transform-es2015-duplicate-keys@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e" integrity sha1-c+s9MQypaePvnskcU3QabxV2Qj4= @@ -1986,14 +2114,14 @@ babel-plugin-transform-es2015-duplicate-keys@^6.22.0, babel-plugin-transform-es2 babel-runtime "^6.22.0" babel-types "^6.24.1" -babel-plugin-transform-es2015-for-of@^6.22.0, babel-plugin-transform-es2015-for-of@^6.23.0: +babel-plugin-transform-es2015-for-of@^6.22.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691" integrity sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE= dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-function-name@^6.22.0, babel-plugin-transform-es2015-function-name@^6.24.1: +babel-plugin-transform-es2015-function-name@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" integrity sha1-g0yJhTvDaxrw86TF26qU/Y6sqos= @@ -2009,7 +2137,7 @@ babel-plugin-transform-es2015-literals@^6.22.0: dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.1: +babel-plugin-transform-es2015-modules-amd@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154" integrity sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ= @@ -2018,7 +2146,7 @@ babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015 babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1: +babel-plugin-transform-es2015-modules-commonjs@^6.24.1: version "6.26.2" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3" integrity sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q== @@ -2028,7 +2156,7 @@ babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-e babel-template "^6.26.0" babel-types "^6.26.0" -babel-plugin-transform-es2015-modules-systemjs@^6.23.0, babel-plugin-transform-es2015-modules-systemjs@^6.24.1: +babel-plugin-transform-es2015-modules-systemjs@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23" integrity sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM= @@ -2037,7 +2165,7 @@ babel-plugin-transform-es2015-modules-systemjs@^6.23.0, babel-plugin-transform-e babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-plugin-transform-es2015-modules-umd@^6.23.0, babel-plugin-transform-es2015-modules-umd@^6.24.1: +babel-plugin-transform-es2015-modules-umd@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468" integrity sha1-rJl+YoXNGO1hdq22B9YCNErThGg= @@ -2046,7 +2174,7 @@ babel-plugin-transform-es2015-modules-umd@^6.23.0, babel-plugin-transform-es2015 babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-plugin-transform-es2015-object-super@^6.22.0, babel-plugin-transform-es2015-object-super@^6.24.1: +babel-plugin-transform-es2015-object-super@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d" integrity sha1-JM72muIcuDp/hgPa0CH1cusnj40= @@ -2054,7 +2182,7 @@ babel-plugin-transform-es2015-object-super@^6.22.0, babel-plugin-transform-es201 babel-helper-replace-supers "^6.24.1" babel-runtime "^6.22.0" -babel-plugin-transform-es2015-parameters@^6.23.0, babel-plugin-transform-es2015-parameters@^6.24.1: +babel-plugin-transform-es2015-parameters@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" integrity sha1-V6w1GrScrxSpfNE7CfZv3wpiXys= @@ -2066,7 +2194,7 @@ babel-plugin-transform-es2015-parameters@^6.23.0, babel-plugin-transform-es2015- babel-traverse "^6.24.1" babel-types "^6.24.1" -babel-plugin-transform-es2015-shorthand-properties@^6.22.0, babel-plugin-transform-es2015-shorthand-properties@^6.24.1: +babel-plugin-transform-es2015-shorthand-properties@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0" integrity sha1-JPh11nIch2YbvZmkYi5R8U3jiqA= @@ -2081,7 +2209,7 @@ babel-plugin-transform-es2015-spread@^6.22.0: dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-sticky-regex@^6.22.0, babel-plugin-transform-es2015-sticky-regex@^6.24.1: +babel-plugin-transform-es2015-sticky-regex@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc" integrity sha1-AMHNsaynERLN8M9hJsLta0V8zbw= @@ -2097,14 +2225,14 @@ babel-plugin-transform-es2015-template-literals@^6.22.0: dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-typeof-symbol@^6.22.0, babel-plugin-transform-es2015-typeof-symbol@^6.23.0: +babel-plugin-transform-es2015-typeof-symbol@^6.22.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372" integrity sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I= dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-unicode-regex@^6.22.0, babel-plugin-transform-es2015-unicode-regex@^6.24.1: +babel-plugin-transform-es2015-unicode-regex@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9" integrity sha1-04sS9C6nMj9yk4fxinxa4frrNek= @@ -2113,7 +2241,7 @@ babel-plugin-transform-es2015-unicode-regex@^6.22.0, babel-plugin-transform-es20 babel-runtime "^6.22.0" regexpu-core "^2.0.0" -babel-plugin-transform-exponentiation-operator@^6.22.0, babel-plugin-transform-exponentiation-operator@^6.24.1: +babel-plugin-transform-exponentiation-operator@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e" integrity sha1-KrDJx/MJj6SJB3cruBP+QejeOg4= @@ -2135,14 +2263,14 @@ babel-plugin-transform-object-rest-spread@^6.22.0: babel-plugin-syntax-object-rest-spread "^6.8.0" babel-runtime "^6.26.0" -babel-plugin-transform-regenerator@^6.22.0, babel-plugin-transform-regenerator@^6.24.1: +babel-plugin-transform-regenerator@^6.24.1: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f" integrity sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8= dependencies: regenerator-transform "^0.10.0" -babel-plugin-transform-runtime@^6.23.0, babel-plugin-transform-runtime@^6.3.13: +babel-plugin-transform-runtime@^6.3.13: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz#88490d446502ea9b8e7efb0fe09ec4d99479b1ee" integrity sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4= @@ -2157,13 +2285,6 @@ babel-plugin-transform-strict-mode@^6.24.1: babel-runtime "^6.22.0" babel-types "^6.24.1" -babel-plugin-transform-vue-jsx@^3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-3.7.0.tgz#d40492e6692a36b594f7e9a1928f43e969740960" - integrity sha512-W39X07/n3oJMQd8tALBO+440NraGSF//Lo1ydd/9Nme3+QiRGFBb1Q39T9iixh0jZPPbfv3so18tNoIgLatymw== - dependencies: - esutils "^2.0.2" - babel-plugin-transform-vue-template@^0.4.2: version "0.4.2" resolved "https://registry.yarnpkg.com/babel-plugin-transform-vue-template/-/babel-plugin-transform-vue-template-0.4.2.tgz#7899dee289ba743644d7091e23db9f46695ce649" @@ -2172,51 +2293,6 @@ babel-plugin-transform-vue-template@^0.4.2: "@babel/template" "^7.0.0-beta.46" vue-template-es2015-compiler "^1.5.3" -babel-polyfill@6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.23.0.tgz#8364ca62df8eafb830499f699177466c3b03499d" - integrity sha1-g2TKYt+Or7gwSZ9pkXdGbDsDSZ0= - dependencies: - babel-runtime "^6.22.0" - core-js "^2.4.0" - regenerator-runtime "^0.10.0" - -babel-preset-env@^1.5.2: - version "1.7.0" - resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.7.0.tgz#dea79fa4ebeb883cd35dab07e260c1c9c04df77a" - integrity sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg== - dependencies: - babel-plugin-check-es2015-constants "^6.22.0" - babel-plugin-syntax-trailing-function-commas "^6.22.0" - babel-plugin-transform-async-to-generator "^6.22.0" - babel-plugin-transform-es2015-arrow-functions "^6.22.0" - babel-plugin-transform-es2015-block-scoped-functions "^6.22.0" - babel-plugin-transform-es2015-block-scoping "^6.23.0" - babel-plugin-transform-es2015-classes "^6.23.0" - babel-plugin-transform-es2015-computed-properties "^6.22.0" - babel-plugin-transform-es2015-destructuring "^6.23.0" - babel-plugin-transform-es2015-duplicate-keys "^6.22.0" - babel-plugin-transform-es2015-for-of "^6.23.0" - babel-plugin-transform-es2015-function-name "^6.22.0" - babel-plugin-transform-es2015-literals "^6.22.0" - babel-plugin-transform-es2015-modules-amd "^6.22.0" - babel-plugin-transform-es2015-modules-commonjs "^6.23.0" - babel-plugin-transform-es2015-modules-systemjs "^6.23.0" - babel-plugin-transform-es2015-modules-umd "^6.23.0" - babel-plugin-transform-es2015-object-super "^6.22.0" - babel-plugin-transform-es2015-parameters "^6.23.0" - babel-plugin-transform-es2015-shorthand-properties "^6.22.0" - babel-plugin-transform-es2015-spread "^6.22.0" - babel-plugin-transform-es2015-sticky-regex "^6.22.0" - babel-plugin-transform-es2015-template-literals "^6.22.0" - babel-plugin-transform-es2015-typeof-symbol "^6.23.0" - babel-plugin-transform-es2015-unicode-regex "^6.22.0" - babel-plugin-transform-exponentiation-operator "^6.22.0" - babel-plugin-transform-regenerator "^6.22.0" - browserslist "^3.2.6" - invariant "^2.2.2" - semver "^5.3.0" - babel-preset-es2015@^6.3.13: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz#d44050d6bc2c9feea702aaf38d727a0210538939" @@ -2247,7 +2323,7 @@ babel-preset-es2015@^6.3.13: babel-plugin-transform-es2015-unicode-regex "^6.24.1" babel-plugin-transform-regenerator "^6.24.1" -babel-preset-stage-2@^6.24.1, babel-preset-stage-2@^6.3.13: +babel-preset-stage-2@^6.3.13: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz#d9e2960fb3d71187f0e64eec62bc07767219bdc1" integrity sha1-2eKWD7PXEYfw5k7sYrwHdnIZvcE= @@ -2281,7 +2357,7 @@ babel-register@^6.26.0: mkdirp "^0.5.1" source-map-support "^0.4.15" -babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0, babel-runtime@^6.3.19, babel-runtime@^6.9.2: +babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0, babel-runtime@^6.3.19: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= @@ -2325,16 +2401,6 @@ babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0, babel-types@^6.7. lodash "^4.17.4" to-fast-properties "^1.0.3" -babylon@7.0.0-beta.19: - version "7.0.0-beta.19" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.19.tgz#e928c7e807e970e0536b078ab3e0c48f9e052503" - integrity sha512-Vg0C9s/REX6/WIXN37UKpv5ZhRi6A4pjHlpkE34+8/a6c2W1Q692n3hmc+SZG5lKRnaExLUbxtJ1SVT+KaCQ/A== - -babylon@7.0.0-beta.40: - version "7.0.0-beta.40" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.40.tgz#91fc8cd56d5eb98b28e6fde41045f2957779940a" - integrity sha512-AVxF2EcxvGD5hhOuLTOLAXBb0VhwWpEX0HyHdAI2zU+AAP4qEwtQj8voz1JR3uclGai0rfcE+dCTHnNMOnimFg== - babylon@7.0.0-beta.44: version "7.0.0-beta.44" resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.44.tgz#89159e15e6e30c5096e22d738d8c0af8a0e8ca1d" @@ -2345,10 +2411,25 @@ babylon@^6.1.0, babylon@^6.18.0: resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== -backbone@1.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/backbone/-/backbone-1.3.3.tgz#4cc80ea7cb1631ac474889ce40f2f8bc683b2999" - integrity sha1-TMgOp8sWMaxHSInOQPL4vGg7KZk= +bach@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/bach/-/bach-1.2.0.tgz#4b3ce96bf27134f79a1b414a51c14e34c3bd9880" + integrity sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA= + dependencies: + arr-filter "^1.1.1" + arr-flatten "^1.0.1" + arr-map "^2.0.0" + array-each "^1.0.0" + array-initial "^1.0.0" + array-last "^1.1.1" + async-done "^1.2.2" + async-settle "^1.0.0" + now-and-later "^2.0.0" + +backbone@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/backbone/-/backbone-1.4.0.tgz#54db4de9df7c3811c3f032f34749a4cd27f3bd12" + integrity sha512-RLmDrRXkVdouTg38jcgHhyQ/2zjg7a8E6sz2zxfz21Hh17xDJYUHBZimVIt5fUyS8vbfpeSmTL3gUjTEvUV3qQ== dependencies: underscore ">=1.8.3" @@ -2362,11 +2443,6 @@ bail@^1.0.0: resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.3.tgz#63cfb9ddbac829b02a3128cd53224be78e6c21a3" integrity sha512-1X8CnjFVQ+a+KW36uBNMTU5s8+v5FzeqrP7hTG5aTb4aPreSbZJlhwPon9VKMuEVgV++JM+SQrALY3kr7eswdg== -balanced-match@^0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" - integrity sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg= - balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" @@ -2412,11 +2488,6 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -beeper@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/beeper/-/beeper-1.1.1.tgz#e6d5ea8c5dad001304a70b22638447f69cb2f809" - integrity sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak= - better-assert@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522" @@ -2437,6 +2508,11 @@ big.js@^3.1.3: resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q== +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + binary-extensions@^1.0.0: version "1.12.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.12.0.tgz#c2d780f53d45bba8317a8902d4ceeaf3a6385b14" @@ -2454,11 +2530,16 @@ block-stream@*: dependencies: inherits "~2.0.0" -bluebird@^3.0.0, bluebird@^3.1.1, bluebird@^3.3.0, bluebird@^3.4.7, bluebird@^3.5.1, bluebird@~3.5.0: +bluebird@^3.0.0, bluebird@^3.1.1, bluebird@^3.3.0, bluebird@^3.5.1: version "3.5.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.2.tgz#1be0908e054a751754549c270489c1505d4ab15a" integrity sha512-dhHTWMI7kMx5whMQntl7Vr9C6BvV10lFXDAasnqnrMYhXVCzzk6IO9Fo2L75jXHT07WrOngL1WDXOp+yYS91Yg== +bluebird@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" + integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw== + bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.8" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" @@ -2497,27 +2578,30 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= -"bootstrap-tour@https://github.com/sorich87/bootstrap-tour.git#6a1028fb562f9aa68c451f0901f8cfeb43cad140": +"bootstrap-tour@https://github.com/sorich87/bootstrap-tour.git#65a49742e131d19f41e3f5bf63588995f7b8a9e0": version "0.12.0" - resolved "https://github.com/sorich87/bootstrap-tour.git#6a1028fb562f9aa68c451f0901f8cfeb43cad140" + resolved "https://github.com/sorich87/bootstrap-tour.git#65a49742e131d19f41e3f5bf63588995f7b8a9e0" dependencies: bootstrap ">=4.0.0-beta" jquery "^3.0.0" popper.js "^1.12.3" -bootstrap-vue@^2.0.0-rc.11: - version "2.0.0-rc.11" - resolved "https://registry.yarnpkg.com/bootstrap-vue/-/bootstrap-vue-2.0.0-rc.11.tgz#47aaa6d2a8d390477de75e636d8ea652b1d03f59" - integrity sha512-LxR+oL8yKr1DVoWUWTX+XhiT0xaTMH6142u2VSFDm4tewTH8HIrzN2YIl7HLZrw2DIuE9bRMIdWJqqn3aQe7Hw== +bootstrap-vue@^2.0.0-rc.16: + version "2.0.0-rc.16" + resolved "https://registry.yarnpkg.com/bootstrap-vue/-/bootstrap-vue-2.0.0-rc.16.tgz#7302313ad4c5e29e88b9009e26fb2c9b6d81a1fb" + integrity sha512-fhiyqG6i3ITF7fAzAjMexikGUgBZ/GTKQi0mCK48FacB5tiq2KUXE0Qilb/CW090PkqEw2W+7AP2/k5/dAa/MQ== dependencies: - bootstrap "^4.1.1" - lodash.get "^4.4.2" - lodash.startcase "^4.4.0" - opencollective "^1.0.3" - popper.js "^1.12.9" - vue-functional-data-merge "^2.0.5" + "@nuxt/opencollective" "^0.2.1" + bootstrap "^4.3.1" + popper.js "^1.14.7" + vue-functional-data-merge "^2.0.7" -bootstrap@>=4.0.0-beta, bootstrap@^4.1.1, bootstrap@^4.1.3: +bootstrap@4.3.1, bootstrap@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.3.1.tgz#280ca8f610504d99d7b6b4bfc4b68cec601704ac" + integrity sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag== + +bootstrap@>=4.0.0-beta: version "4.1.3" resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.1.3.tgz#0eb371af2c8448e8c210411d0cb824a6409a12be" integrity sha512-rDFIzgXcof0jDyjNosjv4Sno77X4KuPeFxG2XZZv1/Kc8DRVGVADdoQyyOVDwPqL36DDmtCQbrpMCqvpPLJQ0w== @@ -2536,7 +2620,7 @@ boxen@^0.5.1: string-width "^1.0.1" widest-line "^1.0.0" -brace-expansion@^1.0.0, brace-expansion@^1.1.7: +brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== @@ -2645,23 +2729,25 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" -browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: - version "1.7.7" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.7.7.tgz#0bd76704258be829b2398bb50e4b62d1a166b0b9" - integrity sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk= +browserslist@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.1.1.tgz#328eb4ff1215b12df6589e9ab82f8adaa4fc8cd6" + integrity sha512-VBorw+tgpOtZ1BYhrVSVTzTt/3+vSE3eFUh0N2GCFK1HffceOaf32YS/bs6WiFhjDAblAFrx85jMy3BG9fBK2Q== dependencies: - caniuse-db "^1.0.30000639" - electron-to-chromium "^1.2.7" + caniuse-lite "^1.0.30000884" + electron-to-chromium "^1.3.62" + node-releases "^1.0.0-alpha.11" -browserslist@^3.2.6: - version "3.2.8" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-3.2.8.tgz#b0005361d6471f0f5952797a76fc985f1f978fc6" - integrity sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ== +browserslist@4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.4.1.tgz#42e828954b6b29a7a53e352277be429478a69062" + integrity sha512-pEBxEXg7JwaakBXjATYw/D1YZh4QUSCX/Mnd/wnqSRPPSi1U39iDhDoKGoBUcraKdxDlrYqJxSI5nNvD+dWP2A== dependencies: - caniuse-lite "^1.0.30000844" - electron-to-chromium "^1.3.47" + caniuse-lite "^1.0.30000929" + electron-to-chromium "^1.3.103" + node-releases "^1.1.3" -browserslist@^4.0.0, browserslist@^4.1.0: +browserslist@^4.0.0: version "4.2.1" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.2.1.tgz#257a24c879d1cd4016348eee5c25de683260b21d" integrity sha512-1oO0c7Zhejwd+LXihS89WqtKionSbz298rJZKJgfrHIZhrV8AC15gw553VcB0lcEugja7IhWD7iAlrsamfYVPA== @@ -2670,6 +2756,15 @@ browserslist@^4.0.0, browserslist@^4.1.0: electron-to-chromium "^1.3.79" node-releases "^1.0.0-alpha.14" +browserslist@^4.4.2, browserslist@^4.5.1: + version "4.5.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.5.1.tgz#2226cada1947b33f4cfcf7b608dcb519b6128106" + integrity sha512-/pPw5IAUyqaQXGuD5vS8tcbudyPZ241jk1W5pQBsGDfcjNQt7p8qxZhgMNuygDShte1PibLFexecWUPgmVLfrg== + dependencies: + caniuse-lite "^1.0.30000949" + electron-to-chromium "^1.3.116" + node-releases "^1.1.11" + buble@0.19.4: version "0.19.4" resolved "https://registry.yarnpkg.com/buble/-/buble-0.19.4.tgz#0b51466592555ee34ff5fbe1030a97f3812e4d0a" @@ -2685,20 +2780,19 @@ buble@0.19.4: regexpu-core "^4.1.3" vlq "^1.0.0" -buble@^0.19.3: - version "0.19.5" - resolved "https://registry.yarnpkg.com/buble/-/buble-0.19.5.tgz#b4abb96905379e2336a23da2824c4dda89b73b0b" - integrity sha512-xgsDCyDdrmNFVOlkn4F+yQCj494I3p7NRHgdZ8J81b+Bjgp8x4qAK6BX5aXoNSNkCwC5b/i4a3le0dDK8ilEWg== +buble@^0.19.7: + version "0.19.7" + resolved "https://registry.yarnpkg.com/buble/-/buble-0.19.7.tgz#1dfd080ab688101aad5388d3304bc82601a244fd" + integrity sha512-YLgWxX/l+NnfotydBlxqCMPR4FREE4ubuHphALz0FxQ7u2hp3BzxTKQ4nKpapOaRJfEm1gukC68KnT2OymRK0g== dependencies: - acorn "^6.0.2" + acorn "^6.1.1" acorn-dynamic-import "^4.0.0" - acorn-jsx "^5.0.0" - chalk "^2.4.1" - magic-string "^0.25.1" + acorn-jsx "^5.0.1" + chalk "^2.4.2" + magic-string "^0.25.2" minimist "^1.2.0" os-homedir "^1.0.1" - regexpu-core "^4.2.0" - vlq "^1.0.0" + regexpu-core "^4.5.4" buf-compare@^1.0.0: version "1.0.1" @@ -2718,6 +2812,11 @@ buffer-alloc@^1.2.0: buffer-alloc-unsafe "^1.1.0" buffer-fill "^1.0.0" +buffer-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" + integrity sha1-WWFrSYME1Var1GaWayLu2j7KX74= + buffer-equals@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/buffer-equals/-/buffer-equals-1.0.4.tgz#0353b54fd07fd9564170671ae6f66b9cf10d27f5" @@ -2786,6 +2885,26 @@ cacache@^10.0.4: unique-filename "^1.1.0" y18n "^4.0.0" +cacache@^11.0.2: + version "11.3.2" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.2.tgz#2d81e308e3d258ca38125b676b98b2ac9ce69bfa" + integrity sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg== + dependencies: + bluebird "^3.5.3" + chownr "^1.1.1" + figgy-pudding "^3.5.1" + glob "^7.1.3" + graceful-fs "^4.1.15" + lru-cache "^5.1.1" + mississippi "^3.0.0" + mkdirp "^0.5.1" + move-concurrently "^1.0.1" + promise-inflight "^1.0.1" + rimraf "^2.6.2" + ssri "^6.0.1" + unique-filename "^1.1.1" + y18n "^4.0.0" + cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -2801,15 +2920,6 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" -cache-point@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/cache-point/-/cache-point-0.4.1.tgz#cc8c9cbd99d90d7b0c66910cd33d77a1aab8840e" - integrity sha512-4TgWfe9SF+bUy5cCql8gWHqKNrviufNwSYxLjf2utB0pY4+bdcuFwMmY1hDB+67Gz/L1vmhFNhePAjJTFBtV+Q== - dependencies: - array-back "^2.0.0" - fs-then-native "^2.0.0" - mkdirp2 "^1.0.3" - caching-transform@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/caching-transform/-/caching-transform-1.0.1.tgz#6dbdb2f20f8d8fbce79f3e94e9d1742dcdf5c0a1" @@ -2829,6 +2939,11 @@ call-matcher@^1.0.0: espurify "^1.6.0" estraverse "^4.0.0" +call-me-maybe@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" + integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= + call-signature@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/call-signature/-/call-signature-0.0.2.tgz#a84abc825a55ef4cb2b028bd74e205a65b9a4996" @@ -2844,14 +2959,6 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.0.0.tgz#fb7eb569b72ad7a45812f93fd9430a3e410b3dd3" integrity sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw== -camel-case@3.0.x: - version "3.0.0" - resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73" - integrity sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M= - dependencies: - no-case "^2.2.0" - upper-case "^1.1.1" - camelcase-keys@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" @@ -2860,6 +2967,11 @@ camelcase-keys@^2.0.0: camelcase "^2.0.0" map-obj "^1.0.0" +camelcase@^1.0.2: + version "1.2.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" + integrity sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk= + camelcase@^2.0.0, camelcase@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" @@ -2875,15 +2987,10 @@ camelcase@^4.1.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= -caniuse-api@^1.5.2: - version "1.6.1" - resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.6.1.tgz#b534e7c734c4f81ec5fbe8aca2ad24354b962c6c" - integrity sha1-tTTnxzTE+B7F++isoq0kNUuWLGw= - dependencies: - browserslist "^1.3.6" - caniuse-db "^1.0.30000529" - lodash.memoize "^4.1.2" - lodash.uniq "^4.5.0" +camelcase@^5.0.0, camelcase@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.2.0.tgz#e7522abda5ed94cc0489e1b8466610e88404cf45" + integrity sha512-IXFsBS2pC+X0j0N/GE7Dm7j3bsEBp+oTpb7F50dwEVX7rf3IgwO9XatnegTsDtniKCUtEJH4fSU6Asw7uoVLfQ== caniuse-api@^3.0.0: version "3.0.0" @@ -2895,16 +3002,21 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: - version "1.0.30000893" - resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000893.tgz#2781b8d2ce50d8dd9d091425461b528ca4556c07" - integrity sha512-JfGWsX2JtyXnjGAAjXMHiv4OEGFZvArB5pxdd5oa5xCuEYYN1rhBMwogMIKnKoUBi6lKRyO4JQAPJwqchv20Yg== - -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30000890: +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000890: version "1.0.30000893" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000893.tgz#284b20932bd41b93e21626975f2050cb01561986" integrity sha512-kOddHcTEef+NgN/fs0zmX2brHTNATVOWMEIhlZHCuwQRtXobjSw9pAECc44Op4bTBcavRjkLaPrGomknH7+Jvg== +caniuse-lite@^1.0.30000884, caniuse-lite@^1.0.30000929: + version "1.0.30000953" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000953.tgz#8054c4e5c4aa69dc3269353a4a5e102909759dbb" + integrity sha512-2stdF/q5MZTDhQ6uC65HWbSgI9UMKbc7+HKvlwH5JBIslKoD/J9dvabP4J4Uiifu3NljbHj3iMpfYflLSNt09A== + +caniuse-lite@^1.0.30000949: + version "1.0.30000950" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000950.tgz#8c559d66e332b34e919d1086cc6d29c1948856ae" + integrity sha512-Cs+4U9T0okW2ftBsCIHuEYXXkki7mjXmjCh4c6PzYShk04qDEr76/iC7KwhLoWoY65wcra1XOsRD+S7BptEb5A== + capture-stack-trace@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d" @@ -2915,18 +3027,19 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -catharsis@~0.8.9: - version "0.8.9" - resolved "https://registry.yarnpkg.com/catharsis/-/catharsis-0.8.9.tgz#98cc890ca652dd2ef0e70b37925310ff9e90fc8b" - integrity sha1-mMyJDKZS3S7w5ws3klMQ/56Q/Is= - dependencies: - underscore-contrib "~0.3.0" - ccount@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.3.tgz#f1cec43f332e2ea5a569fd46f9f5bde4e6102aff" integrity sha512-Jt9tIBkRc9POUof7QA/VwWd+58fKkEEfI+/t1/eOlxKM7ZhrczNzMFefge7Ai+39y1pR/pP6cI19guHy3FSLmw== +center-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" + integrity sha1-qg0yYptu6XIgBBHL1EYckHvCt60= + dependencies: + align-text "^0.1.3" + lazy-cache "^1.0.3" + chai@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5" @@ -2939,16 +3052,23 @@ chai@^4.2.0: pathval "^1.1.0" type-detect "^4.0.5" -chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= +chalk@2.4.1, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" + integrity sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ== dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@2.4.2, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" chalk@^0.4.0: version "0.4.0" @@ -2959,14 +3079,16 @@ chalk@^0.4.0: has-color "~0.1.0" strip-ansi "~0.1.0" -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" - integrity sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ== +chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" character-entities-html4@^1.0.0: version "1.1.2" @@ -2995,11 +3117,6 @@ character-reference-invalid@^1.0.0: resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.2.tgz#21e421ad3d84055952dab4a43a04e73cd425d3ed" integrity sha512-7I/xceXfKyUJmSAn/jw8ve/9DyOP7XxufNYLI9Px7CmsKgEUaZLUTax6nZxGQtaoiZCjpu6cHPj20xC/vqRReQ== -chardet@^0.4.0: - version "0.4.2" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" - integrity sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I= - chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" @@ -3062,7 +3179,7 @@ chokidar@^2.0.0, chokidar@^2.0.2: optionalDependencies: fsevents "^1.2.2" -chownr@^1.0.1: +chownr@^1.0.1, chownr@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g== @@ -3087,18 +3204,6 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" -circular-json@^0.3.1: - version "0.3.3" - resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" - integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A== - -clap@^1.0.9: - version "1.2.3" - resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.3.tgz#4f36745b32008492557f46412d66d50cb99bce51" - integrity sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA== - dependencies: - chalk "^1.1.3" - class-utils@^0.3.5: version "0.3.6" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" @@ -3114,7 +3219,7 @@ classnames@^2.2.6: resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== -clean-css@4.2.x: +clean-css@^4.1.11: version "4.2.1" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.1.tgz#2d411ef76b8569b6d0c84068dabe85b0aa5e5c17" integrity sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g== @@ -3128,6 +3233,13 @@ clean-webpack-plugin@^0.1.19: dependencies: rimraf "^2.6.1" +clean-webpack-plugin@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/clean-webpack-plugin/-/clean-webpack-plugin-1.0.1.tgz#b16ee2f1386aea403010236e632447c7d3505f5a" + integrity sha512-gvwfMsqu3HBgTVvaBa1H3AZKO03CHpr5uP92SPIktP3827EovAitwW+1xoqXyTxCuXnLYpMHG5ytS4AoukHDWA== + dependencies: + rimraf "^2.6.1" + clean-yaml-object@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/clean-yaml-object/-/clean-yaml-object-0.1.0.tgz#63fb110dc2ce1a84dc21f6d9334876d010ae8b68" @@ -3162,6 +3274,11 @@ cli-spinners@^1.1.0: resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.3.1.tgz#002c1990912d0d59580c93bd36c056de99e4259a" integrity sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg== +cli-spinners@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.0.0.tgz#4b078756fc17a8f72043fdc9f1f14bf4fa87e2df" + integrity sha512-yiEBmhaKPPeBj7wWm4GEdtPZK940p9pl3EANIrnJ3JnvWyrPjcFcsEq6qRUuQ7fzB0+Y82ld3p6B34xo95foWw== + cli-truncate@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574" @@ -3175,19 +3292,34 @@ cli-width@^2.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= -cli@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cli/-/cli-1.0.1.tgz#22817534f24bfa4950c34d532d48ecbc621b8c14" - integrity sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ= - dependencies: - exit "0.1.2" - glob "^7.1.1" - clipboard-copy@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/clipboard-copy/-/clipboard-copy-2.0.1.tgz#25214db3aabc282109cfa3429ffd885b014fc8b3" integrity sha512-/JBr7ryeWwl2w33SRMYGfOZU5SWPVNtpB9oTxUzFp7olKKd2HM+cnhSMeETblJMnjgqtL581ncI/pcZX7o7Big== +clipboard-copy@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/clipboard-copy/-/clipboard-copy-3.0.0.tgz#01ab812f4b5828b763e029172fd23ff92641eefb" + integrity sha512-1wgoaIRbRGTuv82jTpUgUkUFUQDF8H9hDa8IlkQfdETDHGk0CuisNPvFWNnPT/s3drikxhN33tvv54190c9lYA== + +clipboard@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.4.tgz#836dafd66cf0fea5d71ce5d5b0bf6e958009112d" + integrity sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ== + dependencies: + good-listener "^1.2.2" + select "^1.1.2" + tiny-emitter "^2.0.0" + +cliui@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" + integrity sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE= + dependencies: + center-align "^0.1.1" + right-align "^0.1.1" + wordwrap "0.0.2" + cliui@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" @@ -3206,6 +3338,11 @@ cliui@^4.0.0: strip-ansi "^4.0.0" wrap-ansi "^2.0.0" +clone-buffer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" + integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg= + clone-deep@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-2.0.2.tgz#00db3a1e173656730d1188c3d6aced6d7ea97713" @@ -3216,21 +3353,35 @@ clone-deep@^2.0.1: kind-of "^6.0.0" shallow-clone "^1.0.0" -clone-stats@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" - integrity sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE= +clone-stats@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" + integrity sha1-s3gt/4u1R04Yuba/D9/ngvh3doA= -clone@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/clone/-/clone-0.2.0.tgz#c6126a90ad4f72dbf5acdb243cc37724fe93fc1f" - integrity sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8= - -clone@^1.0.0, clone@^1.0.2: +clone@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= +clone@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= + +cloneable-readable@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.1.2.tgz#d591dee4a8f8bc15da43ce97dceeba13d43e2a65" + integrity sha512-Bq6+4t+lbM8vhTs/Bef5c5AdEMtapp/iFb6+s4/Hh9MVTt8OLKH7ZOOZSCT+Ys7hsHvqv0GuMPJ1lnQJVHvxpg== + dependencies: + inherits "^2.0.1" + process-nextick-args "^2.0.0" + readable-stream "^2.3.5" + +clsx@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.0.3.tgz#e164004f4064b372888f20fdafbd436fb960bac9" + integrity sha512-xLoSw6DMp7YvbEeLrQJBcWWRRerdHrU1WHoL1hYJOKUeDpVMRq7pv7NI2JHQbCRAe5ptINNzhdYmtfN6MsdCUw== + co-with-promise@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co-with-promise/-/co-with-promise-4.6.0.tgz#413e7db6f5893a60b942cf492c4bec93db415ab7" @@ -3243,13 +3394,6 @@ co@^4.6.0: resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= -coa@~1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/coa/-/coa-1.0.4.tgz#a9ef153660d6a86a8bdec0289a5c684d217432fd" - integrity sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0= - dependencies: - q "^1.1.2" - coa@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.1.tgz#f3f8b0b15073e35d70263fb1042cb2c023db38af" @@ -3284,13 +3428,14 @@ collapse-white-space@^1.0.2: resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.4.tgz#ce05cf49e54c3277ae573036a26851ba430a0091" integrity sha512-YfQ1tAUZm561vpYD+5eyWN8+UsceQbSrqqlc/6zDY2gtAE+uZLSdkkovhnGpmCThsvKBFakq4EdY/FF93E8XIw== -collect-all@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/collect-all/-/collect-all-1.0.3.tgz#1abcc20448b58a1447487fcf34130e9512b0acf8" - integrity sha512-0y0rBgoX8IzIjBAUnO73SEtSb4Mhk3IoceWJq5zZSxb9mWORhWH8xLYo4EDSOE1jRBk1LhmfjqWFFt10h/+MEA== +collection-map@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-map/-/collection-map-1.0.0.tgz#aea0f06f8d26c780c2b75494385544b2255af18c" + integrity sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw= dependencies: - stream-connect "^1.0.2" - stream-via "^1.0.4" + arr-map "^2.0.2" + for-own "^1.0.0" + make-iterator "^1.0.0" collection-visit@^1.0.0: version "1.0.0" @@ -3300,7 +3445,7 @@ collection-visit@^1.0.0: map-visit "^1.0.0" object-visit "^1.0.0" -color-convert@^1.3.0, color-convert@^1.9.0, color-convert@^1.9.1: +color-convert@^1.9.0, color-convert@^1.9.1: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== @@ -3317,13 +3462,6 @@ color-name@^1.0.0: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991" - integrity sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE= - dependencies: - color-name "^1.0.0" - color-string@^1.5.2: version "1.5.3" resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" @@ -3337,15 +3475,6 @@ color-support@^1.1.3: resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== -color@^0.11.0: - version "0.11.4" - resolved "https://registry.yarnpkg.com/color/-/color-0.11.4.tgz#6d7b5c74fb65e841cd48792ad1ed5e07b904d764" - integrity sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q= - dependencies: - clone "^1.0.2" - color-convert "^1.3.0" - color-string "^0.3.0" - color@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/color/-/color-3.1.0.tgz#d8e9fb096732875774c84bf922815df0308d0ffc" @@ -3354,20 +3483,6 @@ color@^3.0.0: color-convert "^1.9.1" color-string "^1.5.2" -colormin@^1.0.5: - version "1.1.2" - resolved "https://registry.yarnpkg.com/colormin/-/colormin-1.1.2.tgz#ea2f7420a72b96881a38aae59ec124a6f7298133" - integrity sha1-6i90IKcrlogaOKrlnsEkpvcpgTM= - dependencies: - color "^0.11.0" - css-color-names "0.0.4" - has "^1.0.1" - -colors@1.0.x: - version "1.0.3" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" - integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs= - colors@^1.1.0: version "1.3.2" resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.2.tgz#2df8ff573dfbf255af562f8ce7181d6b971a359b" @@ -3402,11 +3517,6 @@ commander@2.15.1: resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag== -commander@2.17.x, commander@~2.17.1: - version "2.17.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" - integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== - commander@2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" @@ -3414,7 +3524,7 @@ commander@2.9.0: dependencies: graceful-readlink ">= 1.0.0" -commander@^2.9.0: +commander@^2.19.0: version "2.19.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== @@ -3501,7 +3611,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@1.6.2, concat-stream@^1.5.0: +concat-stream@1.6.2, concat-stream@^1.5.0, concat-stream@^1.6.0: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -3511,21 +3621,6 @@ concat-stream@1.6.2, concat-stream@^1.5.0: readable-stream "^2.2.2" typedarray "^0.0.6" -concurrently@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-4.0.1.tgz#f6310fbadf2f476dd95df952edb5c0ab789f672c" - integrity sha512-D8UI+mlI/bfvrA57SeKOht6sEpb01dKk+8Yee4fbnkk1Ue8r3S+JXoEdFZIpzQlXJGtnxo47Wvvg/kG4ba3U6Q== - dependencies: - chalk "^2.4.1" - date-fns "^1.23.0" - lodash "^4.17.10" - read-pkg "^4.0.1" - rxjs "6.2.2" - spawn-command "^0.0.2-1" - supports-color "^4.5.0" - tree-kill "^1.1.0" - yargs "^12.0.1" - configstore@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/configstore/-/configstore-2.1.0.tgz#737a3a7036e9886102aa6099e47bb33ab1aba1a1" @@ -3556,7 +3651,12 @@ connect@^3.6.0: parseurl "~1.3.2" utils-merge "1.0.1" -console-browserify@1.1.x, console-browserify@^1.1.0: +consola@^2.3.0: + version "2.5.6" + resolved "https://registry.yarnpkg.com/consola/-/consola-2.5.6.tgz#5ce14dbaf6f5b589c8a258ef80ed97b752fa57d5" + integrity sha512-DN0j6ewiNWkT09G3ZoyyzN3pSYrjxWcx49+mHu+oDI5dvW5vzmyuzYsqGS79+yQserH9ymJQbGzeqUejfssr8w== + +console-browserify@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" integrity sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA= @@ -3605,7 +3705,7 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== -convert-source-map@1.X, convert-source-map@^1.1.0, convert-source-map@^1.2.0, convert-source-map@^1.5.1: +convert-source-map@^1.1.0, convert-source-map@^1.2.0, convert-source-map@^1.5.0, convert-source-map@^1.5.1: version "1.6.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20" integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A== @@ -3639,6 +3739,14 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= +copy-props@^2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/copy-props/-/copy-props-2.0.4.tgz#93bb1cadfafd31da5bb8a9d4b41f471ec3a72dfe" + integrity sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A== + dependencies: + each-props "^1.3.0" + is-plain-object "^2.0.1" + copy-webpack-plugin@^4.5.2: version "4.5.4" resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.5.4.tgz#f2b2782b3cd5225535c3dc166a80067e7d940f27" @@ -3653,6 +3761,20 @@ copy-webpack-plugin@^4.5.2: p-limit "^1.0.0" serialize-javascript "^1.4.0" +copy-webpack-plugin@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.6.0.tgz#e7f40dd8a68477d405dd1b7a854aae324b158bae" + integrity sha512-Y+SQCF+0NoWQryez2zXn5J5knmr9z/9qSQt7fbL78u83rxmigOy8X5+BFn8CFSuX+nKT8gpYwJX68ekqtQt6ZA== + dependencies: + cacache "^10.0.4" + find-cache-dir "^1.0.0" + globby "^7.1.1" + is-glob "^4.0.0" + loader-utils "^1.1.0" + minimatch "^3.0.4" + p-limit "^1.0.0" + serialize-javascript "^1.4.0" + core-assert@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/core-assert/-/core-assert-0.2.1.tgz#f85e2cf9bfed28f773cc8b3fa5c5b69bdc02fe3f" @@ -3661,31 +3783,46 @@ core-assert@^0.2.0: buf-compare "^1.0.0" is-error "^2.2.0" +core-js-compat@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.0.0.tgz#cd9810b8000742535a4a43773866185e310bd4f7" + integrity sha512-W/Ppz34uUme3LmXWjMgFlYyGnbo1hd9JvA0LNQ4EmieqVjg2GPYbj3H6tcdP2QGPGWdRKUqZVbVKLNIFVs/HiA== + dependencies: + browserslist "^4.5.1" + core-js "3.0.0" + core-js-pure "3.0.0" + semver "^5.6.0" + +core-js-pure@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.0.0.tgz#a5679adb4875427c8c0488afc93e6f5b7125859b" + integrity sha512-yPiS3fQd842RZDgo/TAKGgS0f3p2nxssF1H65DIZvZv0Od5CygP8puHXn3IQiM/39VAvgCbdaMQpresrbGgt9g== + +core-js@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.0.0.tgz#a8dbfa978d29bfc263bfb66c556d0ca924c28957" + integrity sha512-WBmxlgH2122EzEJ6GH8o9L/FeoUKxxxZ6q6VUxoTlsE4EvbTWKJb447eyVxTEuq0LpXjlq/kCB2qgBvsYRkLvQ== + core-js@^1.2.6: version "1.2.7" resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= -core-js@^2.0.0, core-js@^2.2.0, core-js@^2.4.0, core-js@^2.5.0, core-js@^2.5.7: +core-js@^2.0.0, core-js@^2.2.0, core-js@^2.4.0, core-js@^2.5.0: version "2.5.7" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" integrity sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw== +core-js@^2.6.5: + version "2.6.5" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.5.tgz#44bc8d249e7fb2ff5d00e0341a7ffb94fbf67895" + integrity sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A== + core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= -cosmiconfig@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-4.0.0.tgz#760391549580bbd2df1e562bc177b13c290972dc" - integrity sha512-6e5vDdrXZD+t5v0L8CrurPeybg4Fmf+FCSYxXKYVAqLUtyCSbuyqE059d0kDthTNRzKVjL7QMgNpEUlsoYH3iQ== - dependencies: - is-directory "^0.3.1" - js-yaml "^3.9.0" - parse-json "^4.0.0" - require-from-string "^2.0.1" - cosmiconfig@^5.0.0: version "5.0.6" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.0.6.tgz#dca6cf680a0bd03589aff684700858c81abeeb39" @@ -3733,12 +3870,14 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" -cross-spawn@5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" - integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= +cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== dependencies: - lru-cache "^4.0.1" + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" shebang-command "^1.2.0" which "^1.2.9" @@ -3758,17 +3897,6 @@ cross-spawn@^4.0.0: lru-cache "^4.0.1" which "^1.2.9" -cross-spawn@^6.0.0, cross-spawn@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -3804,59 +3932,28 @@ css-initials@^0.2.0: resolved "https://registry.yarnpkg.com/css-initials/-/css-initials-0.2.0.tgz#14c225bd8656255a6baee07231ef82fa55aacaa3" integrity sha512-t80yjg0pi4VAIc5itIqLh6M+ZlA+cB+gUXEQkDR09+ExTDwLMGfJ8YviBsGW+DklIrb2k9fwB75Io8ooWXDxxw== -css-loader@^0.28.11: - version "0.28.11" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.11.tgz#c3f9864a700be2711bb5a2462b2389b1a392dab7" - integrity sha512-wovHgjAx8ZIMGSL8pTys7edA1ClmzxHeY6n/d97gg5odgsxEgKjULPR0viqyC+FWMCL9sfqoC/QCUBo62tLvPg== +css-loader@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-2.1.1.tgz#d8254f72e412bb2238bb44dd674ffbef497333ea" + integrity sha512-OcKJU/lt232vl1P9EEDamhoO9iKY3tIjY5GU+XDLblAykTdgs6Ux9P1hTHve8nFKy5KPpOXOsVI/hIwi3841+w== dependencies: - babel-code-frame "^6.26.0" - css-selector-tokenizer "^0.7.0" - cssnano "^3.10.0" - icss-utils "^2.1.0" - loader-utils "^1.0.2" - lodash.camelcase "^4.3.0" - object-assign "^4.1.1" - postcss "^5.0.6" - postcss-modules-extract-imports "^1.2.0" - postcss-modules-local-by-default "^1.2.0" - postcss-modules-scope "^1.1.0" - postcss-modules-values "^1.3.0" + camelcase "^5.2.0" + icss-utils "^4.1.0" + loader-utils "^1.2.3" + normalize-path "^3.0.0" + postcss "^7.0.14" + postcss-modules-extract-imports "^2.0.0" + postcss-modules-local-by-default "^2.0.6" + postcss-modules-scope "^2.1.0" + postcss-modules-values "^2.0.0" postcss-value-parser "^3.3.0" - source-list-map "^2.0.0" - -css-loader@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-1.0.0.tgz#9f46aaa5ca41dbe31860e3b62b8e23c42916bf56" - integrity sha512-tMXlTYf3mIMt3b0dDCOQFJiVvxbocJ5Ho577WiGPYPZcqVEO218L2iU22pDXzkTZCLDE+9AmGSUkWxeh/nZReA== - dependencies: - babel-code-frame "^6.26.0" - css-selector-tokenizer "^0.7.0" - icss-utils "^2.1.0" - loader-utils "^1.0.2" - lodash.camelcase "^4.3.0" - postcss "^6.0.23" - postcss-modules-extract-imports "^1.2.0" - postcss-modules-local-by-default "^1.2.0" - postcss-modules-scope "^1.1.0" - postcss-modules-values "^1.3.0" - postcss-value-parser "^3.3.0" - source-list-map "^2.0.0" + schema-utils "^1.0.0" css-select-base-adapter@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.0.tgz#0102b3d14630df86c3eb9fa9f5456270106cf990" integrity sha1-AQKz0UYw34bD65+p9UVicBBs+ZA= -css-select@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" - integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= - dependencies: - boolbase "~1.0.0" - css-what "2.1" - domutils "1.5.1" - nth-check "~1.0.1" - css-select@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.0.0.tgz#7aa2921392114831f68db175c0b6a555df74bbd5" @@ -3867,15 +3964,6 @@ css-select@^2.0.0: domutils "^1.7.0" nth-check "^1.0.1" -css-selector-tokenizer@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz#e6988474ae8c953477bf5e7efecfceccd9cf4c86" - integrity sha1-5piEdK6MlTR3v15+/s/OzNnPTIY= - dependencies: - cssesc "^0.1.0" - fastparse "^1.1.1" - regexpu-core "^1.0.0" - css-tree@1.0.0-alpha.28: version "1.0.0-alpha.28" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.28.tgz#8e8968190d886c9477bc8d61e96f61af3f7ffa7f" @@ -3907,26 +3995,21 @@ css-what@2.1: resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd" integrity sha1-lGfQMsOM+u+58teVASUwYvh/ob0= -css@2.X, css@^2.2.1: - version "2.2.4" - resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929" - integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw== - dependencies: - inherits "^2.0.3" - source-map "^0.6.1" - source-map-resolve "^0.5.2" - urix "^0.1.0" - -cssesc@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4" - integrity sha1-yBSQPkViM3GgR3tAEJqq++6t27Q= - cssesc@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-1.0.1.tgz#ef7bd8d0229ed6a3a7051ff7771265fe7330e0a8" integrity sha512-S2hzrpWvE6G/rW7i7IxJfWBYn27QWfOIncUW++8Rbo1VB5zsJDSVPcnI+Q8z7rhxT6/yZeLOCja4cZnghJrNGA== +cssesc@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703" + integrity sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + cssnano-preset-default@^4.0.2: version "4.0.3" resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.3.tgz#9bfd1b06d4aa3991ed958ad9b9ec25a179261705" @@ -3985,44 +4068,6 @@ cssnano-util-same-parent@^4.0.0: resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3" integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q== -cssnano@^3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38" - integrity sha1-Tzj2zqK5sX+gFJDyPx3GjqZcHDg= - dependencies: - autoprefixer "^6.3.1" - decamelize "^1.1.2" - defined "^1.0.0" - has "^1.0.1" - object-assign "^4.0.1" - postcss "^5.0.14" - postcss-calc "^5.2.0" - postcss-colormin "^2.1.8" - postcss-convert-values "^2.3.4" - postcss-discard-comments "^2.0.4" - postcss-discard-duplicates "^2.0.1" - postcss-discard-empty "^2.0.1" - postcss-discard-overridden "^0.1.1" - postcss-discard-unused "^2.2.1" - postcss-filter-plugins "^2.0.0" - postcss-merge-idents "^2.1.5" - postcss-merge-longhand "^2.0.1" - postcss-merge-rules "^2.0.3" - postcss-minify-font-values "^1.0.2" - postcss-minify-gradients "^1.0.1" - postcss-minify-params "^1.0.4" - postcss-minify-selectors "^2.0.4" - postcss-normalize-charset "^1.1.0" - postcss-normalize-url "^3.0.7" - postcss-ordered-values "^2.1.0" - postcss-reduce-idents "^2.2.2" - postcss-reduce-initial "^1.0.0" - postcss-reduce-transforms "^1.0.3" - postcss-svgo "^2.1.1" - postcss-unique-selectors "^2.0.2" - postcss-value-parser "^3.2.3" - postcss-zindex "^2.0.1" - cssnano@^4.1.0: version "4.1.5" resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.5.tgz#cfe5ffa722079f339ac2a6d547d88dd8ac8a498d" @@ -4040,14 +4085,6 @@ csso@^3.5.0: dependencies: css-tree "1.0.0-alpha.29" -csso@~2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/csso/-/csso-2.3.2.tgz#ddd52c587033f49e94b71fc55569f252e8ff5f85" - integrity sha1-3dUsWHAz9J6Utx/FVWnyUuj/X4U= - dependencies: - clap "^1.0.9" - source-map "^0.5.3" - currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -4060,11 +4097,6 @@ custom-event@~1.0.0: resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" integrity sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU= -cycle@1.0.x: - version "1.0.3" - resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2" - integrity sha1-IegLK+hYD5i0aPN5QwZisEbDStI= - cyclist@~0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" @@ -4089,11 +4121,6 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -date-fns@^1.23.0: - version "1.29.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6" - integrity sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw== - date-now@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" @@ -4104,25 +4131,11 @@ date-time@^0.1.1: resolved "https://registry.yarnpkg.com/date-time/-/date-time-0.1.1.tgz#ed2f6d93d9790ce2fd66d5b5ff3edd5bbcbf3b07" integrity sha1-7S9tk9l5DOL9ZtW1/z7dW7y/Owc= -dateformat@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062" - integrity sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI= - de-indent@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d" integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0= -debug-fabulous@1.X: - version "1.1.0" - resolved "https://registry.yarnpkg.com/debug-fabulous/-/debug-fabulous-1.1.0.tgz#af8a08632465224ef4174a9f06308c3c2a1ebc8e" - integrity sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg== - dependencies: - debug "3.X" - memoizee "0.4.X" - object-assign "4.X" - debug@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" @@ -4151,21 +4164,21 @@ debug@3.1.0, debug@=3.1.0: dependencies: ms "2.0.0" -debug@3.X, debug@^3.1.0: +debug@^3.1.0, debug@^3.2.5: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== dependencies: ms "^2.1.1" -debug@^4.0.1, debug@^4.1.0: +debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== dependencies: ms "^2.1.1" -decamelize@^1.1.1, decamelize@^1.1.2: +decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= @@ -4204,14 +4217,34 @@ deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= -defaults@^1.0.0, defaults@^1.0.3: +default-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/default-compare/-/default-compare-1.0.0.tgz#cb61131844ad84d84788fb68fd01681ca7781a2f" + integrity sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ== + dependencies: + kind-of "^5.0.2" + +default-gateway@^4.0.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b" + integrity sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA== + dependencies: + execa "^1.0.0" + ip-regex "^2.1.0" + +default-resolution@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/default-resolution/-/default-resolution-2.0.0.tgz#bcb82baa72ad79b426a76732f1a81ad6df26d684" + integrity sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ= + +defaults@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= dependencies: clone "^1.0.2" -define-properties@^1.1.2, define-properties@^1.1.3: +define-properties@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== @@ -4240,11 +4273,6 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" -defined@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" - integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= - del@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/del/-/del-3.0.0.tgz#53ecf699ffcbcb39637691ab13baf160819766e5" @@ -4257,11 +4285,28 @@ del@^3.0.0: pify "^3.0.0" rimraf "^2.2.8" +del@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/del/-/del-4.0.0.tgz#4fa27e92c366cb45b9bdaa56a9b8703dced17437" + integrity sha512-/BnSJ+SuZyLu7xMn48kZY0nMXDi+5KNmR4g8n21Wivsl8+B9njV6/5kcTNE9juSprp0zRWBU28JuHUq0FqK1Nw== + dependencies: + globby "^6.1.0" + is-path-cwd "^2.0.0" + is-path-in-cwd "^2.0.0" + p-map "^2.0.0" + pify "^4.0.1" + rimraf "^2.6.2" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +delegate@^3.1.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166" + integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw== + delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" @@ -4272,11 +4317,6 @@ depd@~1.1.2: resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= -deprecated@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/deprecated/-/deprecated-0.0.1.tgz#f9c9af5464afa1e7a971458a8bdef2aa94d5bb19" - integrity sha1-+cmvVGSvoeepcUWKi97yqpTVuxk= - des.js@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" @@ -4314,12 +4354,7 @@ detect-libc@^1.0.2: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= -detect-newline@2.X: - version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" - integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I= - -detect-node@^2.0.3: +detect-node@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== @@ -4356,7 +4391,7 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" -dir-glob@^2.0.0: +dir-glob@2.0.0, dir-glob@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.0.0.tgz#0b205d2b6aef98238ca286598a8204d29d0a0034" integrity sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag== @@ -4399,17 +4434,22 @@ doctrine@^2.0.0, doctrine@^2.1.0: dependencies: esutils "^2.0.2" +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + doctypes@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9" integrity sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk= -dom-converter@~0.2: - version "0.2.0" - resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" - integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== - dependencies: - utila "~0.4" +dom-event-types@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dom-event-types/-/dom-event-types-1.0.0.tgz#5830a0a29e1bf837fe50a70cd80a597232813cae" + integrity sha512-2G2Vwi2zXTHBGqXHsJ4+ak/iP0N8Ar+G8a7LiD2oup5o4sQWytwqqrZu/O6hIMV0KMID2PL69OhpshLO0n7UJQ== dom-serialize@^2.2.0: version "2.2.1" @@ -4444,20 +4484,6 @@ domelementtype@~1.1.1: resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" integrity sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs= -domhandler@2.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.1.0.tgz#d2646f5e57f6c3bab11cf6cb05d3c0acf7412594" - integrity sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ= - dependencies: - domelementtype "1" - -domhandler@2.3: - version "2.3.0" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.3.0.tgz#2de59a0822d5027fabff6f032c2b25a2a8abe738" - integrity sha1-LeWaCCLVAn+r/28DLCsloqir5zg= - dependencies: - domelementtype "1" - domhandler@^2.3.0: version "2.4.2" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" @@ -4465,21 +4491,6 @@ domhandler@^2.3.0: dependencies: domelementtype "1" -domutils@1.1: - version "1.1.6" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.1.6.tgz#bddc3de099b9a2efacc51c623f28f416ecc57485" - integrity sha1-vdw94Jm5ou+sxRxiPyj0FuzFdIU= - dependencies: - domelementtype "1" - -domutils@1.5, domutils@1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" - integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= - dependencies: - dom-serializer "0" - domelementtype "1" - domutils@^1.5.1, domutils@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" @@ -4502,13 +4513,6 @@ dot-prop@^4.1.1: dependencies: is-obj "^1.0.0" -duplexer2@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db" - integrity sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds= - dependencies: - readable-stream "~1.1.9" - duplexer2@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" @@ -4541,6 +4545,14 @@ duplicate-package-checker-webpack-plugin@^3.0.0: lodash "^4.17.4" semver "^5.4.1" +each-props@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/each-props/-/each-props-1.3.2.tgz#ea45a414d16dd5cfa419b1a81720d5ca06892333" + integrity sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA== + dependencies: + is-plain-object "^2.0.1" + object.defaults "^1.1.0" + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" @@ -4559,7 +4571,17 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.47, electron-to-chromium@^1.3.79: +electron-to-chromium@^1.3.103, electron-to-chromium@^1.3.62: + version "1.3.119" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.119.tgz#9a7770da667252aeb81f667853f67c2b26e00197" + integrity sha512-3mtqcAWa4HgG+Djh/oNXlPH0cOH6MmtwxN1nHSaReb9P0Vn51qYPqYwLeoSuAX9loU1wrOBhFbiX3CkeIxPfgg== + +electron-to-chromium@^1.3.116: + version "1.3.116" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.116.tgz#1dbfee6a592a0c14ade77dbdfe54fef86387d702" + integrity sha512-NKwKAXzur5vFCZYBHpdWjTMO8QptNLNP80nItkSIgUOapPAo9Uia+RvkCaZJtO7fhQaVElSvBPWEc2ku6cKsPA== + +electron-to-chromium@^1.3.79: version "1.3.80" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.80.tgz#e99ec7efe64c2c6a269d3885ff411ea88852fa53" integrity sha512-WClidEWEUNx7OfwXehB0qaxCuetjbKjev2SmXWgybWPLKAThBiMTF/2Pd8GSUDtoGOavxVzdkKwfFAPRSWlkLw== @@ -4582,6 +4604,11 @@ elliptic@^6.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.1.1.tgz#c6cd0ec1b0642e2a3c67a1137efc5e796da4f88e" integrity sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4= +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + emojis-list@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" @@ -4601,13 +4628,6 @@ encodeurl@~1.0.1, encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= -encoding@^0.1.11: - version "0.1.12" - resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" - integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s= - dependencies: - iconv-lite "~0.4.13" - end-of-stream@^1.0.0, end-of-stream@^1.1.0: version "1.4.1" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" @@ -4615,13 +4635,6 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0: dependencies: once "^1.4.0" -end-of-stream@~0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-0.1.5.tgz#8e177206c3c80837d85632e8b9359dfe8b2f6eaf" - integrity sha1-jhdyBsPICDfYVjLouTWd/osvbq8= - dependencies: - once "~1.3.0" - engine.io-client@1.8.3: version "1.8.3" resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-1.8.3.tgz#1798ed93451246453d4c6f635d7a201fe940d5ab" @@ -4683,11 +4696,6 @@ ent@~2.2.0: resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0= -entities@1.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-1.0.0.tgz#b2987aa3821347fcde642b24fdfc9e4fb712bf26" - integrity sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY= - entities@^1.1.1, entities@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" @@ -4707,19 +4715,7 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.12.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" - integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg== - dependencies: - es-to-primitive "^1.2.0" - function-bind "^1.1.1" - has "^1.0.3" - is-callable "^1.1.4" - is-regex "^1.0.4" - object-keys "^1.0.12" - -es-abstract@^1.5.1, es-abstract@^1.6.1, es-abstract@^1.7.0: +es-abstract@^1.5.1, es-abstract@^1.6.1: version "1.12.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165" integrity sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA== @@ -4730,7 +4726,7 @@ es-abstract@^1.5.1, es-abstract@^1.6.1, es-abstract@^1.7.0: is-callable "^1.1.3" is-regex "^1.0.4" -es-to-primitive@^1.1.1, es-to-primitive@^1.2.0: +es-to-primitive@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377" integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg== @@ -4739,7 +4735,7 @@ es-to-primitive@^1.1.1, es-to-primitive@^1.2.0: is-date-object "^1.0.1" is-symbol "^1.0.2" -es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.45, es5-ext@^0.10.9, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: +es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14: version "0.10.46" resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.46.tgz#efd99f67c5a7ec789baa3daa7f79870388f7f572" integrity sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw== @@ -4757,16 +4753,21 @@ es6-iterator@^2.0.1, es6-iterator@~2.0.3: es5-ext "^0.10.35" es6-symbol "^3.1.1" -es6-object-assign@~1.1.0: +es6-object-assign@^1.1.0, es6-object-assign@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c" integrity sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw= -es6-promise@^4.0.3, es6-promise@^4.2.4: +es6-promise@^4.0.3: version "4.2.5" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.5.tgz#da6d0d5692efb461e082c14817fe2427d8f5d054" integrity sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg== +es6-promise@^4.2.6: + version "4.2.6" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.6.tgz#b685edd8258886365ea62b57d30de28fadcd974f" + integrity sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q== + es6-symbol@^3.1.1, es6-symbol@~3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" @@ -4775,7 +4776,7 @@ es6-symbol@^3.1.1, es6-symbol@~3.1.1: d "1" es5-ext "~0.10.14" -es6-weak-map@^2.0.2: +es6-weak-map@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f" integrity sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8= @@ -4790,15 +4791,15 @@ escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= -escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.4, escape-string-regexp@^1.0.5, escape-string-regexp@~1.0.5: +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.4, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escodegen@^1.10.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.0.tgz#b27a9389481d5bfd5bec76f7bb1eb3f8f4556589" - integrity sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw== +escodegen@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.1.tgz#c485ff8d6b4cdb89e27f4a856e91f118401ca510" + integrity sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw== dependencies: esprima "^3.1.3" estraverse "^4.2.0" @@ -4807,23 +4808,7 @@ escodegen@^1.10.0: optionalDependencies: source-map "~0.6.1" -eslint-config-airbnb-base@^13.1.0: - version "13.1.0" - resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-13.1.0.tgz#b5a1b480b80dfad16433d6c4ad84e6605052c05c" - integrity sha512-XWwQtf3U3zIoKO1BbHh6aUhJZQweOwSt4c2JrPDg9FP3Ltv3+YfEv7jIDB8275tVnO/qOHbfuYg3kzw6Je7uWw== - dependencies: - eslint-restricted-globals "^0.1.1" - object.assign "^4.1.0" - object.entries "^1.0.4" - -eslint-config-prettier@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-3.3.0.tgz#41afc8d3b852e757f06274ed6c44ca16f939a57d" - integrity sha512-Bc3bh5bAcKNvs3HOpSi6EfGA2IIp7EzWcg2tS4vP7stnXu/J1opihHDM7jI9JCIckyIDTgZLSWn7J3HY0j2JfA== - dependencies: - get-stdin "^6.0.0" - -eslint-import-resolver-node@^0.3.1: +eslint-import-resolver-node@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz#58f15fb839b8d0576ca980413476aab2472db66a" integrity sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q== @@ -4831,36 +4816,36 @@ eslint-import-resolver-node@^0.3.1: debug "^2.6.9" resolve "^1.5.0" -eslint-module-utils@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz#b270362cd88b1a48ad308976ce7fa54e98411746" - integrity sha1-snA2LNiLGkitMIl2zn+lTphBF0Y= +eslint-module-utils@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.3.0.tgz#546178dab5e046c8b562bbb50705e2456d7bda49" + integrity sha512-lmDJgeOOjk8hObTysjqH7wyMi+nsHwwvfBykwfhjR1LNdd7C2uFJBvx4OpWYpXOw4df1yE1cDEVd1yLHitk34w== dependencies: debug "^2.6.8" - pkg-dir "^1.0.0" + pkg-dir "^2.0.0" -eslint-plugin-html@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-html/-/eslint-plugin-html-5.0.0.tgz#396e30a60dedee0122fe08f11d13c5ab22f20d32" - integrity sha512-f7p/7YQdgQUFVAX3nB4dnMQbrDeTalcA01PDhuvTLk0ZadCwM4Pb+639SRuqEf1zMkIxckLY+ckCr0hVP5zl6A== +eslint-plugin-html@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-html/-/eslint-plugin-html-5.0.3.tgz#3db133995e49a73596f6a473c16a1b83634deffd" + integrity sha512-46ruAnp3jVQP/5Bi5eEIOooscjUTPFU3vxCxHe/OG6ORdM7Xv5c25/Nz9fAbHklzCpiXuIiH4/mV/XBkm7MINw== dependencies: htmlparser2 "^3.10.0" -eslint-plugin-import@^2.14.0: - version "2.14.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.14.0.tgz#6b17626d2e3e6ad52cfce8807a845d15e22111a8" - integrity sha512-FpuRtniD/AY6sXByma2Wr0TXvXJ4nA/2/04VPlfpmUDPOpOY264x+ILiwnrk/k4RINgDAyFZByxqPUbSQ5YE7g== +eslint-plugin-import@^2.16.0: + version "2.16.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.16.0.tgz#97ac3e75d0791c4fac0e15ef388510217be7f66f" + integrity sha512-z6oqWlf1x5GkHIFgrSvtmudnqM6Q60KM4KvpWi5ubonMjycLjndvd5+8VAZIsTlHC03djdgJuyKG6XO577px6A== dependencies: contains-path "^0.1.0" - debug "^2.6.8" + debug "^2.6.9" doctrine "1.5.0" - eslint-import-resolver-node "^0.3.1" - eslint-module-utils "^2.2.0" - has "^1.0.1" - lodash "^4.17.4" - minimatch "^3.0.3" + eslint-import-resolver-node "^0.3.2" + eslint-module-utils "^2.3.0" + has "^1.0.3" + lodash "^4.17.11" + minimatch "^3.0.4" read-pkg-up "^2.0.0" - resolve "^1.6.0" + resolve "^1.9.0" eslint-plugin-prettier@^3.0.1: version "3.0.1" @@ -4869,17 +4854,12 @@ eslint-plugin-prettier@^3.0.1: dependencies: prettier-linter-helpers "^1.0.0" -eslint-plugin-vue@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-5.1.0.tgz#d0d373334be8140d698ec4e1fb83f09faa48081b" - integrity sha512-C7avvbGLb9J1PyGiFolPcGR4ljUc+dKm5ZJdrUKXwXFxHHx4SqOmRI29AsFyW7PbCGcnOvIlaq7NJS6HDIak+g== +eslint-plugin-vue@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-5.2.2.tgz#86601823b7721b70bc92d54f1728cfc03b36283c" + integrity sha512-CtGWH7IB0DA6BZOwcV9w9q3Ri6Yuo8qMjx05SmOGJ6X6E0Yo3y9E/gQ5tuNxg2dEt30tRnBoFTbvtmW9iEoyHA== dependencies: - vue-eslint-parser "^4.0.2" - -eslint-restricted-globals@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz#35f0d5cbc64c2e3ed62e93b4b1a7af05ba7ed4d7" - integrity sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc= + vue-eslint-parser "^5.0.0" eslint-scope@3.7.1: version "3.7.1" @@ -4897,6 +4877,14 @@ eslint-scope@^4.0.0: esrecurse "^4.1.0" estraverse "^4.1.1" +eslint-scope@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" + integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + eslint-utils@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.3.1.tgz#9a851ba89ee7c460346f97cf8939c7298827e512" @@ -4907,47 +4895,46 @@ eslint-visitor-keys@^1.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ== -eslint@^5.12.0: - version "5.12.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.12.0.tgz#fab3b908f60c52671fb14e996a450b96c743c859" - integrity sha512-LntwyPxtOHrsJdcSwyQKVtHofPHdv+4+mFwEe91r2V13vqpM8yLr7b1sW+Oo/yheOPkWYsYlYJCkzlFAt8KV7g== +eslint@^5.15.3: + version "5.15.3" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.15.3.tgz#c79c3909dc8a7fa3714fb340c11e30fd2526b8b5" + integrity sha512-vMGi0PjCHSokZxE0NLp2VneGw5sio7SSiDNgIUn2tC0XkWJRNOIoHIg3CliLVfXnJsiHxGAYrkw0PieAu8+KYQ== dependencies: "@babel/code-frame" "^7.0.0" - ajv "^6.5.3" + ajv "^6.9.1" chalk "^2.1.0" cross-spawn "^6.0.5" debug "^4.0.1" - doctrine "^2.1.0" - eslint-scope "^4.0.0" + doctrine "^3.0.0" + eslint-scope "^4.0.3" eslint-utils "^1.3.1" eslint-visitor-keys "^1.0.0" - espree "^5.0.0" + espree "^5.0.1" esquery "^1.0.1" esutils "^2.0.2" - file-entry-cache "^2.0.0" + file-entry-cache "^5.0.1" functional-red-black-tree "^1.0.1" glob "^7.1.2" globals "^11.7.0" ignore "^4.0.6" import-fresh "^3.0.0" imurmurhash "^0.1.4" - inquirer "^6.1.0" + inquirer "^6.2.2" js-yaml "^3.12.0" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.3.0" - lodash "^4.17.5" + lodash "^4.17.11" minimatch "^3.0.4" mkdirp "^0.5.1" natural-compare "^1.4.0" optionator "^0.8.2" path-is-inside "^1.0.2" - pluralize "^7.0.0" progress "^2.0.0" regexpp "^2.0.1" semver "^5.5.1" strip-ansi "^4.0.0" strip-json-comments "^2.0.1" - table "^5.0.2" + table "^5.2.3" text-table "^0.2.0" espower-location-detector@^1.0.0: @@ -4969,23 +4956,16 @@ espree@^4.1.0: acorn-jsx "^5.0.0" eslint-visitor-keys "^1.0.0" -espree@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.0.tgz#fc7f984b62b36a0f543b13fb9cd7b9f4a7f5b65c" - integrity sha512-1MpUfwsdS9MMoN7ZXqAr9e9UKdVHDcvrJpyx7mm1WuQlx/ygErEQBzgi5Nh5qBHIoYweprhtMkTCb9GhcAIcsA== +espree@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.1.tgz#5d6526fa4fc7f0788a5cf75b15f30323e2f81f7a" + integrity sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A== dependencies: - acorn "^6.0.2" + acorn "^6.0.7" acorn-jsx "^5.0.0" eslint-visitor-keys "^1.0.0" -esprima-extract-comments@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/esprima-extract-comments/-/esprima-extract-comments-0.2.1.tgz#9018d8df37ffd95dd615015a8c5f0475ed743423" - integrity sha1-kBjY3zf/2V3WFQFajF8Ede10NCM= - dependencies: - esprima "^2.7.1" - -esprima@^2.1.0, esprima@^2.6.0, esprima@^2.7.1: +esprima@^2.1.0: version "2.7.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" integrity sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE= @@ -5036,14 +5016,6 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= -event-emitter@^0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" - integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk= - dependencies: - d "1" - es5-ext "~0.10.14" - eventemitter3@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" @@ -5061,6 +5033,13 @@ eventsource@0.1.6: dependencies: original ">=0.0.5" +eventsource@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0" + integrity sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ== + dependencies: + original "^1.0.0" + evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" @@ -5082,6 +5061,19 @@ execa@^0.10.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + exists-stat@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/exists-stat/-/exists-stat-1.0.0.tgz#0660e3525a2e89d9e446129440c272edfa24b529" @@ -5092,11 +5084,6 @@ exit-hook@^1.0.0: resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" integrity sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g= -exit@0.1.2, exit@0.1.x: - version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= - expand-braces@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/expand-braces/-/expand-braces-0.1.2.tgz#488b1d1d2451cb3d3a6b192cfc030f44c5855fea" @@ -5216,16 +5203,7 @@ extend@^3.0.0, extend@~3.0.2: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -external-editor@^2.0.1, external-editor@^2.0.4: - version "2.2.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" - integrity sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A== - dependencies: - chardet "^0.4.0" - iconv-lite "^0.4.17" - tmp "^0.0.33" - -external-editor@^3.0.0: +external-editor@^3.0.0, external-editor@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.3.tgz#5866db29a97826dbe4bf3afd24070ead9ea43a27" integrity sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA== @@ -5255,26 +5233,6 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" -extract-comments@^0.10.1: - version "0.10.1" - resolved "https://registry.yarnpkg.com/extract-comments/-/extract-comments-0.10.1.tgz#8b6031808a2f5fde1cd67bf8317b918204304408" - integrity sha1-i2AxgIovX94c1nv4MXuRggQwRAg= - dependencies: - define-property "^0.2.5" - esprima-extract-comments "^0.2.1" - extend-shallow "^2.0.1" - parse-code-context "^0.2.1" - -extract-text-webpack-plugin@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extract-text-webpack-plugin/-/extract-text-webpack-plugin-3.0.2.tgz#5f043eaa02f9750a9258b78c0a6e0dc1408fb2f7" - integrity sha512-bt/LZ4m5Rqt/Crl2HiKuAl/oqg0psx1tsTLkvWbJen1CtD+fftkZhMaQ9HOtY2gWsl2Wq+sABmMVi9z3DhKWQQ== - dependencies: - async "^2.4.1" - loader-utils "^1.1.0" - schema-utils "^0.3.0" - webpack-sources "^1.0.1" - extract-zip@^1.6.5: version "1.6.7" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9" @@ -5295,18 +5253,14 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -eyes@0.1.x: - version "0.1.8" - resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" - integrity sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A= - -fancy-log@^1.1.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.2.tgz#f41125e3d84f2e7d89a43d06d958c8f78be16be1" - integrity sha1-9BEl49hPLn2JpD0G2VjI94vha+E= +fancy-log@^1.3.2: + version "1.3.3" + resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.3.tgz#dbc19154f558690150a23953a0adbd035be45fc7" + integrity sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw== dependencies: ansi-gray "^0.1.1" color-support "^1.1.3" + parse-node-version "^1.0.0" time-stamp "^1.0.0" fast-deep-equal@^1.0.0: @@ -5324,6 +5278,18 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== +fast-glob@^2.0.2: + version "2.2.6" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.6.tgz#a5d5b697ec8deda468d85a74035290a025a95295" + integrity sha512-0BvMaZc1k9F+MeWWMe8pL6YltFzZYcJsYU7D4JyDA6PAczaXvxqQQ/z+mDF7/4Mw01DeUc+i3CTKajnkANkV4w== + dependencies: + "@mrmlnc/readdir-enhanced" "^2.2.1" + "@nodelib/fs.stat" "^1.1.2" + glob-parent "^3.1.0" + is-glob "^4.0.0" + merge2 "^1.2.3" + micromatch "^3.1.10" + fast-json-stable-stringify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" @@ -5334,11 +5300,6 @@ fast-levenshtein@~2.0.4: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= -fastparse@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8" - integrity sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg= - faye-websocket@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" @@ -5346,7 +5307,7 @@ faye-websocket@^0.10.0: dependencies: websocket-driver ">=0.5.1" -faye-websocket@~0.11.0: +faye-websocket@~0.11.0, faye-websocket@~0.11.1: version "0.11.1" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.1.tgz#f0efe18c4f56e4f40afc7e06c719fd5ee6188f38" integrity sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg= @@ -5360,6 +5321,11 @@ fd-slicer@~1.0.1: dependencies: pend "~1.2.0" +figgy-pudding@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" + integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w== + figures@^1.4.0: version "1.7.0" resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" @@ -5375,46 +5341,30 @@ figures@^2.0.0: dependencies: escape-string-regexp "^1.0.5" -file-entry-cache@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" - integrity sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E= +file-entry-cache@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" + integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== dependencies: - flat-cache "^1.2.1" - object-assign "^4.0.1" + flat-cache "^2.0.1" -file-loader@^0.11.2: - version "0.11.2" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-0.11.2.tgz#4ff1df28af38719a6098093b88c82c71d1794a34" - integrity sha512-N+uhF3mswIFeziHQjGScJ/yHXYt3DiLBeC+9vWW+WjUBiClMSOlV1YrXQi+7KM2aA3Rn4Bybgv+uXFQbfkzpvg== - dependencies: - loader-utils "^1.0.2" - -file-loader@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-2.0.0.tgz#39749c82f020b9e85901dcff98e8004e6401cfde" - integrity sha512-YCsBfd1ZGCyonOKLxPiKPdu+8ld9HAaMEvJewzz+b2eTF7uL5Zm/HdBF6FjCrpCMRq25Mi0U1gl4pwn2TlH7hQ== +file-loader@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-3.0.1.tgz#f8e0ba0b599918b51adfe45d66d1e771ad560faa" + integrity sha512-4sNIOXgtH/9WZq4NvlfU3Opn5ynUsqBwSLyM+I7UOwdGigTBYfVVQEwe/msZNX/j4pCJTIM14Fsw66Svo1oVrw== dependencies: loader-utils "^1.0.2" schema-utils "^1.0.0" -file-set@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/file-set/-/file-set-2.0.1.tgz#db9bc4b70a7e5ba81c9d279c20a37f13369c7850" - integrity sha512-XgOUUpgR6FbbfYcniLw0qm1Am7PnNYIAkd+eXxRt42LiYhjaso0WiuQ+VmrNdtwotyM+cLCfZ56AZrySP3QnKA== - dependencies: - array-back "^2.0.0" - glob "^7.1.3" - filename-regex@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" integrity sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY= -filesize@3.5.11: - version "3.5.11" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.5.11.tgz#1919326749433bb3cf77368bd158caabcc19e9ee" - integrity sha512-ZH7loueKBoDb7yG9esn1U+fgq7BzlzW6NRi5/rMdxIZ05dj7GFD/Xc5rq2CDt5Yq86CyfSYVyx4242QQNZbx1g== +filesize@3.6.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317" + integrity sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg== fill-range@^2.1.0: version "2.2.4" @@ -5486,16 +5436,27 @@ find-cache-dir@^1.0.0: make-dir "^1.0.0" pkg-dir "^2.0.0" -find-index@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/find-index/-/find-index-0.1.1.tgz#675d358b2ca3892d795a1ab47232f8b6e2e0dde4" - integrity sha1-Z101iyyjiS15Whq0cjL4tuLg3eQ= +find-cache-dir@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" + integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== + dependencies: + commondir "^1.0.1" + make-dir "^2.0.0" + pkg-dir "^3.0.0" find-root@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== +find-up@3.0.0, find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + find-up@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" @@ -5511,13 +5472,6 @@ find-up@^2.0.0, find-up@^2.1.0: dependencies: locate-path "^2.0.0" -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== - dependencies: - locate-path "^3.0.0" - findup-sync@0.4.3: version "0.4.3" resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.4.3.tgz#40043929e7bc60adf0b7f4827c4c6e75a0deca12" @@ -5557,30 +5511,24 @@ fined@^1.0.1: object.pick "^1.2.0" parse-filepath "^1.0.1" -first-chunk-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz#59bfb50cd905f60d7c394cd3d9acaab4e6ad934e" - integrity sha1-Wb+1DNkF9g18OUzT2ayqtOatk04= - flagged-respawn@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.0.tgz#4e79ae9b2eb38bf86b3bb56bf3e0a56aa5fcabd7" integrity sha1-Tnmumy6zi/hrO7Vr8+ClaqX8q9c= -flat-cache@^1.2.1: - version "1.3.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.4.tgz#2c2ef77525cc2929007dfffa1dd314aa9c9dee6f" - integrity sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg== +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" + integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== dependencies: - circular-json "^0.3.1" - graceful-fs "^4.1.2" - rimraf "~2.6.2" - write "^0.2.1" + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" -flatten@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" - integrity sha1-2uRqnXj74lKSJYzB54CkHZXAN4I= +flatted@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.0.tgz#55122b6536ea496b4b44893ee2608141d10d9916" + integrity sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg== flush-write-stream@^1.0.0: version "1.0.3" @@ -5590,6 +5538,14 @@ flush-write-stream@^1.0.0: inherits "^2.0.1" readable-stream "^2.0.4" +flush-write-stream@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" + integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== + dependencies: + inherits "^2.0.3" + readable-stream "^2.3.6" + fn-name@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-2.0.1.tgz#5214d7537a4d06a4a301c0cc262feb84188002e7" @@ -5698,10 +5654,13 @@ fs-minipass@^1.2.5: dependencies: minipass "^2.2.1" -fs-then-native@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fs-then-native/-/fs-then-native-2.0.0.tgz#19a124d94d90c22c8e045f2e8dd6ebea36d48c67" - integrity sha1-GaEk2U2QwiyOBF8ujdbr6jbUjGc= +fs-mkdirp-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz#0b7815fc3201c6a69e14db98ce098c16935259eb" + integrity sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes= + dependencies: + graceful-fs "^4.1.11" + through2 "^2.0.3" fs-write-stream-atomic@^1.0.8: version "1.0.10" @@ -5765,13 +5724,6 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" -gaze@^0.5.1: - version "0.5.2" - resolved "https://registry.yarnpkg.com/gaze/-/gaze-0.5.2.tgz#40b709537d24d1d45767db5a908689dfe69ac44f" - integrity sha1-QLcJU30k0dRXZ9takIaJ3+aaxE8= - dependencies: - globule "~0.1.0" - gaze@^1.0.0: version "1.1.3" resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a" @@ -5799,16 +5751,18 @@ get-stdin@^4.0.1: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= -get-stdin@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" - integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g== - get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= +get-stream@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -5828,6 +5782,13 @@ github-slugger@^1.2.0: dependencies: emoji-regex ">=6.0.0 <=6.1.1" +github-slugger@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.2.1.tgz#47e904e70bf2dccd0014748142d31126cfd49508" + integrity sha512-SsZUjg/P03KPzQBt7OxJPasGw6NRO5uOgiZ5RGXVud5iSIZ0eNZeNp5rTwCxtavrRUa/A77j8mePVc5lEvk0KQ== + dependencies: + emoji-regex ">=6.0.0 <=6.1.1" + glob-base@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" @@ -5851,31 +5812,38 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-stream@^3.1.5: - version "3.1.18" - resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-3.1.18.tgz#9170a5f12b790306fdfe598f313f8f7954fd143b" - integrity sha1-kXCl8St5Awb9/lmPMT+PeVT9FDs= +glob-stream@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-6.1.0.tgz#7045c99413b3eb94888d83ab46d0b404cc7bdde4" + integrity sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ= dependencies: - glob "^4.3.1" - glob2base "^0.0.12" - minimatch "^2.0.1" - ordered-read-streams "^0.1.0" - through2 "^0.6.1" - unique-stream "^1.0.0" + extend "^3.0.0" + glob "^7.1.1" + glob-parent "^3.1.0" + is-negated-glob "^1.0.0" + ordered-read-streams "^1.0.0" + pumpify "^1.3.5" + readable-stream "^2.1.5" + remove-trailing-separator "^1.0.1" + to-absolute-glob "^2.0.0" + unique-stream "^2.0.2" -glob-watcher@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-0.0.6.tgz#b95b4a8df74b39c83298b0c05c978b4d9a3b710b" - integrity sha1-uVtKjfdLOcgymLDAXJeLTZo7cQs= - dependencies: - gaze "^0.5.1" +glob-to-regexp@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" + integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= -glob2base@^0.0.12: - version "0.0.12" - resolved "https://registry.yarnpkg.com/glob2base/-/glob2base-0.0.12.tgz#9d419b3e28f12e83a362164a277055922c9c0d56" - integrity sha1-nUGbPijxLoOjYhZKJ3BVkiycDVY= +glob-watcher@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-5.0.3.tgz#88a8abf1c4d131eb93928994bc4a593c2e5dd626" + integrity sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg== dependencies: - find-index "^0.1.1" + anymatch "^2.0.0" + async-done "^1.2.0" + chokidar "^2.0.0" + is-negated-glob "^1.0.0" + just-debounce "^1.0.0" + object.defaults "^1.1.0" glob@7.1.2: version "7.1.2" @@ -5889,16 +5857,6 @@ glob@7.1.2: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^4.3.1: - version "4.5.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-4.5.3.tgz#c6cb73d3226c1efef04de3c56d012f03377ee15f" - integrity sha1-xstz0yJsHv7wTePFbQEvAzd+4V8= - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "^2.0.1" - once "^1.3.0" - glob@^6.0.1: version "6.0.4" resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" @@ -5922,20 +5880,6 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, gl once "^1.3.0" path-is-absolute "^1.0.0" -glob@~3.1.21: - version "3.1.21" - resolved "https://registry.yarnpkg.com/glob/-/glob-3.1.21.tgz#d29e0a055dea5138f4d07ed40e8982e83c2066cd" - integrity sha1-0p4KBV3qUTj00H7UDomC6DwgZs0= - dependencies: - graceful-fs "~1.2.0" - inherits "1" - minimatch "~0.2.11" - -global-modules-path@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/global-modules-path/-/global-modules-path-2.3.0.tgz#b0e2bac6beac39745f7db5c59d26a36a0b94f7dc" - integrity sha512-HchvMJNYh9dGSCy8pOQ2O8u/hoXaL+0XhnrwH0RyLiSXMMTl9W3N6KUU73+JFOg5PGjtzl6VZzUQsnrpm7Szag== - global-modules@1.0.0, global-modules@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" @@ -5945,6 +5889,13 @@ global-modules@1.0.0, global-modules@^1.0.0: is-windows "^1.0.1" resolve-dir "^1.0.0" +global-modules@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" + integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== + dependencies: + global-prefix "^3.0.0" + global-modules@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-0.2.3.tgz#ea5a3bed42c6d6ce995a4f8a1269b5dae223828d" @@ -5974,6 +5925,15 @@ global-prefix@^1.0.1: is-windows "^1.0.1" which "^1.2.14" +global-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" + integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== + dependencies: + ini "^1.3.5" + kind-of "^6.0.2" + which "^1.3.1" + globals@^11.1.0: version "11.8.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.8.0.tgz#c1ef45ee9bed6badf0663c5cb90e8d1adec1321d" @@ -5989,6 +5949,32 @@ globals@^9.18.0: resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== +globby@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/globby/-/globby-8.0.1.tgz#b5ad48b8aa80b35b814fc1281ecc851f1d2b5b50" + integrity sha512-oMrYrJERnKBLXNLVTqhm3vPEdJ/b2ZE28xN4YARiix1NOIOBPEpOUnm844K1iu/BkphCaf2WNFwMszv8Soi1pw== + dependencies: + array-union "^1.0.1" + dir-glob "^2.0.0" + fast-glob "^2.0.2" + glob "^7.1.2" + ignore "^3.3.5" + pify "^3.0.0" + slash "^1.0.0" + +globby@8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/globby/-/globby-8.0.2.tgz#5697619ccd95c5275dbb2d6faa42087c1a941d8d" + integrity sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w== + dependencies: + array-union "^1.0.1" + dir-glob "2.0.0" + fast-glob "^2.0.2" + glob "^7.1.2" + ignore "^3.3.5" + pify "^3.0.0" + slash "^1.0.0" + globby@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-4.1.0.tgz#080f54549ec1b82a6c60e631fc82e1211dbe95f8" @@ -6033,15 +6019,6 @@ globule@^1.0.0: lodash "~4.17.10" minimatch "~3.0.2" -globule@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/globule/-/globule-0.1.0.tgz#d9c8edde1da79d125a151b79533b978676346ae5" - integrity sha1-2cjt3h2nnRJaFRt5UzuXhnY0auU= - dependencies: - glob "~3.1.21" - lodash "~1.0.1" - minimatch "~0.2.11" - glogg@^1.0.0, glogg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.1.tgz#dcf758e44789cc3f3d32c1f3562a3676e6a34810" @@ -6049,6 +6026,20 @@ glogg@^1.0.0, glogg@^1.0.1: dependencies: sparkles "^1.0.0" +glogg@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.2.tgz#2d7dd702beda22eb3bffadf880696da6d846313f" + integrity sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA== + dependencies: + sparkles "^1.0.0" + +good-listener@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50" + integrity sha1-1TswzfkxPf+33JoNR3CWqm0UXFA= + dependencies: + delegate "^3.1.2" + got@^5.0.0: version "5.7.1" resolved "https://registry.yarnpkg.com/got/-/got-5.7.1.tgz#5f81635a61e4a6589f180569ea4e381680a51f35" @@ -6070,23 +6061,16 @@ got@^5.0.0: unzip-response "^1.0.2" url-parse-lax "^1.0.0" -graceful-fs@4.X, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9: +graceful-fs@^4.0.0, graceful-fs@^4.1.15: + version "4.1.15" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" + integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== + +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg= -graceful-fs@^3.0.0: - version "3.0.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-3.0.11.tgz#7613c778a1afea62f25c630a086d7f3acbbdd818" - integrity sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg= - dependencies: - natives "^1.1.0" - -graceful-fs@~1.2.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-1.2.3.tgz#15a4806a57547cb2d2dbf27f42e89a8c3451b364" - integrity sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q= - "graceful-readlink@>= 1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" @@ -6097,89 +6081,39 @@ growl@1.10.5: resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== -gulp-babel@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/gulp-babel/-/gulp-babel-8.0.0.tgz#e0da96f4f2ec4a88dd3a3030f476e38ab2126d87" - integrity sha512-oomaIqDXxFkg7lbpBou/gnUkX51/Y/M2ZfSjL2hdqXTAlSWZcgZtd2o0cOH0r/eE8LWD0+Q/PsLsr2DKOoqToQ== - dependencies: - plugin-error "^1.0.1" - replace-ext "^1.0.0" - through2 "^2.0.0" - vinyl-sourcemaps-apply "^0.2.0" - -gulp-sourcemaps@^2.6.4: - version "2.6.4" - resolved "https://registry.yarnpkg.com/gulp-sourcemaps/-/gulp-sourcemaps-2.6.4.tgz#cbb2008450b1bcce6cd23bf98337be751bf6e30a" - integrity sha1-y7IAhFCxvM5s0jv5gze+dRv24wo= - dependencies: - "@gulp-sourcemaps/identity-map" "1.X" - "@gulp-sourcemaps/map-sources" "1.X" - acorn "5.X" - convert-source-map "1.X" - css "2.X" - debug-fabulous "1.X" - detect-newline "2.X" - graceful-fs "4.X" - source-map "~0.6.0" - strip-bom-string "1.X" - through2 "2.X" - -gulp-uglify@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/gulp-uglify/-/gulp-uglify-3.0.1.tgz#8d3eee466521bea6b10fd75dff72adf8b7ea2d97" - integrity sha512-KVffbGY9d4Wv90bW/B1KZJyunLMyfHTBbilpDvmcrj5Go0/a1G3uVpt+1gRBWSw/11dqR3coJ1oWNTt1AiXuWQ== - dependencies: - gulplog "^1.0.0" - has-gulplog "^0.1.0" - lodash "^4.13.1" - make-error-cause "^1.1.1" - safe-buffer "^5.1.2" - through2 "^2.0.0" - uglify-js "^3.0.5" - vinyl-sourcemaps-apply "^0.2.0" - -gulp-util@^3.0.0: - version "3.0.8" - resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f" - integrity sha1-AFTh50RQLifATBh8PsxQXdVLu08= - dependencies: - array-differ "^1.0.0" - array-uniq "^1.0.2" - beeper "^1.0.0" - chalk "^1.0.0" - dateformat "^2.0.0" - fancy-log "^1.1.0" - gulplog "^1.0.0" - has-gulplog "^0.1.0" - lodash._reescape "^3.0.0" - lodash._reevaluate "^3.0.0" - lodash._reinterpolate "^3.0.0" - lodash.template "^3.0.0" - minimist "^1.1.0" - multipipe "^0.1.2" - object-assign "^3.0.0" - replace-ext "0.0.1" - through2 "^2.0.0" - vinyl "^0.5.0" - -gulp@^3.9.1: - version "3.9.1" - resolved "https://registry.yarnpkg.com/gulp/-/gulp-3.9.1.tgz#571ce45928dd40af6514fc4011866016c13845b4" - integrity sha1-VxzkWSjdQK9lFPxAEYZgFsE4RbQ= +gulp-cli@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/gulp-cli/-/gulp-cli-2.0.1.tgz#7847e220cb3662f2be8a6d572bf14e17be5a994b" + integrity sha512-RxujJJdN8/O6IW2nPugl7YazhmrIEjmiVfPKrWt68r71UCaLKS71Hp0gpKT+F6qOUFtr7KqtifDKaAJPRVvMYQ== dependencies: + ansi-colors "^1.0.1" archy "^1.0.0" - chalk "^1.0.0" - deprecated "^0.0.1" - gulp-util "^3.0.0" - interpret "^1.0.0" - liftoff "^2.1.0" - minimist "^1.1.0" - orchestrator "^0.3.0" + array-sort "^1.0.0" + color-support "^1.1.3" + concat-stream "^1.6.0" + copy-props "^2.0.1" + fancy-log "^1.3.2" + gulplog "^1.0.0" + interpret "^1.1.0" + isobject "^3.0.1" + liftoff "^2.5.0" + matchdep "^2.0.0" + mute-stdout "^1.0.0" pretty-hrtime "^1.0.0" - semver "^4.1.0" - tildify "^1.0.0" - v8flags "^2.0.2" - vinyl-fs "^0.3.0" + replace-homedir "^1.0.0" + semver-greatest-satisfied-range "^1.1.0" + v8flags "^3.0.1" + yargs "^7.1.0" + +gulp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/gulp/-/gulp-4.0.0.tgz#95766c601dade4a77ed3e7b2b6dc03881b596366" + integrity sha1-lXZsYB2t5Kd+0+eyttwDiBtZY2Y= + dependencies: + glob-watcher "^5.0.0" + gulp-cli "^2.0.0" + undertaker "^1.0.0" + vinyl-fs "^3.0.0" gulplog@^1.0.0: version "1.0.0" @@ -6188,17 +6122,18 @@ gulplog@^1.0.0: dependencies: glogg "^1.0.0" -gzip-size@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-3.0.0.tgz#546188e9bdc337f673772f81660464b389dce520" - integrity sha1-VGGI6b3DN/Zzdy+BZgRks4nc5SA= +gzip-size@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.0.0.tgz#a55ecd99222f4c48fd8c01c625ce3b349d0a0e80" + integrity sha512-5iI7omclyqrnWw4XbXAmGhPsABkSIDQonv2K0h61lybgofWa6iZyvrI3r2zsJH4P8Nb64fFVzlvfhs0g7BBxAA== dependencies: duplexer "^0.1.1" + pify "^3.0.0" -handle-thing@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4" - integrity sha1-/Xqtcmvxpf0W38KbL3pmAdJxOcQ= +handle-thing@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754" + integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ== handsontable@^2.0.0: version "2.0.0" @@ -6246,11 +6181,6 @@ has-cors@1.1.0: resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39" integrity sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk= -has-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" - integrity sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo= - has-flag@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" @@ -6261,13 +6191,6 @@ has-flag@^3.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= -has-gulplog@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/has-gulplog/-/has-gulplog-0.1.0.tgz#6414c82913697da51590397dafb12f22967811ce" - integrity sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4= - dependencies: - sparkles "^1.0.0" - has-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" @@ -6345,7 +6268,7 @@ hasha@^2.2.0: is-stream "^1.0.1" pinkie-promise "^2.0.0" -he@1.1.1, he@1.1.x: +he@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= @@ -6424,42 +6347,6 @@ html-entities@^1.2.0: resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" integrity sha1-DfKTUfByEWNRXfueVUPl9u7VFi8= -html-minifier@^3.2.3: - version "3.5.20" - resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-3.5.20.tgz#7b19fd3caa0cb79f7cde5ee5c3abdf8ecaa6bb14" - integrity sha512-ZmgNLaTp54+HFKkONyLFEfs5dd/ZOtlquKaTnqIWFmx3Av5zG6ZPcV2d0o9XM2fXOTxxIf6eDcwzFFotke/5zA== - dependencies: - camel-case "3.0.x" - clean-css "4.2.x" - commander "2.17.x" - he "1.1.x" - param-case "2.1.x" - relateurl "0.2.x" - uglify-js "3.4.x" - -html-webpack-plugin@^2.30.1: - version "2.30.1" - resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-2.30.1.tgz#7f9c421b7ea91ec460f56527d78df484ee7537d5" - integrity sha1-f5xCG36pHsRg9WUn1430hO51N9U= - dependencies: - bluebird "^3.4.7" - html-minifier "^3.2.3" - loader-utils "^0.2.16" - lodash "^4.17.3" - pretty-error "^2.0.2" - toposort "^1.0.0" - -htmlparser2@3.8.x: - version "3.8.3" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.8.3.tgz#996c28b191516a8be86501a7d79757e5c70c1068" - integrity sha1-mWwosZFRaovoZQGn15dX5ccMEGg= - dependencies: - domelementtype "1" - domhandler "2.3" - domutils "1.5" - entities "1.0" - readable-stream "1.1" - htmlparser2@^3.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.0.tgz#5f5e422dcf6119c0d983ed36260ce9ded0bee464" @@ -6472,28 +6359,6 @@ htmlparser2@^3.10.0: inherits "^2.0.1" readable-stream "^3.0.6" -htmlparser2@^3.9.2: - version "3.9.2" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338" - integrity sha1-G9+HrMoPP55T+k/M6w9LTLsAszg= - dependencies: - domelementtype "^1.3.0" - domhandler "^2.3.0" - domutils "^1.5.1" - entities "^1.1.1" - inherits "^2.0.1" - readable-stream "^2.0.2" - -htmlparser2@~3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.3.0.tgz#cc70d05a59f6542e43f0e685c982e14c924a9efe" - integrity sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4= - dependencies: - domelementtype "1" - domhandler "2.1" - domutils "1.1" - readable-stream "1.0" - http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" @@ -6514,17 +6379,17 @@ http-parser-js@>=0.4.0: resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.13.tgz#3bd6d6fde6e3172c9334c3b33b6c193d80fe1137" integrity sha1-O9bW/ebjFyyTNMOzO2wZPYD+ETc= -http-proxy-middleware@~0.17.4: - version "0.17.4" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz#642e8848851d66f09d4f124912846dbaeb41b833" - integrity sha1-ZC6ISIUdZvCdTxJJEoRtuutBuDM= +http-proxy-middleware@^0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a" + integrity sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q== dependencies: - http-proxy "^1.16.2" - is-glob "^3.1.0" - lodash "^4.17.2" - micromatch "^2.3.11" + http-proxy "^1.17.0" + is-glob "^4.0.0" + lodash "^4.17.11" + micromatch "^3.1.10" -http-proxy@^1.13.0, http-proxy@^1.16.2: +http-proxy@^1.13.0, http-proxy@^1.17.0: version "1.17.0" resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.17.0.tgz#7ad38494658f84605e2f6db4436df410f4e5be9a" integrity sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g== @@ -6559,7 +6424,7 @@ iconv-lite@0.4.23: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@^0.4.17, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: +iconv-lite@^0.4.24, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -6571,12 +6436,12 @@ icss-replace-symbols@^1.1.0: resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" integrity sha1-Bupvg2ead0njhs/h/oEq5dsiPe0= -icss-utils@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-2.1.0.tgz#83f0a0ec378bf3246178b6c2ad9136f135b1c962" - integrity sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI= +icss-utils@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.0.tgz#339dbbffb9f8729a243b701e1c29d4cc58c52f0e" + integrity sha512-3DEun4VOeMvSczifM3F2cKQrDQ5Pj6WKhkOq6HD4QTnDUAq8MQRxy5TX6Sy1iY6WPBe4gQ3p5vTECjbIkglkkQ== dependencies: - postcss "^6.0.1" + postcss "^7.0.14" ieee754@^1.1.4: version "1.1.12" @@ -6615,12 +6480,15 @@ ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -import-cwd@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" - integrity sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk= - dependencies: - import-from "^2.1.0" +immer@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/immer/-/immer-1.10.0.tgz#bad67605ba9c810275d91e1c2a47d4582e98286d" + integrity sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg== + +immer@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/immer/-/immer-1.7.2.tgz#a51e9723c50b27e132f6566facbec1c85fc69547" + integrity sha512-4Urocwu9+XLDJw4Tc6ZCg7APVjjLInCFvO4TwGsAYV5zT6YYSor14dsZR0+0tHlDIN92cFUOq+i7fC00G5vTxA== import-fresh@^3.0.0: version "3.0.0" @@ -6630,21 +6498,6 @@ import-fresh@^3.0.0: parent-module "^1.0.0" resolve-from "^4.0.0" -import-from@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1" - integrity sha1-M1238qev/VOqpHHUuAId7ja387E= - dependencies: - resolve-from "^3.0.0" - -import-local@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-1.0.0.tgz#5e4ffdc03f4fe6c009c6729beb29631c2f8227bc" - integrity sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ== - dependencies: - pkg-dir "^2.0.0" - resolve-cwd "^2.0.0" - import-local@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" @@ -6696,11 +6549,6 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-1.0.2.tgz#ca4309dadee6b54cc0b8d247e8d7c7a0975bdc9b" - integrity sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js= - inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" @@ -6711,51 +6559,31 @@ inherits@2.0.1: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= -ini@^1.3.4, ini@~1.3.0: +ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== -inquirer@3.0.6: - version "3.0.6" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.0.6.tgz#e04aaa9d05b7a3cb9b0f407d04375f0447190347" - integrity sha1-4EqqnQW3o8ubD0B9BDdfBEcZA0c= - dependencies: - ansi-escapes "^1.1.0" - chalk "^1.0.0" - cli-cursor "^2.1.0" - cli-width "^2.0.0" - external-editor "^2.0.1" - figures "^2.0.0" - lodash "^4.3.0" - mute-stream "0.0.7" - run-async "^2.2.0" - rx "^4.1.0" - string-width "^2.0.0" - strip-ansi "^3.0.0" - through "^2.3.6" - -inquirer@3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" - integrity sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ== +inquirer@6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.0.tgz#51adcd776f661369dc1e894859c2560a224abdd8" + integrity sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg== dependencies: ansi-escapes "^3.0.0" chalk "^2.0.0" cli-cursor "^2.1.0" cli-width "^2.0.0" - external-editor "^2.0.4" + external-editor "^3.0.0" figures "^2.0.0" - lodash "^4.3.0" + lodash "^4.17.10" mute-stream "0.0.7" run-async "^2.2.0" - rx-lite "^4.0.8" - rx-lite-aggregates "^4.0.8" + rxjs "^6.1.0" string-width "^2.1.0" strip-ansi "^4.0.0" through "^2.3.6" -inquirer@^6.1.0: +inquirer@6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.1.tgz#9943fc4882161bdb0b0c9276769c75b32dbfcd52" integrity sha512-088kl3DRT2dLU5riVMKKr1DlImd6X7smDhpXUCkJDCKvTEJeRiXh0G132HG9u5a+6Ylw9plFRY7RuTnwohYSpg== @@ -6774,14 +6602,34 @@ inquirer@^6.1.0: strip-ansi "^5.0.0" through "^2.3.6" -internal-ip@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-1.2.0.tgz#ae9fbf93b984878785d50a8de1b356956058cf5c" - integrity sha1-rp+/k7mEh4eF1QqN4bNWlWBYz1w= +inquirer@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.2.tgz#46941176f65c9eb20804627149b743a218f25406" + integrity sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA== dependencies: - meow "^3.3.0" + ansi-escapes "^3.2.0" + chalk "^2.4.2" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^3.0.3" + figures "^2.0.0" + lodash "^4.17.11" + mute-stream "0.0.7" + run-async "^2.2.0" + rxjs "^6.4.0" + string-width "^2.1.0" + strip-ansi "^5.0.0" + through "^2.3.6" -interpret@^1.0.0, interpret@^1.1.0: +internal-ip@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.2.0.tgz#46e81b638d84c338e5c67e42b1a17db67d0814fa" + integrity sha512-ZY8Rk+hlvFeuMmG5uH1MXhhdeMntmIaxaInvAmzMq/SHV8rv4Kh+6GiQNNDQd0wZFrcO+FiTBo8lui/osKOyJw== + dependencies: + default-gateway "^4.0.1" + ipaddr.js "^1.9.0" + +interpret@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" integrity sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ= @@ -6803,6 +6651,11 @@ invert-kv@^2.0.0: resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== +ip-regex@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" + integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= + ip@^1.1.0, ip@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" @@ -6813,6 +6666,11 @@ ipaddr.js@1.8.0: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.0.tgz#eaa33d6ddd7ace8f7f6fe0c9ca0440e706738b1e" integrity sha1-6qM9bd16zo9/b+DJygRA5wZzix4= +ipaddr.js@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65" + integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA== + irregular-plurals@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-1.4.0.tgz#2ca9b033651111855412f16be5d77c62a458a766" @@ -6880,11 +6738,16 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" -is-buffer@^1.1.4, is-buffer@^1.1.5: +is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== +is-buffer@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725" + integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw== + is-builtin-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" @@ -7065,6 +6928,11 @@ is-in-browser@^1.1.3: resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835" integrity sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU= +is-negated-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" + integrity sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI= + is-npm@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" @@ -7111,6 +6979,11 @@ is-path-cwd@^1.0.0: resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" integrity sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0= +is-path-cwd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.0.0.tgz#d4777a8e227a00096a31f030db3770f84b116c02" + integrity sha512-m5dHHzpOXEiv18JEORttBO64UgTEypx99vCxQLjbBvGhOJxnTNglYoFXxwo6AbsQb79sqqycQEHv2hWkHZAijA== + is-path-in-cwd@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz#5ac48b345ef675339bd6c7a48a912110b241cf52" @@ -7118,6 +6991,13 @@ is-path-in-cwd@^1.0.0: dependencies: is-path-inside "^1.0.0" +is-path-in-cwd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-2.0.0.tgz#68e452a6eec260500cec21e029c0a44cc0dcd2ea" + integrity sha512-6Vz5Gc9s/sDA3JBVu0FzWufm8xaBsqy1zn8Q6gmvGP6nSDMw78aS4poBNeatWjaRpTpxxLn1WOndAiOlk+qY8A== + dependencies: + is-path-inside "^1.0.0" + is-path-inside@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" @@ -7147,7 +7027,7 @@ is-primitive@^2.0.0: resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU= -is-promise@^2.1, is-promise@^2.1.0: +is-promise@^2.0.0, is-promise@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= @@ -7186,23 +7066,16 @@ is-retry-allowed@^1.0.0: resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" integrity sha1-EaBgVotnM5REAz0BJaYaINVk+zQ= -is-root@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-root/-/is-root-1.0.0.tgz#07b6c233bc394cd9d02ba15c966bd6660d6342d5" - integrity sha1-B7bCM7w5TNnQK6FclmvWZg1jQtU= +is-root@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.0.0.tgz#838d1e82318144e5a6f77819d90207645acc7019" + integrity sha512-F/pJIk8QD6OX5DNhRB7hWamLsUilmkDGho48KbgZ6xg/lmAZXHxzXQ91jzB3yRSw5kdQGGGc4yz8HYhTYIMWPg== is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= -is-svg@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-2.1.0.tgz#cf61090da0d9efbcab8722deba6f032208dbb0e9" - integrity sha1-z2EJDaDZ77yrhyLeum8DIgjbsOk= - dependencies: - html-comment-regex "^1.1.0" - is-svg@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75" @@ -7234,11 +7107,16 @@ is-url@^1.2.1: resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== -is-utf8@^0.2.0: +is-utf8@^0.2.0, is-utf8@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= +is-valid-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz#29bf3eff701be2d4d315dbacc39bc39fe8f601aa" + integrity sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao= + is-whitespace-character@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.2.tgz#ede53b4c6f6fb3874533751ec9280d01928d03ed" @@ -7298,7 +7176,7 @@ isobject@^3.0.0, isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= -isstream@0.1.x, isstream@~0.1.2: +isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= @@ -7338,12 +7216,16 @@ jquery@2: resolved "https://registry.yarnpkg.com/jquery/-/jquery-2.2.4.tgz#2c89d6889b5eac522a7eea32c14521559c6cbf02" integrity sha1-LInWiJterFIqfuoywUUhVZxsvwI= +jquery@>=1.12.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.2.1.tgz#5c4d9de652af6cd0a770154a631bba12b015c787" + jquery@^3.0.0: version "3.3.1" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca" integrity sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg== -js-base64@^2.1.8, js-base64@^2.1.9: +js-base64@^2.1.8: version "2.4.9" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.9.tgz#748911fb04f48a60c4771b375cac45a80df11c03" integrity sha512-xcinL3AuDJk7VSzsHgb9DvvIXayBbadtMZ4HFPx8rUszbW1MuNMlwYVC4zzCZ6e1sqZpnNS5ZFYOhXqA39T7LQ== @@ -7381,59 +7263,11 @@ js-yaml@^3.12.0, js-yaml@^3.5.2, js-yaml@^3.9.0: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@~3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" - integrity sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A= - dependencies: - argparse "^1.0.7" - esprima "^2.6.0" - -js2xmlparser@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/js2xmlparser/-/js2xmlparser-3.0.0.tgz#3fb60eaa089c5440f9319f51760ccd07e2499733" - integrity sha1-P7YOqgicVED5MZ9RdgzNB+JJlzM= - dependencies: - xmlcreate "^1.0.1" - jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jsdoc-api@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/jsdoc-api/-/jsdoc-api-4.0.3.tgz#f87357856349a0be40a03e64711c34c74754ba20" - integrity sha512-dfYq9JgB+XahY0XfSEw93PmXmocjwYcvJ5aMuQUJ/OdDRGWamf2SSOk3W06Bsj8qdjp/UdefzqpP/mpwsvHuvA== - dependencies: - array-back "^2.0.0" - cache-point "^0.4.1" - collect-all "^1.0.3" - file-set "^2.0.0" - fs-then-native "^2.0.0" - jsdoc "~3.5.5" - object-to-spawn-args "^1.1.1" - temp-path "^1.0.0" - walk-back "^3.0.0" - -jsdoc@~3.5.5: - version "3.5.5" - resolved "https://registry.yarnpkg.com/jsdoc/-/jsdoc-3.5.5.tgz#484521b126e81904d632ff83ec9aaa096708fa4d" - integrity sha512-6PxB65TAU4WO0Wzyr/4/YhlGovXl0EVYfpKbpSroSj0qBxT4/xod/l40Opkm38dRHRdQgdeY836M0uVnJQG7kg== - dependencies: - babylon "7.0.0-beta.19" - bluebird "~3.5.0" - catharsis "~0.8.9" - escape-string-regexp "~1.0.5" - js2xmlparser "~3.0.0" - klaw "~2.0.0" - marked "~0.3.6" - mkdirp "~0.5.1" - requizzle "~0.2.1" - strip-json-comments "~2.0.1" - taffydb "2.6.2" - underscore "~1.8.3" - jsesc@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" @@ -7449,24 +7283,6 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= -jshint@^2.9.6: - version "2.9.6" - resolved "https://registry.yarnpkg.com/jshint/-/jshint-2.9.6.tgz#19b34e578095a34928fe006135a6cb70137b9c08" - integrity sha512-KO9SIAKTlJQOM4lE64GQUtGBRpTOuvbrRrSZw3AhUxMNG266nX9hK2cKA4SBhXOj0irJGyNyGSLT62HGOVDEOA== - dependencies: - cli "~1.0.0" - console-browserify "1.1.x" - exit "0.1.x" - htmlparser2 "3.8.x" - lodash "~4.17.10" - minimatch "~3.0.2" - shelljs "0.3.x" - strip-json-comments "1.0.x" - unicode-5.2.0 "^0.7.5" - optionalDependencies: - phantom "~4.0.1" - phantomjs-prebuilt "~2.1.7" - json-loader@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d" @@ -7512,6 +7328,20 @@ json5@^0.5.0, json5@^0.5.1: resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" + +json5@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.0.tgz#e7a0c62c48285c628d20a10b85c89bb807c32850" + integrity sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ== + dependencies: + minimist "^1.2.0" + jsonfile@^2.1.0: version "2.4.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" @@ -7581,6 +7411,19 @@ jss@^9.8.7: symbol-observable "^1.1.0" warning "^3.0.0" +jstransformer@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/jstransformer/-/jstransformer-1.0.0.tgz#ed8bf0921e2f3f1ed4d5c1a44f68709ed24722c3" + integrity sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM= + dependencies: + is-promise "^2.0.0" + promise "^7.0.1" + +just-debounce@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/just-debounce/-/just-debounce-1.0.0.tgz#87fccfaeffc0b68cd19d55f6722943f929ea35ea" + integrity sha1-h/zPrv/AtozRnVX2cilD+SnqNeo= + just-extend@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-3.0.0.tgz#cee004031eaabf6406da03a7b84e4fe9d78ef288" @@ -7701,7 +7544,7 @@ kind-of@^4.0.0: dependencies: is-buffer "^1.1.5" -kind-of@^5.0.0: +kind-of@^5.0.0, kind-of@^5.0.2: version "5.1.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== @@ -7718,18 +7561,16 @@ klaw@^1.0.0: optionalDependencies: graceful-fs "^4.1.9" -klaw@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/klaw/-/klaw-2.0.0.tgz#59c128e0dc5ce410201151194eeb9cbf858650f6" - integrity sha1-WcEo4Nxc5BAgEVEZTuucv4WGUPY= - dependencies: - graceful-fs "^4.1.9" - kleur@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/kleur/-/kleur-2.0.2.tgz#b704f4944d95e255d038f0cb05fb8a602c55a300" integrity sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ== +kleur@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.2.tgz#83c7ec858a41098b613d5998a7b653962b504f68" + integrity sha512-3h7B2WRT5LNXOtQiAaWonilegHcPSf9nLVXlSTci8lu1dZUuui61+EsPEZqSVxY7rXYmB2DVKMQILxaO5WL61Q== + last-call-webpack-plugin@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz#9742df0e10e3cf46e5c0381c2de90d3a7a2d7555" @@ -7745,6 +7586,14 @@ last-line-stream@^1.0.0: dependencies: through2 "^2.0.0" +last-run@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/last-run/-/last-run-1.1.1.tgz#45b96942c17b1c79c772198259ba943bebf8ca5b" + integrity sha1-RblpQsF7HHnHchmCWbqUO+v4yls= + dependencies: + default-resolution "^2.0.0" + es6-weak-map "^2.0.1" + latest-version@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-2.0.0.tgz#56f8d6139620847b8017f8f1f4d78e211324168b" @@ -7766,6 +7615,18 @@ latex-to-unicode-converter@^0.5.2: dependencies: latex-parser "^0.6.2" +lazy-cache@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" + integrity sha1-odePw6UEdMuAhF07O24dpJpEbo4= + +lazystream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" + integrity sha1-9plf4PggOS9hOWvolGJAe7dxaOQ= + dependencies: + readable-stream "^2.0.5" + lcid@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" @@ -7780,6 +7641,13 @@ lcid@^2.0.0: dependencies: invert-kv "^2.0.0" +lead@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lead/-/lead-1.0.0.tgz#6f14f99a37be3a9dd784f5495690e5903466ee42" + integrity sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI= + dependencies: + flush-write-stream "^1.0.2" + leven@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580" @@ -7793,7 +7661,7 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -liftoff@^2.1.0: +liftoff@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-2.5.0.tgz#2009291bb31cea861bbf10a7c15a28caf75c31ec" integrity sha1-IAkpG7Mc6oYbvxCnwVooyvdcMew= @@ -7838,17 +7706,7 @@ loader-runner@^2.3.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.1.tgz#026f12fe7c3115992896ac02ba022ba92971b979" integrity sha512-By6ZFY7ETWOc9RFaAIb23IjJVcM4dvJC/N57nmdz9RSkMXvAXGI7SyVlAw3v8vjtDRlqThgVDVmTnr9fqMlxkw== -loader-utils@^0.2.16: - version "0.2.17" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" - integrity sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g= - dependencies: - big.js "^3.1.3" - emojis-list "^2.0.0" - json5 "^0.5.0" - object-assign "^4.0.1" - -loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0: +loader-utils@1.1.0, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd" integrity sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0= @@ -7857,6 +7715,15 @@ loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0: emojis-list "^2.0.0" json5 "^0.5.0" +loader-utils@1.2.3, loader-utils@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" + integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA== + dependencies: + big.js "^5.2.2" + emojis-list "^2.0.0" + json5 "^1.0.1" + locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -7873,61 +7740,11 @@ locate-path@^3.0.0: p-locate "^3.0.0" path-exists "^3.0.0" -lodash._basecopy@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" - integrity sha1-jaDmqHbPNEwK2KVIghEd08XHyjY= - -lodash._basetostring@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz#d1861d877f824a52f669832dcaf3ee15566a07d5" - integrity sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U= - -lodash._basevalues@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz#5b775762802bde3d3297503e26300820fdf661b7" - integrity sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc= - -lodash._getnative@^3.0.0: - version "3.9.1" - resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" - integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U= - -lodash._isiterateecall@^3.0.0: - version "3.0.9" - resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" - integrity sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw= - -lodash._reescape@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reescape/-/lodash._reescape-3.0.0.tgz#2b1d6f5dfe07c8a355753e5f27fac7f1cde1616a" - integrity sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo= - -lodash._reevaluate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz#58bc74c40664953ae0b124d806996daca431e2ed" - integrity sha1-WLx0xAZklTrgsSTYBpltrKQx4u0= - -lodash._reinterpolate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" - integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= - -lodash._root@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692" - integrity sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI= - lodash.assign@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" integrity sha1-DZnzzNem0mHRm9rrkkUAXShYCOc= -lodash.camelcase@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" - integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= - lodash.clonedeep@^4.3.2: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" @@ -7938,37 +7755,11 @@ lodash.debounce@^4.0.3, lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= -lodash.escape@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-3.2.0.tgz#995ee0dc18c1b48cc92effae71a10aab5b487698" - integrity sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg= - dependencies: - lodash._root "^3.0.0" - lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= -lodash.isarguments@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" - integrity sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo= - -lodash.isarray@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" - integrity sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U= - -lodash.keys@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" - integrity sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo= - dependencies: - lodash._getnative "^3.0.0" - lodash.isarguments "^3.0.0" - lodash.isarray "^3.0.0" - lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -7979,44 +7770,11 @@ lodash.mergewith@^4.6.0: resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927" integrity sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ== -lodash.restparam@^3.0.0: - version "3.6.1" - resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" - integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU= - -lodash.startcase@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.startcase/-/lodash.startcase-4.4.0.tgz#9436e34ed26093ed7ffae1936144350915d9add8" - integrity sha1-lDbjTtJgk+1/+uGTYUQ1CRXZrdg= - lodash.tail@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664" integrity sha1-0jM6NtnncXyK0vfKyv7HwytERmQ= -lodash.template@^3.0.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-3.6.2.tgz#f8cdecc6169a255be9098ae8b0c53d378931d14f" - integrity sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8= - dependencies: - lodash._basecopy "^3.0.0" - lodash._basetostring "^3.0.0" - lodash._basevalues "^3.0.0" - lodash._isiterateecall "^3.0.0" - lodash._reinterpolate "^3.0.0" - lodash.escape "^3.0.0" - lodash.keys "^3.0.0" - lodash.restparam "^3.0.0" - lodash.templatesettings "^3.0.0" - -lodash.templatesettings@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz#fb307844753b66b9f1afa54e262c745307dba8e5" - integrity sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU= - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.escape "^3.0.0" - lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" @@ -8027,16 +7785,11 @@ lodash@^3.8.0: resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" integrity sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y= -lodash@^4.0.0, lodash@^4.0.1, lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.5.0, lodash@~4.17.10: +lodash@^4.0.0, lodash@^4.0.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.5.0, lodash@~4.17.10: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== -lodash@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-1.0.2.tgz#8f57560c83b59fc270bd3d561b690043430e2551" - integrity sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE= - log-symbols@^2.1.0, log-symbols@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" @@ -8067,7 +7820,12 @@ longest-streak@^2.0.1: resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.2.tgz#2421b6ba939a443bb9ffebf596585a50b4c38e2e" integrity sha512-TmYTeEYxiAmSVdpbnQDXGtvYOIRsCMg89CVZzwzc2o7GFL1CjoiRPjH5ec0NFAVlAx3fVof9dX/t6KKRAo2OWA== -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1: +longest@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" + integrity sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc= + +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -8082,21 +7840,11 @@ loud-rejection@^1.0.0, loud-rejection@^1.2.0: currently-unhandled "^0.4.1" signal-exit "^3.0.0" -lower-case@^1.1.1: - version "1.1.4" - resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" - integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw= - lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== -lru-cache@2: - version "2.7.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" - integrity sha1-bUUk6LlV+V1PW1iFHOId1y+06VI= - lru-cache@4.1.x, lru-cache@^4.0.1, lru-cache@^4.1.1, lru-cache@^4.1.2, lru-cache@^4.1.3: version "4.1.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c" @@ -8105,12 +7853,20 @@ lru-cache@4.1.x, lru-cache@^4.0.1, lru-cache@^4.1.1, lru-cache@^4.1.2, lru-cache pseudomap "^1.0.2" yallist "^2.1.2" -lru-queue@0.1: - version "0.1.0" - resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" - integrity sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM= +lru-cache@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" + integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== dependencies: - es5-ext "~0.10.2" + pseudomap "^1.0.2" + yallist "^2.1.2" + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" magic-string@^0.22.4: version "0.22.5" @@ -8119,12 +7875,12 @@ magic-string@^0.22.4: dependencies: vlq "^0.2.2" -magic-string@^0.25.1: - version "0.25.1" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.1.tgz#b1c248b399cd7485da0fe7385c2fc7011843266e" - integrity sha512-sCuTz6pYom8Rlt4ISPFn6wuFodbKMIHUMv4Qko9P17dpxb7s52KJTmRuZZqHdGmLCK9AOcDare039nRIcfdkEg== +magic-string@^0.25.2: + version "0.25.2" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.2.tgz#139c3a729515ec55e96e69e82a11fe890a293ad9" + integrity sha512-iLs9mPjh9IuTtRsqqhNGYcZXGei0Nh/A4xirrsqW7c+QhKVFL2vm7U09ru6cHRD22azaP/wMDgI+HCqbETMTtg== dependencies: - sourcemap-codec "^1.4.1" + sourcemap-codec "^1.4.4" make-dir@^1.0.0: version "1.3.0" @@ -8133,17 +7889,13 @@ make-dir@^1.0.0: dependencies: pify "^3.0.0" -make-error-cause@^1.1.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/make-error-cause/-/make-error-cause-1.2.2.tgz#df0388fcd0b37816dff0a5fb8108939777dcbc9d" - integrity sha1-3wOI/NCzeBbf8KX7gQiTl3fcvJ0= +make-dir@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== dependencies: - make-error "^1.2.0" - -make-error@^1.2.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8" - integrity sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g== + pify "^4.0.1" + semver "^5.6.0" make-iterator@^1.0.0: version "1.0.1" @@ -8152,6 +7904,11 @@ make-iterator@^1.0.0: dependencies: kind-of "^6.0.2" +mamacro@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/mamacro/-/mamacro-0.0.3.tgz#ad2c9576197c9f1abf308d0787865bd975a3f3e4" + integrity sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA== + map-age-cleaner@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.2.tgz#098fb15538fd3dbe461f12745b0ca8568d4e3f74" @@ -8194,10 +7951,23 @@ markdown-to-jsx@^6.6.8: prop-types "^15.5.10" unquote "^1.1.0" -marked@~0.3.6: - version "0.3.19" - resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.19.tgz#5d47f709c4c9fc3c216b6d46127280f40b39d790" - integrity sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg== +markdown-to-jsx@^6.9.1: + version "6.9.3" + resolved "https://registry.yarnpkg.com/markdown-to-jsx/-/markdown-to-jsx-6.9.3.tgz#31719e3c54517ba9805db81d53701b89f5d2ed7e" + integrity sha512-iXteiv317VZd1vk/PBH5MWMD4r0XWekoWCHRVVadBcnCtxavhtfV1UaEaQgq9KyckTv31L60ASh5ZVVrOh37Qg== + dependencies: + prop-types "^15.6.2" + unquote "^1.1.0" + +matchdep@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/matchdep/-/matchdep-2.0.0.tgz#c6f34834a0d8dbc3b37c27ee8bbcb27c7775582e" + integrity sha1-xvNINKDY28OzfCfui7yyfHd1WC4= + dependencies: + findup-sync "^2.0.0" + micromatch "^3.0.4" + resolve "^1.4.0" + stack-trace "0.0.10" matcher-collection@^1.0.0: version "1.0.5" @@ -8213,11 +7983,6 @@ matcher@^0.1.1: dependencies: escape-string-regexp "^1.0.4" -math-expression-evaluator@^1.2.14: - version "1.2.17" - resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac" - integrity sha1-3oGf282E3M2PrlnGrreWFbnSZqw= - math-random@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac" @@ -8275,21 +8040,7 @@ mem@^4.0.0: mimic-fn "^1.0.0" p-is-promise "^1.1.0" -memoizee@0.4.X: - version "0.4.14" - resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.14.tgz#07a00f204699f9a95c2d9e77218271c7cd610d57" - integrity sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg== - dependencies: - d "1" - es5-ext "^0.10.45" - es6-weak-map "^2.0.2" - event-emitter "^0.3.5" - is-promise "^2.1" - lru-queue "0.1" - next-tick "1" - timers-ext "^0.1.5" - -memory-fs@^0.4.0, memory-fs@~0.4.1: +memory-fs@^0.4.0, memory-fs@^0.4.1, memory-fs@~0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= @@ -8297,7 +8048,7 @@ memory-fs@^0.4.0, memory-fs@~0.4.1: errno "^0.1.3" readable-stream "^2.0.1" -meow@^3.3.0, meow@^3.7.0: +meow@^3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= @@ -8325,12 +8076,17 @@ merge-source-map@^1.1.0: dependencies: source-map "^0.6.1" +merge2@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.3.tgz#7ee99dbd69bb6481689253f018488a1b902b0ed5" + integrity sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA== + methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= -micromatch@^2.1.5, micromatch@^2.3.11, micromatch@^2.3.7: +micromatch@^2.1.5, micromatch@^2.3.7: version "2.3.11" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" integrity sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU= @@ -8405,12 +8161,12 @@ mime@1.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ== -mime@^1.3.4, mime@^1.5.0: +mime@^1.3.4: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@^2.0.3, mime@^2.3.1: +mime@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/mime/-/mime-2.3.1.tgz#b1621c54d63b97c47d3cfe7f7215f7d64517c369" integrity sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg== @@ -8420,10 +8176,10 @@ mimic-fn@^1.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== -mini-css-extract-plugin@^0.4.4: - version "0.4.4" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.4.tgz#c10410a004951bd3cedac1da69053940fccb625d" - integrity sha512-o+Jm+ocb0asEngdM6FsZWtZsRzA8koFUudIDwYUfl94M3PejPHG7Vopw5hN9V8WsMkSFpm3tZP3Fesz89EyrfQ== +mini-css-extract-plugin@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.5.0.tgz#ac0059b02b9692515a637115b0cc9fed3a35c7b0" + integrity sha512-IuaLjruM0vMKhUUT51fQdQzBYTX49dLj8w68ALEAe2A4iYNpIC4eMac67mt3NzycvjOlf07/kYxJDc0RTl1Wqw== dependencies: loader-utils "^1.1.0" schema-utils "^1.0.0" @@ -8446,41 +8202,19 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2: +"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: brace-expansion "^1.1.7" -minimatch@3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" - integrity sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q= - dependencies: - brace-expansion "^1.0.0" - -minimatch@^2.0.1: - version "2.0.10" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-2.0.10.tgz#8d087c39c6b38c001b97fca7ce6d0e1e80afbac7" - integrity sha1-jQh8OcazjAAbl/ynzm0OHoCvusc= - dependencies: - brace-expansion "^1.0.0" - -minimatch@~0.2.11: - version "0.2.14" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.2.14.tgz#c74e780574f63c6f9a090e90efbe6ef53a6a756a" - integrity sha1-x054BXT2PG+aCQ6Q775u9TpqdWo= - dependencies: - lru-cache "2" - sigmund "~1.0.0" - minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= -minimist@1.2.0, minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0: +minimist@1.2.0, minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= @@ -8521,6 +8255,22 @@ mississippi@^2.0.0: stream-each "^1.1.0" through2 "^2.0.0" +mississippi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" + integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA== + dependencies: + concat-stream "^1.5.0" + duplexify "^3.4.2" + end-of-stream "^1.1.0" + flush-write-stream "^1.0.0" + from2 "^2.1.0" + parallel-transform "^1.1.0" + pump "^3.0.0" + pumpify "^1.3.3" + stream-each "^1.1.0" + through2 "^2.0.0" + mixin-deep@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" @@ -8537,11 +8287,6 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" -mkdirp2@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp2/-/mkdirp2-1.0.4.tgz#56de1f8f5c93cf2199906362eba0f9f262ee4437" - integrity sha512-Q2PKB4ZR4UPtjLl76JfzlgSCUZhSV1AXQgAZa1qt5RiaALFjP/CDrGvFBrOz7Ck6McPcwMAxTsJvWOUjOU8XMw== - mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" @@ -8588,6 +8333,11 @@ move-concurrently@^1.0.1: rimraf "^2.5.4" run-queue "^1.0.3" +mri@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.4.tgz#7cb1dd1b9b40905f1fac053abe25b6720f44744a" + integrity sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w== + ms@0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" @@ -8636,12 +8386,10 @@ multimatch@^2.1.0: arrify "^1.0.0" minimatch "^3.0.0" -multipipe@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/multipipe/-/multipipe-0.1.2.tgz#2a8f2ddf70eed564dff2d57f1e1a137d9f05078b" - integrity sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s= - dependencies: - duplexer2 "0.0.2" +mute-stdout@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mute-stdout/-/mute-stdout-1.0.1.tgz#acb0300eb4de23a7ddeec014e3e96044b3472331" + integrity sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg== mute-stream@0.0.7: version "0.0.7" @@ -8670,11 +8418,6 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" -natives@^1.1.0: - version "1.1.6" - resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.6.tgz#a603b4a498ab77173612b9ea1acdec4d980f00bb" - integrity sha512-6+TDFewD4yxY14ptjKaS63GVdtKiES1pTPyxn9Jb0rBqPMZ7VcCiooEhPNsr+mqHtMGxa/5c/HhcC4uPEUw/nA== - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -8720,13 +8463,6 @@ nise@^1.2.0: path-to-regexp "^1.7.0" text-encoding "^0.6.4" -no-case@^2.2.0: - version "2.3.2" - resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" - integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ== - dependencies: - lower-case "^1.1.1" - node-dir@^0.1.10: version "0.1.17" resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5" @@ -8734,13 +8470,10 @@ node-dir@^0.1.10: dependencies: minimatch "^3.0.2" -node-fetch@1.6.3: - version "1.6.3" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.6.3.tgz#dc234edd6489982d58e8f0db4f695029abcd8c04" - integrity sha1-3CNO3WSJmC1Y6PDbT2lQKavNjAQ= - dependencies: - encoding "^0.1.11" - is-stream "^1.0.1" +node-fetch@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.3.0.tgz#1a1d940bbfb916a1d3e0219f037e89e71f8c5fa5" + integrity sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA== node-forge@0.7.5: version "0.7.5" @@ -8810,6 +8543,13 @@ node-pre-gyp@^0.10.0: semver "^5.3.0" tar "^4" +node-releases@^1.0.0-alpha.11, node-releases@^1.1.11, node-releases@^1.1.3: + version "1.1.11" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.11.tgz#9a0841a4b0d92b7d5141ed179e764f42ad22724a" + integrity sha512-8v1j5KfP+s5WOTa1spNUAOfreajQPN12JXbRR0oDE+YrJBQCXBnNqUDj27EKpPLOoSiU3tKi3xGPB+JaOdUEQQ== + dependencies: + semver "^5.3.0" + node-releases@^1.0.0-alpha.14: version "1.0.0-alpha.14" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.0.0-alpha.14.tgz#da9e2780add4bbb59ad890af9e2018a1d9c0034b" @@ -8817,10 +8557,10 @@ node-releases@^1.0.0-alpha.14: dependencies: semver "^5.3.0" -node-sass@^4.9.4: - version "4.9.4" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.9.4.tgz#349bd7f1c89422ffe7e1e4b60f2055a69fbc5512" - integrity sha512-MXyurANsUoE4/6KmfMkwGcBzAnJQ5xJBGW7Ei6ea8KnUKuzHr/SguVBIi3uaUAHtZCPUYkvlJ3Ef5T5VAwVpaA== +node-sass@^4.11.0: + version "4.11.0" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.11.0.tgz#183faec398e9cbe93ba43362e2768ca988a6369a" + integrity sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA== dependencies: async-foreach "^0.1.3" chalk "^1.1.1" @@ -8879,20 +8619,10 @@ normalize-path@^2.0.0, normalize-path@^2.0.1, normalize-path@^2.1.1: dependencies: remove-trailing-separator "^1.0.1" -normalize-range@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" - integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= - -normalize-url@^1.4.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" - integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw= - dependencies: - object-assign "^4.0.1" - prepend-http "^1.0.0" - query-string "^4.1.0" - sort-keys "^1.0.0" +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== normalize-url@^3.0.0: version "3.3.0" @@ -8906,6 +8636,13 @@ not-so-shallow@^0.1.3: dependencies: buffer-equals "^1.0.3" +now-and-later@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.0.tgz#bc61cbb456d79cb32207ce47ca05136ff2e7d6ee" + integrity sha1-vGHLtFbXnLMiB85HygUTb/Ln1u4= + dependencies: + once "^1.3.2" + npm-bundled@^1.0.1: version "1.0.5" resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.5.tgz#3c1732b7ba936b3a10325aef616467c0ccbcc979" @@ -8936,7 +8673,7 @@ npm-run-path@^2.0.0: gauge "~2.7.3" set-blocking "~2.0.0" -nth-check@^1.0.1, nth-check@~1.0.1: +nth-check@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.1.tgz#9929acdf628fc2c41098deab82ac580cf149aae4" integrity sha1-mSms32KPwsQQmN6rgqxYDPFJquQ= @@ -8948,11 +8685,6 @@ null-check@^1.0.0: resolved "https://registry.yarnpkg.com/null-check/-/null-check-1.0.0.tgz#977dffd7176012b9ec30d2a39db5cf72a0439edd" integrity sha1-l33/1xdgErnsMNKjnbXPcqBDnt0= -num2fraction@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" - integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= - number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" @@ -8973,16 +8705,11 @@ object-assign@4.1.0: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0" integrity sha1-ejs9DpgGPUP0wD8uiubNUahog6A= -object-assign@4.X, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= -object-assign@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" - integrity sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I= - object-component@0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291" @@ -8997,16 +8724,16 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-keys@^1.0.11, object-keys@^1.0.12: +object-keys@^1.0.11: + version "1.1.0" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.0.tgz#11bd22348dd2e096a045ab06f6c85bcc340fa032" + integrity sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg== + +object-keys@^1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2" integrity sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag== -object-to-spawn-args@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-to-spawn-args/-/object-to-spawn-args-1.1.1.tgz#77da8827f073d011c9e1b173f895781470246785" - integrity sha1-d9qIJ/Bz0BHJ4bFz+JV4FHAkZ4U= - object-visit@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" @@ -9014,7 +8741,7 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@^4.1.0: +object.assign@^4.0.4: version "4.1.0" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== @@ -9024,7 +8751,7 @@ object.assign@^4.1.0: has-symbols "^1.0.0" object-keys "^1.0.11" -object.defaults@^1.1.0: +object.defaults@^1.0.0, object.defaults@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" integrity sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8= @@ -9034,16 +8761,6 @@ object.defaults@^1.1.0: for-own "^1.0.0" isobject "^3.0.0" -object.entries@^1.0.4: - version "1.1.0" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.0.tgz#2024fc6d6ba246aee38bdb0ffd5cfbcf371b7519" - integrity sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.12.0" - function-bind "^1.1.1" - has "^1.0.3" - object.getownpropertydescriptors@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" @@ -9075,6 +8792,14 @@ object.pick@^1.2.0, object.pick@^1.3.0: dependencies: isobject "^3.0.1" +object.reduce@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object.reduce/-/object.reduce-1.0.1.tgz#6fe348f2ac7fa0f95ca621226599096825bb03ad" + integrity sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60= + dependencies: + for-own "^1.0.0" + make-iterator "^1.0.0" + object.values@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.0.4.tgz#e524da09b4f66ff05df457546ec72ac99f13069a" @@ -9093,7 +8818,7 @@ observable-to-promise@^0.4.0: is-observable "^0.2.0" symbol-observable "^0.2.2" -obuf@^1.0.0, obuf@^1.1.1: +obuf@^1.0.0, obuf@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== @@ -9110,20 +8835,13 @@ on-headers@~1.0.1: resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7" integrity sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c= -once@^1.3.0, once@^1.3.1, once@^1.4.0: +once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= dependencies: wrappy "1" -once@~1.3.0: - version "1.3.3" - resolved "https://registry.yarnpkg.com/once/-/once-1.3.3.tgz#b2e261557ce4c314ec8304f3fa82663e4297ca20" - integrity sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA= - dependencies: - wrappy "1" - onetime@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" @@ -9136,34 +8854,7 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" -opencollective@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/opencollective/-/opencollective-1.0.3.tgz#aee6372bc28144583690c3ca8daecfc120dd0ef1" - integrity sha1-ruY3K8KBRFg2kMPKja7PwSDdDvE= - dependencies: - babel-polyfill "6.23.0" - chalk "1.1.3" - inquirer "3.0.6" - minimist "1.2.0" - node-fetch "1.6.3" - opn "4.0.2" - -opn@4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/opn/-/opn-4.0.2.tgz#7abc22e644dff63b0a96d5ab7f2790c0f01abc95" - integrity sha1-erwi5kTf9jsKltWrfyeQwPAavJU= - dependencies: - object-assign "^4.0.1" - pinkie-promise "^2.0.0" - -opn@5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/opn/-/opn-5.2.0.tgz#71fdf934d6827d676cecbea1531f95d354641225" - integrity sha512-Jd/GpzPyHF4P2/aNOVmS3lfMSWV9J7cOhCG1s08XCEAsPkB7lp6ddiU0J7XzyQRDUh8BqJ7PchfINjR8jyofRQ== - dependencies: - is-wsl "^1.1.0" - -opn@^5.1.0: +opn@5.4.0, opn@^5.1.0: version "5.4.0" resolved "https://registry.yarnpkg.com/opn/-/opn-5.4.0.tgz#cb545e7aab78562beb11aa3bfabc7042e1761035" integrity sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw== @@ -9222,21 +8913,26 @@ ora@^2.1.0: strip-ansi "^4.0.0" wcwidth "^1.0.1" -orchestrator@^0.3.0: - version "0.3.8" - resolved "https://registry.yarnpkg.com/orchestrator/-/orchestrator-0.3.8.tgz#14e7e9e2764f7315fbac184e506c7aa6df94ad7e" - integrity sha1-FOfp4nZPcxX7rBhOUGx6pt+UrX4= +ora@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ora/-/ora-3.2.0.tgz#67e98a7e11f7f0ac95deaaaf11bb04de3d09e481" + integrity sha512-XHMZA5WieCbtg+tu0uPF8CjvwQdNzKCX6BVh3N6GFsEXH40mTk5dsw/ya1lBTUGJslcEFJFQ8cBhOgkkZXQtMA== dependencies: - end-of-stream "~0.1.5" - sequencify "~0.0.7" - stream-consume "~0.1.0" + chalk "^2.4.2" + cli-cursor "^2.1.0" + cli-spinners "^2.0.0" + log-symbols "^2.2.0" + strip-ansi "^5.0.0" + wcwidth "^1.0.1" -ordered-read-streams@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz#fd565a9af8eb4473ba69b6ed8a34352cb552f126" - integrity sha1-/VZamvjrRHO6abbtijQ1LLVS8SY= +ordered-read-streams@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz#77c0cb37c41525d64166d990ffad7ec6a0e1363e" + integrity sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4= + dependencies: + readable-stream "^2.0.1" -original@>=0.0.5: +original@>=0.0.5, original@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f" integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg== @@ -9330,6 +9026,11 @@ p-map@^1.1.1: resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" integrity sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA== +p-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.0.0.tgz#be18c5a5adeb8e156460651421aceca56c213a50" + integrity sha512-GO107XdrSUmtHxVoi60qc9tUl/KkNKm+X2CF4P9amalpGxv5YqVPJNfSb0wcA+syCopkZvYYIzW8OVTQW59x/w== + p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" @@ -9371,13 +9072,6 @@ parallel-transform@^1.1.0: inherits "^2.0.3" readable-stream "^2.1.5" -param-case@2.1.x: - version "2.1.1" - resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247" - integrity sha1-35T9jPZTHs915r75oIWPvHK+Ikc= - dependencies: - no-case "^2.2.0" - parent-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.0.tgz#df250bdc5391f4a085fb589dad761f5ad6b865b5" @@ -9396,11 +9090,6 @@ parse-asn1@^5.0.0: evp_bytestokey "^1.0.0" pbkdf2 "^3.0.3" -parse-code-context@^0.2.1: - version "0.2.2" - resolved "https://registry.yarnpkg.com/parse-code-context/-/parse-code-context-0.2.2.tgz#144b8afb7219482d7e88c1eb6a765596f3a6ac0d" - integrity sha1-FEuK+3IZSC1+iMHranZVlvOmrA0= - parse-entities@^1.0.2, parse-entities@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.2.0.tgz#9deac087661b2e36814153cb78d7e54a4c5fd6f4" @@ -9457,6 +9146,11 @@ parse-ms@^1.0.0: resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-1.0.1.tgz#56346d4749d78f23430ca0c713850aef91aa361d" integrity sha1-VjRtR0nXjyNDDKDHE4UK75GqNh0= +parse-node-version@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b" + integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA== + parse-passwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" @@ -9613,21 +9307,12 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -phantom@~4.0.1: - version "4.0.12" - resolved "https://registry.yarnpkg.com/phantom/-/phantom-4.0.12.tgz#78d18cf3f2a76fea4909f6160fcabf2742d7dbf0" - integrity sha512-Tz82XhtPmwCk1FFPmecy7yRGZG2btpzY2KI9fcoPT7zT9det0CcMyfBFPp1S8DqzsnQnm8ZYEfdy528mwVtksA== - dependencies: - phantomjs-prebuilt "^2.1.16" - split "^1.0.1" - winston "^2.4.0" - phantomjs-polyfill@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/phantomjs-polyfill/-/phantomjs-polyfill-0.0.2.tgz#8c6a7163e9bc8fd9ffdbe7d605cb5352f9fb891e" integrity sha1-jGpxY+m8j9n/2+fWBctTUvn7iR4= -phantomjs-prebuilt@^2.1.16, phantomjs-prebuilt@^2.1.7, phantomjs-prebuilt@~2.1.7: +phantomjs-prebuilt@^2.1.7: version "2.1.16" resolved "https://registry.yarnpkg.com/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz#efd212a4a3966d3647684ea8ba788549be2aefef" integrity sha1-79ISpKOWbTZHaE6ouniFSb4q7+8= @@ -9652,6 +9337,11 @@ pify@^3.0.0: resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + pikaday@1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/pikaday/-/pikaday-1.5.1.tgz#0a48549bc1a14ea1d08c44074d761bc2f2bfcfd3" @@ -9714,15 +9404,12 @@ pkg-dir@^3.0.0: dependencies: find-up "^3.0.0" -plugin-error@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-1.0.1.tgz#77016bd8919d0ac377fdcdd0322328953ca5781c" - integrity sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA== +pkg-up@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f" + integrity sha1-yBmscoBZpGHKscOImivjxJoATX8= dependencies: - ansi-colors "^1.0.1" - arr-diff "^4.0.0" - arr-union "^3.1.0" - extend-shallow "^3.0.2" + find-up "^2.1.0" plur@^1.0.0: version "1.0.0" @@ -9736,16 +9423,16 @@ plur@^2.0.0: dependencies: irregular-plurals "^1.0.0" -pluralize@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" - integrity sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow== - -popper.js@^1.12.3, popper.js@^1.12.9, popper.js@^1.14.4: +popper.js@^1.12.3: version "1.14.4" resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.4.tgz#8eec1d8ff02a5a3a152dd43414a15c7b79fd69b6" integrity sha1-juwdj/AqWjoVLdQ0FKFce3n9abY= +popper.js@^1.14.7: + version "1.14.7" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.7.tgz#e31ec06cfac6a97a53280c3e55e4e0c860e7738e" + integrity sha512-4q1hNvoUre/8srWsH7hnoSJ5xVmIL4qgz+s4qf2TnJIMyZFUFMGH+9vE7mXynAlHSZ/NdTmmow86muD0myUkVQ== + portfinder@^1.0.9: version "1.0.18" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.18.tgz#cf1106ff336fd4329b7ce32fda7d17d62c6bcf37" @@ -9760,15 +9447,6 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= -postcss-calc@^5.2.0: - version "5.3.1" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-5.3.1.tgz#77bae7ca928ad85716e2fda42f261bf7c1d65b5e" - integrity sha1-d7rnypKK2FcW4v2kLyYb98HWW14= - dependencies: - postcss "^5.0.2" - postcss-message-helpers "^2.0.0" - reduce-css-calc "^1.2.6" - postcss-calc@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.0.tgz#cf0e78e1d7d9f75119b833abc786fa4b61afedda" @@ -9779,15 +9457,6 @@ postcss-calc@^7.0.0: postcss-selector-parser "^5.0.0-rc.3" postcss-value-parser "^3.3.0" -postcss-colormin@^2.1.8: - version "2.2.2" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-2.2.2.tgz#6631417d5f0e909a3d7ec26b24c8a8d1e4f96e4b" - integrity sha1-ZjFBfV8OkJo9fsJrJMio0eT5bks= - dependencies: - colormin "^1.0.5" - postcss "^5.0.13" - postcss-value-parser "^3.2.3" - postcss-colormin@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-4.0.2.tgz#93cd1fa11280008696887db1a528048b18e7ed99" @@ -9799,14 +9468,6 @@ postcss-colormin@^4.0.2: postcss "^7.0.0" postcss-value-parser "^3.0.0" -postcss-convert-values@^2.3.4: - version "2.6.1" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz#bbd8593c5c1fd2e3d1c322bb925dcae8dae4d62d" - integrity sha1-u9hZPFwf0uPRwyK7kl3K6Nrk1i0= - dependencies: - postcss "^5.0.11" - postcss-value-parser "^3.1.2" - postcss-convert-values@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz#ca3813ed4da0f812f9d43703584e449ebe189a7f" @@ -9815,13 +9476,6 @@ postcss-convert-values@^4.0.1: postcss "^7.0.0" postcss-value-parser "^3.0.0" -postcss-discard-comments@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz#befe89fafd5b3dace5ccce51b76b81514be00e3d" - integrity sha1-vv6J+v1bPazlzM5Rt2uBUUvgDj0= - dependencies: - postcss "^5.0.14" - postcss-discard-comments@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.1.tgz#30697735b0c476852a7a11050eb84387a67ef55d" @@ -9829,13 +9483,6 @@ postcss-discard-comments@^4.0.1: dependencies: postcss "^7.0.0" -postcss-discard-duplicates@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz#b9abf27b88ac188158a5eb12abcae20263b91932" - integrity sha1-uavye4isGIFYpesSq8riAmO5GTI= - dependencies: - postcss "^5.0.4" - postcss-discard-duplicates@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz#3fe133cd3c82282e550fc9b239176a9207b784eb" @@ -9843,13 +9490,6 @@ postcss-discard-duplicates@^4.0.2: dependencies: postcss "^7.0.0" -postcss-discard-empty@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz#d2b4bd9d5ced5ebd8dcade7640c7d7cd7f4f92b5" - integrity sha1-0rS9nVztXr2Nyt52QMfXzX9PkrU= - dependencies: - postcss "^5.0.14" - postcss-discard-empty@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz#c8c951e9f73ed9428019458444a02ad90bb9f765" @@ -9857,13 +9497,6 @@ postcss-discard-empty@^4.0.1: dependencies: postcss "^7.0.0" -postcss-discard-overridden@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz#8b1eaf554f686fb288cd874c55667b0aa3668d58" - integrity sha1-ix6vVU9ob7KIzYdMVWZ7CqNmjVg= - dependencies: - postcss "^5.0.16" - postcss-discard-overridden@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz#652aef8a96726f029f5e3e00146ee7a4e755ff57" @@ -9871,55 +9504,6 @@ postcss-discard-overridden@^4.0.1: dependencies: postcss "^7.0.0" -postcss-discard-unused@^2.2.1: - version "2.2.3" - resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz#bce30b2cc591ffc634322b5fb3464b6d934f4433" - integrity sha1-vOMLLMWR/8Y0Mitfs0ZLbZNPRDM= - dependencies: - postcss "^5.0.14" - uniqs "^2.0.0" - -postcss-filter-plugins@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-2.0.3.tgz#82245fdf82337041645e477114d8e593aa18b8ec" - integrity sha512-T53GVFsdinJhgwm7rg1BzbeBRomOg9y5MBVhGcsV0CxurUdVj1UlPdKtn7aqYA/c/QVkzKMjq2bSV5dKG5+AwQ== - dependencies: - postcss "^5.0.4" - -postcss-load-config@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.0.0.tgz#f1312ddbf5912cd747177083c5ef7a19d62ee484" - integrity sha512-V5JBLzw406BB8UIfsAWSK2KSwIJ5yoEIVFb4gVkXci0QdKgA24jLmHZ/ghe/GgX0lJ0/D1uUK1ejhzEY94MChQ== - dependencies: - cosmiconfig "^4.0.0" - import-cwd "^2.0.0" - -postcss-loader@^2.0.8: - version "2.1.6" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.1.6.tgz#1d7dd7b17c6ba234b9bed5af13e0bea40a42d740" - integrity sha512-hgiWSc13xVQAq25cVw80CH0l49ZKlAnU1hKPOdRrNj89bokRr/bZF2nT+hebPPF9c9xs8c3gw3Fr2nxtmXYnNg== - dependencies: - loader-utils "^1.1.0" - postcss "^6.0.0" - postcss-load-config "^2.0.0" - schema-utils "^0.4.0" - -postcss-merge-idents@^2.1.5: - version "2.1.7" - resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz#4c5530313c08e1d5b3bbf3d2bbc747e278eea270" - integrity sha1-TFUwMTwI4dWzu/PSu8dH4njuonA= - dependencies: - has "^1.0.1" - postcss "^5.0.10" - postcss-value-parser "^3.1.1" - -postcss-merge-longhand@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz#23d90cd127b0a77994915332739034a1a4f3d658" - integrity sha1-I9kM0Sewp3mUkVMyc5A0oaTz1lg= - dependencies: - postcss "^5.0.4" - postcss-merge-longhand@^4.0.6: version "4.0.7" resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.7.tgz#77430fa60e36745887c1c725ab3782c40f48363d" @@ -9930,17 +9514,6 @@ postcss-merge-longhand@^4.0.6: postcss-value-parser "^3.0.0" stylehacks "^4.0.0" -postcss-merge-rules@^2.0.3: - version "2.1.2" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz#d1df5dfaa7b1acc3be553f0e9e10e87c61b5f721" - integrity sha1-0d9d+qexrMO+VT8OnhDofGG19yE= - dependencies: - browserslist "^1.5.2" - caniuse-api "^1.5.2" - postcss "^5.0.4" - postcss-selector-parser "^2.2.2" - vendors "^1.0.0" - postcss-merge-rules@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-4.0.2.tgz#2be44401bf19856f27f32b8b12c0df5af1b88e74" @@ -9953,20 +9526,6 @@ postcss-merge-rules@^4.0.2: postcss-selector-parser "^3.0.0" vendors "^1.0.0" -postcss-message-helpers@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz#a4f2f4fab6e4fe002f0aed000478cdf52f9ba60e" - integrity sha1-pPL0+rbk/gAvCu0ABHjN9S+bpg4= - -postcss-minify-font-values@^1.0.2: - version "1.0.5" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz#4b58edb56641eba7c8474ab3526cafd7bbdecb69" - integrity sha1-S1jttWZB66fIR0qzUmyv17vey2k= - dependencies: - object-assign "^4.0.1" - postcss "^5.0.4" - postcss-value-parser "^3.0.2" - postcss-minify-font-values@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz#cd4c344cce474343fac5d82206ab2cbcb8afd5a6" @@ -9975,14 +9534,6 @@ postcss-minify-font-values@^4.0.2: postcss "^7.0.0" postcss-value-parser "^3.0.0" -postcss-minify-gradients@^1.0.1: - version "1.0.5" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz#5dbda11373703f83cfb4a3ea3881d8d75ff5e6e1" - integrity sha1-Xb2hE3NwP4PPtKPqOIHY11/15uE= - dependencies: - postcss "^5.0.12" - postcss-value-parser "^3.3.0" - postcss-minify-gradients@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.1.tgz#6da95c6e92a809f956bb76bf0c04494953e1a7dd" @@ -9993,16 +9544,6 @@ postcss-minify-gradients@^4.0.1: postcss "^7.0.0" postcss-value-parser "^3.0.0" -postcss-minify-params@^1.0.4: - version "1.2.2" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz#ad2ce071373b943b3d930a3fa59a358c28d6f1f3" - integrity sha1-rSzgcTc7lDs9kwo/pZo1jCjW8fM= - dependencies: - alphanum-sort "^1.0.1" - postcss "^5.0.2" - postcss-value-parser "^3.0.2" - uniqs "^2.0.0" - postcss-minify-params@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-4.0.1.tgz#5b2e2d0264dd645ef5d68f8fec0d4c38c1cf93d2" @@ -10015,16 +9556,6 @@ postcss-minify-params@^4.0.1: postcss-value-parser "^3.0.0" uniqs "^2.0.0" -postcss-minify-selectors@^2.0.4: - version "2.1.1" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz#b2c6a98c0072cf91b932d1a496508114311735bf" - integrity sha1-ssapjAByz5G5MtGkllCBFDEXNb8= - dependencies: - alphanum-sort "^1.0.2" - has "^1.0.1" - postcss "^5.0.14" - postcss-selector-parser "^2.0.0" - postcss-minify-selectors@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.1.tgz#a891c197977cc37abf60b3ea06b84248b1c1e9cd" @@ -10035,43 +9566,37 @@ postcss-minify-selectors@^4.0.1: postcss "^7.0.0" postcss-selector-parser "^3.0.0" -postcss-modules-extract-imports@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.0.tgz#66140ecece38ef06bf0d3e355d69bf59d141ea85" - integrity sha1-ZhQOzs447wa/DT41XWm/WdFB6oU= +postcss-modules-extract-imports@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e" + integrity sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ== dependencies: - postcss "^6.0.1" + postcss "^7.0.5" -postcss-modules-local-by-default@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz#f7d80c398c5a393fa7964466bd19500a7d61c069" - integrity sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk= +postcss-modules-local-by-default@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-2.0.6.tgz#dd9953f6dd476b5fd1ef2d8830c8929760b56e63" + integrity sha512-oLUV5YNkeIBa0yQl7EYnxMgy4N6noxmiwZStaEJUSe2xPMcdNc8WmBQuQCx18H5psYbVxz8zoHk0RAAYZXP9gA== dependencies: - css-selector-tokenizer "^0.7.0" - postcss "^6.0.1" + postcss "^7.0.6" + postcss-selector-parser "^6.0.0" + postcss-value-parser "^3.3.1" -postcss-modules-scope@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz#d6ea64994c79f97b62a72b426fbe6056a194bb90" - integrity sha1-1upkmUx5+XtipytCb75gVqGUu5A= +postcss-modules-scope@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.1.0.tgz#ad3f5bf7856114f6fcab901b0502e2a2bc39d4eb" + integrity sha512-91Rjps0JnmtUB0cujlc8KIKCsJXWjzuxGeT/+Q2i2HXKZ7nBUeF9YQTZZTNvHVoNYj1AthsjnGLtqDUE0Op79A== dependencies: - css-selector-tokenizer "^0.7.0" - postcss "^6.0.1" + postcss "^7.0.6" + postcss-selector-parser "^6.0.0" -postcss-modules-values@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz#ecffa9d7e192518389f42ad0e83f72aec456ea20" - integrity sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA= +postcss-modules-values@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-2.0.0.tgz#479b46dc0c5ca3dc7fa5270851836b9ec7152f64" + integrity sha512-Ki7JZa7ff1N3EIMlPnGTZfUMe69FFwiQPnVSXC9mnn3jozCRBYIxiZd44yJOV2AmabOo4qFf8s0dC/+lweG7+w== dependencies: icss-replace-symbols "^1.1.0" - postcss "^6.0.1" - -postcss-normalize-charset@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz#ef9ee71212d7fe759c78ed162f61ed62b5cb93f1" - integrity sha1-757nEhLX/nWceO0WL2HtYrXLk/E= - dependencies: - postcss "^5.0.5" + postcss "^7.0.6" postcss-normalize-charset@^4.0.1: version "4.0.1" @@ -10136,16 +9661,6 @@ postcss-normalize-unicode@^4.0.1: postcss "^7.0.0" postcss-value-parser "^3.0.0" -postcss-normalize-url@^3.0.7: - version "3.0.8" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz#108f74b3f2fcdaf891a2ffa3ea4592279fc78222" - integrity sha1-EI90s/L82viRov+j6kWSJ5/HgiI= - dependencies: - is-absolute-url "^2.0.0" - normalize-url "^1.4.0" - postcss "^5.0.14" - postcss-value-parser "^3.2.3" - postcss-normalize-url@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz#10e437f86bc7c7e58f7b9652ed878daaa95faae1" @@ -10164,14 +9679,6 @@ postcss-normalize-whitespace@^4.0.1: postcss "^7.0.0" postcss-value-parser "^3.0.0" -postcss-ordered-values@^2.1.0: - version "2.2.3" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz#eec6c2a67b6c412a8db2042e77fe8da43f95c11d" - integrity sha1-7sbCpntsQSqNsgQud/6NpD+VwR0= - dependencies: - postcss "^5.0.4" - postcss-value-parser "^3.0.1" - postcss-ordered-values@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-4.1.1.tgz#2e3b432ef3e489b18333aeca1f1295eb89be9fc2" @@ -10181,21 +9688,6 @@ postcss-ordered-values@^4.1.1: postcss "^7.0.0" postcss-value-parser "^3.0.0" -postcss-reduce-idents@^2.2.2: - version "2.4.0" - resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz#c2c6d20cc958284f6abfbe63f7609bf409059ad3" - integrity sha1-wsbSDMlYKE9qv75j92Cb9AkFmtM= - dependencies: - postcss "^5.0.4" - postcss-value-parser "^3.0.2" - -postcss-reduce-initial@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz#68f80695f045d08263a879ad240df8dd64f644ea" - integrity sha1-aPgGlfBF0IJjqHmtJA343WT2ROo= - dependencies: - postcss "^5.0.4" - postcss-reduce-initial@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.2.tgz#bac8e325d67510ee01fa460676dc8ea9e3b40f15" @@ -10206,15 +9698,6 @@ postcss-reduce-initial@^4.0.2: has "^1.0.0" postcss "^7.0.0" -postcss-reduce-transforms@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz#ff76f4d8212437b31c298a42d2e1444025771ae1" - integrity sha1-/3b02CEkN7McKYpC0uFEQCV3GuE= - dependencies: - has "^1.0.1" - postcss "^5.0.8" - postcss-value-parser "^3.0.1" - postcss-reduce-transforms@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.1.tgz#8600d5553bdd3ad640f43bff81eb52f8760d4561" @@ -10225,16 +9708,7 @@ postcss-reduce-transforms@^4.0.1: postcss "^7.0.0" postcss-value-parser "^3.0.0" -postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2: - version "2.2.3" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz#f9437788606c3c9acee16ffe8d8b16297f27bb90" - integrity sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A= - dependencies: - flatten "^1.0.2" - indexes-of "^1.0.1" - uniq "^1.0.1" - -postcss-selector-parser@^3.0.0, postcss-selector-parser@^3.1.1: +postcss-selector-parser@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz#4f875f4afb0c96573d5cf4d74011aee250a7e865" integrity sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU= @@ -10243,6 +9717,15 @@ postcss-selector-parser@^3.0.0, postcss-selector-parser@^3.1.1: indexes-of "^1.0.1" uniq "^1.0.1" +postcss-selector-parser@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz#249044356697b33b64f1a8f7c80922dddee7195c" + integrity sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ== + dependencies: + cssesc "^2.0.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + postcss-selector-parser@^5.0.0-rc.3: version "5.0.0-rc.3" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0-rc.3.tgz#c4525dcc8eb90166c53dcbf0cb9317ceff5a15b5" @@ -10253,15 +9736,14 @@ postcss-selector-parser@^5.0.0-rc.3: indexes-of "^1.0.1" uniq "^1.0.1" -postcss-svgo@^2.1.1: - version "2.1.6" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-2.1.6.tgz#b6df18aa613b666e133f08adb5219c2684ac108d" - integrity sha1-tt8YqmE7Zm4TPwittSGcJoSsEI0= +postcss-selector-parser@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c" + integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg== dependencies: - is-svg "^2.0.0" - postcss "^5.0.14" - postcss-value-parser "^3.2.3" - svgo "^0.7.0" + cssesc "^3.0.0" + indexes-of "^1.0.1" + uniq "^1.0.1" postcss-svgo@^4.0.1: version "4.0.1" @@ -10273,15 +9755,6 @@ postcss-svgo@^4.0.1: postcss-value-parser "^3.0.0" svgo "^1.0.0" -postcss-unique-selectors@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz#981d57d29ddcb33e7b1dfe1fd43b8649f933ca1d" - integrity sha1-mB1X0p3csz57Hf4f1DuGSfkzyh0= - dependencies: - alphanum-sort "^1.0.1" - postcss "^5.0.4" - uniqs "^2.0.0" - postcss-unique-selectors@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz#9446911f3289bfd64c6d680f073c03b1f9ee4bac" @@ -10291,39 +9764,11 @@ postcss-unique-selectors@^4.0.1: postcss "^7.0.0" uniqs "^2.0.0" -postcss-value-parser@^3.0.0, postcss-value-parser@^3.0.1, postcss-value-parser@^3.0.2, postcss-value-parser@^3.1.1, postcss-value-parser@^3.1.2, postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0: +postcss-value-parser@^3.0.0, postcss-value-parser@^3.3.0, postcss-value-parser@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== -postcss-zindex@^2.0.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-2.2.0.tgz#d2109ddc055b91af67fc4cb3b025946639d2af22" - integrity sha1-0hCd3AVbka9n/EyzsCWUZjnSryI= - dependencies: - has "^1.0.1" - postcss "^5.0.4" - uniqs "^2.0.0" - -postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.2.16: - version "5.2.18" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5" - integrity sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg== - dependencies: - chalk "^1.1.3" - js-base64 "^2.1.9" - source-map "^0.5.6" - supports-color "^3.2.3" - -postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.20, postcss@^6.0.23: - version "6.0.23" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" - integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag== - dependencies: - chalk "^2.4.1" - source-map "^0.6.1" - supports-color "^5.4.0" - postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.2: version "7.0.5" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.5.tgz#70e6443e36a6d520b0fd4e7593fcca3635ee9f55" @@ -10333,6 +9778,15 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.2: source-map "^0.6.1" supports-color "^5.5.0" +postcss@^7.0.14, postcss@^7.0.5, postcss@^7.0.6: + version "7.0.14" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.14.tgz#4527ed6b1ca0d82c53ce5ec1a2041c2346bbd6e5" + integrity sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + power-assert-context-formatter@^1.0.7: version "1.2.0" resolved "https://registry.yarnpkg.com/power-assert-context-formatter/-/power-assert-context-formatter-1.2.0.tgz#8fbe72692288ec5a7203cdf215c8b838a6061d2a" @@ -10445,7 +9899,7 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= -prepend-http@^1.0.0, prepend-http@^1.0.1: +prepend-http@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= @@ -10462,23 +9916,15 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier@1.13.7: - version "1.13.7" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.13.7.tgz#850f3b8af784a49a6ea2d2eaa7ed1428a34b7281" - integrity sha512-KIU72UmYPGk4MujZGYMFwinB7lOf2LsDNGSOC8ufevsrPLISrZbNJlWstRi3m0AMuszbH+EFSQ/r6w56RSPK6w== +prettier@1.16.3: + version "1.16.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.3.tgz#8c62168453badef702f34b45b6ee899574a6a65d" + integrity sha512-kn/GU6SMRYPxUakNXhpP0EedT/KmaPzr0H5lIsDogrykbaxOpOfAFfk5XA7DZrJyMAv1wlMV3CPcZruGXVVUZw== -prettier@^1.15.3: - version "1.15.3" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.15.3.tgz#1feaac5bdd181237b54dbe65d874e02a1472786a" - integrity sha512-gAU9AGAPMaKb3NNSUUuhhFAS7SCO4ALTN4nRIn6PJ075Qd28Yn2Ig2ahEJWdJwJmlEBTUfC7mMUSFy8MwsOCfg== - -pretty-error@^2.0.2: - version "2.1.1" - resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3" - integrity sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM= - dependencies: - renderkid "^2.0.1" - utila "~0.4" +prettier@^1.16.4: + version "1.16.4" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.4.tgz#73e37e73e018ad2db9c76742e2647e21790c9717" + integrity sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g== pretty-hrtime@^1.0.0: version "1.0.3" @@ -10501,12 +9947,24 @@ pretty-ms@^2.0.0: parse-ms "^1.0.0" plur "^1.0.0" +prismjs@^1.15.0: + version "1.16.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.16.0.tgz#406eb2c8aacb0f5f0f1167930cb83835d10a4308" + integrity sha512-OA4MKxjFZHSvZcisLGe14THYsug/nF6O1f0pAJc0KN0wTyAcLqmsbE+lTGKSpyh+9pEW57+k6pg2AfYR+coyHA== + optionalDependencies: + clipboard "^2.0.0" + private@^0.1.6, private@^0.1.8, private@~0.1.5: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== -process-nextick-args@~2.0.0: +process-nextick-args@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" + integrity sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M= + +process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== @@ -10531,6 +9989,13 @@ promise-inflight@^1.0.1: resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= +promise@^7.0.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== + dependencies: + asap "~2.0.3" + prop-types@^15.5.10, prop-types@^15.6.0, prop-types@^15.6.2: version "15.6.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102" @@ -10539,6 +10004,15 @@ prop-types@^15.5.10, prop-types@^15.6.0, prop-types@^15.6.2: loose-envify "^1.3.1" object-assign "^4.1.1" +prop-types@^15.7.2: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.8.1" + proxy-addr@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.4.tgz#ecfc733bf22ff8c6f407fa275327b9ab67e48b93" @@ -10574,7 +10048,7 @@ public-encrypt@^4.0.0: randombytes "^2.0.1" safe-buffer "^5.1.2" -pug-attrs@^2.0.2: +pug-attrs@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/pug-attrs/-/pug-attrs-2.0.3.tgz#a3095f970e64151f7bdad957eef55fb5d7905d15" integrity sha1-owlflw5kFR972tlX7vVftdeQXRU= @@ -10583,17 +10057,17 @@ pug-attrs@^2.0.2: js-stringify "^1.0.1" pug-runtime "^2.0.4" -pug-code-gen@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/pug-code-gen/-/pug-code-gen-1.1.1.tgz#1cf72744ef2a039eae6a3340caaa1105871258e8" - integrity sha1-HPcnRO8qA56uajNAyqoRBYcSWOg= +pug-code-gen@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pug-code-gen/-/pug-code-gen-2.0.1.tgz#0951ec83225d74d8cfc476a7f99a259b5f7d050c" + integrity sha1-CVHsgyJddNjPxHan+Zolm199BQw= dependencies: constantinople "^3.0.1" doctypes "^1.1.0" js-stringify "^1.0.1" - pug-attrs "^2.0.2" + pug-attrs "^2.0.3" pug-error "^1.3.2" - pug-runtime "^2.0.3" + pug-runtime "^2.0.4" void-elements "^2.0.1" with "^5.0.0" @@ -10602,28 +10076,83 @@ pug-error@^1.3.2: resolved "https://registry.yarnpkg.com/pug-error/-/pug-error-1.3.2.tgz#53ae7d9d29bb03cf564493a026109f54c47f5f26" integrity sha1-U659nSm7A89WRJOgJhCfVMR/XyY= -pug-lexer@^3.1.0: +pug-filters@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/pug-lexer/-/pug-lexer-3.1.0.tgz#fd087376d4a675b4f59f8fef422883434e9581a2" - integrity sha1-/QhzdtSmdbT1n4/vQiiDQ06VgaI= + resolved "https://registry.yarnpkg.com/pug-filters/-/pug-filters-3.1.0.tgz#27165555bc04c236e4aa2b0366246dfa021b626e" + integrity sha1-JxZVVbwEwjbkqisDZiRt+gIbYm4= + dependencies: + clean-css "^4.1.11" + constantinople "^3.0.1" + jstransformer "1.0.0" + pug-error "^1.3.2" + pug-walk "^1.1.7" + resolve "^1.1.6" + uglify-js "^2.6.1" + +pug-lexer@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/pug-lexer/-/pug-lexer-4.0.0.tgz#210c18457ef2e1760242740c5e647bd794cec278" + integrity sha1-IQwYRX7y4XYCQnQMXmR715TOwng= dependencies: character-parser "^2.1.1" is-expression "^3.0.0" pug-error "^1.3.2" -pug-parser@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/pug-parser/-/pug-parser-2.0.2.tgz#53a680cfd05039dcb0c27d029094bc4a792689b0" - integrity sha1-U6aAz9BQOdywwn0CkJS8SnkmibA= +pug-linker@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/pug-linker/-/pug-linker-3.0.5.tgz#9e9a7ae4005682d027deeb96b000f88eeb83a02f" + integrity sha1-npp65ABWgtAn3uuWsAD4juuDoC8= + dependencies: + pug-error "^1.3.2" + pug-walk "^1.1.7" + +pug-load@^2.0.11: + version "2.0.11" + resolved "https://registry.yarnpkg.com/pug-load/-/pug-load-2.0.11.tgz#e648e57ed113fe2c1f45d57858ea2bad6bc01527" + integrity sha1-5kjlftET/iwfRdV4WOorrWvAFSc= + dependencies: + object-assign "^4.1.0" + pug-walk "^1.1.7" + +pug-parser@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pug-parser/-/pug-parser-5.0.0.tgz#e394ad9b3fca93123940aff885c06e44ab7e68e4" + integrity sha1-45Stmz/KkxI5QK/4hcBuRKt+aOQ= dependencies: pug-error "^1.3.2" token-stream "0.0.1" -pug-runtime@^2.0.3, pug-runtime@^2.0.4: +pug-runtime@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/pug-runtime/-/pug-runtime-2.0.4.tgz#e178e1bda68ab2e8c0acfc9bced2c54fd88ceb58" integrity sha1-4XjhvaaKsujArPybztLFT9iM61g= +pug-strip-comments@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/pug-strip-comments/-/pug-strip-comments-1.0.3.tgz#f1559592206edc6f85310dacf4afb48a025af59f" + integrity sha1-8VWVkiBu3G+FMQ2s9K+0igJa9Z8= + dependencies: + pug-error "^1.3.2" + +pug-walk@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/pug-walk/-/pug-walk-1.1.7.tgz#c00d5c5128bac5806bec15d2b7e7cdabe42531f3" + integrity sha1-wA1cUSi6xYBr7BXSt+fNq+QlMfM= + +pug@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/pug/-/pug-2.0.3.tgz#71cba82537c95a5eab7ed04696e4221f53aa878e" + integrity sha1-ccuoJTfJWl6rftBGluQiH1Oqh44= + dependencies: + pug-code-gen "^2.0.1" + pug-filters "^3.1.0" + pug-lexer "^4.0.0" + pug-linker "^3.0.5" + pug-load "^2.0.11" + pug-parser "^5.0.0" + pug-runtime "^2.0.4" + pug-strip-comments "^1.0.3" + pump@^2.0.0, pump@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" @@ -10632,7 +10161,15 @@ pump@^2.0.0, pump@^2.0.1: end-of-stream "^1.1.0" once "^1.3.1" -pumpify@^1.3.3: +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pumpify@^1.3.3, pumpify@^1.3.5: version "1.5.1" resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== @@ -10685,13 +10222,10 @@ qs@6.5.2, qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -query-string@^4.1.0: - version "4.3.4" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" - integrity sha1-u7aTucqRXCMlFbIosaArYJBD2+s= - dependencies: - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" +qss@^2.0.2, qss@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/qss/-/qss-2.0.3.tgz#630b38b120931b52d04704f3abfb0f861604a9ec" + integrity sha512-j48ZBT5IZbSqJiSU8EX4XrN8nXiflHvmMvv2XpFc31gh7n6EpSs75bNr6+oj3FOLWyT8m09pTmqLNl34L7/uPQ== querystring-es3@^0.2.0: version "0.2.1" @@ -10765,6 +10299,14 @@ raw-body@2.3.3: iconv-lite "0.4.23" unpipe "1.0.0" +raw-loader@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-2.0.0.tgz#e2813d9e1e3f80d1bbade5ad082e809679e20c26" + integrity sha512-kZnO5MoIyrojfrPWqrhFNLZemIAX8edMOCp++yC5RKxzFB3m92DqKNhKlU6+FvpOhWtvyh3jOaD7J6/9tpdIKg== + dependencies: + loader-utils "^1.1.0" + schema-utils "^1.0.0" + rc@^1.0.1, rc@^1.1.6, rc@^1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" @@ -10780,28 +10322,64 @@ react-codemirror2@^5.1.0: resolved "https://registry.yarnpkg.com/react-codemirror2/-/react-codemirror2-5.1.0.tgz#62de4460178adea40eb52eabf7491669bf3794b8" integrity sha512-Cksbgbviuf2mJfMyrKmcu7ycK6zX/ukuQO8dvRZdFWqATf5joalhjFc6etnBdGCcPA2LbhIwz+OPnQxLN/j1Fw== -react-dev-utils@^5.0.1: - version "5.0.3" - resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-5.0.3.tgz#92f97668f03deb09d7fa11ea288832a8c756e35e" - integrity sha512-Mvs6ofsc2xTjeZIrMaIfbXfsPVrbdVy/cVqq6SAacnqfMlcBpDuivhWZ1ODGeJ8HgmyWTLH971PYjj/EPCDVAw== +react-dev-utils@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-6.1.1.tgz#a07e3e8923c4609d9f27e5af5207e3ca20724895" + integrity sha512-ThbJ86coVd6wV/QiTo8klDTvdAJ1WsFCGQN07+UkN+QN9CtCSsl/+YuDJToKGeG8X4j9HMGXNKbk2QhPAZr43w== dependencies: + "@babel/code-frame" "7.0.0" address "1.0.3" - babel-code-frame "6.26.0" - chalk "1.1.3" - cross-spawn "5.1.0" + browserslist "4.1.1" + chalk "2.4.1" + cross-spawn "6.0.5" detect-port-alt "1.1.6" escape-string-regexp "1.0.5" - filesize "3.5.11" + filesize "3.6.1" + find-up "3.0.0" global-modules "1.0.0" - gzip-size "3.0.0" - inquirer "3.3.0" - is-root "1.0.0" - opn "5.2.0" - react-error-overlay "^4.0.1" - recursive-readdir "2.2.1" + globby "8.0.1" + gzip-size "5.0.0" + immer "1.7.2" + inquirer "6.2.0" + is-root "2.0.0" + loader-utils "1.1.0" + opn "5.4.0" + pkg-up "2.0.0" + react-error-overlay "^5.1.0" + recursive-readdir "2.2.2" shell-quote "1.6.1" sockjs-client "1.1.5" - strip-ansi "3.0.1" + strip-ansi "4.0.0" + text-table "0.2.0" + +react-dev-utils@^7.0.3: + version "7.0.5" + resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-7.0.5.tgz#cb95375d01ae71ca27b3c7616006ef7a77d14e8e" + integrity sha512-zJnqqb0x6gd63E3xoz5pXAxBPNaW75Hyz7GgQp0qPhMroBCRQtRvG67AoTZZY1z4yCYVJQZAfQJFdnea0Ujbug== + dependencies: + "@babel/code-frame" "7.0.0" + address "1.0.3" + browserslist "4.4.1" + chalk "2.4.2" + cross-spawn "6.0.5" + detect-port-alt "1.1.6" + escape-string-regexp "1.0.5" + filesize "3.6.1" + find-up "3.0.0" + global-modules "2.0.0" + globby "8.0.2" + gzip-size "5.0.0" + immer "1.10.0" + inquirer "6.2.1" + is-root "2.0.0" + loader-utils "1.2.3" + opn "5.4.0" + pkg-up "2.0.0" + react-error-overlay "^5.1.4" + recursive-readdir "2.2.2" + shell-quote "1.6.1" + sockjs-client "1.3.0" + strip-ansi "5.0.0" text-table "0.2.0" react-docgen-annotation-resolver@^1.0.0: @@ -10809,25 +10387,25 @@ react-docgen-annotation-resolver@^1.0.0: resolved "https://registry.yarnpkg.com/react-docgen-annotation-resolver/-/react-docgen-annotation-resolver-1.1.0.tgz#0a0f8d5d4d7ba924866d479ec7e709ed6cf33807" integrity sha512-wTUI7IqWkV+BNRmEh1eHkU+Ijwh0XcFUdbgktynWVqe++MgtovdlbfMehFAw5b49mv8NN2DK0NF/G8x+UdIyNw== -react-docgen-displayname-handler@^2.1.0: +react-docgen-displayname-handler@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/react-docgen-displayname-handler/-/react-docgen-displayname-handler-2.1.1.tgz#a7863a2cc0058ac3c72e6348fa5e40cc4278c5ae" integrity sha512-Dmu+WnQt5TRDokaQ6uGvgcxrIh92c6uhuljsuueEphTcy/1nKh8/Ep6RUmpd77G1iN2rqg5+4ztGAT7LJjVdpg== dependencies: ast-types "0.11.5" -react-docgen@3.0.0-beta12: - version "3.0.0-beta12" - resolved "https://registry.yarnpkg.com/react-docgen/-/react-docgen-3.0.0-beta12.tgz#1285839296d6b616715b75c9d57f08d17c500be9" - integrity sha512-8DE3R6mPJSpBS2k6nw4hyZ/E+k60tE/AzLe/ncfyf3AQnd1pojo8cCn2ziXU33Vfny5u7G0pCrL01h9rcX6MTw== +react-docgen@3.0.0-rc.2: + version "3.0.0-rc.2" + resolved "https://registry.yarnpkg.com/react-docgen/-/react-docgen-3.0.0-rc.2.tgz#5939c64699fd9959da6d97d890f7b648e542dbcc" + integrity sha512-tXbIvq7Hxdc92jW570rztqsz0adtWEM5FX8bShJYozT2Y6L/LeHvBMQcED6mSqJ72niiNMPV8fi3S37OHrGMEw== dependencies: + "@babel/parser" "^7.1.3" + "@babel/runtime" "^7.0.0" async "^2.1.4" - babel-runtime "^6.9.2" - babylon "7.0.0-beta.40" - commander "^2.9.0" + commander "^2.19.0" doctrine "^2.0.0" node-dir "^0.1.10" - recast "^0.13.0" + recast "^0.16.0" react-dom@^16.4.1: version "16.5.2" @@ -10839,10 +10417,10 @@ react-dom@^16.4.1: prop-types "^15.6.2" schedule "^0.5.0" -react-error-overlay@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-4.0.1.tgz#417addb0814a90f3a7082eacba7cee588d00da89" - integrity sha512-xXUbDAZkU08aAkjtUvldqbvI04ogv+a1XdHxvYuHPYKIVk/42BIOD0zSKTHAWV4+gDy3yGm283z2072rA2gdtw== +react-error-overlay@^5.1.0, react-error-overlay@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-5.1.4.tgz#88dfb88857c18ceb3b9f95076f850d7121776991" + integrity sha512-fp+U98OMZcnduQ+NSEiQa4s/XMsbp+5KlydmkbESOw4P69iWZ68ZMFM5a2BuE0FgqPBKApJyRuYHR95jM8lAmg== react-group@^1.0.6: version "1.0.6" @@ -10851,6 +10429,13 @@ react-group@^1.0.6: dependencies: prop-types "^15.6.0" +react-group@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/react-group/-/react-group-3.0.0.tgz#cc02d67b1b08fe125a664c527aa69f6b9ecab66b" + integrity sha512-BKfGTyszMUA754VqRr6kG5Nzmht07Um0szV7iZkQMSzZ3QNzjSHGMwQ7BpRp3kmVqSg/YjNq/18J5y4OrNAstQ== + dependencies: + prop-types "^15.6.2" + react-icon-base@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/react-icon-base/-/react-icon-base-2.1.0.tgz#a196e33fdf1e7aaa1fda3aefbb68bdad9e82a79d" @@ -10863,37 +10448,46 @@ react-icons@^2.2.7: dependencies: react-icon-base "2.1.0" +react-is@^16.8.1: + version "16.8.5" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.5.tgz#c54ac229dd66b5afe0de5acbe47647c3da692ff8" + integrity sha512-sudt2uq5P/2TznPV4Wtdi+Lnq3yaYW8LfvPKLM9BKD8jJNBkxMVyB0C9/GmVhLw7Jbdmndk/73n7XQGeN9A3QQ== + react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== -react-styleguidist@^7.3.8: - version "7.3.11" - resolved "https://registry.yarnpkg.com/react-styleguidist/-/react-styleguidist-7.3.11.tgz#4538ae407f7f13d3fb79feae0103d91f3d3331b5" - integrity sha512-Y1zgWG1R6NFmScBFIviSnt9vpsQzxriyZPe4AK224Z66PfyhXPBIgbXCqn1zTeOcic7/n0g9pp/K6I0ANecuIg== +react-simple-code-editor@^0.9.3, react-simple-code-editor@^0.9.4: + version "0.9.7" + resolved "https://registry.yarnpkg.com/react-simple-code-editor/-/react-simple-code-editor-0.9.7.tgz#5cba6c0b482c51d97101cb2f69877e75176bb4e4" + integrity sha512-+DEz+odZBwuBgTQB3uF0/RTjDmQXDYLR4KaOAkBtR68fF3xFsGzVk/ujlNQtpHp5OPZcBVz8Zmj8l7Csw3VmLw== + +react-styleguidist@^9.0.4: + version "9.0.4" + resolved "https://registry.yarnpkg.com/react-styleguidist/-/react-styleguidist-9.0.4.tgz#d9dd7d06a029bb4fdafdb86253737bde003c1661" + integrity sha512-HQapITMEksG6oNM0bSNJtWHiqycjCr34n+dl9WM+o2vm7N9DlwaSyPZ51KhBdFtv9mDWBtGmRM1lhIZk4NbljQ== dependencies: "@vxna/mini-html-webpack-template" "^0.1.7" - acorn "^5.7.1" - ast-types "^0.11.5" + acorn "^6.1.0" + acorn-jsx "^5.0.1" + ast-types "^0.12.2" buble "0.19.4" - classnames "^2.2.6" - clean-webpack-plugin "^0.1.19" - clipboard-copy "^2.0.0" - codemirror "^5.39.0" + clean-webpack-plugin "^1.0.1" + clipboard-copy "^3.0.0" + clsx "^1.0.3" common-dir "^2.0.2" - copy-webpack-plugin "^4.5.2" - css-loader "^0.28.11" - doctrine "^2.1.0" + copy-webpack-plugin "^4.6.0" + core-js "^2.6.5" + doctrine "^3.0.0" es6-object-assign "~1.1.0" - es6-promise "^4.2.4" - escodegen "^1.10.0" + es6-promise "^4.2.6" + escodegen "^1.11.1" findup "^0.1.5" function.name-polyfill "^1.0.6" - github-slugger "^1.2.0" - glob "^7.1.2" - glogg "^1.0.1" - highlight.js "^9.12.0" + github-slugger "^1.2.1" + glob "^7.1.3" + glogg "^1.0.2" is-directory "^0.3.1" javascript-stringify "^1.6.0" jss "^9.8.7" @@ -10903,35 +10497,38 @@ react-styleguidist@^7.3.8: jss-global "^3.0.0" jss-isolate "^5.1.0" jss-nested "^6.0.1" - kleur "^2.0.1" + kleur "^3.0.2" leven "^2.1.0" listify "^1.0.0" - loader-utils "^1.1.0" - lodash "^4.17.10" + loader-utils "^1.2.3" + lodash "^4.17.11" lowercase-keys "^1.0.1" - markdown-to-jsx "^6.6.8" + markdown-to-jsx "^6.9.1" mini-html-webpack-plugin "^0.2.3" - minimist "^1.2.0" - ora "^2.1.0" - prop-types "^15.6.2" + mri "^1.1.4" + ora "^3.1.0" + prismjs "^1.15.0" + prop-types "^15.7.2" q-i "^2.0.1" - react-codemirror2 "^5.1.0" - react-dev-utils "^5.0.1" - react-docgen "3.0.0-beta12" + qss "^2.0.2" + react-dev-utils "^6.1.1" + react-docgen "3.0.0-rc.2" react-docgen-annotation-resolver "^1.0.0" - react-docgen-displayname-handler "^2.1.0" - react-group "^1.0.6" + react-docgen-displayname-handler "^2.1.1" + react-group "^3.0.0" react-icons "^2.2.7" react-lifecycles-compat "^3.0.4" - recast "^0.13.0" - remark "^9.0.0" - style-loader "^0.21.0" + react-simple-code-editor "^0.9.3" + recast "^0.17.3" + remark "^10.0.1" + rewrite-imports "1.2.0" + terser-webpack-plugin "^1.2.2" to-ast "^1.0.0" type-detect "^4.0.8" - uglifyjs-webpack-plugin "1.2.7" - unist-util-visit "^1.3.1" - webpack-dev-server "^2.11.2" - webpack-merge "^4.1.3" + unist-util-visit "^1.4.0" + walkes "^0.2.1" + webpack-dev-server "^3.1.14" + webpack-merge "^4.2.1" react@^16.4.1: version "16.5.2" @@ -10985,16 +10582,7 @@ read-pkg@^2.0.0: normalize-package-data "^2.3.2" path-type "^2.0.0" -read-pkg@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-4.0.1.tgz#963625378f3e1c4d48c85872b5a6ec7d5d093237" - integrity sha1-ljYlN48+HE1IyFhytabsfV0JMjc= - dependencies: - normalize-package-data "^2.3.2" - parse-json "^4.0.0" - pify "^3.0.0" - -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.3, readable-stream@^2.3.6: +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, 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.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== @@ -11007,26 +10595,6 @@ read-pkg@^4.0.1: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@1.0, "readable-stream@>=1.0.33-1 <1.1.0-0", readable-stream@~1.0.2: - version "1.0.34" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" - integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw= - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - -readable-stream@1.1: - version "1.1.13" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e" - integrity sha1-9u73ZPUUyJ4rniMUanW6EGdW0j4= - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - readable-stream@^3.0.6: version "3.1.1" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.1.1.tgz#ed6bbc6c5ba58b090039ff18ce670515795aeb06" @@ -11036,10 +10604,10 @@ readable-stream@^3.0.6: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@~1.1.9: - version "1.1.14" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" - integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= +readable-stream@~1.0.2: + version "1.0.34" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw= dependencies: core-util-is "~1.0.0" inherits "~2.0.1" @@ -11055,16 +10623,26 @@ readdirp@^2.0.0: micromatch "^3.1.10" readable-stream "^2.0.2" -recast@^0.13.0: - version "0.13.2" - resolved "https://registry.yarnpkg.com/recast/-/recast-0.13.2.tgz#919e7e856d5154f13284142ed1797753c6756137" - integrity sha512-Xqo0mKljGUWGUhnkdbODk7oJGFrMcpgKQ9cCyZ4y+G9VfoTKdum8nHbf/SxIdKx5aBSZ29VpVy20bTyt7jyC8w== +recast@^0.16.0: + version "0.16.2" + resolved "https://registry.yarnpkg.com/recast/-/recast-0.16.2.tgz#3796ebad5fe49ed85473b479cd6df554ad725dc2" + integrity sha512-O/7qXi51DPjRVdbrpNzoBQH5dnAPQNbfoOFyRiUwreTMJfIHYOEBzwuH+c0+/BTSJ3CQyKs6ILSWXhESH6Op3A== dependencies: - ast-types "0.10.2" + ast-types "0.11.7" esprima "~4.0.0" private "~0.1.5" source-map "~0.6.1" +recast@^0.17.3: + version "0.17.4" + resolved "https://registry.yarnpkg.com/recast/-/recast-0.17.4.tgz#b67f8abbfd4a1d8841485f03f41f2ccf2e051d88" + integrity sha512-94mbtFr2e4XoleJVCQQ138gK7xT2IScq25+thwEzNWd/hjOXQd6ejFiztgZZGVSByoV7/k3pLBXO3RK1BvJsIw== + dependencies: + ast-types "0.12.2" + esprima "~4.0.0" + private "^0.1.8" + source-map "~0.6.1" + rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" @@ -11072,12 +10650,12 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" -recursive-readdir@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.1.tgz#90ef231d0778c5ce093c9a48d74e5c5422d13a99" - integrity sha1-kO8jHQd4xc4JPJpI105cVCLROpk= +recursive-readdir@2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.2.tgz#9946fb3274e1628de6e36b2f6714953b4845094f" + integrity sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg== dependencies: - minimatch "3.0.3" + minimatch "3.0.4" redent@^1.0.0: version "1.0.0" @@ -11087,22 +10665,6 @@ redent@^1.0.0: indent-string "^2.1.0" strip-indent "^1.0.1" -reduce-css-calc@^1.2.6: - version "1.3.0" - resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716" - integrity sha1-dHyRTgSWFKTJz7umKYca0dKSdxY= - dependencies: - balanced-match "^0.4.2" - math-expression-evaluator "^1.2.14" - reduce-function-call "^1.0.1" - -reduce-function-call@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/reduce-function-call/-/reduce-function-call-1.0.2.tgz#5a200bf92e0e37751752fe45b0ab330fd4b6be99" - integrity sha1-WiAL+S4ON3UXUv5FsKszD9S2vpk= - dependencies: - balanced-match "^0.4.2" - regenerate-unicode-properties@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz#107405afcc4a190ec5ed450ecaa00ed0cafa7a4c" @@ -11110,17 +10672,19 @@ regenerate-unicode-properties@^7.0.0: dependencies: regenerate "^1.4.0" +regenerate-unicode-properties@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.0.2.tgz#7b38faa296252376d363558cfbda90c9ce709662" + integrity sha512-SbA/iNrBUf6Pv2zU8Ekv1Qbhv92yxL4hiDa2siuxs4KKn4oOoMDHXjAf7+Nz9qinUQ46B1LcWEi/PhJfPWpZWQ== + dependencies: + regenerate "^1.4.0" + regenerate@^1.2.1, regenerate@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== -regenerator-runtime@^0.10.0: - version "0.10.5" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" - integrity sha1-M2w+/BIgrc7dosn6tntaeVWjNlg= - -regenerator-runtime@^0.11.0, regenerator-runtime@^0.11.1: +regenerator-runtime@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== @@ -11130,6 +10694,11 @@ regenerator-runtime@^0.12.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg== +regenerator-runtime@^0.13.2: + version "0.13.2" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz#32e59c9a6fb9b1a4aff09b4930ca2d4477343447" + integrity sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA== + regenerator-transform@^0.10.0: version "0.10.1" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd" @@ -11139,10 +10708,10 @@ regenerator-transform@^0.10.0: babel-types "^6.19.0" private "^0.1.6" -regenerator-transform@^0.13.3: - version "0.13.3" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.3.tgz#264bd9ff38a8ce24b06e0636496b2c856b57bcbb" - integrity sha512-5ipTrZFSq5vU2YoGoww4uaRVAK4wyYC4TSICibbfEPOruUu8FFP7ErV0BjmbIOEpn3O/k9na9UEdYR/3m7N6uA== +regenerator-transform@^0.13.4: + version "0.13.4" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.4.tgz#18f6763cf1382c69c36df76c6ce122cc694284fb" + integrity sha512-T0QMBjK3J0MtxjPmdIMXm72Wvj2Abb0Bd4HADdfijwMdoIsyQZ6fWC7kDFhk2YinBBEMZDL7Y7wh0J1sGx3S4A== dependencies: private "^0.1.6" @@ -11161,20 +10730,16 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" +regexp-tree@^0.1.0: + version "0.1.5" + resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.5.tgz#7cd71fca17198d04b4176efd79713f2998009397" + integrity sha512-nUmxvfJyAODw+0B13hj8CFVAxhe7fDEAgJgaotBu3nnR+IgGgZq59YedJP5VYTlkEfqjuK6TuRpnymKdatLZfQ== + regexpp@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== -regexpu-core@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b" - integrity sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs= - dependencies: - regenerate "^1.2.1" - regjsgen "^0.2.0" - regjsparser "^0.1.4" - regexpu-core@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" @@ -11184,7 +10749,7 @@ regexpu-core@^2.0.0: regjsgen "^0.2.0" regjsparser "^0.1.4" -regexpu-core@^4.1.3, regexpu-core@^4.2.0: +regexpu-core@^4.1.3: version "4.2.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.2.0.tgz#a3744fa03806cffe146dea4421a3e73bdcc47b1d" integrity sha512-Z835VSnJJ46CNBttalHD/dB+Sj2ezmY6Xp38npwU87peK6mqOzOpV8eYktdkLTEkzzD+JsTcxd84ozd8I14+rw== @@ -11196,6 +10761,18 @@ regexpu-core@^4.1.3, regexpu-core@^4.2.0: unicode-match-property-ecmascript "^1.0.4" unicode-match-property-value-ecmascript "^1.0.2" +regexpu-core@^4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.5.4.tgz#080d9d02289aa87fe1667a4f5136bc98a6aebaae" + integrity sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ== + dependencies: + regenerate "^1.4.0" + regenerate-unicode-properties "^8.0.2" + regjsgen "^0.5.0" + regjsparser "^0.6.0" + unicode-match-property-ecmascript "^1.0.4" + unicode-match-property-value-ecmascript "^1.1.0" + registry-auth-token@^3.0.1: version "3.3.2" resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.2.tgz#851fd49038eecb586911115af845260eec983f20" @@ -11221,6 +10798,11 @@ regjsgen@^0.4.0: resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.4.0.tgz#c1eb4c89a209263f8717c782591523913ede2561" integrity sha512-X51Lte1gCYUdlwhF28+2YMO0U6WeN0GLpgpA7LK7mbdDnkQYiwvEpmpe0F/cv5L14EbxgrdayAG3JETBv0dbXA== +regjsgen@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.0.tgz#a7634dc08f89209c2049adda3525711fb97265dd" + integrity sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA== + regjsparser@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" @@ -11235,15 +10817,17 @@ regjsparser@^0.3.0: dependencies: jsesc "~0.5.0" -relateurl@0.2.x: - version "0.2.7" - resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" - integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= +regjsparser@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.0.tgz#f1e6ae8b7da2bae96c99399b868cd6c933a2ba9c" + integrity sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ== + dependencies: + jsesc "~0.5.0" -remark-parse@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-5.0.0.tgz#4c077f9e499044d1d5c13f80d7a98cf7b9285d95" - integrity sha512-b3iXszZLH1TLoyUzrATcTQUZrwNl1rE70rVdSruJFlDaJ9z5aMkhrG43Pp68OgfHndL/ADz6V69Zow8cTQu+JA== +remark-parse@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-6.0.3.tgz#c99131052809da482108413f87b0ee7f52180a3a" + integrity sha512-QbDXWN4HfKTUC0hHa4teU463KclLAnwpn/FBn87j9cKYJWWawbiLgMfP2Q4XwhxxuuuOxHlw+pSN0OKuJwyVvg== dependencies: collapse-white-space "^1.0.2" is-alphabetical "^1.0.0" @@ -11261,10 +10845,10 @@ remark-parse@^5.0.0: vfile-location "^2.0.0" xtend "^4.0.1" -remark-stringify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-5.0.0.tgz#336d3a4d4a6a3390d933eeba62e8de4bd280afba" - integrity sha512-Ws5MdA69ftqQ/yhRF9XhVV29mhxbfGhbz0Rx5bQH+oJcNhhSM6nCu1EpLod+DjrFGrU0BMPs+czVmJZU7xiS7w== +remark-stringify@^6.0.0: + version "6.0.4" + resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-6.0.4.tgz#16ac229d4d1593249018663c7bddf28aafc4e088" + integrity sha512-eRWGdEPMVudijE/psbIDNcnJLRVx3xhfuEsTDGgH4GsFF91dVhw5nhmnBppafJ7+NWINW6C7ZwWbi30ImJzqWg== dependencies: ccount "^1.0.0" is-alphanumeric "^1.0.0" @@ -11281,31 +10865,37 @@ remark-stringify@^5.0.0: unherit "^1.0.4" xtend "^4.0.1" -remark@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/remark/-/remark-9.0.0.tgz#c5cfa8ec535c73a67c4b0f12bfdbd3a67d8b2f60" - integrity sha512-amw8rGdD5lHbMEakiEsllmkdBP+/KpjW/PRK6NSGPZKCQowh0BT4IWXDAkRMyG3SB9dKPXWMviFjNusXzXNn3A== +remark@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/remark/-/remark-10.0.1.tgz#3058076dc41781bf505d8978c291485fe47667df" + integrity sha512-E6lMuoLIy2TyiokHprMjcWNJ5UxfGQjaMSMhV+f4idM625UjjK4j798+gPs5mfjzDE6vL0oFKVeZM6gZVSVrzQ== dependencies: - remark-parse "^5.0.0" - remark-stringify "^5.0.0" - unified "^6.0.0" + remark-parse "^6.0.0" + remark-stringify "^6.0.0" + unified "^7.0.0" -remove-trailing-separator@^1.0.1: +remove-bom-buffer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz#c2bf1e377520d324f623892e33c10cac2c252b53" + integrity sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ== + dependencies: + is-buffer "^1.1.5" + is-utf8 "^0.2.1" + +remove-bom-stream@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz#05f1a593f16e42e1fb90ebf59de8e569525f9523" + integrity sha1-BfGlk/FuQuH7kOv1nejlaVJflSM= + dependencies: + remove-bom-buffer "^3.0.0" + safe-buffer "^5.1.0" + through2 "^2.0.3" + +remove-trailing-separator@^1.0.1, remove-trailing-separator@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= -renderkid@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.2.tgz#12d310f255360c07ad8fde253f6c9e9de372d2aa" - integrity sha512-FsygIxevi1jSiPY9h7vZmBFUbAOcbYm9UwyiLNdVsLRs/5We9Ob5NMPbGYUTWiLq5L+ezlVdE0A8bbME5CWTpg== - dependencies: - css-select "^1.1.0" - dom-converter "~0.2" - htmlparser2 "~3.3.0" - strip-ansi "^3.0.0" - utila "^0.4.0" - repeat-element@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" @@ -11328,16 +10918,20 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -replace-ext@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" - integrity sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ= - replace-ext@1.0.0, replace-ext@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs= +replace-homedir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/replace-homedir/-/replace-homedir-1.0.0.tgz#e87f6d513b928dde808260c12be7fec6ff6e798c" + integrity sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw= + dependencies: + homedir-polyfill "^1.0.1" + is-absolute "^1.0.0" + remove-trailing-separator "^1.1.0" + request-progress@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-2.0.1.tgz#5d36bb57961c673aa5b788dbc8141fdf23b44e08" @@ -11376,11 +10970,6 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= -require-from-string@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - require-main-filename@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" @@ -11401,13 +10990,6 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= -requizzle@~0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/requizzle/-/requizzle-0.2.1.tgz#6943c3530c4d9a7e46f1cddd51c158fc670cdbde" - integrity sha1-aUPDUwxNmn5G8c3dUcFY/GcM294= - dependencies: - underscore "~1.6.0" - resolve-cwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-1.0.0.tgz#4eaeea41ed040d1702457df64a42b2b07d246f9f" @@ -11453,6 +11035,13 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +resolve-options@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/resolve-options/-/resolve-options-1.1.0.tgz#32bb9e39c06d67338dc9378c0d6d6074566ad131" + integrity sha1-MrueOcBtZzONyTeMDW1gdFZq0TE= + dependencies: + value-or-function "^3.0.0" + resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -11465,14 +11054,21 @@ resolve@1.3.2: dependencies: path-parse "^1.0.5" -resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.2, resolve@^1.8.1: +resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.2: version "1.8.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" integrity sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA== dependencies: path-parse "^1.0.5" -resolve@^1.5.0, resolve@^1.6.0: +resolve@^1.4.0, resolve@^1.9.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba" + integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg== + dependencies: + path-parse "^1.0.6" + +resolve@^1.5.0: version "1.9.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.9.0.tgz#a14c6fdfa8f92a7df1d996cb7105fa744658ea06" integrity sha512-TZNye00tI67lwYvzxCxHGjwTNlUV70io54/Ed4j6PscB8xVfuBJpRenI/o6dVk0cY0PYTY27AgCoGGxRnYuItQ== @@ -11500,6 +11096,11 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== +rewrite-imports@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/rewrite-imports/-/rewrite-imports-1.2.0.tgz#091b05aa0a358e54b6582205b6feeb73977bd8fb" + integrity sha512-oVoZ3QImciK+S7I89vPyxDgohkwhJyWLP5EKcMvF2NOhaFkmdKFiJMJzTM35VeNV1gQfGvv9Cve6sMq5E7unHg== + rgb-regex@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" @@ -11510,6 +11111,13 @@ rgba-regex@^1.0.0: resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= +right-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" + integrity sha1-YTObci/mo1FWiSENJOFMlhSGE+8= + dependencies: + align-text "^0.1.1" + rimraf@2, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" @@ -11517,7 +11125,7 @@ rimraf@2, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2. dependencies: glob "^7.0.5" -rimraf@~2.6.2: +rimraf@2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== @@ -11546,37 +11154,20 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" -rx-lite-aggregates@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" - integrity sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74= - dependencies: - rx-lite "*" - -rx-lite@*, rx-lite@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" - integrity sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ= - -rx@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782" - integrity sha1-pfE/957zt0D+MKqAP7CfmIBdR4I= - -rxjs@6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.2.2.tgz#eb75fa3c186ff5289907d06483a77884586e1cf9" - integrity sha512-0MI8+mkKAXZUF9vMrEoPnaoHkfzBPP4IGwUYRJhIRJF6/w3uByO1e91bEHn8zd43RdkTMKiooYKmwz7RH6zfOQ== - dependencies: - tslib "^1.9.0" - -rxjs@^6.1.0, rxjs@^6.3.3: +rxjs@^6.1.0: version "6.3.3" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.3.3.tgz#3c6a7fa420e844a81390fb1158a9ec614f4bad55" integrity sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw== dependencies: tslib "^1.9.0" +rxjs@^6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.4.0.tgz#f3bb0fe7bda7fb69deac0c16f17b50b0b8790504" + integrity sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw== + dependencies: + tslib "^1.9.0" + safe-buffer@5.1.2, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -11621,7 +11212,7 @@ sass-loader@^7.1.0: pify "^3.0.0" semver "^5.5.0" -sax@>=0.6.0, sax@^1.2.4, sax@~1.2.1, sax@~1.2.4: +sax@>=0.6.0, sax@^1.2.4, sax@~1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== @@ -11633,14 +11224,7 @@ schedule@^0.5.0: dependencies: object-assign "^4.1.1" -schema-utils@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf" - integrity sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8= - dependencies: - ajv "^5.0.0" - -schema-utils@^0.4.0, schema-utils@^0.4.4, schema-utils@^0.4.5: +schema-utils@^0.4.5: version "0.4.7" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187" integrity sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ== @@ -11670,6 +11254,11 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= +select@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" + integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0= + selfsigned@^1.9.1: version "1.10.4" resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.4.tgz#cdd7eccfca4ed7635d47a08bf2d5d3074092e2cd" @@ -11684,12 +11273,19 @@ semver-diff@^2.0.0: dependencies: semver "^5.0.3" -"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1: +semver-greatest-satisfied-range@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz#13e8c2658ab9691cb0cd71093240280d36f77a5b" + integrity sha1-E+jCZYq5aRywzXEJMkAoDTb3els= + dependencies: + sver-compat "^1.5.0" + +"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== -semver@^4.1.0, semver@~4.3.3: +semver@~4.3.3: version "4.3.6" resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" integrity sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto= @@ -11718,11 +11314,6 @@ send@0.16.2: range-parser "~1.2.0" statuses "~1.4.0" -sequencify@~0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/sequencify/-/sequencify-0.0.7.tgz#90cff19d02e07027fd767f5ead3e7b95d1e7380c" - integrity sha1-kM/xnQLgcCf9dn9erT57ldHnOAw= - serialize-javascript@^1.4.0: version "1.5.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.5.0.tgz#1aa336162c88a890ddad5384baebc93a655161fe" @@ -11830,16 +11421,6 @@ shell-quote@1.6.1: array-reduce "~0.0.0" jsonify "~0.0.0" -shelljs@0.3.x: - version "0.3.0" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.3.0.tgz#3596e6307a781544f591f37da618360f31db57b1" - integrity sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E= - -sigmund@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" - integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA= - signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" @@ -11875,10 +11456,10 @@ slice-ansi@0.0.4: resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU= -slice-ansi@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.0.0.tgz#5373bdb8559b45676e8541c66916cdd6251612e7" - integrity sha512-4j2WTWjp3GsZ+AOagyzVbzp4vWGtZ0hEZ/gDY/uTvm6MTxUfTUIsnMIFb1bn8o0RuXiqUw15H1bue8f22Vw2oQ== +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== dependencies: ansi-styles "^3.2.0" astral-regex "^1.0.0" @@ -11889,6 +11470,11 @@ slide@^1.1.5: resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" integrity sha1-VusCfWW00tzmyy4tMsTUr8nh1wc= +slugify@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.3.4.tgz#78d2792d7222b55cd9fc81fa018df99af779efeb" + integrity sha512-KP0ZYk5hJNBS8/eIjGkFDCzGQIoZ1mnfQRYS5WM3273z+fxGWXeN0fkwf2ebEweydv9tioZIHGZKoF21U07/nw== + snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" @@ -11979,6 +11565,18 @@ sockjs-client@1.1.5: json3 "^3.3.2" url-parse "^1.1.8" +sockjs-client@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.3.0.tgz#12fc9d6cb663da5739d3dc5fb6e8687da95cb177" + integrity sha512-R9jxEzhnnrdxLCNln0xg5uGHqMnkhPSTzUZH2eXcR03S/On9Yvoq2wyUZILRUhZCNVu2PmwWVoyuiPz8th8zbg== + dependencies: + debug "^3.2.5" + eventsource "^1.0.7" + faye-websocket "~0.11.1" + inherits "^2.0.3" + json3 "^3.3.2" + url-parse "^1.4.3" + sockjs@0.3.19: version "0.3.19" resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.19.tgz#d976bbe800af7bd20ae08598d582393508993c0d" @@ -11987,7 +11585,7 @@ sockjs@0.3.19: faye-websocket "^0.10.0" uuid "^3.0.1" -sort-keys@^1.0.0, sort-keys@^1.1.1: +sort-keys@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" integrity sha1-RBttTTRnmPG05J6JIK37oOVD+a0= @@ -11999,7 +11597,7 @@ source-list-map@^2.0.0: resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== -source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: +source-map-resolve@^0.5.0: version "0.5.2" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA== @@ -12017,6 +11615,14 @@ source-map-support@^0.4.0, source-map-support@^0.4.15: dependencies: source-map "^0.5.6" +source-map-support@~0.5.10: + version "0.5.11" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.11.tgz#efac2ce0800355d026326a0ca23e162aeac9a4e2" + integrity sha512-//sajEx/fGL3iw6fltKMdPvy8kL3kJ2O3iuYlRoT3k9Kb4BjOoZ+BZzaNHeuaruSt+Kf3Zk9tnfAQg9/AJqUVQ== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" @@ -12029,7 +11635,7 @@ source-map@^0.4.2: dependencies: amdefine ">=0.0.4" -source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7: +source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= @@ -12039,21 +11645,16 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -sourcemap-codec@^1.4.1: - version "1.4.3" - resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.3.tgz#0ba615b73ec35112f63c2f2d9e7c3f87282b0e33" - integrity sha512-vFrY/x/NdsD7Yc8mpTJXuao9S8lq08Z/kOITHz6b7YbfI9xL8Spe5EvSQUHOI7SbpY8bRPr0U3kKSsPuqEGSfA== +sourcemap-codec@^1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.4.tgz#c63ea927c029dd6bd9a2b7fa03b3fec02ad56e9f" + integrity sha512-CYAPYdBu34781kLHkaW3m6b/uUSyMOC2R61gcYMWooeuaGtjof86ZA/8T+qVPPt7np1085CR9hmMGrySwEc8Xg== sparkles@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.1.tgz#008db65edce6c50eec0c5e228e1945061dd0437c" integrity sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw== -spawn-command@^0.0.2-1: - version "0.0.2-1" - resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0" - integrity sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A= - spdx-correct@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.2.tgz#19bb409e91b47b1ad54159243f7312a858db3c2e" @@ -12080,30 +11681,28 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.1.tgz#e2a303236cac54b04031fa7a5a79c7e701df852f" integrity sha512-TfOfPcYGBB5sDuPn3deByxPhmfegAhpDYKSOXZQN81Oyrrif8ZCodOLzK3AesELnCx03kikhyDwh0pfvvQvF8w== -spdy-transport@^2.0.18: - version "2.1.0" - resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-2.1.0.tgz#4bbb15aaffed0beefdd56ad61dbdc8ba3e2cb7a1" - integrity sha512-bpUeGpZcmZ692rrTiqf9/2EUakI6/kXX1Rpe0ib/DyOzbiexVfXkw6GnvI9hVGvIwVaUhkaBojjCZwLNRGQg1g== +spdy-transport@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" + integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== dependencies: - debug "^2.6.8" - detect-node "^2.0.3" + debug "^4.1.0" + detect-node "^2.0.4" hpack.js "^2.1.6" - obuf "^1.1.1" - readable-stream "^2.2.9" - safe-buffer "^5.0.1" - wbuf "^1.7.2" + obuf "^1.1.2" + readable-stream "^3.0.6" + wbuf "^1.7.3" -spdy@^3.4.1: - version "3.4.7" - resolved "https://registry.yarnpkg.com/spdy/-/spdy-3.4.7.tgz#42ff41ece5cc0f99a3a6c28aabb73f5c3b03acbc" - integrity sha1-Qv9B7OXMD5mjpsKKq7c/XDsDrLw= +spdy@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.0.tgz#81f222b5a743a329aa12cea6a390e60e9b613c52" + integrity sha512-ot0oEGT/PGUpzf/6uk4AWLqkq+irlqHXkrdbk51oWONh3bxQmBuljxPNl66zlRRcIJStWq0QkLUCPOPjgjvU0Q== dependencies: - debug "^2.6.8" - handle-thing "^1.2.5" + debug "^4.1.0" + handle-thing "^2.0.0" http-deceiver "^1.2.7" - safe-buffer "^5.0.1" select-hose "^2.0.0" - spdy-transport "^2.0.18" + spdy-transport "^3.0.0" split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" @@ -12112,13 +11711,6 @@ split-string@^3.0.1, split-string@^3.0.2: dependencies: extend-shallow "^3.0.0" -split@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" - integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== - dependencies: - through "2" - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -12146,12 +11738,19 @@ ssri@^5.2.4: dependencies: safe-buffer "^5.1.1" +ssri@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" + integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA== + dependencies: + figgy-pudding "^3.5.1" + stable@~0.1.6: version "0.1.8" resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== -stack-trace@0.0.x: +stack-trace@0.0.10: version "0.0.10" resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= @@ -12209,18 +11808,6 @@ stream-browserify@^2.0.1: inherits "~2.0.1" readable-stream "^2.0.2" -stream-connect@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/stream-connect/-/stream-connect-1.0.2.tgz#18bc81f2edb35b8b5d9a8009200a985314428a97" - integrity sha1-GLyB8u2zW4tdmoAJIAqYUxRCipc= - dependencies: - array-back "^1.0.2" - -stream-consume@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/stream-consume/-/stream-consume-0.1.1.tgz#d3bdb598c2bd0ae82b8cac7ac50b1107a7996c48" - integrity sha512-tNa3hzgkjEP7XbCkbRXe1jpg+ievoa0O4SCFlMOYEscGSS4JJsckGL8swUyAa/ApGU3Ae4t6Honor4HhL+tRyg== - stream-each@^1.1.0: version "1.2.3" resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" @@ -12229,6 +11816,11 @@ stream-each@^1.1.0: end-of-stream "^1.1.0" stream-shift "^1.0.0" +stream-exhaust@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/stream-exhaust/-/stream-exhaust-1.0.2.tgz#acdac8da59ef2bc1e17a2c0ccf6c320d120e555d" + integrity sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw== + stream-http@^2.7.2: version "2.8.3" resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" @@ -12245,16 +11837,6 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI= -stream-via@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/stream-via/-/stream-via-1.0.4.tgz#8dccbb0ac909328eb8bc8e2a4bd3934afdaf606c" - integrity sha512-DBp0lSvX5G9KGRDTkR/R+a29H+Wk2xItOF+MpZLLNDWbEV9tGPnqLPxHEYjmiz8xGtJHRIqmI+hCjmNzqoA4nQ== - -strict-uri-encode@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" - integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= - string-width@^1.0.1, string-width@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -12272,6 +11854,15 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" +string-width@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + string_decoder@^1.0.0, string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -12319,45 +11910,39 @@ stringify-object@^3.2.0: is-obj "^1.0.1" is-regexp "^1.0.0" -strip-ansi@3.0.1, strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^4.0.0: +strip-ansi@4.0.0, strip-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= dependencies: ansi-regex "^3.0.0" -strip-ansi@^5.0.0: +strip-ansi@5.0.0, strip-ansi@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.0.0.tgz#f78f68b5d0866c20b2c9b8c61b5298508dc8756f" integrity sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow== dependencies: ansi-regex "^4.0.0" +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.1.0.tgz#55aaa54e33b4c0649a7338a43437b1887d153ec4" + integrity sha512-TjxrkPONqO2Z8QDCpeE2j6n0M6EwxzyDgzEeGp+FbdvaJAt//ClYi6W5my+3ROlC/hZX2KACUwDfK49Ka5eDvg== + dependencies: + ansi-regex "^4.1.0" + strip-ansi@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.1.1.tgz#39e8a98d044d150660abe4a6808acf70bb7bc991" integrity sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE= -strip-bom-string@1.X: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92" - integrity sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI= - -strip-bom@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-1.0.0.tgz#85b8862f3844b5a6d5ec8467a93598173a36f794" - integrity sha1-hbiGLzhEtabV7IRnqTWYFzo295Q= - dependencies: - first-chunk-stream "^1.0.0" - is-utf8 "^0.2.0" - strip-bom@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" @@ -12370,14 +11955,6 @@ strip-bom@^3.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= -strip-comments@^0.4.4: - version "0.4.4" - resolved "https://registry.yarnpkg.com/strip-comments/-/strip-comments-0.4.4.tgz#b9caafc4fe905f96c091df89f9a7215f2aa629c6" - integrity sha1-ucqvxP6QX5bAkd+J+achXyqmKcY= - dependencies: - extend-shallow "^2.0.1" - extract-comments "^0.10.1" - strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" @@ -12390,24 +11967,11 @@ strip-indent@^1.0.1: dependencies: get-stdin "^4.0.1" -strip-json-comments@1.0.x: - version "1.0.4" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" - integrity sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E= - strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= -style-loader@^0.18.2: - version "0.18.2" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.18.2.tgz#cc31459afbcd6d80b7220ee54b291a9fd66ff5eb" - integrity sha512-WPpJPZGUxWYHWIUMNNOYqql7zh85zGmr84FdTVWq52WTIkqlW9xSxD3QYWi/T31cqn9UNSsietVEgGn2aaSCzw== - dependencies: - loader-utils "^1.0.2" - schema-utils "^0.3.0" - style-loader@^0.21.0: version "0.21.0" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.21.0.tgz#68c52e5eb2afc9ca92b6274be277ee59aea3a852" @@ -12445,39 +12009,27 @@ supports-color@^2.0.0: resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= -supports-color@^3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" - integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY= - dependencies: - has-flag "^1.0.0" - -supports-color@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" - integrity sha1-vnoN5ITexcXN34s9WRJQRJEvY1s= - dependencies: - has-flag "^2.0.0" - -supports-color@^5.1.0, supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0: +supports-color@^5.1.0, supports-color@^5.3.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== dependencies: has-flag "^3.0.0" -svgo@^0.7.0: - version "0.7.2" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" - integrity sha1-n1dyQTlSE1xv779Ar+ak+qiLS7U= +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== dependencies: - coa "~1.0.1" - colors "~1.1.2" - csso "~2.3.1" - js-yaml "~3.7.0" - mkdirp "~0.5.1" - sax "~1.2.1" - whet.extend "~0.9.9" + has-flag "^3.0.0" + +sver-compat@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/sver-compat/-/sver-compat-1.5.0.tgz#3cf87dfeb4d07b4a3f14827bc186b3fd0c645cd8" + integrity sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg= + dependencies: + es6-iterator "^2.0.1" + es6-symbol "^3.1.1" svgo@^1.0.0: version "1.1.1" @@ -12514,20 +12066,15 @@ symbol@^0.2.1: resolved "https://registry.yarnpkg.com/symbol/-/symbol-0.2.3.tgz#3b9873b8a901e47c6efe21526a3ac372ef28bbc7" integrity sha1-O5hzuKkB5Hxu/iFSajrDcu8ou8c= -table@^5.0.2: - version "5.1.1" - resolved "https://registry.yarnpkg.com/table/-/table-5.1.1.tgz#92030192f1b7b51b6eeab23ed416862e47b70837" - integrity sha512-NUjapYb/qd4PeFW03HnAuOJ7OMcBkJlqeClWxeNlQ0lXGSb52oZXGzkO0/I0ARegQ2eUT1g2VDJH0eUxDRcHmw== +table@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/table/-/table-5.2.3.tgz#cde0cc6eb06751c009efab27e8c820ca5b67b7f2" + integrity sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ== dependencies: - ajv "^6.6.1" + ajv "^6.9.1" lodash "^4.17.11" - slice-ansi "2.0.0" - string-width "^2.1.1" - -taffydb@2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/taffydb/-/taffydb-2.6.2.tgz#7cbcb64b5a141b6a2efc2c5d2c67b4e150b2a268" - integrity sha1-fLy2S1oUG2ou/CxdLGe04VCyomg= + slice-ansi "^2.1.0" + string-width "^3.0.0" tapable@^1.0.0, tapable@^1.1.0: version "1.1.0" @@ -12556,10 +12103,28 @@ tar@^4: safe-buffer "^5.1.2" yallist "^3.0.2" -temp-path@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/temp-path/-/temp-path-1.0.0.tgz#24b1543973ab442896d9ad367dd9cbdbfafe918b" - integrity sha1-JLFUOXOrRCiW2a02fdnL2/r+kYs= +terser-webpack-plugin@^1.1.0, terser-webpack-plugin@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.2.3.tgz#3f98bc902fac3e5d0de730869f50668561262ec8" + integrity sha512-GOK7q85oAb/5kE12fMuLdn2btOS9OBZn4VsecpHDywoUC/jLhSAKOiYo0ezx7ss2EXPMzyEWFoE0s1WLE+4+oA== + dependencies: + cacache "^11.0.2" + find-cache-dir "^2.0.0" + schema-utils "^1.0.0" + serialize-javascript "^1.4.0" + source-map "^0.6.1" + terser "^3.16.1" + webpack-sources "^1.1.0" + worker-farm "^1.5.2" + +terser@^3.16.1: + version "3.17.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-3.17.0.tgz#f88ffbeda0deb5637f9d24b0da66f4e15ab10cb2" + integrity sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ== + dependencies: + commander "^2.19.0" + source-map "~0.6.1" + source-map-support "~0.5.10" text-encoding@^0.6.4: version "0.6.4" @@ -12581,7 +12146,15 @@ throttleit@^1.0.0: resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" integrity sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw= -through2@2.X, through2@^2.0.0, through2@^2.0.3: +through2-filter@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-3.0.0.tgz#700e786df2367c2c88cd8aa5be4cf9c1e7831254" + integrity sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA== + dependencies: + through2 "~2.0.0" + xtend "~4.0.0" + +through2@^2.0.0, through2@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" integrity sha1-AARWmzfHx0ujnEPzzteNGtlBQL4= @@ -12589,15 +12162,15 @@ through2@2.X, through2@^2.0.0, through2@^2.0.3: readable-stream "^2.1.5" xtend "~4.0.1" -through2@^0.6.1: - version "0.6.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" - integrity sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg= +through2@~2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== dependencies: - readable-stream ">=1.0.33-1 <1.1.0-0" - xtend ">=4.0.0 <4.1.0-0" + readable-stream "~2.3.6" + xtend "~4.0.1" -through@2, through@^2.3.6: +through@^2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= @@ -12607,13 +12180,6 @@ thunky@^1.0.2: resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.0.3.tgz#f5df732453407b09191dae73e2a8cc73f381a826" integrity sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow== -tildify@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/tildify/-/tildify-1.2.0.tgz#dcec03f55dca9b7aa3e5b04f21817eb56e63588a" - integrity sha1-3OwD9V3Km3qj5bBPIYF+tW5jWIo= - dependencies: - os-homedir "^1.0.0" - time-require@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/time-require/-/time-require-0.1.2.tgz#f9e12cb370fc2605e11404582ba54ef5ca2b2d98" @@ -12629,11 +12195,6 @@ time-stamp@^1.0.0: resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM= -time-stamp@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-2.1.0.tgz#6c5c0b2bc835a244616abcfddf81ce13a1975c9f" - integrity sha512-lJbq6KsFhZJtN3fPUVje1tq/hHsJOKUUcUj/MGCiQR6qWBDcyi5kxL9J7/RnaEChCn0+L/DUN2WvemDrkk4i3Q== - timed-out@^3.0.0: version "3.1.3" resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-3.1.3.tgz#95860bfcc5c76c277f8f8326fd0f5b2e20eba217" @@ -12646,19 +12207,16 @@ timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" -timers-ext@^0.1.5: - version "0.1.7" - resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6" - integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ== - dependencies: - es5-ext "~0.10.46" - next-tick "1" - timsort@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= +tiny-emitter@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" + integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== + tmp@0.0.31: version "0.0.31" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7" @@ -12673,6 +12231,14 @@ tmp@0.0.x, tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" +to-absolute-glob@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1865f43d9e74b0822db9f145b78cff7d0f7c849b" + integrity sha1-GGX0PZ50sIItufFFt4z/fQ98hJs= + dependencies: + is-absolute "^1.0.0" + is-negated-glob "^1.0.0" + to-array@0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" @@ -12726,16 +12292,24 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" safe-regex "^1.1.0" +to-through@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-through/-/to-through-2.0.0.tgz#fc92adaba072647bc0b67d6b03664aa195093af6" + integrity sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY= + dependencies: + through2 "^2.0.3" + +toastr@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/toastr/-/toastr-2.1.4.tgz#8b43be64fb9d0c414871446f2db8e8ca4e95f181" + dependencies: + jquery ">=1.12.0" + token-stream@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/token-stream/-/token-stream-0.0.1.tgz#ceeefc717a76c4316f126d0b9dbaa55d7e7df01a" integrity sha1-zu78cXp2xDFvEm0LnbqlXX598Bo= -toposort@^1.0.0: - version "1.0.7" - resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029" - integrity sha1-LmhELZ9k7HILjMieZEOsbKqVACk= - tough-cookie@~2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" @@ -12749,11 +12323,6 @@ traverse@^0.6.6: resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137" integrity sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc= -tree-kill@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.0.tgz#5846786237b4239014f05db156b643212d4c6f36" - integrity sha512-DlX6dR0lOIRDFxI0mjL9IYg6OTncLm/Zt+JiBhE5OlFcAR8yc9S7FFXU9so0oda47frdM/JFsk7UjNt9vscKcg== - trim-newlines@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" @@ -12786,6 +12355,11 @@ trough@^1.0.0: dependencies: glob "^7.1.2" +ts-map@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/ts-map/-/ts-map-1.0.3.tgz#1c4d218dec813d2103b7e04e4bcf348e1471c1ff" + integrity sha512-vDWbsl26LIcPGmDpoVzjEP6+hvHZkBkLW7JpvwbCv/5IYPJlsbzCVXY3wsCeAxAUeTclNOUZxnLdGh3VBD/J6w== + tslib@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" @@ -12838,10 +12412,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typical@^2.6.0, typical@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/typical/-/typical-2.6.1.tgz#5c080e5d661cbbe38259d2e70a3c7253e873881d" - integrity sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0= +typescript@^3.2.2: + version "3.3.4000" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.4000.tgz#76b0f89cfdbf97827e1112d64f283f1151d6adf0" + integrity sha512-jjOcCZvpkl2+z7JFn0yBOoLQyLoIkNZAs/fYJkUG6VKy6zLPHJGfQJYFHzibB6GJaF/8QrcECtlQ5cpvRHSMEA== uglify-es@^3.3.4: version "3.3.9" @@ -12851,13 +12425,20 @@ uglify-es@^3.3.4: commander "~2.13.0" source-map "~0.6.1" -uglify-js@3.4.x, uglify-js@^3.0.5: - version "3.4.9" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3" - integrity sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q== +uglify-js@^2.6.1: + version "2.8.29" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" + integrity sha1-KcVzMUgFe7Th913zW3qcty5qWd0= dependencies: - commander "~2.17.1" - source-map "~0.6.1" + source-map "~0.5.1" + yargs "~3.10.0" + optionalDependencies: + uglify-to-browserify "~1.0.0" + +uglify-to-browserify@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" + integrity sha1-bgkk1r2mta/jSeOabWMoUKD4grc= uglifyjs-webpack-plugin@1.2.7: version "1.2.7" @@ -12873,20 +12454,6 @@ uglifyjs-webpack-plugin@1.2.7: webpack-sources "^1.1.0" worker-farm "^1.5.2" -uglifyjs-webpack-plugin@^1.2.4: - version "1.3.0" - resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.3.0.tgz#75f548160858163a08643e086d5fefe18a5d67de" - integrity sha512-ovHIch0AMlxjD/97j9AYovZxG5wnHOPkL7T1GKochBADp/Zwc44pEWNqpKl1Loupp1WhFg7SlYmHZRUfdAacgw== - dependencies: - cacache "^10.0.4" - find-cache-dir "^1.0.0" - schema-utils "^0.4.5" - serialize-javascript "^1.4.0" - source-map "^0.6.1" - uglify-es "^3.3.4" - webpack-sources "^1.1.0" - worker-farm "^1.5.2" - uid2@0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82" @@ -12902,27 +12469,30 @@ unc-path-regex@^0.1.2: resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= -underscore-contrib@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/underscore-contrib/-/underscore-contrib-0.3.0.tgz#665b66c24783f8fa2b18c9f8cbb0e2c7d48c26c7" - integrity sha1-ZltmwkeD+PorGMn4y7Dix9SMJsc= - dependencies: - underscore "1.6.0" - -underscore@1.6.0, underscore@~1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8" - integrity sha1-izixDKze9jM3uLJOT/htRa6lKag= - underscore@>=1.8.3, underscore@^1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961" integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg== -underscore@~1.8.3: - version "1.8.3" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022" - integrity sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI= +undertaker-registry@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/undertaker-registry/-/undertaker-registry-1.0.1.tgz#5e4bda308e4a8a2ae584f9b9a4359a499825cc50" + integrity sha1-XkvaMI5KiirlhPm5pDWaSZglzFA= + +undertaker@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/undertaker/-/undertaker-1.2.0.tgz#339da4646252d082dc378e708067299750e11b49" + integrity sha1-M52kZGJS0ILcN45wgGcpl1DhG0k= + dependencies: + arr-flatten "^1.0.1" + arr-map "^2.0.0" + bach "^1.0.0" + collection-map "^1.0.0" + es6-weak-map "^2.0.1" + last-run "^1.1.0" + object.defaults "^1.0.0" + object.reduce "^1.0.0" + undertaker-registry "^1.0.0" unherit@^1.0.4: version "1.1.1" @@ -12932,11 +12502,6 @@ unherit@^1.0.4: inherits "^2.0.1" xtend "^4.0.1" -unicode-5.2.0@^0.7.5: - version "0.7.5" - resolved "https://registry.yarnpkg.com/unicode-5.2.0/-/unicode-5.2.0-0.7.5.tgz#e0df129431a28a95263d8c480fb5e9ab2b0973f0" - integrity sha512-KVGLW1Bri30x00yv4HNM8kBxoqFXr0Sbo55735nvrlsx4PYBZol3UtoWgO492fSwmsetzPEZzy73rbU8OGXJcA== - unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" @@ -12955,21 +12520,28 @@ unicode-match-property-value-ecmascript@^1.0.2: resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.0.2.tgz#9f1dc76926d6ccf452310564fd834ace059663d4" integrity sha512-Rx7yODZC1L/T8XKo/2kNzVAQaRE88AaMvI1EF/Xnj3GW2wzN6fop9DDWuFAKUVFH7vozkz26DzP0qyWLKLIVPQ== +unicode-match-property-value-ecmascript@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz#5b4b426e08d13a80365e0d657ac7a6c1ec46a277" + integrity sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g== + unicode-property-aliases-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz#5a533f31b4317ea76f17d807fa0d116546111dd0" integrity sha512-2WSLa6OdYd2ng8oqiGIWnJqyFArvhn+5vgx5GTxMbUYjCYKUcuKS62YLFF0R/BDGlB1yzXjQOLtPAfHsgirEpg== -unified@^6.0.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/unified/-/unified-6.2.0.tgz#7fbd630f719126d67d40c644b7e3f617035f6dba" - integrity sha512-1k+KPhlVtqmG99RaTbAv/usu85fcSRu3wY8X+vnsEhIxNP5VbVIDiXnLqyKIG+UMdyTg0ZX9EI6k2AfjJkHPtA== +unified@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/unified/-/unified-7.1.0.tgz#5032f1c1ee3364bd09da12e27fdd4a7553c7be13" + integrity sha512-lbk82UOIGuCEsZhPj8rNAkXSDXd6p0QLzIuSsCdxrqnqU56St4eyOB+AlXsVgVeRmetPTYydIuvFfpDIed8mqw== dependencies: + "@types/unist" "^2.0.0" + "@types/vfile" "^3.0.0" bail "^1.0.0" extend "^3.0.0" is-plain-obj "^1.1.0" trough "^1.0.0" - vfile "^2.0.0" + vfile "^3.0.0" x-is-string "^0.1.0" union-value@^1.0.0: @@ -12992,7 +12564,7 @@ uniqs@^2.0.0: resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= -unique-filename@^1.1.0: +unique-filename@^1.1.0, unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== @@ -13006,10 +12578,13 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" -unique-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-1.0.0.tgz#d59a4a75427447d9aa6c91e70263f8d26a4b104b" - integrity sha1-1ZpKdUJ0R9mqbJHnAmP40mpLEEs= +unique-stream@^2.0.2: + version "2.3.1" + resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.3.1.tgz#c65d110e9a4adf9a6c5948b28053d9a8d04cbeac" + integrity sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A== + dependencies: + json-stable-stringify-without-jsonify "^1.0.1" + through2-filter "^3.0.0" unique-temp-dir@^1.0.0: version "1.0.0" @@ -13044,7 +12619,7 @@ unist-util-visit-parents@^2.0.0: dependencies: unist-util-is "^2.1.2" -unist-util-visit@^1.1.0, unist-util-visit@^1.3.1: +unist-util-visit@^1.1.0, unist-util-visit@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.4.0.tgz#1cb763647186dc26f5e1df5db6bd1e48b3cc2fb1" integrity sha512-FiGu34ziNsZA3ZUteZxSFaczIjGmksfSgdKqBfOejrrfzyUy5b7YrlzT1Bcvi+djkYDituJDy2XB7tGTeBieKw== @@ -13093,11 +12668,6 @@ update-notifier@^0.7.0: semver-diff "^2.0.0" xdg-basedir "^2.0.0" -upper-case@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" - integrity sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg= - uri-js@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" @@ -13110,15 +12680,6 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= -url-loader@^1.0.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-1.1.2.tgz#b971d191b83af693c5e3fea4064be9e1f2d7f8d8" - integrity sha512-dXHkKmw8FhPqu8asTc1puBfe3TehOCo2+RmOOev5suNCIYBcT626kxiWg1NBVkwc4rO8BGa7gP70W7VXuqHrjg== - dependencies: - loader-utils "^1.1.0" - mime "^2.0.3" - schema-utils "^1.0.0" - url-parse-lax@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" @@ -13147,11 +12708,6 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== -user-home@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190" - integrity sha1-K1viOjK2Onyd640PKNSFcko98ZA= - useragent@^2.1.12: version "2.3.0" resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.3.0.tgz#217f943ad540cb2128658ab23fc960f6a88c9972" @@ -13187,11 +12743,6 @@ util@^0.10.3: dependencies: inherits "2.0.3" -utila@^0.4.0, utila@~0.4: - version "0.4.0" - resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" - integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw= - utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" @@ -13212,12 +12763,12 @@ v8-compile-cache@^2.0.2: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.2.tgz#a428b28bb26790734c4fc8bc9fa106fccebf6a6c" integrity sha512-1wFuMUIM16MDJRCrpbpuEPTUGmM5QMUg0cr3KFwra2XgOgFcPGDQHDh3CszSCD2Zewc/dh/pamNEW8CbfDebUw== -v8flags@^2.0.2: - version "2.1.1" - resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4" - integrity sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ= +v8flags@^3.0.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.1.2.tgz#fc5cd0c227428181e6c29b2992e4f8f1da5e0c9f" + integrity sha512-MtivA7GF24yMPte9Rp/BWGCYQNaUj86zeYxV/x2RRJMKagImbbv3u8iJC57lNhWLPcGLJmHcHmFWkNsplbbLWw== dependencies: - user-home "^1.1.1" + homedir-polyfill "^1.0.1" validate-npm-package-license@^3.0.1: version "3.0.4" @@ -13227,6 +12778,11 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" +value-or-function@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813" + integrity sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM= + vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -13258,53 +12814,63 @@ vfile-message@^1.0.0: dependencies: unist-util-stringify-position "^1.1.1" -vfile@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/vfile/-/vfile-2.3.0.tgz#e62d8e72b20e83c324bc6c67278ee272488bf84a" - integrity sha512-ASt4mBUHcTpMKD/l5Q+WJXNtshlWxOogYyGYYrg4lt/vuRjC1EFQtlAofL5VmtVNIZJzWYFJjzGWZ0Gw8pzW1w== +vfile@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-3.0.1.tgz#47331d2abe3282424f4a4bb6acd20a44c4121803" + integrity sha512-y7Y3gH9BsUSdD4KzHsuMaCzRjglXN0W2EcMf0gpvu6+SbsGhMje7xDc8AEoeXy6mIwCKMI6BkjMsRjzQbhMEjQ== dependencies: - is-buffer "^1.1.4" + is-buffer "^2.0.0" replace-ext "1.0.0" unist-util-stringify-position "^1.0.0" vfile-message "^1.0.0" -vinyl-fs@^0.3.0: - version "0.3.14" - resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-0.3.14.tgz#9a6851ce1cac1c1cea5fe86c0931d620c2cfa9e6" - integrity sha1-mmhRzhysHBzqX+hsCTHWIMLPqeY= +vinyl-fs@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.3.tgz#c85849405f67428feabbbd5c5dbdd64f47d31bc7" + integrity sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng== dependencies: - defaults "^1.0.0" - glob-stream "^3.1.5" - glob-watcher "^0.0.6" - graceful-fs "^3.0.0" - mkdirp "^0.5.0" - strip-bom "^1.0.0" - through2 "^0.6.1" - vinyl "^0.4.0" + fs-mkdirp-stream "^1.0.0" + glob-stream "^6.1.0" + graceful-fs "^4.0.0" + is-valid-glob "^1.0.0" + lazystream "^1.0.0" + lead "^1.0.0" + object.assign "^4.0.4" + pumpify "^1.3.5" + readable-stream "^2.3.3" + remove-bom-buffer "^3.0.0" + remove-bom-stream "^1.2.0" + resolve-options "^1.1.0" + through2 "^2.0.0" + to-through "^2.0.0" + value-or-function "^3.0.0" + vinyl "^2.0.0" + vinyl-sourcemap "^1.1.0" -vinyl-sourcemaps-apply@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz#ab6549d61d172c2b1b87be5c508d239c8ef87705" - integrity sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU= +vinyl-sourcemap@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz#92a800593a38703a8cdb11d8b300ad4be63b3e16" + integrity sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY= dependencies: - source-map "^0.5.1" + append-buffer "^1.0.2" + convert-source-map "^1.5.0" + graceful-fs "^4.1.6" + normalize-path "^2.1.1" + now-and-later "^2.0.0" + remove-bom-buffer "^3.0.0" + vinyl "^2.0.0" -vinyl@^0.4.0: - version "0.4.6" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.4.6.tgz#2f356c87a550a255461f36bbeb2a5ba8bf784847" - integrity sha1-LzVsh6VQolVGHza76ypbqL94SEc= +vinyl@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.0.tgz#d85b07da96e458d25b2ffe19fece9f2caa13ed86" + integrity sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg== dependencies: - clone "^0.2.0" - clone-stats "^0.0.1" - -vinyl@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.5.3.tgz#b0455b38fc5e0cf30d4325132e461970c2091cde" - integrity sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4= - dependencies: - clone "^1.0.0" - clone-stats "^0.0.1" - replace-ext "0.0.1" + clone "^2.1.1" + clone-buffer "^1.0.0" + clone-stats "^1.0.0" + cloneable-readable "^1.0.0" + remove-trailing-separator "^1.0.1" + replace-ext "^1.0.0" vlq@^0.2.2: version "0.2.3" @@ -13328,37 +12894,27 @@ void-elements@^2.0.0, void-elements@^2.0.1: resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w= -vue-docgen-api@^2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/vue-docgen-api/-/vue-docgen-api-2.5.2.tgz#5e2faf9eae12c6e01c3924667e2412f5ca6564fe" - integrity sha512-o9A0kjxjkN1EsR7E8jRKTfxOrFTPk97zWlqoAl2nBPg+sbfxtWILOagTDifvckCws5aA/dPeCX48LpMEiECCJA== +vue-docgen-api@^3.11.4: + version "3.11.4" + resolved "https://registry.yarnpkg.com/vue-docgen-api/-/vue-docgen-api-3.11.4.tgz#9a19aee94393509838e177a40a7c0d32d3fb3f36" + integrity sha512-ncyowUCW2NZGP67uOZHJMfPj8+hsBnxNMsM3VJvkzIYK4RIP07wnFl8Ye12lAMKdZDT3rkbgWISh2KTzvYvQKw== dependencies: - "@babel/core" "^7.0.1" - "@babel/plugin-proposal-object-rest-spread" "^7.0.0" - "@babel/plugin-syntax-jsx" "^7.0.0" - "@babel/plugin-transform-runtime" "^7.0.0" - "@babel/preset-env" "^7.0.0" - "@babel/preset-flow" "^7.0.0" - "@babel/runtime" "^7.0.0" - babel-helper-vue-jsx-merge-props "^2.0.3" - babel-plugin-syntax-jsx "^6.18.0" - babel-plugin-transform-vue-jsx "^3.7.0" - doctrine "^2.1.0" + "@babel/parser" "^7.2.3" + "@babel/types" "^7.0.0" + ast-types "^0.12.2" hash-sum "^1.0.2" - htmlparser2 "^3.9.2" - jsdoc-api "^4.0.3" - lru-cache "^4.1.3" - pug-code-gen "^1.1.1" - pug-lexer "^3.1.0" - pug-parser "^2.0.2" - pug-runtime "^2.0.4" - vue "^2.4.2" - vue-template-compiler "^2.4.2" + lru-cache "^4.1.5" + pug "^2.0.3" + recast "^0.17.3" + ts-map "^1.0.3" + typescript "^3.2.2" + vue "^2.6.9" + vue-template-compiler "^2.6.10" -vue-eslint-parser@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-4.0.3.tgz#80cf162e484387b2640371ad21ba1f86e0c10a61" - integrity sha512-AUeQsYdO6+7QXCems+WvGlrXd37PHv/zcRQSQdY1xdOMwdFAPEnMBsv7zPvk0TPGulXkK/5p/ITgrjiYB7k3ag== +vue-eslint-parser@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-5.0.0.tgz#00f4e4da94ec974b821a26ff0ed0f7a78402b8a1" + integrity sha512-JlHVZwBBTNVvzmifwjpZYn0oPWH2SgWv5dojlZBsrhablDu95VFD+hriB1rQGwbD+bms6g+rAFhQHk6+NyiS6g== dependencies: debug "^4.1.0" eslint-scope "^4.0.0" @@ -13367,7 +12923,7 @@ vue-eslint-parser@^4.0.2: esquery "^1.0.1" lodash "^4.17.11" -vue-functional-data-merge@^2.0.5: +vue-functional-data-merge@^2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/vue-functional-data-merge/-/vue-functional-data-merge-2.0.7.tgz#bdee655181eacdcb1f96ce95a4cc14e75313d1da" integrity sha512-pvLc+H+x2prwBj/uSEIITyxjz/7ZUVVK8uYbrYMmhDvMXnzh9OvQvVEwcOSBQjsubd4Eq41/CSJaWzy4hemMNQ== @@ -13377,31 +12933,28 @@ vue-hot-reload-api@^2.3.0: resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.1.tgz#b2d3d95402a811602380783ea4f566eb875569a2" integrity sha512-AA86yKZ5uOKz87/q1UpngEXhbRkaYg1b7HMMVRobNV1IVKqZe8oLIzo6iMocVwZXnYitlGwf2k4ZRLOZlS8oPQ== -vue-loader@^15.2.4, vue-loader@^15.4.2: - version "15.4.2" - resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.4.2.tgz#812bb26e447dd3b84c485eb634190d914ce125e2" - integrity sha512-nVV27GNIA9MeoD8yQ3dkUzwlAaAsWeYSWZHsu/K04KCD339lW0Jv2sJWsjj3721SP7sl2lYdPmjcHgkWQSp5bg== +vue-loader@^15.7.0: + version "15.7.0" + resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.7.0.tgz#27275aa5a3ef4958c5379c006dd1436ad04b25b3" + integrity sha512-x+NZ4RIthQOxcFclEcs8sXGEWqnZHodL2J9Vq+hUz+TDZzBaDIh1j3d9M2IUlTjtrHTZy4uMuRdTi8BGws7jLA== dependencies: - "@vue/component-compiler-utils" "^2.0.0" + "@vue/component-compiler-utils" "^2.5.1" hash-sum "^1.0.2" loader-utils "^1.1.0" vue-hot-reload-api "^2.3.0" vue-style-loader "^4.1.0" -vue-router@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.1.tgz#d9b05ad9c7420ba0f626d6500d693e60092cc1e9" - integrity sha512-vLLoY452L+JBpALMP5UHum9+7nzR9PeIBCghU9ZtJ1eWm6ieUI8Zb/DI3MYxH32bxkjzYV1LRjNv4qr8d+uX/w== +vue-router@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.2.tgz#dedc67afe6c4e2bc25682c8b1c2a8c0d7c7e56be" + integrity sha512-opKtsxjp9eOcFWdp6xLQPLmRGgfM932Tl56U9chYTnoWqKxQ8M20N7AkdEbM5beUh6wICoFGYugAX9vQjyJLFg== -vue-style-loader@^3.0.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-3.1.2.tgz#6b66ad34998fc9520c2f1e4d5fa4091641c1597a" - integrity sha512-ICtVdK/p+qXWpdSs2alWtsXt9YnDoYjQe0w5616j9+/EhjoxZkbun34uWgsMFnC1MhrMMwaWiImz3K2jK1Yp2Q== - dependencies: - hash-sum "^1.0.2" - loader-utils "^1.0.2" +vue-rx@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/vue-rx/-/vue-rx-6.1.0.tgz#75c378ef7d58195344b9bf95379d61497c9863ae" + integrity sha512-2nfkmmoZ1C4hBZ6eC1c2vvLzc3DextyNpXIDLQcPfcSHyarQCClyEO5y5Pr/P7MDzT5mrRzfunlLQaH3/lQoXA== -vue-style-loader@^4.1.0, vue-style-loader@^4.1.2: +vue-style-loader@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.2.tgz#dedf349806f25ceb4e64f3ad7c0a44fba735fcf8" integrity sha512-0ip8ge6Gzz/Bk0iHovU9XAUQaFt/G2B61bnWa2tCcqqdgfHs1lF9xXorFbE55Gmy92okFT+8bfmySuUOu13vxQ== @@ -13409,32 +12962,32 @@ vue-style-loader@^4.1.0, vue-style-loader@^4.1.2: hash-sum "^1.0.2" loader-utils "^1.0.2" -vue-styleguidist@^1.8.9: - version "1.8.9" - resolved "https://registry.yarnpkg.com/vue-styleguidist/-/vue-styleguidist-1.8.9.tgz#1a53ce79c44b851c7927d1bdc94df3ce677f8756" - integrity sha512-yf5lNAjq+iTsEYCAOwIh5RkOtybbKNT+l1teQT/quODKNFOx013HJiQbNwD1kU9jrUvaLSEH+Gso//3J5zVtFg== +vue-styleguidist@^3.11.4: + version "3.11.4" + resolved "https://registry.yarnpkg.com/vue-styleguidist/-/vue-styleguidist-3.11.4.tgz#9dc7dd9d59b0d5e48a36b60488fb102b8acd18b0" + integrity sha512-KdYBpxVaahIWhJDr6e98nosHDQSRcRSwQxVSmSl1Is2SZmnzlVJDiw2EWwvaBW1T1T9ZQikKXtqMC8cf6Ar6ig== dependencies: "@vxna/mini-html-webpack-template" "^0.1.7" - acorn "^5.7.1" - ast-types "^0.11.5" - buble "^0.19.3" + acorn "^6.0.5" + ast-types "^0.12.2" + buble "^0.19.7" classnames "^2.2.6" clean-webpack-plugin "^0.1.19" clipboard-copy "^2.0.0" codemirror "^5.39.0" common-dir "^2.0.2" copy-webpack-plugin "^4.5.2" - css-loader "^0.28.11" + css-loader "^2.1.1" doctrine "^2.1.0" - es6-object-assign "~1.1.0" - es6-promise "^4.2.4" - escodegen "^1.10.0" - esprima "^4.0.0" + es6-object-assign "^1.1.0" + es6-promise "^4.2.6" + escodegen "^1.11.1" findup "^0.1.5" function.name-polyfill "^1.0.6" github-slugger "^1.2.0" glob "^7.1.2" glogg "^1.0.1" + hash-sum "^1.0.2" highlight.js "^9.12.0" is-directory "^0.3.1" javascript-stringify "^1.6.0" @@ -13451,77 +13004,63 @@ vue-styleguidist@^1.8.9: loader-utils "^1.1.0" lodash "^4.17.10" lowercase-keys "^1.0.1" + lru-cache "^4.1.3" markdown-to-jsx "^6.6.8" mini-html-webpack-plugin "^0.2.3" minimist "^1.2.0" ora "^2.1.0" + prismjs "^1.15.0" prop-types "^15.6.2" q-i "^2.0.1" + qss "^2.0.3" react "^16.4.1" react-codemirror2 "^5.1.0" - react-dev-utils "^5.0.1" + react-dev-utils "^7.0.3" react-dom "^16.4.1" react-group "^1.0.6" - react-icons "^2.2.7" react-lifecycles-compat "^3.0.4" - react-styleguidist "^7.3.8" - recast "^0.13.0" - remark "^9.0.0" - strip-comments "^0.4.4" + react-simple-code-editor "^0.9.4" + react-styleguidist "^9.0.4" style-loader "^0.21.0" to-ast "^1.0.0" - type-detect "^4.0.8" uglifyjs-webpack-plugin "1.2.7" - unist-util-visit "^1.3.1" - vue-docgen-api "^2.5.2" - vue-webpack-loaders "^1.0.8" - webpack-dev-server "^2.11.2" - webpack-merge "^4.1.3" + vue-docgen-api "^3.11.4" + webpack "^4.28.4" + webpack-dev-server "^3.1.14" + webpack-merge "^4.0.0" -vue-template-compiler@2.5.17, vue-template-compiler@^2.4.2, vue-template-compiler@^2.5.16: - version "2.5.17" - resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.5.17.tgz#52a4a078c327deb937482a509ae85c06f346c3cb" - integrity sha512-63uI4syCwtGR5IJvZM0LN5tVsahrelomHtCxvRkZPJ/Tf3ADm1U1wG6KWycK3qCfqR+ygM5vewUvmJ0REAYksg== +vue-template-compiler@^2.6.10: + version "2.6.10" + resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.10.tgz#323b4f3495f04faa3503337a82f5d6507799c9cc" + integrity sha512-jVZkw4/I/HT5ZMvRnhv78okGusqe0+qH2A0Em0Cp8aq78+NK9TII263CDVz2QXZsIT+yyV/gZc/j/vlwa+Epyg== dependencies: de-indent "^1.0.2" he "^1.1.0" -vue-template-es2015-compiler@^1.5.3, vue-template-es2015-compiler@^1.6.0: +vue-template-es2015-compiler@^1.5.3: version "1.6.0" resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.6.0.tgz#dc42697133302ce3017524356a6c61b7b69b4a18" integrity sha512-x3LV3wdmmERhVCYy3quqA57NJW7F3i6faas++pJQWtknWT+n7k30F4TVdHvCLn48peTJFRvCpxs3UuFPqgeELg== -vue-webpack-loaders@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/vue-webpack-loaders/-/vue-webpack-loaders-1.0.8.tgz#1476bfa1feb40401663b5eb72de83ab611f1f047" - integrity sha512-Na9v5yh00izdIJjUk9vvaLP8H2w75UmAWUsQ6nQ8aegPdEV0A7WuNPjUmjH4UMRRHWjIkCNy5ybx6TjgPdAOVQ== - dependencies: - babel-loader "^7.1.4" - babel-plugin-transform-runtime "^6.23.0" - babel-preset-env "^1.5.2" - babel-preset-stage-2 "^6.24.1" - css-loader "^0.28.11" - extract-text-webpack-plugin "^3.0.0" - file-loader "^0.11.2" - html-webpack-plugin "^2.30.1" - json-loader "^0.5.7" - postcss-loader "^2.0.8" - style-loader "^0.18.2" - url-loader "^1.0.1" - vue "^2.5.16" - vue-loader "^15.2.4" - vue-style-loader "^3.0.1" - vue-template-compiler "^2.5.16" +vue-template-es2015-compiler@^1.8.2: + version "1.8.2" + resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.8.2.tgz#dd73e80ba58bb65dd7a8aa2aeef6089cf6116f2a" + integrity sha512-cliV19VHLJqFUYbz/XeWXe5CO6guzwd0yrrqqp0bmjlMP3ZZULY7fu8RTC4+3lmHwo6ESVDHFDsvjB15hcR5IA== -vue@2.5.17, vue@^2.4.2, vue@^2.5.16: +vue@^2.5.16: version "2.5.17" resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.17.tgz#0f8789ad718be68ca1872629832ed533589c6ada" integrity sha512-mFbcWoDIJi0w0Za4emyLiW72Jae0yjANHbCVquMKijcavBGypqlF7zHRgMa5k4sesdv7hv2rB4JPdZfR+TPfhQ== -walk-back@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/walk-back/-/walk-back-3.0.0.tgz#2358787a35da91032dad5e92f80b12370d8795c5" - integrity sha1-I1h4ejXakQMtrV6S+AsSNw2HlcU= +vue@^2.6.10, vue@^2.6.9: + version "2.6.10" + resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.10.tgz#a72b1a42a4d82a721ea438d1b6bf55e66195c637" + integrity sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ== + +vuex@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.1.0.tgz#634b81515cf0cfe976bd1ffe9601755e51f843b9" + integrity sha512-mdHeHT/7u4BncpUZMlxNaIdcN/HIt1GsGG5LKByArvYG/v6DvHcOxvDCts+7SRdCoIRGllK8IMZvQtQXLppDYg== walk-sync@0.3.1: version "0.3.1" @@ -13531,6 +13070,11 @@ walk-sync@0.3.1: ensure-posix-path "^1.0.0" matcher-collection "^1.0.0" +walkes@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/walkes/-/walkes-0.2.1.tgz#7eca144fe67ed32782fffe6e8e95fb4481864796" + integrity sha1-fsoUT+Z+0yeC//5ujpX7RIGGR5Y= + warning@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c" @@ -13547,7 +13091,7 @@ watchpack@^1.5.0: graceful-fs "^4.1.2" neo-async "^2.5.0" -wbuf@^1.1.0, wbuf@^1.7.2: +wbuf@^1.1.0, wbuf@^1.7.3: version "1.7.3" resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== @@ -13561,32 +13105,22 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" -webpack-cli@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.1.2.tgz#17d7e01b77f89f884a2bbf9db545f0f6a648e746" - integrity sha512-Cnqo7CeqeSvC6PTdts+dywNi5CRlIPbLx1AoUPK2T6vC1YAugMG3IOoO9DmEscd+Dghw7uRlnzV1KwOe5IrtgQ== +webpack-cli@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.0.tgz#55c8a74cae1e88117f9dda3a801c7272e93ca318" + integrity sha512-t1M7G4z5FhHKJ92WRKwZ1rtvi7rHc0NZoZRbSkol0YKl4HvcC8+DsmGDmK7MmZxHSAetHagiOsjOB6MmzC2TUw== dependencies: chalk "^2.4.1" cross-spawn "^6.0.5" enhanced-resolve "^4.1.0" - global-modules-path "^2.3.0" + findup-sync "^2.0.0" + global-modules "^1.0.0" import-local "^2.0.0" interpret "^1.1.0" loader-utils "^1.1.0" supports-color "^5.5.0" v8-compile-cache "^2.0.2" - yargs "^12.0.2" - -webpack-dev-middleware@1.12.2: - version "1.12.2" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.12.2.tgz#f8fc1120ce3b4fc5680ceecb43d777966b21105e" - integrity sha512-FCrqPy1yy/sN6U/SaEZcHKRXGlqU0DUaEBL45jkUYoB8foVb6wCnbIJ1HKIx+qUFTW+3JpVcCJCxZ8VATL4e+A== - dependencies: - memory-fs "~0.4.1" - mime "^1.5.0" - path-is-absolute "^1.0.0" - range-parser "^1.0.3" - time-stamp "^2.0.0" + yargs "^12.0.5" webpack-dev-middleware@^3.2.0: version "3.4.0" @@ -13598,38 +13132,51 @@ webpack-dev-middleware@^3.2.0: range-parser "^1.0.3" webpack-log "^2.0.0" -webpack-dev-server@^2.11.2: - version "2.11.3" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.11.3.tgz#3fd48a402164a6569d94d3d17f131432631b4873" - integrity sha512-Qz22YEFhWx+M2vvJ+rQppRv39JA0h5NNbOOdODApdX6iZ52Diz7vTPXjF7kJlfn+Uc24Qr48I3SZ9yncQwRycg== +webpack-dev-middleware@^3.5.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.6.1.tgz#91f2531218a633a99189f7de36045a331a4b9cd4" + integrity sha512-XQmemun8QJexMEvNFbD2BIg4eSKrmSIMrTfnl2nql2Sc6OGAYFyb8rwuYrCjl/IiEYYuyTEiimMscu7EXji/Dw== + dependencies: + memory-fs "^0.4.1" + mime "^2.3.1" + range-parser "^1.0.3" + webpack-log "^2.0.0" + +webpack-dev-server@^3.1.14: + version "3.2.1" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.2.1.tgz#1b45ce3ecfc55b6ebe5e36dab2777c02bc508c4e" + integrity sha512-sjuE4mnmx6JOh9kvSbPYw3u/6uxCLHNWfhWaIPwcXWsvWOPN+nc5baq4i9jui3oOBRXGonK9+OI0jVkaz6/rCw== dependencies: ansi-html "0.0.7" - array-includes "^3.0.3" bonjour "^3.5.0" chokidar "^2.0.0" compression "^1.5.2" connect-history-api-fallback "^1.3.0" - debug "^3.1.0" + debug "^4.1.1" del "^3.0.0" express "^4.16.2" html-entities "^1.2.0" - http-proxy-middleware "~0.17.4" - import-local "^1.0.0" - internal-ip "1.2.0" + http-proxy-middleware "^0.19.1" + import-local "^2.0.0" + internal-ip "^4.2.0" ip "^1.1.5" killable "^1.0.0" loglevel "^1.4.1" opn "^5.1.0" portfinder "^1.0.9" + schema-utils "^1.0.0" selfsigned "^1.9.1" + semver "^5.6.0" serve-index "^1.7.2" sockjs "0.3.19" - sockjs-client "1.1.5" - spdy "^3.4.1" + sockjs-client "1.3.0" + spdy "^4.0.0" strip-ansi "^3.0.0" - supports-color "^5.1.0" - webpack-dev-middleware "1.12.2" - yargs "6.6.0" + supports-color "^6.1.0" + url "^0.11.0" + webpack-dev-middleware "^3.5.1" + webpack-log "^2.0.0" + yargs "12.0.2" webpack-log@^2.0.0: version "2.0.0" @@ -13639,14 +13186,14 @@ webpack-log@^2.0.0: ansi-colors "^3.0.0" uuid "^3.3.2" -webpack-merge@^4.1.3, webpack-merge@^4.1.4: - version "4.1.4" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.1.4.tgz#0fde38eabf2d5fd85251c24a5a8c48f8a3f4eb7b" - integrity sha512-TmSe1HZKeOPey3oy1Ov2iS3guIZjWvMT2BBJDzzT5jScHTjVC3mpjJofgueEzaEd6ibhxRDD6MIblDr8tzh8iQ== +webpack-merge@^4.0.0, webpack-merge@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.1.tgz#5e923cf802ea2ace4fd5af1d3247368a633489b4" + integrity sha512-4p8WQyS98bUJcCvFMbdGZyZmsKuWjWVnVHnAS3FFg0HDaRVrPbkivx2RYCre8UiemD67RsiFFLfn4JhLAin8Vw== dependencies: lodash "^4.17.5" -webpack-sources@^1.0.1, webpack-sources@^1.1.0, webpack-sources@^1.3.0: +webpack-sources@^1.1.0, webpack-sources@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.3.0.tgz#2a28dcb9f1f45fe960d8f1493252b5ee6530fa85" integrity sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA== @@ -13654,17 +13201,17 @@ webpack-sources@^1.0.1, webpack-sources@^1.1.0, webpack-sources@^1.3.0: source-list-map "^2.0.0" source-map "~0.6.1" -webpack@^4.23.0: - version "4.23.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.23.0.tgz#0cfbf324b7f6789ac2e6c98fa83eabe96fa56232" - integrity sha512-Osh/3U9y4swhEKDjy8fF48v2Qx5VC6VvdQ8bEm1HMaVVddiQBw4+mIyDrzVcVRCPT/+4uJFOcklPuoB+I3Zw0w== +webpack@^4.28.4, webpack@^4.29.6: + version "4.29.6" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.29.6.tgz#66bf0ec8beee4d469f8b598d3988ff9d8d90e955" + integrity sha512-MwBwpiE1BQpMDkbnUUaW6K8RFZjljJHArC6tWQJoFm0oQtfoSebtg4Y7/QHnJ/SddtjYLHaKGX64CFjG5rehJw== dependencies: - "@webassemblyjs/ast" "1.7.10" - "@webassemblyjs/helper-module-context" "1.7.10" - "@webassemblyjs/wasm-edit" "1.7.10" - "@webassemblyjs/wasm-parser" "1.7.10" - acorn "^5.6.2" - acorn-dynamic-import "^3.0.0" + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-module-context" "1.8.5" + "@webassemblyjs/wasm-edit" "1.8.5" + "@webassemblyjs/wasm-parser" "1.8.5" + acorn "^6.0.5" + acorn-dynamic-import "^4.0.0" ajv "^6.1.0" ajv-keywords "^3.1.0" chrome-trace-event "^1.0.0" @@ -13678,9 +13225,9 @@ webpack@^4.23.0: mkdirp "~0.5.0" neo-async "^2.5.0" node-libs-browser "^2.0.0" - schema-utils "^0.4.4" + schema-utils "^1.0.0" tapable "^1.1.0" - uglifyjs-webpack-plugin "^1.2.4" + terser-webpack-plugin "^1.1.0" watchpack "^1.5.0" webpack-sources "^1.3.0" @@ -13697,11 +13244,6 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" integrity sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg== -whet.extend@~0.9.9: - version "0.9.9" - resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1" - integrity sha1-+HfVv2SMl+WqVC+twW1qJZucEaE= - which-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" @@ -13712,7 +13254,7 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which@1, which@^1.2.1, which@^1.2.10, which@^1.2.12, which@^1.2.14, which@^1.2.9: +which@1, which@^1.2.1, which@^1.2.10, which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -13733,17 +13275,10 @@ widest-line@^1.0.0: dependencies: string-width "^1.0.1" -winston@^2.4.0: - version "2.4.4" - resolved "https://registry.yarnpkg.com/winston/-/winston-2.4.4.tgz#a01e4d1d0a103cf4eada6fc1f886b3110d71c34b" - integrity sha512-NBo2Pepn4hK4V01UfcWcDlmiVTs7VTB1h7bgnB0rgP146bYhMxX0ypCz3lBOfNxCO4Zuek7yeT+y/zM1OfMw4Q== - dependencies: - async "~1.0.0" - colors "1.0.x" - cycle "1.0.x" - eyes "0.1.x" - isstream "0.1.x" - stack-trace "0.0.x" +window-size@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" + integrity sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0= with@^5.0.0: version "5.1.1" @@ -13753,6 +13288,11 @@ with@^5.0.0: acorn "^3.1.0" acorn-globals "^3.0.0" +wordwrap@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" + integrity sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8= + wordwrap@~0.0.2: version "0.0.3" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" @@ -13812,10 +13352,10 @@ write-pkg@^1.0.0: dependencies: write-json-file "^1.1.0" -write@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" - integrity sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c= +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" + integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== dependencies: mkdirp "^0.5.1" @@ -13857,11 +13397,6 @@ xmlbuilder@~9.0.1: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= -xmlcreate@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/xmlcreate/-/xmlcreate-1.0.2.tgz#fa6bf762a60a413fb3dd8f4b03c5b269238d308f" - integrity sha1-+mv3YqYKQT+z3Y9LA8WyaSONMI8= - xmlhttprequest-ssl@1.5.3: version "1.5.3" resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz#185a888c04eca46c3e4070d99f7b49de3528992d" @@ -13872,7 +13407,7 @@ xregexp@4.0.0: resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.0.0.tgz#e698189de49dd2a18cc5687b05e17c8e43943020" integrity sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg== -"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: +xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.0, xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68= @@ -13911,12 +13446,13 @@ yargs-parser@^10.1.0: dependencies: camelcase "^4.1.0" -yargs-parser@^4.2.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c" - integrity sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw= +yargs-parser@^11.1.1: + version "11.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4" + integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ== dependencies: - camelcase "^3.0.0" + camelcase "^5.0.0" + decamelize "^1.2.0" yargs-parser@^5.0.0: version "5.0.0" @@ -13925,26 +13461,7 @@ yargs-parser@^5.0.0: dependencies: camelcase "^3.0.0" -yargs@6.6.0: - version "6.6.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208" - integrity sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg= - dependencies: - camelcase "^3.0.0" - cliui "^3.2.0" - decamelize "^1.1.1" - get-caller-file "^1.0.1" - os-locale "^1.4.0" - read-pkg-up "^1.0.1" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^1.0.2" - which-module "^1.0.0" - y18n "^3.2.1" - yargs-parser "^4.2.0" - -yargs@^12.0.1, yargs@^12.0.2: +yargs@12.0.2: version "12.0.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.2.tgz#fe58234369392af33ecbef53819171eff0f5aadc" integrity sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ== @@ -13962,7 +13479,25 @@ yargs@^12.0.1, yargs@^12.0.2: y18n "^3.2.1 || ^4.0.0" yargs-parser "^10.1.0" -yargs@^7.0.0: +yargs@^12.0.5: + version "12.0.5" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13" + integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw== + dependencies: + cliui "^4.0.0" + decamelize "^1.2.0" + find-up "^3.0.0" + get-caller-file "^1.0.1" + os-locale "^3.0.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1 || ^4.0.0" + yargs-parser "^11.1.1" + +yargs@^7.0.0, yargs@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8" integrity sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg= @@ -13981,6 +13516,16 @@ yargs@^7.0.0: y18n "^3.2.1" yargs-parser "^5.0.0" +yargs@~3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" + integrity sha1-9+572FfdfB0tOMDnTvvWgdFDH9E= + dependencies: + camelcase "^1.0.2" + cliui "^2.1.0" + decamelize "^1.0.0" + window-size "0.1.0" + yauzl@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005" diff --git a/config/datatypes_conf.xml.sample b/config/datatypes_conf.xml.sample index 8ca2e47859d..4a181ce15e8 100644 --- a/config/datatypes_conf.xml.sample +++ b/config/datatypes_conf.xml.sample @@ -3,6 +3,19 @@ + + + + + + + + + + + + + @@ -88,6 +101,7 @@ + @@ -143,6 +157,7 @@ + @@ -223,6 +238,12 @@ + + + + + + @@ -272,6 +293,7 @@ + @@ -401,6 +423,7 @@ + @@ -521,7 +544,9 @@ - + + + @@ -741,9 +766,12 @@ + + + @@ -751,6 +779,12 @@ + + + + + + @@ -828,6 +862,7 @@ + @@ -838,13 +873,16 @@ + - + + + diff --git a/config/environment_modules_mapping.yml.sample b/config/environment_modules_mapping.yml.sample new file mode 100644 index 00000000000..8fefec6ad55 --- /dev/null +++ b/config/environment_modules_mapping.yml.sample @@ -0,0 +1,47 @@ +# This is an example mapping file for the module (http://modules.sourceforge.net/) Dependency resolver (in YAML format) +# +# The goal of this file is to map tool's requirements to existing modules available on your system +# Of course, if the name of a requirement and the name of a module match perfectly, there is no need to map them together through this mapping file. +# +# This is a sample file so the first thing to do to activate the mapping system is to create a copy of this file called "environment_modules_mapping.yml". +# The module dependency resolver is programmed to search and use this YAML file automatically if it exists in the "config" folder of your Galaxy instance. +# Alternatively, you can also use the "mapping_files" attribute of the resolver in the dependency_resolvers_conf.xml file to specify a custom mapping file +# +# Example 1: +# +# Let's say that one of the wrapper installed on your Galaxy instance has the following requirement: +# +# +# PIPITS +# +# +# But unfortunately, the name of the corresponding module file on your system is "pipits_pipeline/1.5.0" +# +# Then, to make Galaxy load/unload the appropriate module, you just have to add the following lines (without the #) to the "environment_modules_mapping.yml" file: +# +#- from: +# name: PIPITS +# version: 1.5.0 +# to: +# name: pipits_pipeline +# version: 1.5.0.6 +# +# +# Example 2: +# +# The requirements section specify a requirement on the PIPITS tool but do not ask for a specific version of it: +# +# +# PIPITS +# +# +# Although, there is no version required you may want to force the loading of a version that is known to run well on your system. +# +# In that case you can add the following lines to the "environment_modules_mapping.yml" file: +# +#- from: +# name: PIPITS +# unversioned: true +# to: +# name: pipits_pipeline +# version: 1.4.0 diff --git a/config/error_report.yml.sample b/config/error_report.yml.sample index 6a2be692613..cd4ab91ef56 100644 --- a/config/error_report.yml.sample +++ b/config/error_report.yml.sample @@ -34,14 +34,6 @@ - type: sentry user_submission: false -# Allow users to submit error reports to biostars. This requires that the -# biostars integration is configured. This *only* makes sense when -# user_submission is true, as it only generates the link for the user to click -# on and submit the bug report, it does not actually submit the bug report on -# their behalf. -# - type: biostars -# user_submission: true - # InfluxDB error reporting backend. You will need to `pip install # influxdb` in the galaxy virtualenv yourself. This sends well tagged # errors InfluxDB allowing you to notice relationships between tool errors and @@ -58,9 +50,34 @@ # comment on existing, open issues. The issues are labelled based on tool ID / # version and include all of the information the normal emailed bug reports # include. If you use a private Github Enterprise deployment, you can set -# github_base_url='https://...' +# github_base_url='https://...' and github_api_url='https://api.....' as shown below. +# The 'github_default_repo_only' flag restores the previous behaviour. When this is set +# to true it will automatically only submit to the default git repository. # - type: github +# verbose: false # user_submission: true # github_oauth_token: 00000000000 -# github_repo_owner: galaxyproject -# github_repo_name: galaxy +# github_base_url: https://github.com +# github_api_url: https://api.github.com +# github_default_repo_owner: galaxyproject +# github_default_repo_name: galaxy +# github_default_repo_only: true + +# GitLab error reporting backend. You will need to `pip install python-gitlab` +# in the galaxy virtualenv. This will create a new issue if none exists, and +# comment on existing, open issues. The issues are labelled based on tool ID / +# version and include all of the information the normal emailed bug reports +# include. If you use a private GitLab deployment, you can set +# gitlab_base_url='https://...'. It supports creating an issue on the git +# repository of the tool by querying the ToolShed where the tool comes from +# (if applicable). Set verbose to true if you want the message to be displayed +# to the user. The 'gitlab_default_repo_only' flags ensures all errors are +# submitted to the default repository only. +# - type: gitlab +# verbose: false +# user_submission: true +# gitlab_base_url: https://gitlab.com +# gitlab_private_token: 00000000000 +# gitlab_default_repo_owner: galaxyproject +# gitlab_default_repo_name: galaxy +# gitlab_default_repo_only: true \ No newline at end of file diff --git a/config/galaxy.yml.sample b/config/galaxy.yml.sample index 06a29868639..e33feb9aeda 100644 --- a/config/galaxy.yml.sample +++ b/config/galaxy.yml.sample @@ -66,6 +66,14 @@ uwsgi: # Reports, etc.) that you are loading. module: galaxy.webapps.galaxy.buildapp:uwsgi_app() + # Mount the web application (e.g. Galaxy, Reports, etc.) at the given + # URL prefix. Cannot be used together with 'module:' above. + #mount: /galaxy=galaxy.webapps.galaxy.buildapp:uwsgi_app() + + # Make uWSGI rewrite PATH_INFO and SCRIPT_NAME according to mount- + # points. Set this to true if a URL prefix is used. + manage-script-name: false + # It is usually a good idea to set this to ``true`` if processes is # greater than 1. thunder-lock: false @@ -90,16 +98,11 @@ uwsgi: galaxy: - # If running behind a proxy server and Galaxy is served from a - # subdirectory, enable the proxy-prefix filter and set the prefix in - # the [filter:proxy-prefix] section above. - #filter-with: proxy-prefix - - # If proxy-prefix is enabled and you're running more than one Galaxy - # instance behind one hostname, you will want to set this to the same - # path as the prefix in the filter above. This value becomes the - # "path" attribute set in the cookie so the cookies from each instance - # will not clobber each other. + # When running multiple Galaxy instances under separate URL prefixes + # on a single hostname, you will want to set this to the same path as + # the prefix set in the uWSGI "mount" configuration option above. This + # value becomes the "path" attribute set in the cookie so the cookies + # from one instance will not clobber those from another. #cookie_path: '' # By default, Galaxy uses a SQLite database at @@ -107,7 +110,7 @@ galaxy: # string to specify an external database instead. This string takes # many options which are explained in detail in the config file # documentation. - #database_connection: 'sqlite:///./database/universe.sqlite?isolation_level=IMMEDIATE' + #database_connection: sqlite:///./database/universe.sqlite?isolation_level=IMMEDIATE # If the server logs errors about not having enough database pool # connections, you will want to increase these values, or consider @@ -160,33 +163,35 @@ galaxy: # instances with pretested installs. The following option can be used # to separate the tool shed install database (all other options listed # above but prefixed with install_ are also available). - #install_database_connection: 'sqlite:///./database/universe.sqlite?isolation_level=IMMEDIATE' + #install_database_connection: sqlite:///./database/universe.sqlite?isolation_level=IMMEDIATE # Setting the following option to true will cause Galaxy to # automatically migrate the database forward after updates. This is # not recommended for production use. #database_auto_migrate: false - # Dataset files are stored in this directory. + # Where dataset files are stored. It must accessible at the same path + # on any cluster nodes that will run Galaxy jobs, unless using Pulsar. #file_path: database/files - # Temporary files are stored in this directory. + # Where temporary files are stored. It must accessible at the same + # path on any cluster nodes that will run Galaxy jobs, unless using + # Pulsar. #new_file_path: database/tmp # Tool config files, defines what tools are available in Galaxy. Tools # can be locally developed or installed from Galaxy tool sheds. # (config/tool_conf.xml.sample will be used if left unset and # config/tool_conf.xml does not exist). - #tool_config_file: 'config/tool_conf.xml,config/shed_tool_conf.xml' + #tool_config_file: config/tool_conf.xml,config/shed_tool_conf.xml # Enable / disable checking if any tools defined in the above non-shed # tool_config_files (i.e., tool_conf.xml) have been migrated from the - # Galaxy code distribution to the Tool Shed. This setting should - # generally be set to False only for development Galaxy environments - # that are often rebuilt from scratch where migrated tools do not need - # to be available in the Galaxy tool panel. If the following setting - # remains commented, the default setting will be True. - #check_migrate_tools: true + # Galaxy code distribution to the Tool Shed. This functionality is + # largely untested in modern Galaxy releases and has serious issues + # such as #7273 and the possibility of slowing down Galaxy startup, so + # the default and recommended value is False. + #check_migrate_tools: false # Tool config maintained by tool migration scripts. If you use the # migration scripts to install tools that have been migrated to the @@ -240,7 +245,7 @@ galaxy: # conda channels to enable by default (https://conda.io/docs/user- # guide/tasks/manage-channels.html) - #conda_ensure_channels: 'iuc,conda-forge,bioconda,defaults' + #conda_ensure_channels: iuc,conda-forge,bioconda,defaults # Use locally-built conda packages. #conda_use_local: false @@ -345,7 +350,7 @@ galaxy: # Conda channels to use when building Docker or Singularity containers # using involucro. - #mulled_channels: 'conda-forge,bioconda' + #mulled_channels: conda-forge,bioconda # Enable automatic polling of relative tool sheds to see if any # updates are available for installed repositories. Ideally only one @@ -577,7 +582,7 @@ galaxy: # URL of the support resource for the galaxy instance. Used in # activation emails. - #instance_resource_url: 'https://galaxyproject.org/' + #instance_resource_url: https://galaxyproject.org/ # E-mail domains blacklist is used for filtering out users that are # using disposable email address during the registration. If their @@ -599,13 +604,12 @@ galaxy: # Activation grace period (in hours). Activation is not forced (login # is not disabled) until grace period has passed. Users under grace - # period can't run jobs. Enter 0 to disable grace period. Users with - # OpenID logins have grace period forever. + # period can't run jobs. Enter 0 to disable grace period. #activation_grace_period: 3 # Shown in warning box to users that were not activated yet. In use # only if activation_grace_period is set. - #inactivity_box_content: 'Your account has not been activated yet. Feel free to browse around and see what''s available, but you won''t be able to upload data or run jobs until you have verified your email address.' + #inactivity_box_content: Your account has not been activated yet. Feel free to browse around and see what's available, but you won't be able to upload data or run jobs until you have verified your email address. # Password expiration period (in days). Users are required to change # their password every x days. Users will be redirected to the change @@ -636,7 +640,7 @@ galaxy: # Main, Test and Archaea browsers, but the default if left commented # is to not allow any display sites to bypass security (you must # uncomment the line below to allow them). - #display_servers: 'hgw1.cse.ucsc.edu,hgw2.cse.ucsc.edu,hgw3.cse.ucsc.edu,hgw4.cse.ucsc.edu,hgw5.cse.ucsc.edu,hgw6.cse.ucsc.edu,hgw7.cse.ucsc.edu,hgw8.cse.ucsc.edu,lowepub.cse.ucsc.edu' + #display_servers: hgw1.cse.ucsc.edu,hgw2.cse.ucsc.edu,hgw3.cse.ucsc.edu,hgw4.cse.ucsc.edu,hgw5.cse.ucsc.edu,hgw6.cse.ucsc.edu,hgw7.cse.ucsc.edu,hgw8.cse.ucsc.edu,lowepub.cse.ucsc.edu # To disable the old-style display applications that are hardcoded # into datatype classes, set enable_old_display_applications = False. @@ -670,6 +674,10 @@ galaxy: # format string as specified by ISO 8601 international standard). #pretty_datetime_format: $locale (UTC) + # Location of the configuration file containing extra user + # preferences. + #user_preferences_extra_conf_path: config/user_preferences_extra_conf.yml + # Default localization for Galaxy UI. Allowed values are listed at the # end of client/galaxy/scripts/nls/locale.js. With the default value # (auto), the locale will be automatically adjusted to the user's @@ -686,7 +694,7 @@ galaxy: # your machine on the virtualbox network (vboxnet0) setup for the # Docker host VM. This can found by running ifconfig and using the IP # address of the network vboxnet0. - #galaxy_infrastructure_url: 'http://localhost:8080' + #galaxy_infrastructure_url: http://localhost:8080 # If the above URL cannot be determined ahead of time in dynamic # environments but the port which should be used to access Galaxy can @@ -708,41 +716,26 @@ galaxy: #helpsite_url: '' # The URL linked by the "Wiki" link in the "Help" menu. - #wiki_url: 'https://galaxyproject.org/' + #wiki_url: https://galaxyproject.org/ # The URL linked by the "Support" link in the "Help" menu. - #support_url: 'https://galaxyproject.org/support/' - - # Enable integration with a custom Biostar instance. - #biostar_url: '' - - # Enable integration with a custom Biostar instance. - #biostar_key_name: '' - - # Enable integration with a custom Biostar instance. - #biostar_key: '' - - # Enable integration with a custom Biostar instance. - #biostar_enable_bug_reports: true - - # Enable integration with a custom Biostar instance. - #biostar_never_authenticate: false + #support_url: https://galaxyproject.org/support/ # The URL linked by the "How to Cite Galaxy" link in the "Help" menu. - #citation_url: 'https://galaxyproject.org/citing-galaxy' + #citation_url: https://galaxyproject.org/citing-galaxy # The URL linked by the "Search" link in the "Help" menu. - #search_url: 'https://galaxyproject.org/search/' + #search_url: https://galaxyproject.org/search/ # The URL linked by the "Mailing Lists" link in the "Help" menu. - #mailing_lists_url: 'https://galaxyproject.org/mailing-lists' + #mailing_lists_url: https://galaxyproject.org/mailing-lists # The URL linked by the "Videos" link in the "Help" menu. - #screencasts_url: 'https://vimeo.com/galaxyproject' + #screencasts_url: https://vimeo.com/galaxyproject # Points to the GenomeSpace UI service which will be used by the # GenomeSpace importer and exporter tools - #genomespace_ui_url: 'https://gsui.genomespace.org/jsui/' + #genomespace_ui_url: https://gsui.genomespace.org/jsui/ # The URL linked by the "Terms and Conditions" link in the "Help" # menu, as well as on the user registration and login forms and in the @@ -914,7 +907,7 @@ galaxy: # The golang proxy needs to know how to talk to your docker daemon. # Currently TLS is not supported, that will come in an update. - #dynamic_proxy_golang_docker_address: 'unix:///var/run/docker.sock' + #dynamic_proxy_golang_docker_address: unix:///var/run/docker.sock # The golang proxy uses a RESTful HTTP API for communication with # Galaxy instead of a JSON or SQLite file for IPC. If you do not @@ -1048,7 +1041,7 @@ galaxy: # Heartbeat log filename. Can accept the template variables # {server_name} and {pid} - #heartbeat_log: 'heartbeat_{server_name}.log' + #heartbeat_log: heartbeat_{server_name}.log # Log to Sentry Sentry is an open source logging and error aggregation # platform. Setting sentry_dsn will enable the Sentry middleware and @@ -1353,25 +1346,14 @@ galaxy: # Enable the new container interface for Interactive Environments #enable_beta_containers_interface: false - # Set the following to a number of threads greater than 1 to spawn a - # Python task queue for dealing with large tool submissions (either - # through the tool form or as part of an individual workflow step - # across large collection). This affects workflow scheduling and web - # processes, not job handlers. This is a beta option and should not be - # used in production. - #tool_submission_burst_threads: 1 - - # If tool_submission_burst_threads is set to a number greater than 1, - # this is the number of jobs to schedule at which the task queue will - # be created. - #tool_submission_burst_at: 10 - # Enable beta workflow modules that should not yet be considered part # of Galaxy's stable API. #enable_beta_workflow_modules: false - # Enable import and export of workflows as Galaxy Format 2 workflows. - #enable_beta_workflow_format: false + # Export of workflows as Galaxy Format 2 instead of GA workflows by + # default. The default value of this configuration variable will + # switch to True in 19.09. + #enable_beta_export_format2_default: false # Following options only apply to workflows scheduled using the legacy # workflow run API (running workflows via a POST to /api/workflows). @@ -1428,18 +1410,6 @@ galaxy: # particular history #history_local_serial_workflow_scheduling: false - # Enable authentication via OpenID. Allows users to log in to their - # Galaxy account by authenticating with an OpenID provider. - #enable_openid: false - - # If OpenID is enabled, this configuration file specifies providers to - # use. Falls back to the .sample variant in config if default does not - # exist. - #openid_config_file: config/openid_conf.xml - - # If OpenID is enabled, consumer cache directory to use. - #openid_consumer_cache_path: database/openid_consumer_cache - # Enables and disables OpenID Connect (OIDC) support. #enable_oidc: false @@ -1464,6 +1434,12 @@ galaxy: # to set this on public servers. #master_api_key: changethis + # Enable access to post-authentication options via OpenID. + #enable_openid: false + + # If OpenID is enabled, consumer cache directory to use. + #openid_consumer_cache_path: database/openid_consumer_cache + # Enable tool tags (associating tools with tags). This has its own # option since its implementation has a few performance implications # on startup for large servers. @@ -1480,7 +1456,7 @@ galaxy: # The URL to the myExperiment instance being used (omit scheme but # include port) - #myexperiment_url: 'www.myexperiment.org:80' + #myexperiment_url: www.myexperiment.org:80 # Enable Galaxy's "Upload via FTP" interface. You'll need to install # and configure an FTP server (we've used ProFTPd since it can use @@ -1502,7 +1478,7 @@ galaxy: # Python string template used to determine an FTP upload directory for # a particular user. - #ftp_upload_dir_template: '${ftp_upload_dir}/${ftp_upload_dir_identifier}' + #ftp_upload_dir_template: ${ftp_upload_dir}/${ftp_upload_dir_identifier # This should be set to False to prevent Galaxy from deleting uploaded # FTP files as it imports them. @@ -1736,22 +1712,22 @@ galaxy: # Define toolbox filters (https://galaxyproject.org/user-defined- # toolbox-filters/) that users may use to restrict the tools to # display. - #user_tool_filters: 'examples:restrict_upload_to_admins, examples:restrict_encode' + #user_tool_filters: examples:restrict_upload_to_admins, examples:restrict_encode # Define toolbox filters (https://galaxyproject.org/user-defined- # toolbox-filters/) that users may use to restrict the tool sections # to display. - #user_tool_section_filters: 'examples:restrict_text' + #user_tool_section_filters: examples:restrict_text # Define toolbox filters (https://galaxyproject.org/user-defined- # toolbox-filters/) that users may use to restrict the tool labels to # display. - #user_tool_label_filters: 'examples:restrict_upload_to_admins, examples:restrict_encode' + #user_tool_label_filters: examples:restrict_upload_to_admins, examples:restrict_encode # The base module(s) that are searched for modules for toolbox # filtering (https://galaxyproject.org/user-defined-toolbox-filters/) # functions. - #toolbox_filter_base_modules: 'galaxy.tools.toolbox.filters,galaxy.tools.filters' + #toolbox_filter_base_modules: galaxy.tools.toolbox.filters,galaxy.tools.filters # Galaxy uses AMQP internally for communicating between processes. # For example, when reloading the toolbox or locking job execution, @@ -1763,13 +1739,13 @@ galaxy: # not specified either, Galaxy will automatically create and use a # separate sqlite database located in your /database folder # (indicated in the commented out line below). - #amqp_internal_connection: 'sqlalchemy+sqlite:///./database/control.sqlite?isolation_level=IMMEDIATE' + #amqp_internal_connection: sqlalchemy+sqlite:///./database/control.sqlite?isolation_level=IMMEDIATE # Galaxy real time communication server settings #enable_communication_server: false # Galaxy real time communication server settings - #communication_server_host: 'http://localhost' + #communication_server_host: http://localhost # Galaxy real time communication server settings #communication_server_port: 7070 diff --git a/config/job_conf.xml.sample_advanced b/config/job_conf.xml.sample_advanced index cdcabe17c30..76661bb992d 100644 --- a/config/job_conf.xml.sample_advanced +++ b/config/job_conf.xml.sample_advanced @@ -120,11 +120,6 @@ /path/to/kubeconfig @@ -196,14 +191,12 @@ Kubernetes 1.2 and on. Changing this a much newer version in the future might require changes to the plugin runner code. Value extensions/v1beta1 is also supported for pre 1.2 legacy installations. --> - galaxy_pvc - + galaxy_pvc1:/mount_point1,galaxy_pvc2:/mount_point2 + + Typical mount paths are the file_path, job_working_directory, all paths containing tools and scripts + and all library paths set in galaxy.yml (or equivalent general galaxy config file). --> @@ -36,7 +36,7 @@ - + @@ -46,22 +46,44 @@ - + --> - + --> + + + + + + --> + + + + + + + --> diff --git a/config/oidc_backends_config.xml.sample b/config/oidc_backends_config.xml.sample index eee3c02daf2..b2317c45652 100644 --- a/config/oidc_backends_config.xml.sample +++ b/config/oidc_backends_config.xml.sample @@ -1,4 +1,57 @@ + ... @@ -22,4 +75,29 @@ login to Galaxy using their Google account. --> + + + + ... + ... + http://localhost:8080/authnz/globus/callback + + consent + + + + ... + ... + ... + http://localhost:8000/authnz/custos/callback + + ... + + + + + + + diff --git a/config/plugins/interactive_environments/cellxgene/config/allowed_images.yml.sample b/config/plugins/interactive_environments/cellxgene/config/allowed_images.yml.sample new file mode 100644 index 00000000000..f3ec2cde2b8 --- /dev/null +++ b/config/plugins/interactive_environments/cellxgene/config/allowed_images.yml.sample @@ -0,0 +1,5 @@ +--- +- + image: quay.io/galaxy/cellxgene-galaxy-ie:2019.01.30 + description: | + An interactive data explorer for single-cell transcriptomics datasets diff --git a/config/plugins/interactive_environments/cellxgene/config/cellxgene.ini.sample b/config/plugins/interactive_environments/cellxgene/config/cellxgene.ini.sample new file mode 100644 index 00000000000..9347211cc48 --- /dev/null +++ b/config/plugins/interactive_environments/cellxgene/config/cellxgene.ini.sample @@ -0,0 +1,46 @@ +[main] +# Following options are ignored if using the Galaxy dynamic proxy but +# are useful if mapping a range of ports for environment consumption. +#password_auth = False +#ssl = False + +[docker] +# Command to launch docker container. For example `sudo docker` or `docker-lxc`. +# If you need to use a command like `sg` you can do that here, just be sure to +# wrap all of the docker portion in single quotes. E.g. `sg 'docker' 'docker {docker_args}'` +# +# It is recommended that you use command_inject if you need to inject +# additional parameters. This command string is re-used for a `docker inspect` +# command and will likely cause errors if it is extensively modified, past the +# usual group/sudo changes. +#command = docker {docker_args} + +# The image argument was moved to "allowed_images.yml.sample" + +# Additional arguments that are passed to the `docker run` command. +command_inject = --sig-proxy=true -e DEBUG=false -e KILL_MODE=True -e DEFAULT_CONTAINER_RUNTIME=120 + +# URL to access the Galaxy API with from the spawn Docker containter, if empty +# this falls back to galaxy.ini's galaxy_infrastructure_url and finally to the +# Docker host of the spawned container if that is also not set. +#galaxy_url = + +# The Docker hostname. It can be useful to run the Docker daemon on a different +# host than Galaxy. +#docker_hostname = localhost + +# Try to set the tempdirectory to world execute - this can fix the issue +# where 'sudo docker' is not able to mount the folder otherwise. +# "finalize namespace chdir to /import permission denied" +#wx_tempdir = False + +# Overwride the IE tempdirectory. This can be useful if you regular tempdir is +# located on an NFS share, which does not work well as Docker volume. In this case +# you can have a shared sshfs share which you can use as temporary directory to +# share data between the IE and Galaxy. +#docker_galaxy_temp_dir = None + +# If your Docker container exposes more then one port, Galaxy needs to know to +# which ports it needs to connect. With this option you can specify the port number +# inside your container to which Galaxy should connect. +docker_connect_port = 80 diff --git a/config/plugins/interactive_environments/cellxgene/config/cellxgene.xml b/config/plugins/interactive_environments/cellxgene/config/cellxgene.xml new file mode 100644 index 00000000000..1761bbe71f4 --- /dev/null +++ b/config/plugins/interactive_environments/cellxgene/config/cellxgene.xml @@ -0,0 +1,18 @@ + + + + + + HistoryDatasetAssociation + binary.Anndata + + dataset_id + + + + dataset_id + + cellxgene.mako + diff --git a/config/plugins/interactive_environments/cellxgene/docker/Dockerfile b/config/plugins/interactive_environments/cellxgene/docker/Dockerfile new file mode 100644 index 00000000000..b172e4a0ec5 --- /dev/null +++ b/config/plugins/interactive_environments/cellxgene/docker/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.6.8-slim + +RUN pip3 install cellxgene && rm -rf ~/.cache + +ADD ./run_cellxgene.sh / + +ENTRYPOINT ["/run_cellxgene.sh"] + +EXPOSE 80 diff --git a/config/plugins/interactive_environments/cellxgene/docker/run_cellxgene.sh b/config/plugins/interactive_environments/cellxgene/docker/run_cellxgene.sh new file mode 100755 index 00000000000..0759da7af8e --- /dev/null +++ b/config/plugins/interactive_environments/cellxgene/docker/run_cellxgene.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +cellxgene launch --host 0.0.0.0 --port 80 /input/file.* diff --git a/config/plugins/interactive_environments/cellxgene/templates/cellxgene.mako b/config/plugins/interactive_environments/cellxgene/templates/cellxgene.mako new file mode 100644 index 00000000000..3d91ae1c9b5 --- /dev/null +++ b/config/plugins/interactive_environments/cellxgene/templates/cellxgene.mako @@ -0,0 +1,39 @@ +<%namespace name="ie" file="ie.mako" /> +<% + ie_request.load_deploy_config() + + # Make the dataset available inside the container + data_file = ie_request.volume('/input/file.' + hda.ext, hda.file_name, mode='ro') + + ie_request.launch( + image = trans.request.params.get('image_tag', None), + volumes = [data_file] + ) + + url = ie_request.url_template('${PROXY_URL}') +%> + + + ${ ie.load_default_js() } + + + +
+
+ + diff --git a/config/plugins/interactive_environments/ethercalc/static/js/ethercalc.js b/config/plugins/interactive_environments/ethercalc/static/js/ethercalc.js index a08f05d5126..bcf564666ae 100644 --- a/config/plugins/interactive_environments/ethercalc/static/js/ethercalc.js +++ b/config/plugins/interactive_environments/ethercalc/static/js/ethercalc.js @@ -7,39 +7,3 @@ function load_notebook(url){ IES.append_notebook(url); }); } - -function keep_alive(notebook_access_url){ - /** - * This is needed to keep the container alive. If the user leaves this site - * this function is not constantly pinging the container, the container will - * terminate itself. - */ - - var request_count = 0; - var interval = window.setInterval(function(){ - $.ajax({ - url: notebook_access_url, - xhrFields: { - withCredentials: true - }, - type: "GET", - timeout: 500, - success: function(){ - console.log("Connected to IE, returning"); - }, - error: function(jqxhr, status, error){ - request_count++; - console.log("Request " + request_count); - if(request_count > 30){ - window.clearInterval(interval); - IES.clear_main_area(); - toastr.error( - "Could not connect to IE, contact your administrator", - "Error", - {'closeButton': true, 'timeOut': 20000, 'tapToDismiss': false} - ); - } - } - }); - }, 10000); -} diff --git a/config/plugins/interactive_environments/ethercalc/templates/ethercalc.mako b/config/plugins/interactive_environments/ethercalc/templates/ethercalc.mako index 28c3fa0d58b..5f2e83bd63a 100644 --- a/config/plugins/interactive_environments/ethercalc/templates/ethercalc.mako +++ b/config/plugins/interactive_environments/ethercalc/templates/ethercalc.mako @@ -28,7 +28,7 @@ ${ ie.plugin_require_config() } // Keep container running and load IE requirejs(['galaxy.interactive_environments', 'plugin/ethercalc'], function(IES){ window.IES = IES; - keep_alive(url); + IES.keepAlive(url); IES.load_when_ready(ie_readiness_url, function(){ load_notebook(url); }); diff --git a/config/plugins/interactive_environments/rstudio/config/allowed_images.yml.sample b/config/plugins/interactive_environments/rstudio/config/allowed_images.yml.sample index 6c9e7691b53..a07739283d7 100644 --- a/config/plugins/interactive_environments/rstudio/config/allowed_images.yml.sample +++ b/config/plugins/interactive_environments/rstudio/config/allowed_images.yml.sample @@ -6,9 +6,8 @@ # appropriate `apt-get/pip install` statements. --- - - image: erasche/docker-rstudio-notebook:17.01 + image: erasche/docker-rstudio-notebook:19.05 description: | - If you've ever done R analysis, you probably used RStudio. This - familiar R analysis software suite will let you explore your datasets - in depth. Comes with ggplot2, RODBC, maps, shinyapps, knitr, LaTeX, - bioconductor, cummeRbund, and many more pre-installed packages. + This familiar R analysis software suite will let you explore your + datasets in depth. Comes with ggplot2, RODBC, maps, shinyapps, knitr, + LaTeX, bioconductor, cummeRbund, and many more pre-installed packages. diff --git a/config/plugins/interactive_environments/rstudio/config/rstudio.ini.sample b/config/plugins/interactive_environments/rstudio/config/rstudio.ini.sample index ad4d0cf9c0c..6eff4b726de 100644 --- a/config/plugins/interactive_environments/rstudio/config/rstudio.ini.sample +++ b/config/plugins/interactive_environments/rstudio/config/rstudio.ini.sample @@ -31,7 +31,7 @@ password_auth = True # host than Galaxy. #docker_hostname = localhost -# Try to set the tempdirectory to world execute - this can fix the issue +# Try to set the tempdirectory to world execute - this can fix the issue # where 'sudo docker' is not able to mount the folder otherwise. # "finalize namespace chdir to /import permission denied" #wx_tempdir = False @@ -45,7 +45,7 @@ password_auth = True # If your Docker container exposes more then one port, Galaxy needs to know to # which ports it needs to connect. With this option you can specify the port number # inside your container to which Galaxy should connect the UI. -#docker_connect_port = None +docker_connect_port = 80 # Set the following value to false if Docker volumes between Galaxy server and Docker # container cannot or should not be used. diff --git a/config/plugins/interactive_environments/rstudio/static/js/crypto/base64.js b/config/plugins/interactive_environments/rstudio/static/js/crypto/base64.js deleted file mode 100644 index 68e51f6e036..00000000000 --- a/config/plugins/interactive_environments/rstudio/static/js/crypto/base64.js +++ /dev/null @@ -1,73 +0,0 @@ -// ==== File: base64.js -var b64map="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -var b64pad="="; - -function hex2b64(h) { - var i; - var c; - var ret = ""; - for(i = 0; i+3 <= h.length; i+=3) { - c = parseInt(h.substring(i,i+3),16); - ret += b64map.charAt(c >> 6) + b64map.charAt(c & 63); - } - if(i+1 == h.length) { - c = parseInt(h.substring(i,i+1),16); - ret += b64map.charAt(c << 2); - } - else if(i+2 == h.length) { - c = parseInt(h.substring(i,i+2),16); - ret += b64map.charAt(c >> 2) + b64map.charAt((c & 3) << 4); - } - while((ret.length & 3) > 0) ret += b64pad; - return ret; -} - -// convert a base64 string to hex -function b64tohex(s) { - var ret = "" - var i; - var k = 0; // b64 state, 0-3 - var slop; - for(i = 0; i < s.length; ++i) { - if(s.charAt(i) == b64pad) break; - var v = b64map.indexOf(s.charAt(i)); - if(v < 0) continue; - if(k == 0) { - ret += int2char(v >> 2); - slop = v & 3; - k = 1; - } - else if(k == 1) { - ret += int2char((slop << 2) | (v >> 4)); - slop = v & 0xf; - k = 2; - } - else if(k == 2) { - ret += int2char(slop); - ret += int2char(v >> 2); - slop = v & 3; - k = 3; - } - else { - ret += int2char((slop << 2) | (v >> 4)); - ret += int2char(v & 0xf); - k = 0; - } - } - if(k == 1) - ret += int2char(slop << 2); - return ret; -} - -// convert a base64 string to a byte/number array -function b64toBA(s) { - //piggyback on b64tohex for now, optimize later - var h = b64tohex(s); - var i; - var a = new Array(); - for(i = 0; 2*i < h.length; ++i) { - a[i] = parseInt(h.substring(2*i,2*i+2),16); - } - return a; -} - diff --git a/config/plugins/interactive_environments/rstudio/static/js/crypto/jsbn.js b/config/plugins/interactive_environments/rstudio/static/js/crypto/jsbn.js deleted file mode 100644 index 801841d7184..00000000000 --- a/config/plugins/interactive_environments/rstudio/static/js/crypto/jsbn.js +++ /dev/null @@ -1,562 +0,0 @@ -// Downloaded from http://www-cs-students.stanford.edu/~tjw/ at Tue Nov 30 00:42:57 PST 2010 -// ==== File: jsbn.js -// Copyright (c) 2005 Tom Wu -// All Rights Reserved. -// See "LICENSE" for details. - -// Basic JavaScript BN library - subset useful for RSA encryption. - -// Bits per digit -var dbits; - -// JavaScript engine analysis -var canary = 0xdeadbeefcafe; -var j_lm = ((canary&0xffffff)==0xefcafe); - -// (public) Constructor -function BigInteger(a,b,c) { - if(a != null) - if("number" == typeof a) this.fromNumber(a,b,c); - else if(b == null && "string" != typeof a) this.fromString(a,256); - else this.fromString(a,b); -} - -// return new, unset BigInteger -function nbi() { return new BigInteger(null); } - -// am: Compute w_j += (x*this_i), propagate carries, -// c is initial carry, returns final carry. -// c < 3*dvalue, x < 2*dvalue, this_i < dvalue -// We need to select the fastest one that works in this environment. - -// am1: use a single mult and divide to get the high bits, -// max digit bits should be 26 because -// max internal value = 2*dvalue^2-2*dvalue (< 2^53) -function am1(i,x,w,j,c,n) { - while(--n >= 0) { - var v = x*this[i++]+w[j]+c; - c = Math.floor(v/0x4000000); - w[j++] = v&0x3ffffff; - } - return c; -} -// am2 avoids a big mult-and-extract completely. -// Max digit bits should be <= 30 because we do bitwise ops -// on values up to 2*hdvalue^2-hdvalue-1 (< 2^31) -function am2(i,x,w,j,c,n) { - var xl = x&0x7fff, xh = x>>15; - while(--n >= 0) { - var l = this[i]&0x7fff; - var h = this[i++]>>15; - var m = xh*l+h*xl; - l = xl*l+((m&0x7fff)<<15)+w[j]+(c&0x3fffffff); - c = (l>>>30)+(m>>>15)+xh*h+(c>>>30); - w[j++] = l&0x3fffffff; - } - return c; -} -// Alternately, set max digit bits to 28 since some -// browsers slow down when dealing with 32-bit numbers. -function am3(i,x,w,j,c,n) { - var xl = x&0x3fff, xh = x>>14; - while(--n >= 0) { - var l = this[i]&0x3fff; - var h = this[i++]>>14; - var m = xh*l+h*xl; - l = xl*l+((m&0x3fff)<<14)+w[j]+c; - c = (l>>28)+(m>>14)+xh*h; - w[j++] = l&0xfffffff; - } - return c; -} -if(j_lm && (navigator.appName == "Microsoft Internet Explorer")) { - BigInteger.prototype.am = am2; - dbits = 30; -} -else if(j_lm && (navigator.appName != "Netscape")) { - BigInteger.prototype.am = am1; - dbits = 26; -} -else { // Mozilla/Netscape seems to prefer am3 - BigInteger.prototype.am = am3; - dbits = 28; -} - -BigInteger.prototype.DB = dbits; -BigInteger.prototype.DM = ((1<= 0; --i) r[i] = this[i]; - r.t = this.t; - r.s = this.s; -} - -// (protected) set from integer value x, -DV <= x < DV -function bnpFromInt(x) { - this.t = 1; - this.s = (x<0)?-1:0; - if(x > 0) this[0] = x; - else if(x < -1) this[0] = x+DV; - else this.t = 0; -} - -// return bigint initialized to value -function nbv(i) { var r = nbi(); r.fromInt(i); return r; } - -// (protected) set from string and radix -function bnpFromString(s,b) { - var k; - if(b == 16) k = 4; - else if(b == 8) k = 3; - else if(b == 256) k = 8; // byte array - else if(b == 2) k = 1; - else if(b == 32) k = 5; - else if(b == 4) k = 2; - else { this.fromRadix(s,b); return; } - this.t = 0; - this.s = 0; - var i = s.length, mi = false, sh = 0; - while(--i >= 0) { - var x = (k==8)?s[i]&0xff:intAt(s,i); - if(x < 0) { - if(s.charAt(i) == "-") mi = true; - continue; - } - mi = false; - if(sh == 0) - this[this.t++] = x; - else if(sh+k > this.DB) { - this[this.t-1] |= (x&((1<<(this.DB-sh))-1))<>(this.DB-sh)); - } - else - this[this.t-1] |= x<= this.DB) sh -= this.DB; - } - if(k == 8 && (s[0]&0x80) != 0) { - this.s = -1; - if(sh > 0) this[this.t-1] |= ((1<<(this.DB-sh))-1)< 0 && this[this.t-1] == c) --this.t; -} - -// (public) return string representation in given radix -function bnToString(b) { - if(this.s < 0) return "-"+this.negate().toString(b); - var k; - if(b == 16) k = 4; - else if(b == 8) k = 3; - else if(b == 2) k = 1; - else if(b == 32) k = 5; - else if(b == 4) k = 2; - else return this.toRadix(b); - var km = (1< 0) { - if(p < this.DB && (d = this[i]>>p) > 0) { m = true; r = int2char(d); } - while(i >= 0) { - if(p < k) { - d = (this[i]&((1<>(p+=this.DB-k); - } - else { - d = (this[i]>>(p-=k))&km; - if(p <= 0) { p += this.DB; --i; } - } - if(d > 0) m = true; - if(m) r += int2char(d); - } - } - return m?r:"0"; -} - -// (public) -this -function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this,r); return r; } - -// (public) |this| -function bnAbs() { return (this.s<0)?this.negate():this; } - -// (public) return + if this > a, - if this < a, 0 if equal -function bnCompareTo(a) { - var r = this.s-a.s; - if(r != 0) return r; - var i = this.t; - r = i-a.t; - if(r != 0) return r; - while(--i >= 0) if((r=this[i]-a[i]) != 0) return r; - return 0; -} - -// returns bit length of the integer x -function nbits(x) { - var r = 1, t; - if((t=x>>>16) != 0) { x = t; r += 16; } - if((t=x>>8) != 0) { x = t; r += 8; } - if((t=x>>4) != 0) { x = t; r += 4; } - if((t=x>>2) != 0) { x = t; r += 2; } - if((t=x>>1) != 0) { x = t; r += 1; } - return r; -} - -// (public) return the number of bits in "this" -function bnBitLength() { - if(this.t <= 0) return 0; - return this.DB*(this.t-1)+nbits(this[this.t-1]^(this.s&this.DM)); -} - -// (protected) r = this << n*DB -function bnpDLShiftTo(n,r) { - var i; - for(i = this.t-1; i >= 0; --i) r[i+n] = this[i]; - for(i = n-1; i >= 0; --i) r[i] = 0; - r.t = this.t+n; - r.s = this.s; -} - -// (protected) r = this >> n*DB -function bnpDRShiftTo(n,r) { - for(var i = n; i < this.t; ++i) r[i-n] = this[i]; - r.t = Math.max(this.t-n,0); - r.s = this.s; -} - -// (protected) r = this << n -function bnpLShiftTo(n,r) { - var bs = n%this.DB; - var cbs = this.DB-bs; - var bm = (1<= 0; --i) { - r[i+ds+1] = (this[i]>>cbs)|c; - c = (this[i]&bm)<= 0; --i) r[i] = 0; - r[ds] = c; - r.t = this.t+ds+1; - r.s = this.s; - r.clamp(); -} - -// (protected) r = this >> n -function bnpRShiftTo(n,r) { - r.s = this.s; - var ds = Math.floor(n/this.DB); - if(ds >= this.t) { r.t = 0; return; } - var bs = n%this.DB; - var cbs = this.DB-bs; - var bm = (1<>bs; - for(var i = ds+1; i < this.t; ++i) { - r[i-ds-1] |= (this[i]&bm)<>bs; - } - if(bs > 0) r[this.t-ds-1] |= (this.s&bm)<>= this.DB; - } - if(a.t < this.t) { - c -= a.s; - while(i < this.t) { - c += this[i]; - r[i++] = c&this.DM; - c >>= this.DB; - } - c += this.s; - } - else { - c += this.s; - while(i < a.t) { - c -= a[i]; - r[i++] = c&this.DM; - c >>= this.DB; - } - c -= a.s; - } - r.s = (c<0)?-1:0; - if(c < -1) r[i++] = this.DV+c; - else if(c > 0) r[i++] = c; - r.t = i; - r.clamp(); -} - -// (protected) r = this * a, r != this,a (HAC 14.12) -// "this" should be the larger one if appropriate. -function bnpMultiplyTo(a,r) { - var x = this.abs(), y = a.abs(); - var i = x.t; - r.t = i+y.t; - while(--i >= 0) r[i] = 0; - for(i = 0; i < y.t; ++i) r[i+x.t] = x.am(0,y[i],r,i,0,x.t); - r.s = 0; - r.clamp(); - if(this.s != a.s) BigInteger.ZERO.subTo(r,r); -} - -// (protected) r = this^2, r != this (HAC 14.16) -function bnpSquareTo(r) { - var x = this.abs(); - var i = r.t = 2*x.t; - while(--i >= 0) r[i] = 0; - for(i = 0; i < x.t-1; ++i) { - var c = x.am(i,x[i],r,2*i,0,1); - if((r[i+x.t]+=x.am(i+1,2*x[i],r,2*i+1,c,x.t-i-1)) >= x.DV) { - r[i+x.t] -= x.DV; - r[i+x.t+1] = 1; - } - } - if(r.t > 0) r[r.t-1] += x.am(i,x[i],r,2*i,0,1); - r.s = 0; - r.clamp(); -} - -// (protected) divide this by m, quotient and remainder to q, r (HAC 14.20) -// r != q, this != m. q or r may be null. -function bnpDivRemTo(m,q,r) { - var pm = m.abs(); - if(pm.t <= 0) return; - var pt = this.abs(); - if(pt.t < pm.t) { - if(q != null) q.fromInt(0); - if(r != null) this.copyTo(r); - return; - } - if(r == null) r = nbi(); - var y = nbi(), ts = this.s, ms = m.s; - var nsh = this.DB-nbits(pm[pm.t-1]); // normalize modulus - if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); } - else { pm.copyTo(y); pt.copyTo(r); } - var ys = y.t; - var y0 = y[ys-1]; - if(y0 == 0) return; - var yt = y0*(1<1)?y[ys-2]>>this.F2:0); - var d1 = this.FV/yt, d2 = (1<= 0) { - r[r.t++] = 1; - r.subTo(t,r); - } - BigInteger.ONE.dlShiftTo(ys,t); - t.subTo(y,y); // "negative" y so we can replace sub with am later - while(y.t < ys) y[y.t++] = 0; - while(--j >= 0) { - // Estimate quotient digit - var qd = (r[--i]==y0)?this.DM:Math.floor(r[i]*d1+(r[i-1]+e)*d2); - if((r[i]+=y.am(0,qd,r,j,0,ys)) < qd) { // Try it out - y.dlShiftTo(j,t); - r.subTo(t,r); - while(r[i] < --qd) r.subTo(t,r); - } - } - if(q != null) { - r.drShiftTo(ys,q); - if(ts != ms) BigInteger.ZERO.subTo(q,q); - } - r.t = ys; - r.clamp(); - if(nsh > 0) r.rShiftTo(nsh,r); // Denormalize remainder - if(ts < 0) BigInteger.ZERO.subTo(r,r); -} - -// (public) this mod a -function bnMod(a) { - var r = nbi(); - this.abs().divRemTo(a,null,r); - if(this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r,r); - return r; -} - -// Modular reduction using "classic" algorithm -function Classic(m) { this.m = m; } -function cConvert(x) { - if(x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m); - else return x; -} -function cRevert(x) { return x; } -function cReduce(x) { x.divRemTo(this.m,null,x); } -function cMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } -function cSqrTo(x,r) { x.squareTo(r); this.reduce(r); } - -Classic.prototype.convert = cConvert; -Classic.prototype.revert = cRevert; -Classic.prototype.reduce = cReduce; -Classic.prototype.mulTo = cMulTo; -Classic.prototype.sqrTo = cSqrTo; - -// (protected) return "-1/this % 2^DB"; useful for Mont. reduction -// justification: -// xy == 1 (mod m) -// xy = 1+km -// xy(2-xy) = (1+km)(1-km) -// x[y(2-xy)] = 1-k^2m^2 -// x[y(2-xy)] == 1 (mod m^2) -// if y is 1/x mod m, then y(2-xy) is 1/x mod m^2 -// should reduce x and y(2-xy) by m^2 at each step to keep size bounded. -// JS multiply "overflows" differently from C/C++, so care is needed here. -function bnpInvDigit() { - if(this.t < 1) return 0; - var x = this[0]; - if((x&1) == 0) return 0; - var y = x&3; // y == 1/x mod 2^2 - y = (y*(2-(x&0xf)*y))&0xf; // y == 1/x mod 2^4 - y = (y*(2-(x&0xff)*y))&0xff; // y == 1/x mod 2^8 - y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff; // y == 1/x mod 2^16 - // last step - calculate inverse mod DV directly; - // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints - y = (y*(2-x*y%this.DV))%this.DV; // y == 1/x mod 2^dbits - // we really want the negative inverse, and -DV < y < DV - return (y>0)?this.DV-y:-y; -} - -// Montgomery reduction -function Montgomery(m) { - this.m = m; - this.mp = m.invDigit(); - this.mpl = this.mp&0x7fff; - this.mph = this.mp>>15; - this.um = (1<<(m.DB-15))-1; - this.mt2 = 2*m.t; -} - -// xR mod m -function montConvert(x) { - var r = nbi(); - x.abs().dlShiftTo(this.m.t,r); - r.divRemTo(this.m,null,r); - if(x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r,r); - return r; -} - -// x/R mod m -function montRevert(x) { - var r = nbi(); - x.copyTo(r); - this.reduce(r); - return r; -} - -// x = x/R mod m (HAC 14.32) -function montReduce(x) { - while(x.t <= this.mt2) // pad x so am has enough room later - x[x.t++] = 0; - for(var i = 0; i < this.m.t; ++i) { - // faster way of calculating u0 = x[i]*mp mod DV - var j = x[i]&0x7fff; - var u0 = (j*this.mpl+(((j*this.mph+(x[i]>>15)*this.mpl)&this.um)<<15))&x.DM; - // use am to combine the multiply-shift-add into one call - j = i+this.m.t; - x[j] += this.m.am(0,u0,x,i,0,this.m.t); - // propagate carry - while(x[j] >= x.DV) { x[j] -= x.DV; x[++j]++; } - } - x.clamp(); - x.drShiftTo(this.m.t,x); - if(x.compareTo(this.m) >= 0) x.subTo(this.m,x); -} - -// r = "x^2/R mod m"; x != r -function montSqrTo(x,r) { x.squareTo(r); this.reduce(r); } - -// r = "xy/R mod m"; x,y != r -function montMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } - -Montgomery.prototype.convert = montConvert; -Montgomery.prototype.revert = montRevert; -Montgomery.prototype.reduce = montReduce; -Montgomery.prototype.mulTo = montMulTo; -Montgomery.prototype.sqrTo = montSqrTo; - -// (protected) true iff this is even -function bnpIsEven() { return ((this.t>0)?(this[0]&1):this.s) == 0; } - -// (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79) -function bnpExp(e,z) { - if(e > 0xffffffff || e < 1) return BigInteger.ONE; - var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1; - g.copyTo(r); - while(--i >= 0) { - z.sqrTo(r,r2); - if((e&(1< 0) z.mulTo(r2,g,r); - else { var t = r; r = r2; r2 = t; } - } - return z.revert(r); -} - -// (public) this^e % m, 0 <= e < 2^32 -function bnModPowInt(e,m) { - var z; - if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m); - return this.exp(e,z); -} - -// protected -BigInteger.prototype.copyTo = bnpCopyTo; -BigInteger.prototype.fromInt = bnpFromInt; -BigInteger.prototype.fromString = bnpFromString; -BigInteger.prototype.clamp = bnpClamp; -BigInteger.prototype.dlShiftTo = bnpDLShiftTo; -BigInteger.prototype.drShiftTo = bnpDRShiftTo; -BigInteger.prototype.lShiftTo = bnpLShiftTo; -BigInteger.prototype.rShiftTo = bnpRShiftTo; -BigInteger.prototype.subTo = bnpSubTo; -BigInteger.prototype.multiplyTo = bnpMultiplyTo; -BigInteger.prototype.squareTo = bnpSquareTo; -BigInteger.prototype.divRemTo = bnpDivRemTo; -BigInteger.prototype.invDigit = bnpInvDigit; -BigInteger.prototype.isEven = bnpIsEven; -BigInteger.prototype.exp = bnpExp; - -// public -BigInteger.prototype.toString = bnToString; -BigInteger.prototype.negate = bnNegate; -BigInteger.prototype.abs = bnAbs; -BigInteger.prototype.compareTo = bnCompareTo; -BigInteger.prototype.bitLength = bnBitLength; -BigInteger.prototype.mod = bnMod; -BigInteger.prototype.modPowInt = bnModPowInt; - -// "constants" -BigInteger.ZERO = nbv(0); -BigInteger.ONE = nbv(1); - diff --git a/config/plugins/interactive_environments/rstudio/static/js/crypto/prng4.js b/config/plugins/interactive_environments/rstudio/static/js/crypto/prng4.js deleted file mode 100644 index 5cd681256c1..00000000000 --- a/config/plugins/interactive_environments/rstudio/static/js/crypto/prng4.js +++ /dev/null @@ -1,47 +0,0 @@ -// ==== File: prng4.js -// prng4.js - uses Arcfour as a PRNG - -function Arcfour() { - this.i = 0; - this.j = 0; - this.S = new Array(); -} - -// Initialize arcfour context from key, an array of ints, each from [0..255] -function ARC4init(key) { - var i, j, t; - for(i = 0; i < 256; ++i) - this.S[i] = i; - j = 0; - for(i = 0; i < 256; ++i) { - j = (j + this.S[i] + key[i % key.length]) & 255; - t = this.S[i]; - this.S[i] = this.S[j]; - this.S[j] = t; - } - this.i = 0; - this.j = 0; -} - -function ARC4next() { - var t; - this.i = (this.i + 1) & 255; - this.j = (this.j + this.S[this.i]) & 255; - t = this.S[this.i]; - this.S[this.i] = this.S[this.j]; - this.S[this.j] = t; - return this.S[(t + this.S[this.i]) & 255]; -} - -Arcfour.prototype.init = ARC4init; -Arcfour.prototype.next = ARC4next; - -// Plug in your RNG constructor here -function prng_newstate() { - return new Arcfour(); -} - -// Pool size must be a multiple of 4 and greater than 32. -// An array of bytes the size of the pool will be passed to init() -var rng_psize = 256; - diff --git a/config/plugins/interactive_environments/rstudio/static/js/crypto/rng.js b/config/plugins/interactive_environments/rstudio/static/js/crypto/rng.js deleted file mode 100644 index 24bae0f8ec8..00000000000 --- a/config/plugins/interactive_environments/rstudio/static/js/crypto/rng.js +++ /dev/null @@ -1,70 +0,0 @@ -// ==== File: rng.js -// Random number generator - requires a PRNG backend, e.g. prng4.js - -// For best results, put code like -// -// in your main HTML document. - -var rng_state; -var rng_pool; -var rng_pptr; - -// Mix in a 32-bit integer into the pool -function rng_seed_int(x) { - rng_pool[rng_pptr++] ^= x & 255; - rng_pool[rng_pptr++] ^= (x >> 8) & 255; - rng_pool[rng_pptr++] ^= (x >> 16) & 255; - rng_pool[rng_pptr++] ^= (x >> 24) & 255; - if(rng_pptr >= rng_psize) rng_pptr -= rng_psize; -} - -// Mix in the current time (w/milliseconds) into the pool -function rng_seed_time() { - rng_seed_int(new Date().getTime()); -} - -// Initialize the pool with junk if needed. -if(rng_pool == null) { - rng_pool = new Array(); - rng_pptr = 0; - var t; - if(navigator.appName == "Netscape" && navigator.appVersion < "5" && window.crypto) { - // Extract entropy (256 bits) from NS4 RNG if available - var z = window.crypto.random(32); - for(t = 0; t < z.length; ++t) - rng_pool[rng_pptr++] = z.charCodeAt(t) & 255; - } - while(rng_pptr < rng_psize) { // extract some randomness from Math.random() - t = Math.floor(65536 * Math.random()); - rng_pool[rng_pptr++] = t >>> 8; - rng_pool[rng_pptr++] = t & 255; - } - rng_pptr = 0; - rng_seed_time(); - //rng_seed_int(window.screenX); - //rng_seed_int(window.screenY); -} - -function rng_get_byte() { - if(rng_state == null) { - rng_seed_time(); - rng_state = prng_newstate(); - rng_state.init(rng_pool); - for(rng_pptr = 0; rng_pptr < rng_pool.length; ++rng_pptr) - rng_pool[rng_pptr] = 0; - rng_pptr = 0; - //rng_pool = null; - } - // TODO: allow reseeding after first request - return rng_state.next(); -} - -function rng_get_bytes(ba) { - var i; - for(i = 0; i < ba.length; ++i) ba[i] = rng_get_byte(); -} - -function SecureRandom() {} - -SecureRandom.prototype.nextBytes = rng_get_bytes; - diff --git a/config/plugins/interactive_environments/rstudio/static/js/crypto/rsa.js b/config/plugins/interactive_environments/rstudio/static/js/crypto/rsa.js deleted file mode 100644 index b2e37c35540..00000000000 --- a/config/plugins/interactive_environments/rstudio/static/js/crypto/rsa.js +++ /dev/null @@ -1,114 +0,0 @@ -// ==== File: rsa.js -// Depends on jsbn.js and rng.js - -// Version 1.1: support utf-8 encoding in pkcs1pad2 - -// convert a (hex) string to a bignum object -function parseBigInt(str,r) { - return new BigInteger(str,r); -} - -function linebrk(s,n) { - var ret = ""; - var i = 0; - while(i + n < s.length) { - ret += s.substring(i,i+n) + "\n"; - i += n; - } - return ret + s.substring(i,s.length); -} - -function byte2Hex(b) { - if(b < 0x10) - return "0" + b.toString(16); - else - return b.toString(16); -} - -// PKCS#1 (type 2, random) pad input string s to n bytes, and return a bigint -function pkcs1pad2(s,n) { - if(n < s.length + 11) { // TODO: fix for utf-8 - alert("Message too long for RSA"); - return null; - } - var ba = new Array(); - var i = s.length - 1; - while(i >= 0 && n > 0) { - var c = s.charCodeAt(i--); - if(c < 128) { // encode using utf-8 - ba[--n] = c; - } - else if((c > 127) && (c < 2048)) { - ba[--n] = (c & 63) | 128; - ba[--n] = (c >> 6) | 192; - } - else { - ba[--n] = (c & 63) | 128; - ba[--n] = ((c >> 6) & 63) | 128; - ba[--n] = (c >> 12) | 224; - } - } - ba[--n] = 0; - var rng = new SecureRandom(); - var x = new Array(); - while(n > 2) { // random non-zero pad - x[0] = 0; - while(x[0] == 0) rng.nextBytes(x); - ba[--n] = x[0]; - } - ba[--n] = 2; - ba[--n] = 0; - return new BigInteger(ba); -} - -// "empty" RSA key constructor -function RSAKey() { - this.n = null; - this.e = 0; - this.d = null; - this.p = null; - this.q = null; - this.dmp1 = null; - this.dmq1 = null; - this.coeff = null; -} - -// Set the public key fields N and e from hex strings -function RSASetPublic(N,E) { - if(N != null && E != null && N.length > 0 && E.length > 0) { - this.n = parseBigInt(N,16); - this.e = parseInt(E,16); - } - else - alert("Invalid RSA public key"); -} - -// Perform raw public operation on "x": return x^e (mod n) -function RSADoPublic(x) { - return x.modPowInt(this.e, this.n); -} - -// Return the PKCS#1 RSA encryption of "text" as an even-length hex string -function RSAEncrypt(text) { - var m = pkcs1pad2(text,(this.n.bitLength()+7)>>3); - if(m == null) return null; - var c = this.doPublic(m); - if(c == null) return null; - var h = c.toString(16); - if((h.length & 1) == 0) return h; else return "0" + h; -} - -// Return the PKCS#1 RSA encryption of "text" as a Base64-encoded string -//function RSAEncryptB64(text) { -// var h = this.encrypt(text); -// if(h) return hex2b64(h); else return null; -//} - -// protected -RSAKey.prototype.doPublic = RSADoPublic; - -// public -RSAKey.prototype.setPublic = RSASetPublic; -RSAKey.prototype.encrypt = RSAEncrypt; -//RSAKey.prototype.encrypt_b64 = RSAEncryptB64; - diff --git a/config/plugins/interactive_environments/rstudio/static/js/crypto/rstudio.big.js b/config/plugins/interactive_environments/rstudio/static/js/crypto/rstudio.big.js deleted file mode 100644 index db13dbbeec8..00000000000 --- a/config/plugins/interactive_environments/rstudio/static/js/crypto/rstudio.big.js +++ /dev/null @@ -1,861 +0,0 @@ -// Downloaded from http://www-cs-students.stanford.edu/~tjw/ at Tue Nov 30 00:42:57 PST 2010 -// ==== File: jsbn.js -// Copyright (c) 2005 Tom Wu -// All Rights Reserved. -// See "LICENSE" for details. - -// Basic JavaScript BN library - subset useful for RSA encryption. - -// Bits per digit -var dbits; - -// JavaScript engine analysis -var canary = 0xdeadbeefcafe; -var j_lm = ((canary&0xffffff)==0xefcafe); - -// (public) Constructor -function BigInteger(a,b,c) { - if(a != null) - if("number" == typeof a) this.fromNumber(a,b,c); - else if(b == null && "string" != typeof a) this.fromString(a,256); - else this.fromString(a,b); -} - -// return new, unset BigInteger -function nbi() { return new BigInteger(null); } - -// am: Compute w_j += (x*this_i), propagate carries, -// c is initial carry, returns final carry. -// c < 3*dvalue, x < 2*dvalue, this_i < dvalue -// We need to select the fastest one that works in this environment. - -// am1: use a single mult and divide to get the high bits, -// max digit bits should be 26 because -// max internal value = 2*dvalue^2-2*dvalue (< 2^53) -function am1(i,x,w,j,c,n) { - while(--n >= 0) { - var v = x*this[i++]+w[j]+c; - c = Math.floor(v/0x4000000); - w[j++] = v&0x3ffffff; - } - return c; -} -// am2 avoids a big mult-and-extract completely. -// Max digit bits should be <= 30 because we do bitwise ops -// on values up to 2*hdvalue^2-hdvalue-1 (< 2^31) -function am2(i,x,w,j,c,n) { - var xl = x&0x7fff, xh = x>>15; - while(--n >= 0) { - var l = this[i]&0x7fff; - var h = this[i++]>>15; - var m = xh*l+h*xl; - l = xl*l+((m&0x7fff)<<15)+w[j]+(c&0x3fffffff); - c = (l>>>30)+(m>>>15)+xh*h+(c>>>30); - w[j++] = l&0x3fffffff; - } - return c; -} -// Alternately, set max digit bits to 28 since some -// browsers slow down when dealing with 32-bit numbers. -function am3(i,x,w,j,c,n) { - var xl = x&0x3fff, xh = x>>14; - while(--n >= 0) { - var l = this[i]&0x3fff; - var h = this[i++]>>14; - var m = xh*l+h*xl; - l = xl*l+((m&0x3fff)<<14)+w[j]+c; - c = (l>>28)+(m>>14)+xh*h; - w[j++] = l&0xfffffff; - } - return c; -} -if(j_lm && (navigator.appName == "Microsoft Internet Explorer")) { - BigInteger.prototype.am = am2; - dbits = 30; -} -else if(j_lm && (navigator.appName != "Netscape")) { - BigInteger.prototype.am = am1; - dbits = 26; -} -else { // Mozilla/Netscape seems to prefer am3 - BigInteger.prototype.am = am3; - dbits = 28; -} - -BigInteger.prototype.DB = dbits; -BigInteger.prototype.DM = ((1<= 0; --i) r[i] = this[i]; - r.t = this.t; - r.s = this.s; -} - -// (protected) set from integer value x, -DV <= x < DV -function bnpFromInt(x) { - this.t = 1; - this.s = (x<0)?-1:0; - if(x > 0) this[0] = x; - else if(x < -1) this[0] = x+DV; - else this.t = 0; -} - -// return bigint initialized to value -function nbv(i) { var r = nbi(); r.fromInt(i); return r; } - -// (protected) set from string and radix -function bnpFromString(s,b) { - var k; - if(b == 16) k = 4; - else if(b == 8) k = 3; - else if(b == 256) k = 8; // byte array - else if(b == 2) k = 1; - else if(b == 32) k = 5; - else if(b == 4) k = 2; - else { this.fromRadix(s,b); return; } - this.t = 0; - this.s = 0; - var i = s.length, mi = false, sh = 0; - while(--i >= 0) { - var x = (k==8)?s[i]&0xff:intAt(s,i); - if(x < 0) { - if(s.charAt(i) == "-") mi = true; - continue; - } - mi = false; - if(sh == 0) - this[this.t++] = x; - else if(sh+k > this.DB) { - this[this.t-1] |= (x&((1<<(this.DB-sh))-1))<>(this.DB-sh)); - } - else - this[this.t-1] |= x<= this.DB) sh -= this.DB; - } - if(k == 8 && (s[0]&0x80) != 0) { - this.s = -1; - if(sh > 0) this[this.t-1] |= ((1<<(this.DB-sh))-1)< 0 && this[this.t-1] == c) --this.t; -} - -// (public) return string representation in given radix -function bnToString(b) { - if(this.s < 0) return "-"+this.negate().toString(b); - var k; - if(b == 16) k = 4; - else if(b == 8) k = 3; - else if(b == 2) k = 1; - else if(b == 32) k = 5; - else if(b == 4) k = 2; - else return this.toRadix(b); - var km = (1< 0) { - if(p < this.DB && (d = this[i]>>p) > 0) { m = true; r = int2char(d); } - while(i >= 0) { - if(p < k) { - d = (this[i]&((1<>(p+=this.DB-k); - } - else { - d = (this[i]>>(p-=k))&km; - if(p <= 0) { p += this.DB; --i; } - } - if(d > 0) m = true; - if(m) r += int2char(d); - } - } - return m?r:"0"; -} - -// (public) -this -function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this,r); return r; } - -// (public) |this| -function bnAbs() { return (this.s<0)?this.negate():this; } - -// (public) return + if this > a, - if this < a, 0 if equal -function bnCompareTo(a) { - var r = this.s-a.s; - if(r != 0) return r; - var i = this.t; - r = i-a.t; - if(r != 0) return r; - while(--i >= 0) if((r=this[i]-a[i]) != 0) return r; - return 0; -} - -// returns bit length of the integer x -function nbits(x) { - var r = 1, t; - if((t=x>>>16) != 0) { x = t; r += 16; } - if((t=x>>8) != 0) { x = t; r += 8; } - if((t=x>>4) != 0) { x = t; r += 4; } - if((t=x>>2) != 0) { x = t; r += 2; } - if((t=x>>1) != 0) { x = t; r += 1; } - return r; -} - -// (public) return the number of bits in "this" -function bnBitLength() { - if(this.t <= 0) return 0; - return this.DB*(this.t-1)+nbits(this[this.t-1]^(this.s&this.DM)); -} - -// (protected) r = this << n*DB -function bnpDLShiftTo(n,r) { - var i; - for(i = this.t-1; i >= 0; --i) r[i+n] = this[i]; - for(i = n-1; i >= 0; --i) r[i] = 0; - r.t = this.t+n; - r.s = this.s; -} - -// (protected) r = this >> n*DB -function bnpDRShiftTo(n,r) { - for(var i = n; i < this.t; ++i) r[i-n] = this[i]; - r.t = Math.max(this.t-n,0); - r.s = this.s; -} - -// (protected) r = this << n -function bnpLShiftTo(n,r) { - var bs = n%this.DB; - var cbs = this.DB-bs; - var bm = (1<= 0; --i) { - r[i+ds+1] = (this[i]>>cbs)|c; - c = (this[i]&bm)<= 0; --i) r[i] = 0; - r[ds] = c; - r.t = this.t+ds+1; - r.s = this.s; - r.clamp(); -} - -// (protected) r = this >> n -function bnpRShiftTo(n,r) { - r.s = this.s; - var ds = Math.floor(n/this.DB); - if(ds >= this.t) { r.t = 0; return; } - var bs = n%this.DB; - var cbs = this.DB-bs; - var bm = (1<>bs; - for(var i = ds+1; i < this.t; ++i) { - r[i-ds-1] |= (this[i]&bm)<>bs; - } - if(bs > 0) r[this.t-ds-1] |= (this.s&bm)<>= this.DB; - } - if(a.t < this.t) { - c -= a.s; - while(i < this.t) { - c += this[i]; - r[i++] = c&this.DM; - c >>= this.DB; - } - c += this.s; - } - else { - c += this.s; - while(i < a.t) { - c -= a[i]; - r[i++] = c&this.DM; - c >>= this.DB; - } - c -= a.s; - } - r.s = (c<0)?-1:0; - if(c < -1) r[i++] = this.DV+c; - else if(c > 0) r[i++] = c; - r.t = i; - r.clamp(); -} - -// (protected) r = this * a, r != this,a (HAC 14.12) -// "this" should be the larger one if appropriate. -function bnpMultiplyTo(a,r) { - var x = this.abs(), y = a.abs(); - var i = x.t; - r.t = i+y.t; - while(--i >= 0) r[i] = 0; - for(i = 0; i < y.t; ++i) r[i+x.t] = x.am(0,y[i],r,i,0,x.t); - r.s = 0; - r.clamp(); - if(this.s != a.s) BigInteger.ZERO.subTo(r,r); -} - -// (protected) r = this^2, r != this (HAC 14.16) -function bnpSquareTo(r) { - var x = this.abs(); - var i = r.t = 2*x.t; - while(--i >= 0) r[i] = 0; - for(i = 0; i < x.t-1; ++i) { - var c = x.am(i,x[i],r,2*i,0,1); - if((r[i+x.t]+=x.am(i+1,2*x[i],r,2*i+1,c,x.t-i-1)) >= x.DV) { - r[i+x.t] -= x.DV; - r[i+x.t+1] = 1; - } - } - if(r.t > 0) r[r.t-1] += x.am(i,x[i],r,2*i,0,1); - r.s = 0; - r.clamp(); -} - -// (protected) divide this by m, quotient and remainder to q, r (HAC 14.20) -// r != q, this != m. q or r may be null. -function bnpDivRemTo(m,q,r) { - var pm = m.abs(); - if(pm.t <= 0) return; - var pt = this.abs(); - if(pt.t < pm.t) { - if(q != null) q.fromInt(0); - if(r != null) this.copyTo(r); - return; - } - if(r == null) r = nbi(); - var y = nbi(), ts = this.s, ms = m.s; - var nsh = this.DB-nbits(pm[pm.t-1]); // normalize modulus - if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); } - else { pm.copyTo(y); pt.copyTo(r); } - var ys = y.t; - var y0 = y[ys-1]; - if(y0 == 0) return; - var yt = y0*(1<1)?y[ys-2]>>this.F2:0); - var d1 = this.FV/yt, d2 = (1<= 0) { - r[r.t++] = 1; - r.subTo(t,r); - } - BigInteger.ONE.dlShiftTo(ys,t); - t.subTo(y,y); // "negative" y so we can replace sub with am later - while(y.t < ys) y[y.t++] = 0; - while(--j >= 0) { - // Estimate quotient digit - var qd = (r[--i]==y0)?this.DM:Math.floor(r[i]*d1+(r[i-1]+e)*d2); - if((r[i]+=y.am(0,qd,r,j,0,ys)) < qd) { // Try it out - y.dlShiftTo(j,t); - r.subTo(t,r); - while(r[i] < --qd) r.subTo(t,r); - } - } - if(q != null) { - r.drShiftTo(ys,q); - if(ts != ms) BigInteger.ZERO.subTo(q,q); - } - r.t = ys; - r.clamp(); - if(nsh > 0) r.rShiftTo(nsh,r); // Denormalize remainder - if(ts < 0) BigInteger.ZERO.subTo(r,r); -} - -// (public) this mod a -function bnMod(a) { - var r = nbi(); - this.abs().divRemTo(a,null,r); - if(this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r,r); - return r; -} - -// Modular reduction using "classic" algorithm -function Classic(m) { this.m = m; } -function cConvert(x) { - if(x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m); - else return x; -} -function cRevert(x) { return x; } -function cReduce(x) { x.divRemTo(this.m,null,x); } -function cMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } -function cSqrTo(x,r) { x.squareTo(r); this.reduce(r); } - -Classic.prototype.convert = cConvert; -Classic.prototype.revert = cRevert; -Classic.prototype.reduce = cReduce; -Classic.prototype.mulTo = cMulTo; -Classic.prototype.sqrTo = cSqrTo; - -// (protected) return "-1/this % 2^DB"; useful for Mont. reduction -// justification: -// xy == 1 (mod m) -// xy = 1+km -// xy(2-xy) = (1+km)(1-km) -// x[y(2-xy)] = 1-k^2m^2 -// x[y(2-xy)] == 1 (mod m^2) -// if y is 1/x mod m, then y(2-xy) is 1/x mod m^2 -// should reduce x and y(2-xy) by m^2 at each step to keep size bounded. -// JS multiply "overflows" differently from C/C++, so care is needed here. -function bnpInvDigit() { - if(this.t < 1) return 0; - var x = this[0]; - if((x&1) == 0) return 0; - var y = x&3; // y == 1/x mod 2^2 - y = (y*(2-(x&0xf)*y))&0xf; // y == 1/x mod 2^4 - y = (y*(2-(x&0xff)*y))&0xff; // y == 1/x mod 2^8 - y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff; // y == 1/x mod 2^16 - // last step - calculate inverse mod DV directly; - // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints - y = (y*(2-x*y%this.DV))%this.DV; // y == 1/x mod 2^dbits - // we really want the negative inverse, and -DV < y < DV - return (y>0)?this.DV-y:-y; -} - -// Montgomery reduction -function Montgomery(m) { - this.m = m; - this.mp = m.invDigit(); - this.mpl = this.mp&0x7fff; - this.mph = this.mp>>15; - this.um = (1<<(m.DB-15))-1; - this.mt2 = 2*m.t; -} - -// xR mod m -function montConvert(x) { - var r = nbi(); - x.abs().dlShiftTo(this.m.t,r); - r.divRemTo(this.m,null,r); - if(x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r,r); - return r; -} - -// x/R mod m -function montRevert(x) { - var r = nbi(); - x.copyTo(r); - this.reduce(r); - return r; -} - -// x = x/R mod m (HAC 14.32) -function montReduce(x) { - while(x.t <= this.mt2) // pad x so am has enough room later - x[x.t++] = 0; - for(var i = 0; i < this.m.t; ++i) { - // faster way of calculating u0 = x[i]*mp mod DV - var j = x[i]&0x7fff; - var u0 = (j*this.mpl+(((j*this.mph+(x[i]>>15)*this.mpl)&this.um)<<15))&x.DM; - // use am to combine the multiply-shift-add into one call - j = i+this.m.t; - x[j] += this.m.am(0,u0,x,i,0,this.m.t); - // propagate carry - while(x[j] >= x.DV) { x[j] -= x.DV; x[++j]++; } - } - x.clamp(); - x.drShiftTo(this.m.t,x); - if(x.compareTo(this.m) >= 0) x.subTo(this.m,x); -} - -// r = "x^2/R mod m"; x != r -function montSqrTo(x,r) { x.squareTo(r); this.reduce(r); } - -// r = "xy/R mod m"; x,y != r -function montMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } - -Montgomery.prototype.convert = montConvert; -Montgomery.prototype.revert = montRevert; -Montgomery.prototype.reduce = montReduce; -Montgomery.prototype.mulTo = montMulTo; -Montgomery.prototype.sqrTo = montSqrTo; - -// (protected) true iff this is even -function bnpIsEven() { return ((this.t>0)?(this[0]&1):this.s) == 0; } - -// (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79) -function bnpExp(e,z) { - if(e > 0xffffffff || e < 1) return BigInteger.ONE; - var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1; - g.copyTo(r); - while(--i >= 0) { - z.sqrTo(r,r2); - if((e&(1< 0) z.mulTo(r2,g,r); - else { var t = r; r = r2; r2 = t; } - } - return z.revert(r); -} - -// (public) this^e % m, 0 <= e < 2^32 -function bnModPowInt(e,m) { - var z; - if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m); - return this.exp(e,z); -} - -// protected -BigInteger.prototype.copyTo = bnpCopyTo; -BigInteger.prototype.fromInt = bnpFromInt; -BigInteger.prototype.fromString = bnpFromString; -BigInteger.prototype.clamp = bnpClamp; -BigInteger.prototype.dlShiftTo = bnpDLShiftTo; -BigInteger.prototype.drShiftTo = bnpDRShiftTo; -BigInteger.prototype.lShiftTo = bnpLShiftTo; -BigInteger.prototype.rShiftTo = bnpRShiftTo; -BigInteger.prototype.subTo = bnpSubTo; -BigInteger.prototype.multiplyTo = bnpMultiplyTo; -BigInteger.prototype.squareTo = bnpSquareTo; -BigInteger.prototype.divRemTo = bnpDivRemTo; -BigInteger.prototype.invDigit = bnpInvDigit; -BigInteger.prototype.isEven = bnpIsEven; -BigInteger.prototype.exp = bnpExp; - -// public -BigInteger.prototype.toString = bnToString; -BigInteger.prototype.negate = bnNegate; -BigInteger.prototype.abs = bnAbs; -BigInteger.prototype.compareTo = bnCompareTo; -BigInteger.prototype.bitLength = bnBitLength; -BigInteger.prototype.mod = bnMod; -BigInteger.prototype.modPowInt = bnModPowInt; - -// "constants" -BigInteger.ZERO = nbv(0); -BigInteger.ONE = nbv(1); -// ==== File: prng4.js -// prng4.js - uses Arcfour as a PRNG - -function Arcfour() { - this.i = 0; - this.j = 0; - this.S = new Array(); -} - -// Initialize arcfour context from key, an array of ints, each from [0..255] -function ARC4init(key) { - var i, j, t; - for(i = 0; i < 256; ++i) - this.S[i] = i; - j = 0; - for(i = 0; i < 256; ++i) { - j = (j + this.S[i] + key[i % key.length]) & 255; - t = this.S[i]; - this.S[i] = this.S[j]; - this.S[j] = t; - } - this.i = 0; - this.j = 0; -} - -function ARC4next() { - var t; - this.i = (this.i + 1) & 255; - this.j = (this.j + this.S[this.i]) & 255; - t = this.S[this.i]; - this.S[this.i] = this.S[this.j]; - this.S[this.j] = t; - return this.S[(t + this.S[this.i]) & 255]; -} - -Arcfour.prototype.init = ARC4init; -Arcfour.prototype.next = ARC4next; - -// Plug in your RNG constructor here -function prng_newstate() { - return new Arcfour(); -} - -// Pool size must be a multiple of 4 and greater than 32. -// An array of bytes the size of the pool will be passed to init() -var rng_psize = 256; -// ==== File: rng.js -// Random number generator - requires a PRNG backend, e.g. prng4.js - -// For best results, put code like -// -// in your main HTML document. - -var rng_state; -var rng_pool; -var rng_pptr; - -// Mix in a 32-bit integer into the pool -function rng_seed_int(x) { - rng_pool[rng_pptr++] ^= x & 255; - rng_pool[rng_pptr++] ^= (x >> 8) & 255; - rng_pool[rng_pptr++] ^= (x >> 16) & 255; - rng_pool[rng_pptr++] ^= (x >> 24) & 255; - if(rng_pptr >= rng_psize) rng_pptr -= rng_psize; -} - -// Mix in the current time (w/milliseconds) into the pool -function rng_seed_time() { - rng_seed_int(new Date().getTime()); -} - -// Initialize the pool with junk if needed. -if(rng_pool == null) { - rng_pool = new Array(); - rng_pptr = 0; - var t; - if(navigator.appName == "Netscape" && navigator.appVersion < "5" && window.crypto) { - // Extract entropy (256 bits) from NS4 RNG if available - var z = window.crypto.random(32); - for(t = 0; t < z.length; ++t) - rng_pool[rng_pptr++] = z.charCodeAt(t) & 255; - } - while(rng_pptr < rng_psize) { // extract some randomness from Math.random() - t = Math.floor(65536 * Math.random()); - rng_pool[rng_pptr++] = t >>> 8; - rng_pool[rng_pptr++] = t & 255; - } - rng_pptr = 0; - rng_seed_time(); - //rng_seed_int(window.screenX); - //rng_seed_int(window.screenY); -} - -function rng_get_byte() { - if(rng_state == null) { - rng_seed_time(); - rng_state = prng_newstate(); - rng_state.init(rng_pool); - for(rng_pptr = 0; rng_pptr < rng_pool.length; ++rng_pptr) - rng_pool[rng_pptr] = 0; - rng_pptr = 0; - //rng_pool = null; - } - // TODO: allow reseeding after first request - return rng_state.next(); -} - -function rng_get_bytes(ba) { - var i; - for(i = 0; i < ba.length; ++i) ba[i] = rng_get_byte(); -} - -function SecureRandom() {} - -SecureRandom.prototype.nextBytes = rng_get_bytes; -// ==== File: rsa.js -// Depends on jsbn.js and rng.js - -// Version 1.1: support utf-8 encoding in pkcs1pad2 - -// convert a (hex) string to a bignum object -function parseBigInt(str,r) { - return new BigInteger(str,r); -} - -function linebrk(s,n) { - var ret = ""; - var i = 0; - while(i + n < s.length) { - ret += s.substring(i,i+n) + "\n"; - i += n; - } - return ret + s.substring(i,s.length); -} - -function byte2Hex(b) { - if(b < 0x10) - return "0" + b.toString(16); - else - return b.toString(16); -} - -// PKCS#1 (type 2, random) pad input string s to n bytes, and return a bigint -function pkcs1pad2(s,n) { - if(n < s.length + 11) { // TODO: fix for utf-8 - alert("Message too long for RSA"); - return null; - } - var ba = new Array(); - var i = s.length - 1; - while(i >= 0 && n > 0) { - var c = s.charCodeAt(i--); - if(c < 128) { // encode using utf-8 - ba[--n] = c; - } - else if((c > 127) && (c < 2048)) { - ba[--n] = (c & 63) | 128; - ba[--n] = (c >> 6) | 192; - } - else { - ba[--n] = (c & 63) | 128; - ba[--n] = ((c >> 6) & 63) | 128; - ba[--n] = (c >> 12) | 224; - } - } - ba[--n] = 0; - var rng = new SecureRandom(); - var x = new Array(); - while(n > 2) { // random non-zero pad - x[0] = 0; - while(x[0] == 0) rng.nextBytes(x); - ba[--n] = x[0]; - } - ba[--n] = 2; - ba[--n] = 0; - return new BigInteger(ba); -} - -// "empty" RSA key constructor -function RSAKey() { - this.n = null; - this.e = 0; - this.d = null; - this.p = null; - this.q = null; - this.dmp1 = null; - this.dmq1 = null; - this.coeff = null; -} - -// Set the public key fields N and e from hex strings -function RSASetPublic(N,E) { - if(N != null && E != null && N.length > 0 && E.length > 0) { - this.n = parseBigInt(N,16); - this.e = parseInt(E,16); - } - else - alert("Invalid RSA public key"); -} - -// Perform raw public operation on "x": return x^e (mod n) -function RSADoPublic(x) { - return x.modPowInt(this.e, this.n); -} - -// Return the PKCS#1 RSA encryption of "text" as an even-length hex string -function RSAEncrypt(text) { - var m = pkcs1pad2(text,(this.n.bitLength()+7)>>3); - if(m == null) return null; - var c = this.doPublic(m); - if(c == null) return null; - var h = c.toString(16); - if((h.length & 1) == 0) return h; else return "0" + h; -} - -// Return the PKCS#1 RSA encryption of "text" as a Base64-encoded string -//function RSAEncryptB64(text) { -// var h = this.encrypt(text); -// if(h) return hex2b64(h); else return null; -//} - -// protected -RSAKey.prototype.doPublic = RSADoPublic; - -// public -RSAKey.prototype.setPublic = RSASetPublic; -RSAKey.prototype.encrypt = RSAEncrypt; -//RSAKey.prototype.encrypt_b64 = RSAEncryptB64; -// ==== File: base64.js -var b64map="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -var b64pad="="; - -function hex2b64(h) { - var i; - var c; - var ret = ""; - for(i = 0; i+3 <= h.length; i+=3) { - c = parseInt(h.substring(i,i+3),16); - ret += b64map.charAt(c >> 6) + b64map.charAt(c & 63); - } - if(i+1 == h.length) { - c = parseInt(h.substring(i,i+1),16); - ret += b64map.charAt(c << 2); - } - else if(i+2 == h.length) { - c = parseInt(h.substring(i,i+2),16); - ret += b64map.charAt(c >> 2) + b64map.charAt((c & 3) << 4); - } - while((ret.length & 3) > 0) ret += b64pad; - return ret; -} - -// convert a base64 string to hex -function b64tohex(s) { - var ret = "" - var i; - var k = 0; // b64 state, 0-3 - var slop; - for(i = 0; i < s.length; ++i) { - if(s.charAt(i) == b64pad) break; - var v = b64map.indexOf(s.charAt(i)); - if(v < 0) continue; - if(k == 0) { - ret += int2char(v >> 2); - slop = v & 3; - k = 1; - } - else if(k == 1) { - ret += int2char((slop << 2) | (v >> 4)); - slop = v & 0xf; - k = 2; - } - else if(k == 2) { - ret += int2char(slop); - ret += int2char(v >> 2); - slop = v & 3; - k = 3; - } - else { - ret += int2char((slop << 2) | (v >> 4)); - ret += int2char(v & 0xf); - k = 0; - } - } - if(k == 1) - ret += int2char(slop << 2); - return ret; -} - -// convert a base64 string to a byte/number array -function b64toBA(s) { - //piggyback on b64tohex for now, optimize later - var h = b64tohex(s); - var i; - var a = new Array(); - for(i = 0; 2*i < h.length; ++i) { - a[i] = parseInt(h.substring(2*i,2*i+2),16); - } - return a; -} diff --git a/config/plugins/interactive_environments/rstudio/static/js/crypto/rstudio.min.js b/config/plugins/interactive_environments/rstudio/static/js/crypto/rstudio.min.js deleted file mode 100644 index f74debec619..00000000000 --- a/config/plugins/interactive_environments/rstudio/static/js/crypto/rstudio.min.js +++ /dev/null @@ -1,24 +0,0 @@ -var g,j,k=(244837814094590&16777215)==15715070;function l(b,a,c){if(b!=null)if("number"==typeof b)this.ca(b,a,c);else a==null&&"string"!=typeof b?this.z(b,256):this.z(b,a)}function m(){return new l(null)}function o(b,a,c,d,e,f){for(;--f>=0;){var h=a*this[b++]+c[d]+e;e=Math.floor(h/67108864);c[d++]=h&67108863}return e} -function p(b,a,c,d,e,f){var h=a&32767;for(a=a>>15;--f>=0;){var i=this[b]&32767,n=this[b++]>>15,r=a*i+n*h;i=h*i+((r&32767)<<15)+c[d]+(e&1073741823);e=(i>>>30)+(r>>>15)+a*n+(e>>>30);c[d++]=i&1073741823}return e}function s(b,a,c,d,e,f){var h=a&16383;for(a=a>>14;--f>=0;){var i=this[b]&16383,n=this[b++]>>14,r=a*i+n*h;i=h*i+((r&16383)<<14)+c[d]+e;e=(i>>28)+(r>>14)+a*n;c[d++]=i&268435455}return e} -if(k&&navigator.appName=="Microsoft Internet Explorer"){l.prototype.i=p;j=30}else if(k&&navigator.appName!="Netscape"){l.prototype.i=o;j=26}else{l.prototype.i=s;j=28}g=l.prototype;g.c=j;g.g=(1<=0;--a)b[a]=this[a];b.a=this.a;b.b=this.b}function D(b){this.a=1;this.b=b<0?-1:0;if(b>0)this[0]=b;else if(b<-1)this[0]=b+DV;else this.a=0}function E(b){var a=m();a.w(b);return a} -function F(b,a){if(a==16)a=4;else if(a==8)a=3;else if(a==256)a=8;else if(a==2)a=1;else if(a==32)a=5;else if(a==4)a=2;else{this.da(b,a);return}this.b=this.a=0;for(var c=b.length,d=false,e=0;--c>=0;){var f=a==8?b[c]&255:A(b,c);if(f<0){if(b.charAt(c)=="-")d=true}else{d=false;if(e==0)this[this.a++]=f;else if(e+a>this.c){this[this.a-1]|=(f&(1<>this.c-e}else this[this.a-1]|=f<=this.c)e-=this.c}}if(a==8&&(b[0]&128)!=0){this.b=-1;if(e>0)this[this.a-1]|=(1<0&&this[this.a-1]==b;)--this.a} -function I(b){if(this.b<0)return"-"+this.G().toString(b);if(b==16)b=4;else if(b==8)b=3;else if(b==2)b=1;else if(b==32)b=5;else if(b==4)b=2;else return this.ga(b);var a=(1<0){if(h>h)>0){d=true;e=t.charAt(c)}for(;f>=0;){if(h>(h+=this.c-b)}else{c=this[f]>>(h-=b)&a;if(h<=0){h+=this.c;--f}}if(c>0)d=true;if(d)e+=t.charAt(c)}}return d?e:"0"} -function J(){var b=m();G.f(this,b);return b}function K(){return this.b<0?this.G():this}function L(b){var a=this.b-b.b;if(a!=0)return a;var c=this.a;a=c-b.a;if(a!=0)return a;for(;--c>=0;)if((a=this[c]-b[c])!=0)return a;return 0}function M(b){var a=1,c;if((c=b>>>16)!=0){b=c;a+=16}if((c=b>>8)!=0){b=c;a+=8}if((c=b>>4)!=0){b=c;a+=4}if((c=b>>2)!=0){b=c;a+=2}if(b>>1!=0)a+=1;return a}function N(){if(this.a<=0)return 0;return this.c*(this.a-1)+M(this[this.a-1]^this.b&this.g)} -function aa(b,a){var c;for(c=this.a-1;c>=0;--c)a[c+b]=this[c];for(c=b-1;c>=0;--c)a[c]=0;a.a=this.a+b;a.b=this.b}function ba(b,a){for(var c=b;c=0;--h){a[h+b+1]=this[h]>>d|f;f=(this[h]&e)<=0;--h)a[h]=0;a[b]=f;a.a=this.a+b+1;a.b=this.b;a.j()} -function da(b,a){a.b=this.b;var c=Math.floor(b/this.c);if(c>=this.a)a.a=0;else{b=b%this.c;var d=this.c-b,e=(1<>b;for(var f=c+1;f>b}if(b>0)a[this.a-c-1]|=(this.b&e)<>=this.c}if(b.a>=this.c}d+=this.b}else{for(d+=this.b;c>=this.c}d-=b.b}a.b=d<0?-1:0;if(d<-1)a[c++]=this.h+d;else if(d>0)a[c++]=d;a.a=c;a.j()}function fa(b,a){var c=this.abs(),d=b.abs(),e=c.a;for(a.a=e+d.a;--e>=0;)a[e]=0;for(e=0;e=0;)b[c]=0;for(c=0;c=a.h){b[c+a.a]-=a.h;b[c+a.a+1]=1}}if(b.a>0)b[b.a-1]+=a.i(c,a[c],b,2*c,0,1);b.b=0;b.j()} -function ha(b,a,c){var d=b.abs();if(!(d.a<=0)){var e=this.abs();if(e.a0){d.A(i,f);e.A(i,c)}else{d.m(f);e.m(c)}d=f.a;e=f[d-1];if(e!=0){var n=e*(1<1?f[d-2]>>this.s:0),r=this.K/n;n=(1<=0){c[c.a++]=1;c.f(q,c)}O.o(d,q);for(q.f(f,f);f.a=0;){var C=c[--w]==e?this.g:Math.floor(c[w]* -r+(c[w-1]+ia)*n);if((c[w]+=f.i(0,C,c,y,0,d))0&&c.W(i,c);h<0&&G.f(c,c)}}}}function ja(b){var a=m();this.abs().p(b,null,a);this.b<0&&a.l(G)>0&&b.f(a,a);return a}function P(b){this.d=b}function ka(b){return b.b<0||b.l(this.d)>=0?b.R(this.d):b}function la(b){return b}function ma(b){b.p(this.d,null,b)}function na(b,a,c){b.F(a,c);this.reduce(c)}function oa(b,a){b.J(a);this.reduce(a)}g=P.prototype;g.t=ka; -g.H=la;g.reduce=ma;g.D=na;g.I=oa;function pa(){if(this.a<1)return 0;var b=this[0];if((b&1)==0)return 0;var a=b&3;a=a*(2-(b&15)*a)&15;a=a*(2-(b&255)*a)&255;a=a*(2-((b&65535)*a&65535))&65535;a=a*(2-b*a%this.h)%this.h;return a>0?this.h-a:-a}function Q(b){this.d=b;this.B=b.P();this.C=this.B&32767;this.T=this.B>>15;this.Y=(1<0&&this.d.f(a,a);return a} -function ra(b){var a=m();b.m(a);this.reduce(a);return a}function sa(b){for(;b.a<=this.U;)b[b.a++]=0;for(var a=0;a>15)*this.C&this.Y)<<15)&b.g;c=a+this.d.a;for(b[c]+=this.d.i(0,d,b,a,0,this.d.a);b[c]>=b.h;){b[c]-=b.h;b[++c]++}}b.j();b.u(this.d.a,b);b.l(this.d)>=0&&b.f(this.d,b)}function ta(b,a){b.J(a);this.reduce(a)}function ua(b,a,c){b.F(a,c);this.reduce(c)}g=Q.prototype;g.t=qa;g.H=ra;g.reduce=sa;g.D=ua;g.I=ta; -function va(){return(this.a>0?this[0]&1:this.b)==0}function wa(b,a){if(b>4294967295||b<1)return O;var c=m(),d=m(),e=a.t(this),f=M(b)-1;for(e.m(c);--f>=0;){a.I(c,d);if((b&1<0)a.D(d,e,c);else{var h=c;c=d;d=h}}return a.H(c)}function xa(b,a){a=b<256||a.Q()?new P(a):new Q(a);return this.exp(b,a)}g=l.prototype;g.m=B;g.w=D;g.z=F;g.j=H;g.o=aa;g.u=ba;g.A=ca;g.W=da;g.f=ea;g.F=fa;g.J=ga;g.p=ha;g.P=pa;g.Q=va;g.exp=wa;g.toString=I;g.G=J;g.abs=K;g.l=L;g.L=N;g.R=ja;g.S=xa;var G=E(0),O=E(1); -function R(){this.n=this.k=0;this.e=[]}function ya(b){var a,c,d;for(a=0;a<256;++a)this.e[a]=a;for(a=c=0;a<256;++a){c=c+this.e[a]+b[a%b.length]&255;d=this.e[a];this.e[a]=this.e[c];this.e[c]=d}this.n=this.k=0}function za(){var b;this.k=this.k+1&255;this.n=this.n+this.e[this.k]&255;b=this.e[this.k];this.e[this.k]=this.e[this.n];this.e[this.n]=b;return this.e[b+this.e[this.k]&255]}R.prototype.O=ya;R.prototype.next=za;var S=256,T,U,V; -function W(b){U[V++]^=b&255;U[V++]^=b>>8&255;U[V++]^=b>>16&255;U[V++]^=b>>24&255;if(V>=S)V-=S}if(U==null){U=[];V=0;var X;if(navigator.appName=="Netscape"&&navigator.appVersion<"5"&&window.crypto){var Y=window.crypto.random(32);for(X=0;X>>8;U[V++]=X&255}V=0;W((new Date).getTime())}function Aa(){if(T==null){W((new Date).getTime());T=new R;T.O(U);for(V=0;V0&&a.length>0){this.q=new l(b,16);this.v=parseInt(a,16)}else alert("Invalid RSA public key")}function Da(b){return b.S(this.v,this.q)} -function Ea(b){var a;a=this.q.L()+7>>3;if(a=0&&a>0;){var e=b.charCodeAt(d--);if(e<128)c[--a]=e;else if(e>127&&e<2048){c[--a]=e&63|128;c[--a]=e>>6|192}else{c[--a]=e&63|128;c[--a]=e>>6&63|128;c[--a]=e>>12|224}}c[--a]=0;b=new Z;for(d=[];a>2;){for(d[0]=0;d[0]==0;)b.V(d);c[--a]=d[0]}c[--a]=2;c[--a]=0;a=new l(c)}if(a==null)return null;a=this.M(a);if(a==null)return null;a=a.toString(16);return(a.length&1)==0?a:"0"+a} -ZZZ.prototype.M=Da;ZZZ.prototype.X=Ca;ZZZ.prototype.N=Ea;window.encrypt=function(b,a,c){var d=new ZZZ;d.X(c,a);b=d.N(b);d="";for(a=0;a+3<=b.length;a+=3){c=parseInt(b.substring(a,a+3),16);d+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(c>>6)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(c&63)}if(a+1==b.length){c=parseInt(b.substring(a,a+1),16);d+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(c<<2)}else if(a+2==b.length){c=parseInt(b.substring(a,a+2),16);d+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(c>> -2)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt((c&3)<<4)}for(;(d.length&3)>0;)d+="=";return d}; - diff --git a/config/plugins/interactive_environments/rstudio/static/js/crypto/rstudio/base64.js b/config/plugins/interactive_environments/rstudio/static/js/crypto/rstudio/base64.js deleted file mode 100644 index 68e51f6e036..00000000000 --- a/config/plugins/interactive_environments/rstudio/static/js/crypto/rstudio/base64.js +++ /dev/null @@ -1,73 +0,0 @@ -// ==== File: base64.js -var b64map="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -var b64pad="="; - -function hex2b64(h) { - var i; - var c; - var ret = ""; - for(i = 0; i+3 <= h.length; i+=3) { - c = parseInt(h.substring(i,i+3),16); - ret += b64map.charAt(c >> 6) + b64map.charAt(c & 63); - } - if(i+1 == h.length) { - c = parseInt(h.substring(i,i+1),16); - ret += b64map.charAt(c << 2); - } - else if(i+2 == h.length) { - c = parseInt(h.substring(i,i+2),16); - ret += b64map.charAt(c >> 2) + b64map.charAt((c & 3) << 4); - } - while((ret.length & 3) > 0) ret += b64pad; - return ret; -} - -// convert a base64 string to hex -function b64tohex(s) { - var ret = "" - var i; - var k = 0; // b64 state, 0-3 - var slop; - for(i = 0; i < s.length; ++i) { - if(s.charAt(i) == b64pad) break; - var v = b64map.indexOf(s.charAt(i)); - if(v < 0) continue; - if(k == 0) { - ret += int2char(v >> 2); - slop = v & 3; - k = 1; - } - else if(k == 1) { - ret += int2char((slop << 2) | (v >> 4)); - slop = v & 0xf; - k = 2; - } - else if(k == 2) { - ret += int2char(slop); - ret += int2char(v >> 2); - slop = v & 3; - k = 3; - } - else { - ret += int2char((slop << 2) | (v >> 4)); - ret += int2char(v & 0xf); - k = 0; - } - } - if(k == 1) - ret += int2char(slop << 2); - return ret; -} - -// convert a base64 string to a byte/number array -function b64toBA(s) { - //piggyback on b64tohex for now, optimize later - var h = b64tohex(s); - var i; - var a = new Array(); - for(i = 0; 2*i < h.length; ++i) { - a[i] = parseInt(h.substring(2*i,2*i+2),16); - } - return a; -} - diff --git a/config/plugins/interactive_environments/rstudio/static/js/crypto/rstudio/jsbn.js b/config/plugins/interactive_environments/rstudio/static/js/crypto/rstudio/jsbn.js deleted file mode 100644 index 801841d7184..00000000000 --- a/config/plugins/interactive_environments/rstudio/static/js/crypto/rstudio/jsbn.js +++ /dev/null @@ -1,562 +0,0 @@ -// Downloaded from http://www-cs-students.stanford.edu/~tjw/ at Tue Nov 30 00:42:57 PST 2010 -// ==== File: jsbn.js -// Copyright (c) 2005 Tom Wu -// All Rights Reserved. -// See "LICENSE" for details. - -// Basic JavaScript BN library - subset useful for RSA encryption. - -// Bits per digit -var dbits; - -// JavaScript engine analysis -var canary = 0xdeadbeefcafe; -var j_lm = ((canary&0xffffff)==0xefcafe); - -// (public) Constructor -function BigInteger(a,b,c) { - if(a != null) - if("number" == typeof a) this.fromNumber(a,b,c); - else if(b == null && "string" != typeof a) this.fromString(a,256); - else this.fromString(a,b); -} - -// return new, unset BigInteger -function nbi() { return new BigInteger(null); } - -// am: Compute w_j += (x*this_i), propagate carries, -// c is initial carry, returns final carry. -// c < 3*dvalue, x < 2*dvalue, this_i < dvalue -// We need to select the fastest one that works in this environment. - -// am1: use a single mult and divide to get the high bits, -// max digit bits should be 26 because -// max internal value = 2*dvalue^2-2*dvalue (< 2^53) -function am1(i,x,w,j,c,n) { - while(--n >= 0) { - var v = x*this[i++]+w[j]+c; - c = Math.floor(v/0x4000000); - w[j++] = v&0x3ffffff; - } - return c; -} -// am2 avoids a big mult-and-extract completely. -// Max digit bits should be <= 30 because we do bitwise ops -// on values up to 2*hdvalue^2-hdvalue-1 (< 2^31) -function am2(i,x,w,j,c,n) { - var xl = x&0x7fff, xh = x>>15; - while(--n >= 0) { - var l = this[i]&0x7fff; - var h = this[i++]>>15; - var m = xh*l+h*xl; - l = xl*l+((m&0x7fff)<<15)+w[j]+(c&0x3fffffff); - c = (l>>>30)+(m>>>15)+xh*h+(c>>>30); - w[j++] = l&0x3fffffff; - } - return c; -} -// Alternately, set max digit bits to 28 since some -// browsers slow down when dealing with 32-bit numbers. -function am3(i,x,w,j,c,n) { - var xl = x&0x3fff, xh = x>>14; - while(--n >= 0) { - var l = this[i]&0x3fff; - var h = this[i++]>>14; - var m = xh*l+h*xl; - l = xl*l+((m&0x3fff)<<14)+w[j]+c; - c = (l>>28)+(m>>14)+xh*h; - w[j++] = l&0xfffffff; - } - return c; -} -if(j_lm && (navigator.appName == "Microsoft Internet Explorer")) { - BigInteger.prototype.am = am2; - dbits = 30; -} -else if(j_lm && (navigator.appName != "Netscape")) { - BigInteger.prototype.am = am1; - dbits = 26; -} -else { // Mozilla/Netscape seems to prefer am3 - BigInteger.prototype.am = am3; - dbits = 28; -} - -BigInteger.prototype.DB = dbits; -BigInteger.prototype.DM = ((1<= 0; --i) r[i] = this[i]; - r.t = this.t; - r.s = this.s; -} - -// (protected) set from integer value x, -DV <= x < DV -function bnpFromInt(x) { - this.t = 1; - this.s = (x<0)?-1:0; - if(x > 0) this[0] = x; - else if(x < -1) this[0] = x+DV; - else this.t = 0; -} - -// return bigint initialized to value -function nbv(i) { var r = nbi(); r.fromInt(i); return r; } - -// (protected) set from string and radix -function bnpFromString(s,b) { - var k; - if(b == 16) k = 4; - else if(b == 8) k = 3; - else if(b == 256) k = 8; // byte array - else if(b == 2) k = 1; - else if(b == 32) k = 5; - else if(b == 4) k = 2; - else { this.fromRadix(s,b); return; } - this.t = 0; - this.s = 0; - var i = s.length, mi = false, sh = 0; - while(--i >= 0) { - var x = (k==8)?s[i]&0xff:intAt(s,i); - if(x < 0) { - if(s.charAt(i) == "-") mi = true; - continue; - } - mi = false; - if(sh == 0) - this[this.t++] = x; - else if(sh+k > this.DB) { - this[this.t-1] |= (x&((1<<(this.DB-sh))-1))<>(this.DB-sh)); - } - else - this[this.t-1] |= x<= this.DB) sh -= this.DB; - } - if(k == 8 && (s[0]&0x80) != 0) { - this.s = -1; - if(sh > 0) this[this.t-1] |= ((1<<(this.DB-sh))-1)< 0 && this[this.t-1] == c) --this.t; -} - -// (public) return string representation in given radix -function bnToString(b) { - if(this.s < 0) return "-"+this.negate().toString(b); - var k; - if(b == 16) k = 4; - else if(b == 8) k = 3; - else if(b == 2) k = 1; - else if(b == 32) k = 5; - else if(b == 4) k = 2; - else return this.toRadix(b); - var km = (1< 0) { - if(p < this.DB && (d = this[i]>>p) > 0) { m = true; r = int2char(d); } - while(i >= 0) { - if(p < k) { - d = (this[i]&((1<>(p+=this.DB-k); - } - else { - d = (this[i]>>(p-=k))&km; - if(p <= 0) { p += this.DB; --i; } - } - if(d > 0) m = true; - if(m) r += int2char(d); - } - } - return m?r:"0"; -} - -// (public) -this -function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this,r); return r; } - -// (public) |this| -function bnAbs() { return (this.s<0)?this.negate():this; } - -// (public) return + if this > a, - if this < a, 0 if equal -function bnCompareTo(a) { - var r = this.s-a.s; - if(r != 0) return r; - var i = this.t; - r = i-a.t; - if(r != 0) return r; - while(--i >= 0) if((r=this[i]-a[i]) != 0) return r; - return 0; -} - -// returns bit length of the integer x -function nbits(x) { - var r = 1, t; - if((t=x>>>16) != 0) { x = t; r += 16; } - if((t=x>>8) != 0) { x = t; r += 8; } - if((t=x>>4) != 0) { x = t; r += 4; } - if((t=x>>2) != 0) { x = t; r += 2; } - if((t=x>>1) != 0) { x = t; r += 1; } - return r; -} - -// (public) return the number of bits in "this" -function bnBitLength() { - if(this.t <= 0) return 0; - return this.DB*(this.t-1)+nbits(this[this.t-1]^(this.s&this.DM)); -} - -// (protected) r = this << n*DB -function bnpDLShiftTo(n,r) { - var i; - for(i = this.t-1; i >= 0; --i) r[i+n] = this[i]; - for(i = n-1; i >= 0; --i) r[i] = 0; - r.t = this.t+n; - r.s = this.s; -} - -// (protected) r = this >> n*DB -function bnpDRShiftTo(n,r) { - for(var i = n; i < this.t; ++i) r[i-n] = this[i]; - r.t = Math.max(this.t-n,0); - r.s = this.s; -} - -// (protected) r = this << n -function bnpLShiftTo(n,r) { - var bs = n%this.DB; - var cbs = this.DB-bs; - var bm = (1<= 0; --i) { - r[i+ds+1] = (this[i]>>cbs)|c; - c = (this[i]&bm)<= 0; --i) r[i] = 0; - r[ds] = c; - r.t = this.t+ds+1; - r.s = this.s; - r.clamp(); -} - -// (protected) r = this >> n -function bnpRShiftTo(n,r) { - r.s = this.s; - var ds = Math.floor(n/this.DB); - if(ds >= this.t) { r.t = 0; return; } - var bs = n%this.DB; - var cbs = this.DB-bs; - var bm = (1<>bs; - for(var i = ds+1; i < this.t; ++i) { - r[i-ds-1] |= (this[i]&bm)<>bs; - } - if(bs > 0) r[this.t-ds-1] |= (this.s&bm)<>= this.DB; - } - if(a.t < this.t) { - c -= a.s; - while(i < this.t) { - c += this[i]; - r[i++] = c&this.DM; - c >>= this.DB; - } - c += this.s; - } - else { - c += this.s; - while(i < a.t) { - c -= a[i]; - r[i++] = c&this.DM; - c >>= this.DB; - } - c -= a.s; - } - r.s = (c<0)?-1:0; - if(c < -1) r[i++] = this.DV+c; - else if(c > 0) r[i++] = c; - r.t = i; - r.clamp(); -} - -// (protected) r = this * a, r != this,a (HAC 14.12) -// "this" should be the larger one if appropriate. -function bnpMultiplyTo(a,r) { - var x = this.abs(), y = a.abs(); - var i = x.t; - r.t = i+y.t; - while(--i >= 0) r[i] = 0; - for(i = 0; i < y.t; ++i) r[i+x.t] = x.am(0,y[i],r,i,0,x.t); - r.s = 0; - r.clamp(); - if(this.s != a.s) BigInteger.ZERO.subTo(r,r); -} - -// (protected) r = this^2, r != this (HAC 14.16) -function bnpSquareTo(r) { - var x = this.abs(); - var i = r.t = 2*x.t; - while(--i >= 0) r[i] = 0; - for(i = 0; i < x.t-1; ++i) { - var c = x.am(i,x[i],r,2*i,0,1); - if((r[i+x.t]+=x.am(i+1,2*x[i],r,2*i+1,c,x.t-i-1)) >= x.DV) { - r[i+x.t] -= x.DV; - r[i+x.t+1] = 1; - } - } - if(r.t > 0) r[r.t-1] += x.am(i,x[i],r,2*i,0,1); - r.s = 0; - r.clamp(); -} - -// (protected) divide this by m, quotient and remainder to q, r (HAC 14.20) -// r != q, this != m. q or r may be null. -function bnpDivRemTo(m,q,r) { - var pm = m.abs(); - if(pm.t <= 0) return; - var pt = this.abs(); - if(pt.t < pm.t) { - if(q != null) q.fromInt(0); - if(r != null) this.copyTo(r); - return; - } - if(r == null) r = nbi(); - var y = nbi(), ts = this.s, ms = m.s; - var nsh = this.DB-nbits(pm[pm.t-1]); // normalize modulus - if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); } - else { pm.copyTo(y); pt.copyTo(r); } - var ys = y.t; - var y0 = y[ys-1]; - if(y0 == 0) return; - var yt = y0*(1<1)?y[ys-2]>>this.F2:0); - var d1 = this.FV/yt, d2 = (1<= 0) { - r[r.t++] = 1; - r.subTo(t,r); - } - BigInteger.ONE.dlShiftTo(ys,t); - t.subTo(y,y); // "negative" y so we can replace sub with am later - while(y.t < ys) y[y.t++] = 0; - while(--j >= 0) { - // Estimate quotient digit - var qd = (r[--i]==y0)?this.DM:Math.floor(r[i]*d1+(r[i-1]+e)*d2); - if((r[i]+=y.am(0,qd,r,j,0,ys)) < qd) { // Try it out - y.dlShiftTo(j,t); - r.subTo(t,r); - while(r[i] < --qd) r.subTo(t,r); - } - } - if(q != null) { - r.drShiftTo(ys,q); - if(ts != ms) BigInteger.ZERO.subTo(q,q); - } - r.t = ys; - r.clamp(); - if(nsh > 0) r.rShiftTo(nsh,r); // Denormalize remainder - if(ts < 0) BigInteger.ZERO.subTo(r,r); -} - -// (public) this mod a -function bnMod(a) { - var r = nbi(); - this.abs().divRemTo(a,null,r); - if(this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r,r); - return r; -} - -// Modular reduction using "classic" algorithm -function Classic(m) { this.m = m; } -function cConvert(x) { - if(x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m); - else return x; -} -function cRevert(x) { return x; } -function cReduce(x) { x.divRemTo(this.m,null,x); } -function cMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } -function cSqrTo(x,r) { x.squareTo(r); this.reduce(r); } - -Classic.prototype.convert = cConvert; -Classic.prototype.revert = cRevert; -Classic.prototype.reduce = cReduce; -Classic.prototype.mulTo = cMulTo; -Classic.prototype.sqrTo = cSqrTo; - -// (protected) return "-1/this % 2^DB"; useful for Mont. reduction -// justification: -// xy == 1 (mod m) -// xy = 1+km -// xy(2-xy) = (1+km)(1-km) -// x[y(2-xy)] = 1-k^2m^2 -// x[y(2-xy)] == 1 (mod m^2) -// if y is 1/x mod m, then y(2-xy) is 1/x mod m^2 -// should reduce x and y(2-xy) by m^2 at each step to keep size bounded. -// JS multiply "overflows" differently from C/C++, so care is needed here. -function bnpInvDigit() { - if(this.t < 1) return 0; - var x = this[0]; - if((x&1) == 0) return 0; - var y = x&3; // y == 1/x mod 2^2 - y = (y*(2-(x&0xf)*y))&0xf; // y == 1/x mod 2^4 - y = (y*(2-(x&0xff)*y))&0xff; // y == 1/x mod 2^8 - y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff; // y == 1/x mod 2^16 - // last step - calculate inverse mod DV directly; - // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints - y = (y*(2-x*y%this.DV))%this.DV; // y == 1/x mod 2^dbits - // we really want the negative inverse, and -DV < y < DV - return (y>0)?this.DV-y:-y; -} - -// Montgomery reduction -function Montgomery(m) { - this.m = m; - this.mp = m.invDigit(); - this.mpl = this.mp&0x7fff; - this.mph = this.mp>>15; - this.um = (1<<(m.DB-15))-1; - this.mt2 = 2*m.t; -} - -// xR mod m -function montConvert(x) { - var r = nbi(); - x.abs().dlShiftTo(this.m.t,r); - r.divRemTo(this.m,null,r); - if(x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r,r); - return r; -} - -// x/R mod m -function montRevert(x) { - var r = nbi(); - x.copyTo(r); - this.reduce(r); - return r; -} - -// x = x/R mod m (HAC 14.32) -function montReduce(x) { - while(x.t <= this.mt2) // pad x so am has enough room later - x[x.t++] = 0; - for(var i = 0; i < this.m.t; ++i) { - // faster way of calculating u0 = x[i]*mp mod DV - var j = x[i]&0x7fff; - var u0 = (j*this.mpl+(((j*this.mph+(x[i]>>15)*this.mpl)&this.um)<<15))&x.DM; - // use am to combine the multiply-shift-add into one call - j = i+this.m.t; - x[j] += this.m.am(0,u0,x,i,0,this.m.t); - // propagate carry - while(x[j] >= x.DV) { x[j] -= x.DV; x[++j]++; } - } - x.clamp(); - x.drShiftTo(this.m.t,x); - if(x.compareTo(this.m) >= 0) x.subTo(this.m,x); -} - -// r = "x^2/R mod m"; x != r -function montSqrTo(x,r) { x.squareTo(r); this.reduce(r); } - -// r = "xy/R mod m"; x,y != r -function montMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } - -Montgomery.prototype.convert = montConvert; -Montgomery.prototype.revert = montRevert; -Montgomery.prototype.reduce = montReduce; -Montgomery.prototype.mulTo = montMulTo; -Montgomery.prototype.sqrTo = montSqrTo; - -// (protected) true iff this is even -function bnpIsEven() { return ((this.t>0)?(this[0]&1):this.s) == 0; } - -// (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79) -function bnpExp(e,z) { - if(e > 0xffffffff || e < 1) return BigInteger.ONE; - var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1; - g.copyTo(r); - while(--i >= 0) { - z.sqrTo(r,r2); - if((e&(1< 0) z.mulTo(r2,g,r); - else { var t = r; r = r2; r2 = t; } - } - return z.revert(r); -} - -// (public) this^e % m, 0 <= e < 2^32 -function bnModPowInt(e,m) { - var z; - if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m); - return this.exp(e,z); -} - -// protected -BigInteger.prototype.copyTo = bnpCopyTo; -BigInteger.prototype.fromInt = bnpFromInt; -BigInteger.prototype.fromString = bnpFromString; -BigInteger.prototype.clamp = bnpClamp; -BigInteger.prototype.dlShiftTo = bnpDLShiftTo; -BigInteger.prototype.drShiftTo = bnpDRShiftTo; -BigInteger.prototype.lShiftTo = bnpLShiftTo; -BigInteger.prototype.rShiftTo = bnpRShiftTo; -BigInteger.prototype.subTo = bnpSubTo; -BigInteger.prototype.multiplyTo = bnpMultiplyTo; -BigInteger.prototype.squareTo = bnpSquareTo; -BigInteger.prototype.divRemTo = bnpDivRemTo; -BigInteger.prototype.invDigit = bnpInvDigit; -BigInteger.prototype.isEven = bnpIsEven; -BigInteger.prototype.exp = bnpExp; - -// public -BigInteger.prototype.toString = bnToString; -BigInteger.prototype.negate = bnNegate; -BigInteger.prototype.abs = bnAbs; -BigInteger.prototype.compareTo = bnCompareTo; -BigInteger.prototype.bitLength = bnBitLength; -BigInteger.prototype.mod = bnMod; -BigInteger.prototype.modPowInt = bnModPowInt; - -// "constants" -BigInteger.ZERO = nbv(0); -BigInteger.ONE = nbv(1); - diff --git a/config/plugins/interactive_environments/rstudio/static/js/crypto/rstudio/prng4.js b/config/plugins/interactive_environments/rstudio/static/js/crypto/rstudio/prng4.js deleted file mode 100644 index 5cd681256c1..00000000000 --- a/config/plugins/interactive_environments/rstudio/static/js/crypto/rstudio/prng4.js +++ /dev/null @@ -1,47 +0,0 @@ -// ==== File: prng4.js -// prng4.js - uses Arcfour as a PRNG - -function Arcfour() { - this.i = 0; - this.j = 0; - this.S = new Array(); -} - -// Initialize arcfour context from key, an array of ints, each from [0..255] -function ARC4init(key) { - var i, j, t; - for(i = 0; i < 256; ++i) - this.S[i] = i; - j = 0; - for(i = 0; i < 256; ++i) { - j = (j + this.S[i] + key[i % key.length]) & 255; - t = this.S[i]; - this.S[i] = this.S[j]; - this.S[j] = t; - } - this.i = 0; - this.j = 0; -} - -function ARC4next() { - var t; - this.i = (this.i + 1) & 255; - this.j = (this.j + this.S[this.i]) & 255; - t = this.S[this.i]; - this.S[this.i] = this.S[this.j]; - this.S[this.j] = t; - return this.S[(t + this.S[this.i]) & 255]; -} - -Arcfour.prototype.init = ARC4init; -Arcfour.prototype.next = ARC4next; - -// Plug in your RNG constructor here -function prng_newstate() { - return new Arcfour(); -} - -// Pool size must be a multiple of 4 and greater than 32. -// An array of bytes the size of the pool will be passed to init() -var rng_psize = 256; - diff --git a/config/plugins/interactive_environments/rstudio/static/js/crypto/rstudio/rng.js b/config/plugins/interactive_environments/rstudio/static/js/crypto/rstudio/rng.js deleted file mode 100644 index 24bae0f8ec8..00000000000 --- a/config/plugins/interactive_environments/rstudio/static/js/crypto/rstudio/rng.js +++ /dev/null @@ -1,70 +0,0 @@ -// ==== File: rng.js -// Random number generator - requires a PRNG backend, e.g. prng4.js - -// For best results, put code like -// -// in your main HTML document. - -var rng_state; -var rng_pool; -var rng_pptr; - -// Mix in a 32-bit integer into the pool -function rng_seed_int(x) { - rng_pool[rng_pptr++] ^= x & 255; - rng_pool[rng_pptr++] ^= (x >> 8) & 255; - rng_pool[rng_pptr++] ^= (x >> 16) & 255; - rng_pool[rng_pptr++] ^= (x >> 24) & 255; - if(rng_pptr >= rng_psize) rng_pptr -= rng_psize; -} - -// Mix in the current time (w/milliseconds) into the pool -function rng_seed_time() { - rng_seed_int(new Date().getTime()); -} - -// Initialize the pool with junk if needed. -if(rng_pool == null) { - rng_pool = new Array(); - rng_pptr = 0; - var t; - if(navigator.appName == "Netscape" && navigator.appVersion < "5" && window.crypto) { - // Extract entropy (256 bits) from NS4 RNG if available - var z = window.crypto.random(32); - for(t = 0; t < z.length; ++t) - rng_pool[rng_pptr++] = z.charCodeAt(t) & 255; - } - while(rng_pptr < rng_psize) { // extract some randomness from Math.random() - t = Math.floor(65536 * Math.random()); - rng_pool[rng_pptr++] = t >>> 8; - rng_pool[rng_pptr++] = t & 255; - } - rng_pptr = 0; - rng_seed_time(); - //rng_seed_int(window.screenX); - //rng_seed_int(window.screenY); -} - -function rng_get_byte() { - if(rng_state == null) { - rng_seed_time(); - rng_state = prng_newstate(); - rng_state.init(rng_pool); - for(rng_pptr = 0; rng_pptr < rng_pool.length; ++rng_pptr) - rng_pool[rng_pptr] = 0; - rng_pptr = 0; - //rng_pool = null; - } - // TODO: allow reseeding after first request - return rng_state.next(); -} - -function rng_get_bytes(ba) { - var i; - for(i = 0; i < ba.length; ++i) ba[i] = rng_get_byte(); -} - -function SecureRandom() {} - -SecureRandom.prototype.nextBytes = rng_get_bytes; - diff --git a/config/plugins/interactive_environments/rstudio/static/js/crypto/rstudio/rsa.js b/config/plugins/interactive_environments/rstudio/static/js/crypto/rstudio/rsa.js deleted file mode 100644 index b2e37c35540..00000000000 --- a/config/plugins/interactive_environments/rstudio/static/js/crypto/rstudio/rsa.js +++ /dev/null @@ -1,114 +0,0 @@ -// ==== File: rsa.js -// Depends on jsbn.js and rng.js - -// Version 1.1: support utf-8 encoding in pkcs1pad2 - -// convert a (hex) string to a bignum object -function parseBigInt(str,r) { - return new BigInteger(str,r); -} - -function linebrk(s,n) { - var ret = ""; - var i = 0; - while(i + n < s.length) { - ret += s.substring(i,i+n) + "\n"; - i += n; - } - return ret + s.substring(i,s.length); -} - -function byte2Hex(b) { - if(b < 0x10) - return "0" + b.toString(16); - else - return b.toString(16); -} - -// PKCS#1 (type 2, random) pad input string s to n bytes, and return a bigint -function pkcs1pad2(s,n) { - if(n < s.length + 11) { // TODO: fix for utf-8 - alert("Message too long for RSA"); - return null; - } - var ba = new Array(); - var i = s.length - 1; - while(i >= 0 && n > 0) { - var c = s.charCodeAt(i--); - if(c < 128) { // encode using utf-8 - ba[--n] = c; - } - else if((c > 127) && (c < 2048)) { - ba[--n] = (c & 63) | 128; - ba[--n] = (c >> 6) | 192; - } - else { - ba[--n] = (c & 63) | 128; - ba[--n] = ((c >> 6) & 63) | 128; - ba[--n] = (c >> 12) | 224; - } - } - ba[--n] = 0; - var rng = new SecureRandom(); - var x = new Array(); - while(n > 2) { // random non-zero pad - x[0] = 0; - while(x[0] == 0) rng.nextBytes(x); - ba[--n] = x[0]; - } - ba[--n] = 2; - ba[--n] = 0; - return new BigInteger(ba); -} - -// "empty" RSA key constructor -function RSAKey() { - this.n = null; - this.e = 0; - this.d = null; - this.p = null; - this.q = null; - this.dmp1 = null; - this.dmq1 = null; - this.coeff = null; -} - -// Set the public key fields N and e from hex strings -function RSASetPublic(N,E) { - if(N != null && E != null && N.length > 0 && E.length > 0) { - this.n = parseBigInt(N,16); - this.e = parseInt(E,16); - } - else - alert("Invalid RSA public key"); -} - -// Perform raw public operation on "x": return x^e (mod n) -function RSADoPublic(x) { - return x.modPowInt(this.e, this.n); -} - -// Return the PKCS#1 RSA encryption of "text" as an even-length hex string -function RSAEncrypt(text) { - var m = pkcs1pad2(text,(this.n.bitLength()+7)>>3); - if(m == null) return null; - var c = this.doPublic(m); - if(c == null) return null; - var h = c.toString(16); - if((h.length & 1) == 0) return h; else return "0" + h; -} - -// Return the PKCS#1 RSA encryption of "text" as a Base64-encoded string -//function RSAEncryptB64(text) { -// var h = this.encrypt(text); -// if(h) return hex2b64(h); else return null; -//} - -// protected -RSAKey.prototype.doPublic = RSADoPublic; - -// public -RSAKey.prototype.setPublic = RSASetPublic; -RSAKey.prototype.encrypt = RSAEncrypt; -//RSAKey.prototype.encrypt_b64 = RSAEncryptB64; - diff --git a/config/plugins/interactive_environments/rstudio/static/js/crypto/wu/base64.js b/config/plugins/interactive_environments/rstudio/static/js/crypto/wu/base64.js deleted file mode 100644 index 1421d8772a4..00000000000 --- a/config/plugins/interactive_environments/rstudio/static/js/crypto/wu/base64.js +++ /dev/null @@ -1,71 +0,0 @@ -var b64map="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -var b64padchar="="; - -function hex2b64(h) { - var i; - var c; - var ret = ""; - for(i = 0; i+3 <= h.length; i+=3) { - c = parseInt(h.substring(i,i+3),16); - ret += b64map.charAt(c >> 6) + b64map.charAt(c & 63); - } - if(i+1 == h.length) { - c = parseInt(h.substring(i,i+1),16); - ret += b64map.charAt(c << 2); - } - else if(i+2 == h.length) { - c = parseInt(h.substring(i,i+2),16); - ret += b64map.charAt(c >> 2) + b64map.charAt((c & 3) << 4); - } - while((ret.length & 3) > 0) ret += b64padchar; - return ret; -} - -// convert a base64 string to hex -function b64tohex(s) { - var ret = "" - var i; - var k = 0; // b64 state, 0-3 - var slop; - for(i = 0; i < s.length; ++i) { - if(s.charAt(i) == b64padchar) break; - var v = b64map.indexOf(s.charAt(i)); - if(v < 0) continue; - if(k == 0) { - ret += int2char(v >> 2); - slop = v & 3; - k = 1; - } - else if(k == 1) { - ret += int2char((slop << 2) | (v >> 4)); - slop = v & 0xf; - k = 2; - } - else if(k == 2) { - ret += int2char(slop); - ret += int2char(v >> 2); - slop = v & 3; - k = 3; - } - else { - ret += int2char((slop << 2) | (v >> 4)); - ret += int2char(v & 0xf); - k = 0; - } - } - if(k == 1) - ret += int2char(slop << 2); - return ret; -} - -// convert a base64 string to a byte/number array -function b64toBA(s) { - //piggyback on b64tohex for now, optimize later - var h = b64tohex(s); - var i; - var a = new Array(); - for(i = 0; 2*i < h.length; ++i) { - a[i] = parseInt(h.substring(2*i,2*i+2),16); - } - return a; -} diff --git a/config/plugins/interactive_environments/rstudio/static/js/crypto/wu/jsbn.js b/config/plugins/interactive_environments/rstudio/static/js/crypto/wu/jsbn.js deleted file mode 100644 index 4ed7c8362da..00000000000 --- a/config/plugins/interactive_environments/rstudio/static/js/crypto/wu/jsbn.js +++ /dev/null @@ -1,559 +0,0 @@ -// Copyright (c) 2005 Tom Wu -// All Rights Reserved. -// See "LICENSE" for details. - -// Basic JavaScript BN library - subset useful for RSA encryption. - -// Bits per digit -var dbits; - -// JavaScript engine analysis -var canary = 0xdeadbeefcafe; -var j_lm = ((canary&0xffffff)==0xefcafe); - -// (public) Constructor -function BigInteger(a,b,c) { - if(a != null) - if("number" == typeof a) this.fromNumber(a,b,c); - else if(b == null && "string" != typeof a) this.fromString(a,256); - else this.fromString(a,b); -} - -// return new, unset BigInteger -function nbi() { return new BigInteger(null); } - -// am: Compute w_j += (x*this_i), propagate carries, -// c is initial carry, returns final carry. -// c < 3*dvalue, x < 2*dvalue, this_i < dvalue -// We need to select the fastest one that works in this environment. - -// am1: use a single mult and divide to get the high bits, -// max digit bits should be 26 because -// max internal value = 2*dvalue^2-2*dvalue (< 2^53) -function am1(i,x,w,j,c,n) { - while(--n >= 0) { - var v = x*this[i++]+w[j]+c; - c = Math.floor(v/0x4000000); - w[j++] = v&0x3ffffff; - } - return c; -} -// am2 avoids a big mult-and-extract completely. -// Max digit bits should be <= 30 because we do bitwise ops -// on values up to 2*hdvalue^2-hdvalue-1 (< 2^31) -function am2(i,x,w,j,c,n) { - var xl = x&0x7fff, xh = x>>15; - while(--n >= 0) { - var l = this[i]&0x7fff; - var h = this[i++]>>15; - var m = xh*l+h*xl; - l = xl*l+((m&0x7fff)<<15)+w[j]+(c&0x3fffffff); - c = (l>>>30)+(m>>>15)+xh*h+(c>>>30); - w[j++] = l&0x3fffffff; - } - return c; -} -// Alternately, set max digit bits to 28 since some -// browsers slow down when dealing with 32-bit numbers. -function am3(i,x,w,j,c,n) { - var xl = x&0x3fff, xh = x>>14; - while(--n >= 0) { - var l = this[i]&0x3fff; - var h = this[i++]>>14; - var m = xh*l+h*xl; - l = xl*l+((m&0x3fff)<<14)+w[j]+c; - c = (l>>28)+(m>>14)+xh*h; - w[j++] = l&0xfffffff; - } - return c; -} -if(j_lm && (navigator.appName == "Microsoft Internet Explorer")) { - BigInteger.prototype.am = am2; - dbits = 30; -} -else if(j_lm && (navigator.appName != "Netscape")) { - BigInteger.prototype.am = am1; - dbits = 26; -} -else { // Mozilla/Netscape seems to prefer am3 - BigInteger.prototype.am = am3; - dbits = 28; -} - -BigInteger.prototype.DB = dbits; -BigInteger.prototype.DM = ((1<= 0; --i) r[i] = this[i]; - r.t = this.t; - r.s = this.s; -} - -// (protected) set from integer value x, -DV <= x < DV -function bnpFromInt(x) { - this.t = 1; - this.s = (x<0)?-1:0; - if(x > 0) this[0] = x; - else if(x < -1) this[0] = x+this.DV; - else this.t = 0; -} - -// return bigint initialized to value -function nbv(i) { var r = nbi(); r.fromInt(i); return r; } - -// (protected) set from string and radix -function bnpFromString(s,b) { - var k; - if(b == 16) k = 4; - else if(b == 8) k = 3; - else if(b == 256) k = 8; // byte array - else if(b == 2) k = 1; - else if(b == 32) k = 5; - else if(b == 4) k = 2; - else { this.fromRadix(s,b); return; } - this.t = 0; - this.s = 0; - var i = s.length, mi = false, sh = 0; - while(--i >= 0) { - var x = (k==8)?s[i]&0xff:intAt(s,i); - if(x < 0) { - if(s.charAt(i) == "-") mi = true; - continue; - } - mi = false; - if(sh == 0) - this[this.t++] = x; - else if(sh+k > this.DB) { - this[this.t-1] |= (x&((1<<(this.DB-sh))-1))<>(this.DB-sh)); - } - else - this[this.t-1] |= x<= this.DB) sh -= this.DB; - } - if(k == 8 && (s[0]&0x80) != 0) { - this.s = -1; - if(sh > 0) this[this.t-1] |= ((1<<(this.DB-sh))-1)< 0 && this[this.t-1] == c) --this.t; -} - -// (public) return string representation in given radix -function bnToString(b) { - if(this.s < 0) return "-"+this.negate().toString(b); - var k; - if(b == 16) k = 4; - else if(b == 8) k = 3; - else if(b == 2) k = 1; - else if(b == 32) k = 5; - else if(b == 4) k = 2; - else return this.toRadix(b); - var km = (1< 0) { - if(p < this.DB && (d = this[i]>>p) > 0) { m = true; r = int2char(d); } - while(i >= 0) { - if(p < k) { - d = (this[i]&((1<>(p+=this.DB-k); - } - else { - d = (this[i]>>(p-=k))&km; - if(p <= 0) { p += this.DB; --i; } - } - if(d > 0) m = true; - if(m) r += int2char(d); - } - } - return m?r:"0"; -} - -// (public) -this -function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this,r); return r; } - -// (public) |this| -function bnAbs() { return (this.s<0)?this.negate():this; } - -// (public) return + if this > a, - if this < a, 0 if equal -function bnCompareTo(a) { - var r = this.s-a.s; - if(r != 0) return r; - var i = this.t; - r = i-a.t; - if(r != 0) return (this.s<0)?-r:r; - while(--i >= 0) if((r=this[i]-a[i]) != 0) return r; - return 0; -} - -// returns bit length of the integer x -function nbits(x) { - var r = 1, t; - if((t=x>>>16) != 0) { x = t; r += 16; } - if((t=x>>8) != 0) { x = t; r += 8; } - if((t=x>>4) != 0) { x = t; r += 4; } - if((t=x>>2) != 0) { x = t; r += 2; } - if((t=x>>1) != 0) { x = t; r += 1; } - return r; -} - -// (public) return the number of bits in "this" -function bnBitLength() { - if(this.t <= 0) return 0; - return this.DB*(this.t-1)+nbits(this[this.t-1]^(this.s&this.DM)); -} - -// (protected) r = this << n*DB -function bnpDLShiftTo(n,r) { - var i; - for(i = this.t-1; i >= 0; --i) r[i+n] = this[i]; - for(i = n-1; i >= 0; --i) r[i] = 0; - r.t = this.t+n; - r.s = this.s; -} - -// (protected) r = this >> n*DB -function bnpDRShiftTo(n,r) { - for(var i = n; i < this.t; ++i) r[i-n] = this[i]; - r.t = Math.max(this.t-n,0); - r.s = this.s; -} - -// (protected) r = this << n -function bnpLShiftTo(n,r) { - var bs = n%this.DB; - var cbs = this.DB-bs; - var bm = (1<= 0; --i) { - r[i+ds+1] = (this[i]>>cbs)|c; - c = (this[i]&bm)<= 0; --i) r[i] = 0; - r[ds] = c; - r.t = this.t+ds+1; - r.s = this.s; - r.clamp(); -} - -// (protected) r = this >> n -function bnpRShiftTo(n,r) { - r.s = this.s; - var ds = Math.floor(n/this.DB); - if(ds >= this.t) { r.t = 0; return; } - var bs = n%this.DB; - var cbs = this.DB-bs; - var bm = (1<>bs; - for(var i = ds+1; i < this.t; ++i) { - r[i-ds-1] |= (this[i]&bm)<>bs; - } - if(bs > 0) r[this.t-ds-1] |= (this.s&bm)<>= this.DB; - } - if(a.t < this.t) { - c -= a.s; - while(i < this.t) { - c += this[i]; - r[i++] = c&this.DM; - c >>= this.DB; - } - c += this.s; - } - else { - c += this.s; - while(i < a.t) { - c -= a[i]; - r[i++] = c&this.DM; - c >>= this.DB; - } - c -= a.s; - } - r.s = (c<0)?-1:0; - if(c < -1) r[i++] = this.DV+c; - else if(c > 0) r[i++] = c; - r.t = i; - r.clamp(); -} - -// (protected) r = this * a, r != this,a (HAC 14.12) -// "this" should be the larger one if appropriate. -function bnpMultiplyTo(a,r) { - var x = this.abs(), y = a.abs(); - var i = x.t; - r.t = i+y.t; - while(--i >= 0) r[i] = 0; - for(i = 0; i < y.t; ++i) r[i+x.t] = x.am(0,y[i],r,i,0,x.t); - r.s = 0; - r.clamp(); - if(this.s != a.s) BigInteger.ZERO.subTo(r,r); -} - -// (protected) r = this^2, r != this (HAC 14.16) -function bnpSquareTo(r) { - var x = this.abs(); - var i = r.t = 2*x.t; - while(--i >= 0) r[i] = 0; - for(i = 0; i < x.t-1; ++i) { - var c = x.am(i,x[i],r,2*i,0,1); - if((r[i+x.t]+=x.am(i+1,2*x[i],r,2*i+1,c,x.t-i-1)) >= x.DV) { - r[i+x.t] -= x.DV; - r[i+x.t+1] = 1; - } - } - if(r.t > 0) r[r.t-1] += x.am(i,x[i],r,2*i,0,1); - r.s = 0; - r.clamp(); -} - -// (protected) divide this by m, quotient and remainder to q, r (HAC 14.20) -// r != q, this != m. q or r may be null. -function bnpDivRemTo(m,q,r) { - var pm = m.abs(); - if(pm.t <= 0) return; - var pt = this.abs(); - if(pt.t < pm.t) { - if(q != null) q.fromInt(0); - if(r != null) this.copyTo(r); - return; - } - if(r == null) r = nbi(); - var y = nbi(), ts = this.s, ms = m.s; - var nsh = this.DB-nbits(pm[pm.t-1]); // normalize modulus - if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); } - else { pm.copyTo(y); pt.copyTo(r); } - var ys = y.t; - var y0 = y[ys-1]; - if(y0 == 0) return; - var yt = y0*(1<1)?y[ys-2]>>this.F2:0); - var d1 = this.FV/yt, d2 = (1<= 0) { - r[r.t++] = 1; - r.subTo(t,r); - } - BigInteger.ONE.dlShiftTo(ys,t); - t.subTo(y,y); // "negative" y so we can replace sub with am later - while(y.t < ys) y[y.t++] = 0; - while(--j >= 0) { - // Estimate quotient digit - var qd = (r[--i]==y0)?this.DM:Math.floor(r[i]*d1+(r[i-1]+e)*d2); - if((r[i]+=y.am(0,qd,r,j,0,ys)) < qd) { // Try it out - y.dlShiftTo(j,t); - r.subTo(t,r); - while(r[i] < --qd) r.subTo(t,r); - } - } - if(q != null) { - r.drShiftTo(ys,q); - if(ts != ms) BigInteger.ZERO.subTo(q,q); - } - r.t = ys; - r.clamp(); - if(nsh > 0) r.rShiftTo(nsh,r); // Denormalize remainder - if(ts < 0) BigInteger.ZERO.subTo(r,r); -} - -// (public) this mod a -function bnMod(a) { - var r = nbi(); - this.abs().divRemTo(a,null,r); - if(this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r,r); - return r; -} - -// Modular reduction using "classic" algorithm -function Classic(m) { this.m = m; } -function cConvert(x) { - if(x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m); - else return x; -} -function cRevert(x) { return x; } -function cReduce(x) { x.divRemTo(this.m,null,x); } -function cMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } -function cSqrTo(x,r) { x.squareTo(r); this.reduce(r); } - -Classic.prototype.convert = cConvert; -Classic.prototype.revert = cRevert; -Classic.prototype.reduce = cReduce; -Classic.prototype.mulTo = cMulTo; -Classic.prototype.sqrTo = cSqrTo; - -// (protected) return "-1/this % 2^DB"; useful for Mont. reduction -// justification: -// xy == 1 (mod m) -// xy = 1+km -// xy(2-xy) = (1+km)(1-km) -// x[y(2-xy)] = 1-k^2m^2 -// x[y(2-xy)] == 1 (mod m^2) -// if y is 1/x mod m, then y(2-xy) is 1/x mod m^2 -// should reduce x and y(2-xy) by m^2 at each step to keep size bounded. -// JS multiply "overflows" differently from C/C++, so care is needed here. -function bnpInvDigit() { - if(this.t < 1) return 0; - var x = this[0]; - if((x&1) == 0) return 0; - var y = x&3; // y == 1/x mod 2^2 - y = (y*(2-(x&0xf)*y))&0xf; // y == 1/x mod 2^4 - y = (y*(2-(x&0xff)*y))&0xff; // y == 1/x mod 2^8 - y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff; // y == 1/x mod 2^16 - // last step - calculate inverse mod DV directly; - // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints - y = (y*(2-x*y%this.DV))%this.DV; // y == 1/x mod 2^dbits - // we really want the negative inverse, and -DV < y < DV - return (y>0)?this.DV-y:-y; -} - -// Montgomery reduction -function Montgomery(m) { - this.m = m; - this.mp = m.invDigit(); - this.mpl = this.mp&0x7fff; - this.mph = this.mp>>15; - this.um = (1<<(m.DB-15))-1; - this.mt2 = 2*m.t; -} - -// xR mod m -function montConvert(x) { - var r = nbi(); - x.abs().dlShiftTo(this.m.t,r); - r.divRemTo(this.m,null,r); - if(x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r,r); - return r; -} - -// x/R mod m -function montRevert(x) { - var r = nbi(); - x.copyTo(r); - this.reduce(r); - return r; -} - -// x = x/R mod m (HAC 14.32) -function montReduce(x) { - while(x.t <= this.mt2) // pad x so am has enough room later - x[x.t++] = 0; - for(var i = 0; i < this.m.t; ++i) { - // faster way of calculating u0 = x[i]*mp mod DV - var j = x[i]&0x7fff; - var u0 = (j*this.mpl+(((j*this.mph+(x[i]>>15)*this.mpl)&this.um)<<15))&x.DM; - // use am to combine the multiply-shift-add into one call - j = i+this.m.t; - x[j] += this.m.am(0,u0,x,i,0,this.m.t); - // propagate carry - while(x[j] >= x.DV) { x[j] -= x.DV; x[++j]++; } - } - x.clamp(); - x.drShiftTo(this.m.t,x); - if(x.compareTo(this.m) >= 0) x.subTo(this.m,x); -} - -// r = "x^2/R mod m"; x != r -function montSqrTo(x,r) { x.squareTo(r); this.reduce(r); } - -// r = "xy/R mod m"; x,y != r -function montMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } - -Montgomery.prototype.convert = montConvert; -Montgomery.prototype.revert = montRevert; -Montgomery.prototype.reduce = montReduce; -Montgomery.prototype.mulTo = montMulTo; -Montgomery.prototype.sqrTo = montSqrTo; - -// (protected) true iff this is even -function bnpIsEven() { return ((this.t>0)?(this[0]&1):this.s) == 0; } - -// (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79) -function bnpExp(e,z) { - if(e > 0xffffffff || e < 1) return BigInteger.ONE; - var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1; - g.copyTo(r); - while(--i >= 0) { - z.sqrTo(r,r2); - if((e&(1< 0) z.mulTo(r2,g,r); - else { var t = r; r = r2; r2 = t; } - } - return z.revert(r); -} - -// (public) this^e % m, 0 <= e < 2^32 -function bnModPowInt(e,m) { - var z; - if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m); - return this.exp(e,z); -} - -// protected -BigInteger.prototype.copyTo = bnpCopyTo; -BigInteger.prototype.fromInt = bnpFromInt; -BigInteger.prototype.fromString = bnpFromString; -BigInteger.prototype.clamp = bnpClamp; -BigInteger.prototype.dlShiftTo = bnpDLShiftTo; -BigInteger.prototype.drShiftTo = bnpDRShiftTo; -BigInteger.prototype.lShiftTo = bnpLShiftTo; -BigInteger.prototype.rShiftTo = bnpRShiftTo; -BigInteger.prototype.subTo = bnpSubTo; -BigInteger.prototype.multiplyTo = bnpMultiplyTo; -BigInteger.prototype.squareTo = bnpSquareTo; -BigInteger.prototype.divRemTo = bnpDivRemTo; -BigInteger.prototype.invDigit = bnpInvDigit; -BigInteger.prototype.isEven = bnpIsEven; -BigInteger.prototype.exp = bnpExp; - -// public -BigInteger.prototype.toString = bnToString; -BigInteger.prototype.negate = bnNegate; -BigInteger.prototype.abs = bnAbs; -BigInteger.prototype.compareTo = bnCompareTo; -BigInteger.prototype.bitLength = bnBitLength; -BigInteger.prototype.mod = bnMod; -BigInteger.prototype.modPowInt = bnModPowInt; - -// "constants" -BigInteger.ZERO = nbv(0); -BigInteger.ONE = nbv(1); diff --git a/config/plugins/interactive_environments/rstudio/static/js/crypto/wu/prng4.js b/config/plugins/interactive_environments/rstudio/static/js/crypto/wu/prng4.js deleted file mode 100644 index 3034f3f1158..00000000000 --- a/config/plugins/interactive_environments/rstudio/static/js/crypto/wu/prng4.js +++ /dev/null @@ -1,45 +0,0 @@ -// prng4.js - uses Arcfour as a PRNG - -function Arcfour() { - this.i = 0; - this.j = 0; - this.S = new Array(); -} - -// Initialize arcfour context from key, an array of ints, each from [0..255] -function ARC4init(key) { - var i, j, t; - for(i = 0; i < 256; ++i) - this.S[i] = i; - j = 0; - for(i = 0; i < 256; ++i) { - j = (j + this.S[i] + key[i % key.length]) & 255; - t = this.S[i]; - this.S[i] = this.S[j]; - this.S[j] = t; - } - this.i = 0; - this.j = 0; -} - -function ARC4next() { - var t; - this.i = (this.i + 1) & 255; - this.j = (this.j + this.S[this.i]) & 255; - t = this.S[this.i]; - this.S[this.i] = this.S[this.j]; - this.S[this.j] = t; - return this.S[(t + this.S[this.i]) & 255]; -} - -Arcfour.prototype.init = ARC4init; -Arcfour.prototype.next = ARC4next; - -// Plug in your RNG constructor here -function prng_newstate() { - return new Arcfour(); -} - -// Pool size must be a multiple of 4 and greater than 32. -// An array of bytes the size of the pool will be passed to init() -var rng_psize = 256; diff --git a/config/plugins/interactive_environments/rstudio/static/js/crypto/wu/rng.js b/config/plugins/interactive_environments/rstudio/static/js/crypto/wu/rng.js deleted file mode 100644 index 9db13825fb6..00000000000 --- a/config/plugins/interactive_environments/rstudio/static/js/crypto/wu/rng.js +++ /dev/null @@ -1,75 +0,0 @@ -// Random number generator - requires a PRNG backend, e.g. prng4.js - -// For best results, put code like -// -// in your main HTML document. - -var rng_state; -var rng_pool; -var rng_pptr; - -// Mix in a 32-bit integer into the pool -function rng_seed_int(x) { - rng_pool[rng_pptr++] ^= x & 255; - rng_pool[rng_pptr++] ^= (x >> 8) & 255; - rng_pool[rng_pptr++] ^= (x >> 16) & 255; - rng_pool[rng_pptr++] ^= (x >> 24) & 255; - if(rng_pptr >= rng_psize) rng_pptr -= rng_psize; -} - -// Mix in the current time (w/milliseconds) into the pool -function rng_seed_time() { - rng_seed_int(new Date().getTime()); -} - -// Initialize the pool with junk if needed. -if(rng_pool == null) { - rng_pool = new Array(); - rng_pptr = 0; - var t; - if(window.crypto && window.crypto.getRandomValues) { - // Use webcrypto if available - var ua = new Uint8Array(32); - window.crypto.getRandomValues(ua); - for(t = 0; t < 32; ++t) - rng_pool[rng_pptr++] = ua[t]; - } - if(navigator.appName == "Netscape" && navigator.appVersion < "5" && window.crypto) { - // Extract entropy (256 bits) from NS4 RNG if available - var z = window.crypto.random(32); - for(t = 0; t < z.length; ++t) - rng_pool[rng_pptr++] = z.charCodeAt(t) & 255; - } - while(rng_pptr < rng_psize) { // extract some randomness from Math.random() - t = Math.floor(65536 * Math.random()); - rng_pool[rng_pptr++] = t >>> 8; - rng_pool[rng_pptr++] = t & 255; - } - rng_pptr = 0; - rng_seed_time(); - //rng_seed_int(window.screenX); - //rng_seed_int(window.screenY); -} - -function rng_get_byte() { - if(rng_state == null) { - rng_seed_time(); - rng_state = prng_newstate(); - rng_state.init(rng_pool); - for(rng_pptr = 0; rng_pptr < rng_pool.length; ++rng_pptr) - rng_pool[rng_pptr] = 0; - rng_pptr = 0; - //rng_pool = null; - } - // TODO: allow reseeding after first request - return rng_state.next(); -} - -function rng_get_bytes(ba) { - var i; - for(i = 0; i < ba.length; ++i) ba[i] = rng_get_byte(); -} - -function SecureRandom() {} - -SecureRandom.prototype.nextBytes = rng_get_bytes; diff --git a/config/plugins/interactive_environments/rstudio/static/js/crypto/wu/rsa.js b/config/plugins/interactive_environments/rstudio/static/js/crypto/wu/rsa.js deleted file mode 100644 index 9f8664037c4..00000000000 --- a/config/plugins/interactive_environments/rstudio/static/js/crypto/wu/rsa.js +++ /dev/null @@ -1,112 +0,0 @@ -// Depends on jsbn.js and rng.js - -// Version 1.1: support utf-8 encoding in pkcs1pad2 - -// convert a (hex) string to a bignum object -function parseBigInt(str,r) { - return new BigInteger(str,r); -} - -function linebrk(s,n) { - var ret = ""; - var i = 0; - while(i + n < s.length) { - ret += s.substring(i,i+n) + "\n"; - i += n; - } - return ret + s.substring(i,s.length); -} - -function byte2Hex(b) { - if(b < 0x10) - return "0" + b.toString(16); - else - return b.toString(16); -} - -// PKCS#1 (type 2, random) pad input string s to n bytes, and return a bigint -function pkcs1pad2(s,n) { - if(n < s.length + 11) { // TODO: fix for utf-8 - alert("Message too long for RSA"); - return null; - } - var ba = new Array(); - var i = s.length - 1; - while(i >= 0 && n > 0) { - var c = s.charCodeAt(i--); - if(c < 128) { // encode using utf-8 - ba[--n] = c; - } - else if((c > 127) && (c < 2048)) { - ba[--n] = (c & 63) | 128; - ba[--n] = (c >> 6) | 192; - } - else { - ba[--n] = (c & 63) | 128; - ba[--n] = ((c >> 6) & 63) | 128; - ba[--n] = (c >> 12) | 224; - } - } - ba[--n] = 0; - var rng = new SecureRandom(); - var x = new Array(); - while(n > 2) { // random non-zero pad - x[0] = 0; - while(x[0] == 0) rng.nextBytes(x); - ba[--n] = x[0]; - } - ba[--n] = 2; - ba[--n] = 0; - return new BigInteger(ba); -} - -// "empty" RSA key constructor -function RSAKey() { - this.n = null; - this.e = 0; - this.d = null; - this.p = null; - this.q = null; - this.dmp1 = null; - this.dmq1 = null; - this.coeff = null; -} - -// Set the public key fields N and e from hex strings -function RSASetPublic(N,E) { - if(N != null && E != null && N.length > 0 && E.length > 0) { - this.n = parseBigInt(N,16); - this.e = parseInt(E,16); - } - else - alert("Invalid RSA public key"); -} - -// Perform raw public operation on "x": return x^e (mod n) -function RSADoPublic(x) { - return x.modPowInt(this.e, this.n); -} - -// Return the PKCS#1 RSA encryption of "text" as an even-length hex string -function RSAEncrypt(text) { - var m = pkcs1pad2(text,(this.n.bitLength()+7)>>3); - if(m == null) return null; - var c = this.doPublic(m); - if(c == null) return null; - var h = c.toString(16); - if((h.length & 1) == 0) return h; else return "0" + h; -} - -// Return the PKCS#1 RSA encryption of "text" as a Base64-encoded string -//function RSAEncryptB64(text) { -// var h = this.encrypt(text); -// if(h) return hex2b64(h); else return null; -//} - -// protected -RSAKey.prototype.doPublic = RSADoPublic; - -// public -RSAKey.prototype.setPublic = RSASetPublic; -RSAKey.prototype.encrypt = RSAEncrypt; -//RSAKey.prototype.encrypt_b64 = RSAEncryptB64; diff --git a/config/plugins/interactive_environments/rstudio/static/js/rstudio.js b/config/plugins/interactive_environments/rstudio/static/js/rstudio.js old mode 100644 new mode 100755 index 39271ac0015..c8be493fd5c --- a/config/plugins/interactive_environments/rstudio/static/js/rstudio.js +++ b/config/plugins/interactive_environments/rstudio/static/js/rstudio.js @@ -13,62 +13,18 @@ function message_failed_connection(){ /** * Load an interactive environment (IE) from a remote URL - * @param {String} password: password used to authenticate to the remote resource - * @param {String} notebook_login_url: URL that should be POSTed to for login * @param {String} notebook_access_url: the URL embeded in the page and loaded * */ -function load_notebook(notebook_login_url, notebook_access_url, notebook_pubkey_url){ +function load_notebook(notebook_access_url){ // Test notebook_login_url for accessibility, executing the login+load function whenever // we've successfully connected to the IE. - IES.test_ie_availability(notebook_pubkey_url, function(){ - var payload = "rstudio\nrstudio"; + IES.test_ie_availability(notebook_access_url, function(){ $.ajax({ type: 'GET', - url: notebook_pubkey_url, - xhrFields: { - withCredentials: true - }, + url: notebook_access_url, success: function(response_text){ - var chunks = response_text.split(':', 2); - var exp = chunks[0]; - var mod = chunks[1]; - console.log("Found " + exp +" and " + mod); - var rsa = new RSAKey(); - rsa.setPublic(mod, exp); - var enc_hex = rsa.encrypt(payload); - var encrypted = hex2b64(enc_hex); - console.log("E: " + encrypted); - - // Now we can login - $.ajax({ - type: "POST", - // to the Login URL - url: notebook_login_url, - // With our password - data: { - 'v': encrypted, - 'persist': 1, - 'clientPath': '/rstudio/auth-sign-in', - 'appUri': '', - }, - contentType: "application/x-www-form-urlencoded", - xhrFields: { - withCredentials: true - }, - // If that is successful, load the notebook - success: function(){ - IES.append_notebook(notebook_access_url); - }, - error: function(jqxhr, status, error){ - // TODO: Uncomment when rstudio / nginx play nicely and - // don't return http when we asked for HTTPS, thereby - // issuing a mixed-content warning. - //message_failed_connection(); - // Do we want to try and load the notebook anyway? Just in case? - IES.append_notebook(notebook_access_url); - } - }); + IES.append_notebook(notebook_access_url); } }); diff --git a/config/plugins/interactive_environments/rstudio/templates/rstudio.mako b/config/plugins/interactive_environments/rstudio/templates/rstudio.mako old mode 100644 new mode 100755 index 6abece60cd1..a9c0bf776db --- a/config/plugins/interactive_environments/rstudio/templates/rstudio.mako +++ b/config/plugins/interactive_environments/rstudio/templates/rstudio.mako @@ -1,17 +1,10 @@ <%namespace name="ie" file="ie.mako"/> <% -import os -import shutil -import time # Sets ID and sets up a lot of other variables ie_request.load_deploy_config() ie_request.attr.docker_port = 80 -PASSWORD = "rstudio" -USERNAME = "rstudio" -# Then override it again -ie_request.notebook_pw = "rstudio" additional_ids = trans.request.params.get('additional_dataset_ids', None) if not additional_ids: @@ -23,21 +16,12 @@ DATASET_HID = hda.hid # Add all environment variables collected from Galaxy's IE infrastructure ie_request.launch( - image=trans.request.params.get('image_tag', None), - additional_ids=additional_ids if ie_request.use_volumes else None, - env_override={ - 'notebook_username': USERNAME, - 'notebook_password': PASSWORD, - 'dataset_hid': DATASET_HID, - 'additional_ids': additional_ids if not ie_request.use_volumes else None, - } + image=trans.request.params.get('image_tag', None) ) ## General IE specific # Access URLs for the notebook from within galaxy. notebook_access_url = ie_request.url_template('${PROXY_URL}/rstudio/') -notebook_login_url = ie_request.url_template('${PROXY_URL}/rstudio/auth-do-sign-in') -notebook_pubkey_url = ie_request.url_template('${PROXY_URL}/rstudio/auth-public-key') %> @@ -48,29 +32,12 @@ ${ ie.load_default_js() } diff --git a/config/plugins/visualizations/aequatus/static/aequatus-vis/scripts/jquery/js/jquery.svg.js b/config/plugins/visualizations/aequatus/static/aequatus-vis/scripts/jquery/js/jquery.svg.js index dcbf95f2006..b5bd9736fa2 100644 --- a/config/plugins/visualizations/aequatus/static/aequatus-vis/scripts/jquery/js/jquery.svg.js +++ b/config/plugins/visualizations/aequatus/static/aequatus-vis/scripts/jquery/js/jquery.svg.js @@ -1,4 +1,4 @@ -/* http://keith-wood.name/svg.html +/* http://keith-wood.name/svg.html SVG for jQuery v1.4.5. Written by Keith Wood (kbwood{at}iinet.com.au) August 2007. Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and diff --git a/config/plugins/visualizations/aequatus/static/scripts/d3.min.js b/config/plugins/visualizations/aequatus/static/scripts/d3.min.js new file mode 100644 index 00000000000..83f0133b485 --- /dev/null +++ b/config/plugins/visualizations/aequatus/static/scripts/d3.min.js @@ -0,0 +1 @@ +!function(){var d3={version:"3.5.17"};var d3_arraySlice=[].slice,d3_array=function(list){return d3_arraySlice.call(list)};var d3_document=this.document;function d3_documentElement(node){return node&&(node.ownerDocument||node.document||node).documentElement}function d3_window(node){return node&&(node.ownerDocument&&node.ownerDocument.defaultView||node.document&&node||node.defaultView)}if(d3_document){try{d3_array(d3_document.documentElement.childNodes)[0].nodeType}catch(e){d3_array=function(list){var i=list.length,array=new Array(i);while(i--)array[i]=list[i];return array}}}if(!Date.now)Date.now=function(){return+new Date};if(d3_document){try{d3_document.createElement("DIV").style.setProperty("opacity",0,"")}catch(error){var d3_element_prototype=this.Element.prototype,d3_element_setAttribute=d3_element_prototype.setAttribute,d3_element_setAttributeNS=d3_element_prototype.setAttributeNS,d3_style_prototype=this.CSSStyleDeclaration.prototype,d3_style_setProperty=d3_style_prototype.setProperty;d3_element_prototype.setAttribute=function(name,value){d3_element_setAttribute.call(this,name,value+"")};d3_element_prototype.setAttributeNS=function(space,local,value){d3_element_setAttributeNS.call(this,space,local,value+"")};d3_style_prototype.setProperty=function(name,value,priority){d3_style_setProperty.call(this,name,value+"",priority)}}}d3.ascending=d3_ascending;function d3_ascending(a,b){return ab?1:a>=b?0:NaN}d3.descending=function(a,b){return ba?1:b>=a?0:NaN};d3.min=function(array,f){var i=-1,n=array.length,a,b;if(arguments.length===1){while(++i=b){a=b;break}while(++ib)a=b}else{while(++i=b){a=b;break}while(++ib)a=b}return a};d3.max=function(array,f){var i=-1,n=array.length,a,b;if(arguments.length===1){while(++i=b){a=b;break}while(++ia)a=b}else{while(++i=b){a=b;break}while(++ia)a=b}return a};d3.extent=function(array,f){var i=-1,n=array.length,a,b,c;if(arguments.length===1){while(++i=b){a=c=b;break}while(++ib)a=b;if(c=b){a=c=b;break}while(++ib)a=b;if(c1)return s/(j-1)};d3.deviation=function(){var v=d3.variance.apply(this,arguments);return v?Math.sqrt(v):v};function d3_bisector(compare){return{left:function(a,x,lo,hi){if(arguments.length<3)lo=0;if(arguments.length<4)hi=a.length;while(lo>>1;if(compare(a[mid],x)<0)lo=mid+1;else hi=mid}return lo},right:function(a,x,lo,hi){if(arguments.length<3)lo=0;if(arguments.length<4)hi=a.length;while(lo>>1;if(compare(a[mid],x)>0)hi=mid;else lo=mid+1}return lo}}}var d3_bisect=d3_bisector(d3_ascending);d3.bisectLeft=d3_bisect.left;d3.bisect=d3.bisectRight=d3_bisect.right;d3.bisector=function(f){return d3_bisector(f.length===1?function(d,x){return d3_ascending(f(d),x)}:f)};d3.shuffle=function(array,i0,i1){if((m=arguments.length)<3){i1=array.length;if(m<2)i0=0}var m=i1-i0,t,i;while(m){i=Math.random()*m--|0;t=array[m+i0],array[m+i0]=array[i+i0],array[i+i0]=t}return array};d3.permute=function(array,indexes){var i=indexes.length,permutes=new Array(i);while(i--)permutes[i]=array[indexes[i]];return permutes};d3.pairs=function(array){var i=0,n=array.length-1,p0,p1=array[0],pairs=new Array(n<0?0:n);while(i=0){array=arrays[n];m=array.length;while(--m>=0){merged[--j]=array[m]}}return merged};var abs=Math.abs;d3.range=function(start,stop,step){if(arguments.length<3){step=1;if(arguments.length<2){stop=start;start=0}}if((stop-start)/step===Infinity)throw new Error("infinite range");var range=[],k=d3_range_integerScale(abs(step)),i=-1,j;start*=k,stop*=k,step*=k;if(step<0)while((j=start+step*++i)>stop)range.push(j/k);else while((j=start+step*++i)=keys.length)return rollup?rollup.call(nest,array):sortValues?array.sort(sortValues):array;var i=-1,n=array.length,key=keys[depth++],keyValue,object,setter,valuesByKey=new d3_Map,values;while(++i=keys.length)return map;var array=[],sortKey=sortKeys[depth++];map.forEach(function(key,keyMap){array.push({key:key,values:entries(keyMap,depth)})});return sortKey?array.sort(function(a,b){return sortKey(a.key,b.key)}):array}nest.map=function(array,mapType){return map(mapType,array,0)};nest.entries=function(array){return entries(map(d3.map,array,0),0)};nest.key=function(d){keys.push(d);return nest};nest.sortKeys=function(order){sortKeys[keys.length-1]=order;return nest};nest.sortValues=function(order){sortValues=order;return nest};nest.rollup=function(f){rollup=f;return nest};return nest};d3.set=function(array){var set=new d3_Set;if(array)for(var i=0,n=array.length;i=0){name=type.slice(i+1);type=type.slice(0,i)}if(type)return arguments.length<2?this[type].on(name):this[type].on(name,listener);if(arguments.length===2){if(listener==null)for(type in this){if(this.hasOwnProperty(type))this[type].on(name,null)}return this}};function d3_dispatch_event(dispatch){var listeners=[],listenerByName=new d3_Map;function event(){var z=listeners,i=-1,n=z.length,l;while(++i=0&&(prefix=name.slice(0,i))!=="xmlns")name=name.slice(i+1);return d3_nsPrefix.hasOwnProperty(prefix)?{space:d3_nsPrefix[prefix],local:name}:name}};d3_selectionPrototype.attr=function(name,value){if(arguments.length<2){if(typeof name==="string"){var node=this.node();name=d3.ns.qualify(name);return name.local?node.getAttributeNS(name.space,name.local):node.getAttribute(name)}for(value in name)this.each(d3_selection_attr(value,name[value]));return this}return this.each(d3_selection_attr(name,value))};function d3_selection_attr(name,value){name=d3.ns.qualify(name);function attrNull(){this.removeAttribute(name)}function attrNullNS(){this.removeAttributeNS(name.space,name.local)}function attrConstant(){this.setAttribute(name,value)}function attrConstantNS(){this.setAttributeNS(name.space,name.local,value)}function attrFunction(){var x=value.apply(this,arguments);if(x==null)this.removeAttribute(name);else this.setAttribute(name,x)}function attrFunctionNS(){var x=value.apply(this,arguments);if(x==null)this.removeAttributeNS(name.space,name.local);else this.setAttributeNS(name.space,name.local,x)}return value==null?name.local?attrNullNS:attrNull:typeof value==="function"?name.local?attrFunctionNS:attrFunction:name.local?attrConstantNS:attrConstant}function d3_collapse(s){return s.trim().replace(/\s+/g," ")}d3_selectionPrototype.classed=function(name,value){if(arguments.length<2){if(typeof name==="string"){var node=this.node(),n=(name=d3_selection_classes(name)).length,i=-1;if(value=node.classList){while(++i=0;){if(node=group[i]){if(next&&next!==node.nextSibling)next.parentNode.insertBefore(node,next);next=node}}}return this};d3_selectionPrototype.sort=function(comparator){comparator=d3_selection_sortComparator.apply(this,arguments);for(var j=-1,m=this.length;++j=i0)i0=i+1;while(!(node=group[i0])&&++i00)type=type.slice(0,i);var filter=d3_selection_onFilters.get(type);if(filter)type=filter,wrap=d3_selection_onFilter;function onRemove(){var l=this[name];if(l){this.removeEventListener(type,l,l.$);delete this[name]}}function onAdd(){var l=wrap(listener,d3_array(arguments));onRemove.call(this);this.addEventListener(type,this[name]=l,l.$=capture);l._=listener}function removeAll(){var re=new RegExp("^__on([^.]+)"+d3.requote(type)+"$"),match;for(var name in this){if(match=name.match(re)){var l=this[name];this.removeEventListener(match[1],l,l.$);delete this[name]}}}return i?listener?onAdd:onRemove:listener?d3_noop:removeAll}var d3_selection_onFilters=d3.map({mouseenter:"mouseover",mouseleave:"mouseout"});if(d3_document){d3_selection_onFilters.forEach(function(k){if("on"+k in d3_document)d3_selection_onFilters.remove(k)})}function d3_selection_onListener(listener,argumentz){return function(e){var o=d3.event;d3.event=e;argumentz[0]=this.__data__;try{listener.apply(this,argumentz)}finally{d3.event=o}}}function d3_selection_onFilter(listener,argumentz){var l=d3_selection_onListener(listener,argumentz);return function(e){var target=this,related=e.relatedTarget;if(!related||related!==target&&!(related.compareDocumentPosition(target)&8)){l.call(target,e)}}}var d3_event_dragSelect,d3_event_dragId=0;function d3_event_dragSuppress(node){var name=".dragsuppress-"+ ++d3_event_dragId,click="click"+name,w=d3.select(d3_window(node)).on("touchmove"+name,d3_eventPreventDefault).on("dragstart"+name,d3_eventPreventDefault).on("selectstart"+name,d3_eventPreventDefault);if(d3_event_dragSelect==null){d3_event_dragSelect="onselectstart"in node?false:d3_vendorSymbol(node.style,"userSelect")}if(d3_event_dragSelect){var style=d3_documentElement(node).style,select=style[d3_event_dragSelect];style[d3_event_dragSelect]="none"}return function(suppressClick){w.on(name,null);if(d3_event_dragSelect)style[d3_event_dragSelect]=select;if(suppressClick){var off=function(){w.on(click,null)};w.on(click,function(){d3_eventPreventDefault();off()},true);setTimeout(off,0)}}}d3.mouse=function(container){return d3_mousePoint(container,d3_eventSource())};var d3_mouse_bug44083=this.navigator&&/WebKit/.test(this.navigator.userAgent)?-1:0;function d3_mousePoint(container,e){if(e.changedTouches)e=e.changedTouches[0];var svg=container.ownerSVGElement||container;if(svg.createSVGPoint){var point=svg.createSVGPoint();if(d3_mouse_bug44083<0){var window=d3_window(container);if(window.scrollX||window.scrollY){svg=d3.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var ctm=svg[0][0].getScreenCTM();d3_mouse_bug44083=!(ctm.f||ctm.e);svg.remove()}}if(d3_mouse_bug44083)point.x=e.pageX,point.y=e.pageY;else point.x=e.clientX,point.y=e.clientY;point=point.matrixTransform(container.getScreenCTM().inverse());return[point.x,point.y]}var rect=container.getBoundingClientRect();return[e.clientX-rect.left-container.clientLeft,e.clientY-rect.top-container.clientTop]}d3.touch=function(container,touches,identifier){if(arguments.length<3)identifier=touches,touches=d3_eventSource().changedTouches;if(touches)for(var i=0,n=touches.length,touch;i0?1:x<0?-1:0}function d3_cross2d(a,b,c){return(b[0]-a[0])*(c[1]-a[1])-(b[1]-a[1])*(c[0]-a[0])}function d3_acos(x){return x>1?0:x<-1?π:Math.acos(x)}function d3_asin(x){return x>1?halfπ:x<-1?-halfπ:Math.asin(x)}function d3_sinh(x){return((x=Math.exp(x))-1/x)/2}function d3_cosh(x){return((x=Math.exp(x))+1/x)/2}function d3_tanh(x){return((x=Math.exp(2*x))-1)/(x+1)}function d3_haversin(x){return(x=Math.sin(x/2))*x}var ρ=Math.SQRT2,ρ2=2,ρ4=4;d3.interpolateZoom=function(p0,p1){var ux0=p0[0],uy0=p0[1],w0=p0[2],ux1=p1[0],uy1=p1[1],w1=p1[2],dx=ux1-ux0,dy=uy1-uy0,d2=dx*dx+dy*dy,i,S;if(d2<ε2){S=Math.log(w1/w0)/ρ;i=function(t){return[ux0+t*dx,uy0+t*dy,w0*Math.exp(ρ*t*S)]}}else{var d1=Math.sqrt(d2),b0=(w1*w1-w0*w0+ρ4*d2)/(2*w0*ρ2*d1),b1=(w1*w1-w0*w0-ρ4*d2)/(2*w1*ρ2*d1),r0=Math.log(Math.sqrt(b0*b0+1)-b0),r1=Math.log(Math.sqrt(b1*b1+1)-b1);S=(r1-r0)/ρ;i=function(t){var s=t*S,coshr0=d3_cosh(r0),u=w0/(ρ2*d1)*(coshr0*d3_tanh(ρ*s+r0)-d3_sinh(r0));return[ux0+u*dx,uy0+u*dy,w0*coshr0/d3_cosh(ρ*s+r0)]}}i.duration=S*1e3;return i};d3.behavior.zoom=function(){var view={x:0,y:0,k:1},translate0,center0,center,size=[960,500],scaleExtent=d3_behavior_zoomInfinity,duration=250,zooming=0,mousedown="mousedown.zoom",mousemove="mousemove.zoom",mouseup="mouseup.zoom",mousewheelTimer,touchstart="touchstart.zoom",touchtime,event=d3_eventDispatch(zoom,"zoomstart","zoom","zoomend"),x0,x1,y0,y1;if(!d3_behavior_zoomWheel){d3_behavior_zoomWheel="onwheel"in d3_document?(d3_behavior_zoomDelta=function(){return-d3.event.deltaY*(d3.event.deltaMode?120:1)},"wheel"):"onmousewheel"in d3_document?(d3_behavior_zoomDelta=function(){return d3.event.wheelDelta},"mousewheel"):(d3_behavior_zoomDelta=function(){return-d3.event.detail},"MozMousePixelScroll")}function zoom(g){g.on(mousedown,mousedowned).on(d3_behavior_zoomWheel+".zoom",mousewheeled).on("dblclick.zoom",dblclicked).on(touchstart,touchstarted)}zoom.event=function(g){g.each(function(){var dispatch=event.of(this,arguments),view1=view;if(d3_transitionInheritId){d3.select(this).transition().each("start.zoom",function(){view=this.__chart__||{x:0,y:0,k:1};zoomstarted(dispatch)}).tween("zoom:zoom",function(){var dx=size[0],dy=size[1],cx=center0?center0[0]:dx/2,cy=center0?center0[1]:dy/2,i=d3.interpolateZoom([(cx-view.x)/view.k,(cy-view.y)/view.k,dx/view.k],[(cx-view1.x)/view1.k,(cy-view1.y)/view1.k,dx/view1.k]);return function(t){var l=i(t),k=dx/l[2];this.__chart__=view={x:cx-l[0]*k,y:cy-l[1]*k,k:k};zoomed(dispatch)}}).each("interrupt.zoom",function(){zoomended(dispatch)}).each("end.zoom",function(){zoomended(dispatch)})}else{this.__chart__=view;zoomstarted(dispatch);zoomed(dispatch);zoomended(dispatch)}})};zoom.translate=function(_){if(!arguments.length)return[view.x,view.y];view={x:+_[0],y:+_[1],k:view.k};rescale();return zoom};zoom.scale=function(_){if(!arguments.length)return view.k;view={x:view.x,y:view.y,k:null};scaleTo(+_);rescale();return zoom};zoom.scaleExtent=function(_){if(!arguments.length)return scaleExtent;scaleExtent=_==null?d3_behavior_zoomInfinity:[+_[0],+_[1]];return zoom};zoom.center=function(_){if(!arguments.length)return center;center=_&&[+_[0],+_[1]];return zoom};zoom.size=function(_){if(!arguments.length)return size;size=_&&[+_[0],+_[1]];return zoom};zoom.duration=function(_){if(!arguments.length)return duration;duration=+_;return zoom};zoom.x=function(z){if(!arguments.length)return x1;x1=z;x0=z.copy();view={x:0,y:0,k:1};return zoom};zoom.y=function(z){if(!arguments.length)return y1;y1=z;y0=z.copy();view={x:0,y:0,k:1};return zoom};function location(p){return[(p[0]-view.x)/view.k,(p[1]-view.y)/view.k]}function point(l){return[l[0]*view.k+view.x,l[1]*view.k+view.y]}function scaleTo(s){view.k=Math.max(scaleExtent[0],Math.min(scaleExtent[1],s))}function translateTo(p,l){l=point(l);view.x+=p[0]-l[0];view.y+=p[1]-l[1]}function zoomTo(that,p,l,k){that.__chart__={x:view.x,y:view.y,k:view.k};scaleTo(Math.pow(2,k));translateTo(center0=p,l);that=d3.select(that);if(duration>0)that=that.transition().duration(duration);that.call(zoom.event)}function rescale(){if(x1)x1.domain(x0.range().map(function(x){return(x-view.x)/view.k}).map(x0.invert));if(y1)y1.domain(y0.range().map(function(y){return(y-view.y)/view.k}).map(y0.invert))}function zoomstarted(dispatch){if(!zooming++)dispatch({type:"zoomstart"})}function zoomed(dispatch){rescale();dispatch({type:"zoom",scale:view.k,translate:[view.x,view.y]})}function zoomended(dispatch){if(!--zooming)dispatch({type:"zoomend"}),center0=null}function mousedowned(){var that=this,dispatch=event.of(that,arguments),dragged=0,subject=d3.select(d3_window(that)).on(mousemove,moved).on(mouseup,ended),location0=location(d3.mouse(that)),dragRestore=d3_event_dragSuppress(that);d3_selection_interrupt.call(that);zoomstarted(dispatch);function moved(){dragged=1;translateTo(d3.mouse(that),location0);zoomed(dispatch)}function ended(){subject.on(mousemove,null).on(mouseup,null);dragRestore(dragged);zoomended(dispatch)}}function touchstarted(){var that=this,dispatch=event.of(that,arguments),locations0={},distance0=0,scale0,zoomName=".zoom-"+d3.event.changedTouches[0].identifier,touchmove="touchmove"+zoomName,touchend="touchend"+zoomName,targets=[],subject=d3.select(that),dragRestore=d3_event_dragSuppress(that);started();zoomstarted(dispatch);subject.on(mousedown,null).on(touchstart,started);function relocate(){var touches=d3.touches(that);scale0=view.k;touches.forEach(function(t){if(t.identifier in locations0)locations0[t.identifier]=location(t)});return touches}function started(){var target=d3.event.target;d3.select(target).on(touchmove,moved).on(touchend,ended);targets.push(target);var changed=d3.event.changedTouches;for(var i=0,n=changed.length;i1){var p=touches[0],q=touches[1],dx=p[0]-q[0],dy=p[1]-q[1];distance0=dx*dx+dy*dy}}function moved(){var touches=d3.touches(that),p0,l0,p1,l1;d3_selection_interrupt.call(that);for(var i=0,n=touches.length;i1?1:s;l=l<0?0:l>1?1:l;m2=l<=.5?l*(1+s):l+s-l*s;m1=2*l-m2;function v(h){if(h>360)h-=360;else if(h<0)h+=360;if(h<60)return m1+(m2-m1)*h/60;if(h<180)return m2;if(h<240)return m1+(m2-m1)*(240-h)/60;return m1}function vv(h){return Math.round(v(h)*255)}return new d3_rgb(vv(h+120),vv(h),vv(h-120))}d3.hcl=d3_hcl;function d3_hcl(h,c,l){return this instanceof d3_hcl?void(this.h=+h,this.c=+c,this.l=+l):arguments.length<2?h instanceof d3_hcl?new d3_hcl(h.h,h.c,h.l):h instanceof d3_lab?d3_lab_hcl(h.l,h.a,h.b):d3_lab_hcl((h=d3_rgb_lab((h=d3.rgb(h)).r,h.g,h.b)).l,h.a,h.b):new d3_hcl(h,c,l)}var d3_hclPrototype=d3_hcl.prototype=new d3_color;d3_hclPrototype.brighter=function(k){return new d3_hcl(this.h,this.c,Math.min(100,this.l+d3_lab_K*(arguments.length?k:1)))};d3_hclPrototype.darker=function(k){return new d3_hcl(this.h,this.c,Math.max(0,this.l-d3_lab_K*(arguments.length?k:1)))};d3_hclPrototype.rgb=function(){return d3_hcl_lab(this.h,this.c,this.l).rgb()};function d3_hcl_lab(h,c,l){if(isNaN(h))h=0;if(isNaN(c))c=0;return new d3_lab(l,Math.cos(h*=d3_radians)*c,Math.sin(h)*c)}d3.lab=d3_lab;function d3_lab(l,a,b){return this instanceof d3_lab?void(this.l=+l,this.a=+a,this.b=+b):arguments.length<2?l instanceof d3_lab?new d3_lab(l.l,l.a,l.b):l instanceof d3_hcl?d3_hcl_lab(l.h,l.c,l.l):d3_rgb_lab((l=d3_rgb(l)).r,l.g,l.b):new d3_lab(l,a,b)}var d3_lab_K=18;var d3_lab_X=.95047,d3_lab_Y=1,d3_lab_Z=1.08883;var d3_labPrototype=d3_lab.prototype=new d3_color;d3_labPrototype.brighter=function(k){return new d3_lab(Math.min(100,this.l+d3_lab_K*(arguments.length?k:1)),this.a,this.b)};d3_labPrototype.darker=function(k){return new d3_lab(Math.max(0,this.l-d3_lab_K*(arguments.length?k:1)),this.a,this.b)};d3_labPrototype.rgb=function(){return d3_lab_rgb(this.l,this.a,this.b)};function d3_lab_rgb(l,a,b){var y=(l+16)/116,x=y+a/500,z=y-b/200;x=d3_lab_xyz(x)*d3_lab_X;y=d3_lab_xyz(y)*d3_lab_Y;z=d3_lab_xyz(z)*d3_lab_Z;return new d3_rgb(d3_xyz_rgb(3.2404542*x-1.5371385*y-.4985314*z),d3_xyz_rgb(-.969266*x+1.8760108*y+.041556*z),d3_xyz_rgb(.0556434*x-.2040259*y+1.0572252*z))}function d3_lab_hcl(l,a,b){return l>0?new d3_hcl(Math.atan2(b,a)*d3_degrees,Math.sqrt(a*a+b*b),l):new d3_hcl(NaN,NaN,l)}function d3_lab_xyz(x){return x>.206893034?x*x*x:(x-4/29)/7.787037}function d3_xyz_lab(x){return x>.008856?Math.pow(x,1/3):7.787037*x+4/29}function d3_xyz_rgb(r){return Math.round(255*(r<=.00304?12.92*r:1.055*Math.pow(r,1/2.4)-.055))}d3.rgb=d3_rgb;function d3_rgb(r,g,b){return this instanceof d3_rgb?void(this.r=~~r,this.g=~~g,this.b=~~b):arguments.length<2?r instanceof d3_rgb?new d3_rgb(r.r,r.g,r.b):d3_rgb_parse(""+r,d3_rgb,d3_hsl_rgb):new d3_rgb(r,g,b)}function d3_rgbNumber(value){return new d3_rgb(value>>16,value>>8&255,value&255)}function d3_rgbString(value){return d3_rgbNumber(value)+""}var d3_rgbPrototype=d3_rgb.prototype=new d3_color;d3_rgbPrototype.brighter=function(k){k=Math.pow(.7,arguments.length?k:1);var r=this.r,g=this.g,b=this.b,i=30;if(!r&&!g&&!b)return new d3_rgb(i,i,i);if(r&&r>4;r=r>>4|r;g=color&240;g=g>>4|g;b=color&15;b=b<<4|b}else if(format.length===7){r=(color&16711680)>>16;g=(color&65280)>>8;b=color&255}}return rgb(r,g,b)}function d3_rgb_hsl(r,g,b){var min=Math.min(r/=255,g/=255,b/=255),max=Math.max(r,g,b),d=max-min,h,s,l=(max+min)/2;if(d){s=l<.5?d/(max+min):d/(2-max-min);if(r==max)h=(g-b)/d+(g0&&l<1?0:h}return new d3_hsl(h,s,l)}function d3_rgb_lab(r,g,b){r=d3_rgb_xyz(r);g=d3_rgb_xyz(g);b=d3_rgb_xyz(b);var x=d3_xyz_lab((.4124564*r+.3575761*g+.1804375*b)/d3_lab_X),y=d3_xyz_lab((.2126729*r+.7151522*g+.072175*b)/d3_lab_Y),z=d3_xyz_lab((.0193339*r+.119192*g+.9503041*b)/d3_lab_Z);return d3_lab(116*y-16,500*(x-y),200*(y-z))}function d3_rgb_xyz(r){return(r/=255)<=.04045?r/12.92:Math.pow((r+.055)/1.055,2.4)}function d3_rgb_parseNumber(c){var f=parseFloat(c);return c.charAt(c.length-1)==="%"?Math.round(f*2.55):f}var d3_rgb_names=d3.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});d3_rgb_names.forEach(function(key,value){d3_rgb_names.set(key,d3_rgbNumber(value))});function d3_functor(v){return typeof v==="function"?v:function(){return v}}d3.functor=d3_functor;d3.xhr=d3_xhrType(d3_identity);function d3_xhrType(response){return function(url,mimeType,callback){if(arguments.length===2&&typeof mimeType==="function")callback=mimeType,mimeType=null;return d3_xhr(url,mimeType,response,callback)}}function d3_xhr(url,mimeType,response,callback){var xhr={},dispatch=d3.dispatch("beforesend","progress","load","error"),headers={},request=new XMLHttpRequest,responseType=null;if(this.XDomainRequest&&!("withCredentials"in request)&&/^(http(s)?:)?\/\//.test(url))request=new XDomainRequest;"onload"in request?request.onload=request.onerror=respond:request.onreadystatechange=function(){request.readyState>3&&respond()};function respond(){var status=request.status,result;if(!status&&d3_xhrHasResponse(request)||status>=200&&status<300||status===304){try{result=response.call(xhr,request)}catch(e){dispatch.error.call(xhr,e);return}dispatch.load.call(xhr,result)}else{dispatch.error.call(xhr,request)}}request.onprogress=function(event){var o=d3.event;d3.event=event;try{dispatch.progress.call(xhr,request)}finally{d3.event=o}};xhr.header=function(name,value){name=(name+"").toLowerCase();if(arguments.length<2)return headers[name];if(value==null)delete headers[name];else headers[name]=value+"";return xhr};xhr.mimeType=function(value){if(!arguments.length)return mimeType;mimeType=value==null?null:value+"";return xhr};xhr.responseType=function(value){if(!arguments.length)return responseType;responseType=value;return xhr};xhr.response=function(value){response=value;return xhr};["get","post"].forEach(function(method){xhr[method]=function(){return xhr.send.apply(xhr,[method].concat(d3_array(arguments)))}});xhr.send=function(method,data,callback){if(arguments.length===2&&typeof data==="function")callback=data,data=null;request.open(method,url,true);if(mimeType!=null&&!("accept"in headers))headers["accept"]=mimeType+",*/*";if(request.setRequestHeader)for(var name in headers)request.setRequestHeader(name,headers[name]);if(mimeType!=null&&request.overrideMimeType)request.overrideMimeType(mimeType);if(responseType!=null)request.responseType=responseType;if(callback!=null)xhr.on("error",callback).on("load",function(request){callback(null,request)});dispatch.beforesend.call(xhr,request);request.send(data==null?null:data);return xhr};xhr.abort=function(){request.abort();return xhr};d3.rebind(xhr,dispatch,"on");return callback==null?xhr:xhr.get(d3_xhr_fixCallback(callback))}function d3_xhr_fixCallback(callback){return callback.length===1?function(error,request){callback(error==null?request:null)}:callback}function d3_xhrHasResponse(request){var type=request.responseType;return type&&type!=="text"?request.response:request.responseText}d3.dsv=function(delimiter,mimeType){var reFormat=new RegExp('["'+delimiter+"\n]"),delimiterCode=delimiter.charCodeAt(0);function dsv(url,row,callback){if(arguments.length<3)callback=row,row=null;var xhr=d3_xhr(url,mimeType,row==null?response:typedResponse(row),callback);xhr.row=function(_){return arguments.length?xhr.response((row=_)==null?response:typedResponse(_)):row};return xhr}function response(request){return dsv.parse(request.responseText)}function typedResponse(f){return function(request){return dsv.parse(request.responseText,f)}}dsv.parse=function(text,f){var o;return dsv.parseRows(text,function(row,i){if(o)return o(row,i-1);var a=new Function("d","return {"+row.map(function(name,i){return JSON.stringify(name)+": d["+i+"]"}).join(",")+"}");o=f?function(row,i){return f(a(row),i)}:a})};dsv.parseRows=function(text,f){var EOL={},EOF={},rows=[],N=text.length,I=0,n=0,t,eol;function token(){if(I>=N)return EOF;if(eol)return eol=false,EOL;var j=I;if(text.charCodeAt(j)===34){var i=j;while(i++24){if(isFinite(delay)){clearTimeout(d3_timer_timeout);d3_timer_timeout=setTimeout(d3_timer_step,delay)}d3_timer_interval=0}else{d3_timer_interval=1;d3_timer_frame(d3_timer_step)}}d3.timer.flush=function(){d3_timer_mark();d3_timer_sweep()};function d3_timer_mark(){var now=Date.now(),timer=d3_timer_queueHead;while(timer){if(now>=timer.t&&timer.c(now-timer.t))timer.c=null;timer=timer.n}return now}function d3_timer_sweep(){var t0,t1=d3_timer_queueHead,time=Infinity;while(t1){if(t1.c){if(t1.t8?function(d){return d/k}:function(d){return d*k},symbol:d}}function d3_locale_numberFormat(locale){var locale_decimal=locale.decimal,locale_thousands=locale.thousands,locale_grouping=locale.grouping,locale_currency=locale.currency,formatGroup=locale_grouping&&locale_thousands?function(value,width){var i=value.length,t=[],j=0,g=locale_grouping[0],length=0;while(i>0&&g>0){if(length+g+1>width)g=Math.max(1,width-length);t.push(value.substring(i-=g,i+g));if((length+=g+1)>width)break;g=locale_grouping[j=(j+1)%locale_grouping.length]}return t.reverse().join(locale_thousands)}:d3_identity;return function(specifier){var match=d3_format_re.exec(specifier),fill=match[1]||" ",align=match[2]||">",sign=match[3]||"-",symbol=match[4]||"",zfill=match[5],width=+match[6],comma=match[7],precision=match[8],type=match[9],scale=1,prefix="",suffix="",integer=false,exponent=true;if(precision)precision=+precision.substring(1);if(zfill||fill==="0"&&align==="="){zfill=fill="0";align="="}switch(type){case"n":comma=true;type="g";break;case"%":scale=100;suffix="%";type="f";break;case"p":scale=100;suffix="%";type="r";break;case"b":case"o":case"x":case"X":if(symbol==="#")prefix="0"+type.toLowerCase();case"c":exponent=false;case"d":integer=true;precision=0;break;case"s":scale=-1;type="r";break}if(symbol==="$")prefix=locale_currency[0],suffix=locale_currency[1];if(type=="r"&&!precision)type="g";if(precision!=null){if(type=="g")precision=Math.max(1,Math.min(21,precision));else if(type=="e"||type=="f")precision=Math.max(0,Math.min(20,precision))}type=d3_format_types.get(type)||d3_format_typeDefault;var zcomma=zfill&,return function(value){var fullSuffix=suffix;if(integer&&value%1)return"";var negative=value<0||value===0&&1/value<0?(value=-value,"-"):sign==="-"?"":sign;if(scale<0){var unit=d3.formatPrefix(value,precision);value=unit.scale(value);fullSuffix=unit.symbol+suffix}else{value*=scale}value=type(value,precision);var i=value.lastIndexOf("."),before,after;if(i<0){var j=exponent?value.lastIndexOf("e"):-1;if(j<0)before=value,after="";else before=value.substring(0,j),after=value.substring(j)}else{before=value.substring(0,i);after=locale_decimal+value.substring(i+1)}if(!zfill&&comma)before=formatGroup(before,Infinity);var length=prefix.length+before.length+after.length+(zcomma?0:negative.length),padding=length"?padding+negative+value:align==="^"?padding.substring(0,length>>=1)+negative+value+padding.substring(length):negative+(zcomma?value:padding+value))+fullSuffix}}}var d3_format_re=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i;var d3_format_types=d3.map({b:function(x){return x.toString(2)},c:function(x){return String.fromCharCode(x)},o:function(x){return x.toString(8)},x:function(x){return x.toString(16)},X:function(x){return x.toString(16).toUpperCase()},g:function(x,p){return x.toPrecision(p)},e:function(x,p){return x.toExponential(p)},f:function(x,p){return x.toFixed(p)},r:function(x,p){return(x=d3.round(x,d3_format_precision(x,p))).toFixed(Math.max(0,Math.min(20,d3_format_precision(x*(1+1e-15),p))))}});function d3_format_typeDefault(x){return x+""}var d3_time=d3.time={},d3_date=Date;function d3_date_utc(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}d3_date_utc.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){d3_time_prototype.setUTCDate.apply(this._,arguments)},setDay:function(){d3_time_prototype.setUTCDay.apply(this._,arguments)},setFullYear:function(){d3_time_prototype.setUTCFullYear.apply(this._,arguments)},setHours:function(){d3_time_prototype.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){d3_time_prototype.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){d3_time_prototype.setUTCMinutes.apply(this._,arguments)},setMonth:function(){d3_time_prototype.setUTCMonth.apply(this._,arguments)},setSeconds:function(){d3_time_prototype.setUTCSeconds.apply(this._,arguments)},setTime:function(){d3_time_prototype.setTime.apply(this._,arguments)}};var d3_time_prototype=Date.prototype;function d3_time_interval(local,step,number){function round(date){var d0=local(date),d1=offset(d0,1);return date-d01){while(time=m)return-1;c=template.charCodeAt(i++);if(c===37){t=template.charAt(i++);p=d3_time_parsers[t in d3_time_formatPads?template.charAt(i++):t];if(!p||(j=p(date,string,j))<0)return-1}else if(c!=string.charCodeAt(j++)){return-1}}return j}d3_time_format.utc=function(template){var local=d3_time_format(template);function format(date){try{d3_date=d3_date_utc;var utc=new d3_date;utc._=date;return local(utc)}finally{d3_date=Date}}format.parse=function(string){try{d3_date=d3_date_utc;var date=local.parse(string);return date&&date._}finally{d3_date=Date}};format.toString=local.toString;return format};d3_time_format.multi=d3_time_format.utc.multi=d3_time_formatMulti;var d3_time_periodLookup=d3.map(),d3_time_dayRe=d3_time_formatRe(locale_days),d3_time_dayLookup=d3_time_formatLookup(locale_days),d3_time_dayAbbrevRe=d3_time_formatRe(locale_shortDays),d3_time_dayAbbrevLookup=d3_time_formatLookup(locale_shortDays),d3_time_monthRe=d3_time_formatRe(locale_months),d3_time_monthLookup=d3_time_formatLookup(locale_months),d3_time_monthAbbrevRe=d3_time_formatRe(locale_shortMonths),d3_time_monthAbbrevLookup=d3_time_formatLookup(locale_shortMonths);locale_periods.forEach(function(p,i){d3_time_periodLookup.set(p.toLowerCase(),i)});var d3_time_formats={a:function(d){return locale_shortDays[d.getDay()]},A:function(d){return locale_days[d.getDay()]},b:function(d){return locale_shortMonths[d.getMonth()]},B:function(d){return locale_months[d.getMonth()]},c:d3_time_format(locale_dateTime),d:function(d,p){return d3_time_formatPad(d.getDate(),p,2)},e:function(d,p){return d3_time_formatPad(d.getDate(),p,2)},H:function(d,p){return d3_time_formatPad(d.getHours(),p,2)},I:function(d,p){return d3_time_formatPad(d.getHours()%12||12,p,2)},j:function(d,p){return d3_time_formatPad(1+d3_time.dayOfYear(d),p,3)},L:function(d,p){return d3_time_formatPad(d.getMilliseconds(),p,3)},m:function(d,p){return d3_time_formatPad(d.getMonth()+1,p,2)},M:function(d,p){return d3_time_formatPad(d.getMinutes(),p,2)},p:function(d){return locale_periods[+(d.getHours()>=12)]},S:function(d,p){return d3_time_formatPad(d.getSeconds(),p,2)},U:function(d,p){return d3_time_formatPad(d3_time.sundayOfYear(d),p,2)},w:function(d){return d.getDay()},W:function(d,p){return d3_time_formatPad(d3_time.mondayOfYear(d),p,2)},x:d3_time_format(locale_date),X:d3_time_format(locale_time),y:function(d,p){return d3_time_formatPad(d.getFullYear()%100,p,2)},Y:function(d,p){return d3_time_formatPad(d.getFullYear()%1e4,p,4)},Z:d3_time_zone,"%":function(){return"%"}};var d3_time_parsers={a:d3_time_parseWeekdayAbbrev,A:d3_time_parseWeekday,b:d3_time_parseMonthAbbrev,B:d3_time_parseMonth,c:d3_time_parseLocaleFull,d:d3_time_parseDay,e:d3_time_parseDay,H:d3_time_parseHour24,I:d3_time_parseHour24,j:d3_time_parseDayOfYear,L:d3_time_parseMilliseconds,m:d3_time_parseMonthNumber,M:d3_time_parseMinutes,p:d3_time_parseAmPm,S:d3_time_parseSeconds,U:d3_time_parseWeekNumberSunday,w:d3_time_parseWeekdayNumber,W:d3_time_parseWeekNumberMonday,x:d3_time_parseLocaleDate,X:d3_time_parseLocaleTime,y:d3_time_parseYear,Y:d3_time_parseFullYear,Z:d3_time_parseZone,"%":d3_time_parseLiteralPercent};function d3_time_parseWeekdayAbbrev(date,string,i){d3_time_dayAbbrevRe.lastIndex=0;var n=d3_time_dayAbbrevRe.exec(string.slice(i));return n?(date.w=d3_time_dayAbbrevLookup.get(n[0].toLowerCase()),i+n[0].length):-1}function d3_time_parseWeekday(date,string,i){d3_time_dayRe.lastIndex=0;var n=d3_time_dayRe.exec(string.slice(i));return n?(date.w=d3_time_dayLookup.get(n[0].toLowerCase()),i+n[0].length):-1}function d3_time_parseMonthAbbrev(date,string,i){d3_time_monthAbbrevRe.lastIndex=0;var n=d3_time_monthAbbrevRe.exec(string.slice(i));return n?(date.m=d3_time_monthAbbrevLookup.get(n[0].toLowerCase()),i+n[0].length):-1}function d3_time_parseMonth(date,string,i){d3_time_monthRe.lastIndex=0;var n=d3_time_monthRe.exec(string.slice(i));return n?(date.m=d3_time_monthLookup.get(n[0].toLowerCase()),i+n[0].length):-1}function d3_time_parseLocaleFull(date,string,i){return d3_time_parse(date,d3_time_formats.c.toString(),string,i)}function d3_time_parseLocaleDate(date,string,i){return d3_time_parse(date,d3_time_formats.x.toString(),string,i)}function d3_time_parseLocaleTime(date,string,i){return d3_time_parse(date,d3_time_formats.X.toString(),string,i)}function d3_time_parseAmPm(date,string,i){var n=d3_time_periodLookup.get(string.slice(i,i+=2).toLowerCase());return n==null?-1:(date.p=n,i)}return d3_time_format}var d3_time_formatPads={"-":"",_:" ",0:"0"},d3_time_numberRe=/^\s*\d+/,d3_time_percentRe=/^%/;function d3_time_formatPad(value,fill,width){var sign=value<0?"-":"",string=(sign?-value:value)+"",length=string.length;return sign+(length68?1900:2e3)}function d3_time_parseMonthNumber(date,string,i){d3_time_numberRe.lastIndex=0;var n=d3_time_numberRe.exec(string.slice(i,i+2));return n?(date.m=n[0]-1,i+n[0].length):-1}function d3_time_parseDay(date,string,i){d3_time_numberRe.lastIndex=0;var n=d3_time_numberRe.exec(string.slice(i,i+2));return n?(date.d=+n[0],i+n[0].length):-1}function d3_time_parseDayOfYear(date,string,i){d3_time_numberRe.lastIndex=0;var n=d3_time_numberRe.exec(string.slice(i,i+3));return n?(date.j=+n[0],i+n[0].length):-1}function d3_time_parseHour24(date,string,i){d3_time_numberRe.lastIndex=0;var n=d3_time_numberRe.exec(string.slice(i,i+2));return n?(date.H=+n[0],i+n[0].length):-1}function d3_time_parseMinutes(date,string,i){d3_time_numberRe.lastIndex=0;var n=d3_time_numberRe.exec(string.slice(i,i+2));return n?(date.M=+n[0],i+n[0].length):-1}function d3_time_parseSeconds(date,string,i){d3_time_numberRe.lastIndex=0;var n=d3_time_numberRe.exec(string.slice(i,i+2));return n?(date.S=+n[0],i+n[0].length):-1}function d3_time_parseMilliseconds(date,string,i){d3_time_numberRe.lastIndex=0;var n=d3_time_numberRe.exec(string.slice(i,i+3));return n?(date.L=+n[0],i+n[0].length):-1}function d3_time_zone(d){var z=d.getTimezoneOffset(),zs=z>0?"-":"+",zh=abs(z)/60|0,zm=abs(z)%60;return zs+d3_time_formatPad(zh,"0",2)+d3_time_formatPad(zm,"0",2)}function d3_time_parseLiteralPercent(date,string,i){d3_time_percentRe.lastIndex=0;var n=d3_time_percentRe.exec(string.slice(i,i+1));return n?i+n[0].length:-1}function d3_time_formatMulti(formats){var n=formats.length,i=-1;while(++i=0?1:-1,adλ=sdλ*dλ,cosφ=Math.cos(φ),sinφ=Math.sin(φ),k=sinφ0*sinφ,u=cosφ0*cosφ+k*Math.cos(adλ),v=k*sdλ*Math.sin(adλ);d3_geo_areaRingSum.add(Math.atan2(v,u));λ0=λ,cosφ0=cosφ,sinφ0=sinφ}d3_geo_area.lineEnd=function(){nextPoint(λ00,φ00)}}function d3_geo_cartesian(spherical){var λ=spherical[0],φ=spherical[1],cosφ=Math.cos(φ);return[cosφ*Math.cos(λ),cosφ*Math.sin(λ),Math.sin(φ)]}function d3_geo_cartesianDot(a,b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]}function d3_geo_cartesianCross(a,b){return[a[1]*b[2]-a[2]*b[1],a[2]*b[0]-a[0]*b[2],a[0]*b[1]-a[1]*b[0]]}function d3_geo_cartesianAdd(a,b){a[0]+=b[0];a[1]+=b[1];a[2]+=b[2]}function d3_geo_cartesianScale(vector,k){return[vector[0]*k,vector[1]*k,vector[2]*k]}function d3_geo_cartesianNormalize(d){var l=Math.sqrt(d[0]*d[0]+d[1]*d[1]+d[2]*d[2]);d[0]/=l;d[1]/=l;d[2]/=l}function d3_geo_spherical(cartesian){return[Math.atan2(cartesian[1],cartesian[0]),d3_asin(cartesian[2])]}function d3_geo_sphericalEqual(a,b){return abs(a[0]-b[0])<ε&&abs(a[1]-b[1])<ε}d3.geo.bounds=function(){var λ0,φ0,λ1,φ1,λ_,λ__,φ__,p0,dλSum,ranges,range;var bound={point:point,lineStart:lineStart,lineEnd:lineEnd,polygonStart:function(){bound.point=ringPoint;bound.lineStart=ringStart;bound.lineEnd=ringEnd;dλSum=0;d3_geo_area.polygonStart()},polygonEnd:function(){d3_geo_area.polygonEnd();bound.point=point;bound.lineStart=lineStart;bound.lineEnd=lineEnd;if(d3_geo_areaRingSum<0)λ0=-(λ1=180),φ0=-(φ1=90);else if(dλSum>ε)φ1=90;else if(dλSum<-ε)φ0=-90;range[0]=λ0,range[1]=λ1}};function point(λ,φ){ranges.push(range=[λ0=λ,λ1=λ]);if(φ<φ0)φ0=φ;if(φ>φ1)φ1=φ}function linePoint(λ,φ){var p=d3_geo_cartesian([λ*d3_radians,φ*d3_radians]);if(p0){var normal=d3_geo_cartesianCross(p0,p),equatorial=[normal[1],-normal[0],0],inflection=d3_geo_cartesianCross(equatorial,normal);d3_geo_cartesianNormalize(inflection);inflection=d3_geo_spherical(inflection);var dλ=λ-λ_,s=dλ>0?1:-1,λi=inflection[0]*d3_degrees*s,antimeridian=abs(dλ)>180;if(antimeridian^(s*λ_<λi&&λiφ1)φ1=φi}else if(λi=(λi+360)%360-180,antimeridian^(s*λ_<λi&&λiφ1)φ1=φ}if(antimeridian){if(λ<λ_){if(angle(λ0,λ)>angle(λ0,λ1))λ1=λ}else{if(angle(λ,λ1)>angle(λ0,λ1))λ0=λ}}else{if(λ1>=λ0){if(λ<λ0)λ0=λ;if(λ>λ1)λ1=λ}else{if(λ>λ_){if(angle(λ0,λ)>angle(λ0,λ1))λ1=λ}else{if(angle(λ,λ1)>angle(λ0,λ1))λ0=λ}}}}else{point(λ,φ)}p0=p,λ_=λ}function lineStart(){bound.point=linePoint}function lineEnd(){range[0]=λ0,range[1]=λ1;bound.point=point;p0=null}function ringPoint(λ,φ){if(p0){var dλ=λ-λ_;dλSum+=abs(dλ)>180?dλ+(dλ>0?360:-360):dλ}else λ__=λ,φ__=φ;d3_geo_area.point(λ,φ);linePoint(λ,φ)}function ringStart(){d3_geo_area.lineStart()}function ringEnd(){ringPoint(λ__,φ__);d3_geo_area.lineEnd();if(abs(dλSum)>ε)λ0=-(λ1=180);range[0]=λ0,range[1]=λ1;p0=null}function angle(λ0,λ1){return(λ1-=λ0)<0?λ1+360:λ1}function compareRanges(a,b){return a[0]-b[0]}function withinRange(x,range){return range[0]<=range[1]?range[0]<=x&&x<=range[1]:xangle(a[0],a[1]))a[1]=b[1];if(angle(b[0],a[1])>angle(a[0],a[1]))a[0]=b[0]}else{merged.push(a=b)}}var best=-Infinity,dλ;for(var n=merged.length-1,i=0,a=merged[n],b;i<=n;a=b,++i){b=merged[i];if((dλ=angle(a[1],b[0]))>best)best=dλ,λ0=b[0],λ1=a[1]}}ranges=range=null;return λ0===Infinity||φ0===Infinity?[[NaN,NaN],[NaN,NaN]]:[[λ0,φ0],[λ1,φ1]]}}();d3.geo.centroid=function(object){d3_geo_centroidW0=d3_geo_centroidW1=d3_geo_centroidX0=d3_geo_centroidY0=d3_geo_centroidZ0=d3_geo_centroidX1=d3_geo_centroidY1=d3_geo_centroidZ1=d3_geo_centroidX2=d3_geo_centroidY2=d3_geo_centroidZ2=0;d3.geo.stream(object,d3_geo_centroid);var x=d3_geo_centroidX2,y=d3_geo_centroidY2,z=d3_geo_centroidZ2,m=x*x+y*y+z*z;if(m<ε2){x=d3_geo_centroidX1,y=d3_geo_centroidY1,z=d3_geo_centroidZ1;if(d3_geo_centroidW1<ε)x=d3_geo_centroidX0,y=d3_geo_centroidY0,z=d3_geo_centroidZ0;m=x*x+y*y+z*z;if(m<ε2)return[NaN,NaN]}return[Math.atan2(y,x)*d3_degrees,d3_asin(z/Math.sqrt(m))*d3_degrees]};var d3_geo_centroidW0,d3_geo_centroidW1,d3_geo_centroidX0,d3_geo_centroidY0,d3_geo_centroidZ0,d3_geo_centroidX1,d3_geo_centroidY1,d3_geo_centroidZ1,d3_geo_centroidX2,d3_geo_centroidY2,d3_geo_centroidZ2;var d3_geo_centroid={sphere:d3_noop,point:d3_geo_centroidPoint,lineStart:d3_geo_centroidLineStart,lineEnd:d3_geo_centroidLineEnd,polygonStart:function(){d3_geo_centroid.lineStart=d3_geo_centroidRingStart},polygonEnd:function(){d3_geo_centroid.lineStart=d3_geo_centroidLineStart}};function d3_geo_centroidPoint(λ,φ){λ*=d3_radians;var cosφ=Math.cos(φ*=d3_radians);d3_geo_centroidPointXYZ(cosφ*Math.cos(λ),cosφ*Math.sin(λ),Math.sin(φ))}function d3_geo_centroidPointXYZ(x,y,z){++d3_geo_centroidW0;d3_geo_centroidX0+=(x-d3_geo_centroidX0)/d3_geo_centroidW0;d3_geo_centroidY0+=(y-d3_geo_centroidY0)/d3_geo_centroidW0;d3_geo_centroidZ0+=(z-d3_geo_centroidZ0)/d3_geo_centroidW0}function d3_geo_centroidLineStart(){var x0,y0,z0;d3_geo_centroid.point=function(λ,φ){λ*=d3_radians;var cosφ=Math.cos(φ*=d3_radians);x0=cosφ*Math.cos(λ);y0=cosφ*Math.sin(λ);z0=Math.sin(φ);d3_geo_centroid.point=nextPoint;d3_geo_centroidPointXYZ(x0,y0,z0)};function nextPoint(λ,φ){λ*=d3_radians;var cosφ=Math.cos(φ*=d3_radians),x=cosφ*Math.cos(λ),y=cosφ*Math.sin(λ),z=Math.sin(φ),w=Math.atan2(Math.sqrt((w=y0*z-z0*y)*w+(w=z0*x-x0*z)*w+(w=x0*y-y0*x)*w),x0*x+y0*y+z0*z);d3_geo_centroidW1+=w;d3_geo_centroidX1+=w*(x0+(x0=x));d3_geo_centroidY1+=w*(y0+(y0=y));d3_geo_centroidZ1+=w*(z0+(z0=z));d3_geo_centroidPointXYZ(x0,y0,z0)}}function d3_geo_centroidLineEnd(){d3_geo_centroid.point=d3_geo_centroidPoint}function d3_geo_centroidRingStart(){var λ00,φ00,x0,y0,z0;d3_geo_centroid.point=function(λ,φ){λ00=λ,φ00=φ;d3_geo_centroid.point=nextPoint;λ*=d3_radians;var cosφ=Math.cos(φ*=d3_radians);x0=cosφ*Math.cos(λ);y0=cosφ*Math.sin(λ);z0=Math.sin(φ);d3_geo_centroidPointXYZ(x0,y0,z0)};d3_geo_centroid.lineEnd=function(){nextPoint(λ00,φ00);d3_geo_centroid.lineEnd=d3_geo_centroidLineEnd;d3_geo_centroid.point=d3_geo_centroidPoint};function nextPoint(λ,φ){λ*=d3_radians;var cosφ=Math.cos(φ*=d3_radians),x=cosφ*Math.cos(λ),y=cosφ*Math.sin(λ),z=Math.sin(φ),cx=y0*z-z0*y,cy=z0*x-x0*z,cz=x0*y-y0*x,m=Math.sqrt(cx*cx+cy*cy+cz*cz),u=x0*x+y0*y+z0*z,v=m&&-d3_acos(u)/m,w=Math.atan2(m,u);d3_geo_centroidX2+=v*cx;d3_geo_centroidY2+=v*cy;d3_geo_centroidZ2+=v*cz;d3_geo_centroidW1+=w;d3_geo_centroidX1+=w*(x0+(x0=x));d3_geo_centroidY1+=w*(y0+(y0=y));d3_geo_centroidZ1+=w*(z0+(z0=z));d3_geo_centroidPointXYZ(x0,y0,z0)}}function d3_geo_compose(a,b){function compose(x,y){return x=a(x,y),b(x[0],x[1])}if(a.invert&&b.invert)compose.invert=function(x,y){return x=b.invert(x,y),x&&a.invert(x[0],x[1])};return compose}function d3_true(){return true}function d3_geo_clipPolygon(segments,compare,clipStartInside,interpolate,listener){var subject=[],clip=[];segments.forEach(function(segment){if((n=segment.length-1)<=0)return;var n,p0=segment[0],p1=segment[n];if(d3_geo_sphericalEqual(p0,p1)){listener.lineStart();for(var i=0;i=0;--i)listener.point((point=points[i])[0],point[1])}else{interpolate(current.x,current.p.x,-1,listener)}current=current.p}current=current.o;points=current.z;isSubject=!isSubject}while(!current.v);listener.lineEnd()}}function d3_geo_clipPolygonLinkCircular(array){if(!(n=array.length))return;var n,i=0,a=array[0],b;while(++i0){if(!polygonStarted)listener.polygonStart(),polygonStarted=true;listener.lineStart();while(++i1&&clean&2)ringSegments.push(ringSegments.pop().concat(ringSegments.shift()));segments.push(ringSegments.filter(d3_geo_clipSegmentLength1))}return clip}}function d3_geo_clipSegmentLength1(segment){return segment.length>1}function d3_geo_clipBufferListener(){var lines=[],line;return{lineStart:function(){lines.push(line=[])},point:function(λ,φ){line.push([λ,φ])},lineEnd:d3_noop,buffer:function(){var buffer=lines;lines=[];line=null;return buffer},rejoin:function(){if(lines.length>1)lines.push(lines.pop().concat(lines.shift()))}}}function d3_geo_clipSort(a,b){return((a=a.x)[0]<0?a[1]-halfπ-ε:halfπ-a[1])-((b=b.x)[0]<0?b[1]-halfπ-ε:halfπ-b[1])}var d3_geo_clipAntimeridian=d3_geo_clip(d3_true,d3_geo_clipAntimeridianLine,d3_geo_clipAntimeridianInterpolate,[-π,-π/2]);function d3_geo_clipAntimeridianLine(listener){var λ0=NaN,φ0=NaN,sλ0=NaN,clean;return{lineStart:function(){listener.lineStart();clean=1},point:function(λ1,φ1){var sλ1=λ1>0?π:-π,dλ=abs(λ1-λ0);if(abs(dλ-π)<ε){listener.point(λ0,φ0=(φ0+φ1)/2>0?halfπ:-halfπ);listener.point(sλ0,φ0);listener.lineEnd();listener.lineStart();listener.point(sλ1,φ0);listener.point(λ1,φ0);clean=0}else if(sλ0!==sλ1&&dλ>=π){if(abs(λ0-sλ0)<ε)λ0-=sλ0*ε;if(abs(λ1-sλ1)<ε)λ1-=sλ1*ε;φ0=d3_geo_clipAntimeridianIntersect(λ0,φ0,λ1,φ1);listener.point(sλ0,φ0);listener.lineEnd();listener.lineStart();listener.point(sλ1,φ0);clean=0}listener.point(λ0=λ1,φ0=φ1);sλ0=sλ1},lineEnd:function(){listener.lineEnd();λ0=φ0=NaN},clean:function(){return 2-clean}}}function d3_geo_clipAntimeridianIntersect(λ0,φ0,λ1,φ1){var cosφ0,cosφ1,sinλ0_λ1=Math.sin(λ0-λ1);return abs(sinλ0_λ1)>ε?Math.atan((Math.sin(φ0)*(cosφ1=Math.cos(φ1))*Math.sin(λ1)-Math.sin(φ1)*(cosφ0=Math.cos(φ0))*Math.sin(λ0))/(cosφ0*cosφ1*sinλ0_λ1)):(φ0+φ1)/2}function d3_geo_clipAntimeridianInterpolate(from,to,direction,listener){var φ;if(from==null){φ=direction*halfπ;listener.point(-π,φ);listener.point(0,φ);listener.point(π,φ);listener.point(π,0);listener.point(π,-φ);listener.point(0,-φ);listener.point(-π,-φ);listener.point(-π,0);listener.point(-π,φ)}else if(abs(from[0]-to[0])>ε){var s=from[0]=0?1:-1,adλ=sdλ*dλ,antimeridian=adλ>π,k=sinφ0*sinφ;d3_geo_areaRingSum.add(Math.atan2(k*sdλ*Math.sin(adλ),cosφ0*cosφ+k*Math.cos(adλ)));polarAngle+=antimeridian?dλ+sdλ*τ:dλ;if(antimeridian^λ0>=meridian^λ>=meridian){var arc=d3_geo_cartesianCross(d3_geo_cartesian(point0),d3_geo_cartesian(point));d3_geo_cartesianNormalize(arc);var intersection=d3_geo_cartesianCross(meridianNormal,arc);d3_geo_cartesianNormalize(intersection);var φarc=(antimeridian^dλ>=0?-1:1)*d3_asin(intersection[2]);if(parallel>φarc||parallel===φarc&&(arc[0]||arc[1])){winding+=antimeridian^dλ>=0?1:-1}}if(!j++)break;λ0=λ,sinφ0=sinφ,cosφ0=cosφ,point0=point}}return(polarAngle<-ε||polarAngle<ε&&d3_geo_areaRingSum<-ε)^winding&1}function d3_geo_clipCircle(radius){var cr=Math.cos(radius),smallRadius=cr>0,notHemisphere=abs(cr)>ε,interpolate=d3_geo_circleInterpolate(radius,6*d3_radians);return d3_geo_clip(visible,clipLine,interpolate,smallRadius?[0,-radius]:[-π,radius-π]);function visible(λ,φ){return Math.cos(λ)*Math.cos(φ)>cr}function clipLine(listener){var point0,c0,v0,v00,clean;return{lineStart:function(){v00=v0=false;clean=1},point:function(λ,φ){var point1=[λ,φ],point2,v=visible(λ,φ),c=smallRadius?v?0:code(λ,φ):v?code(λ+(λ<0?π:-π),φ):0;if(!point0&&(v00=v0=v))listener.lineStart();if(v!==v0){point2=intersect(point0,point1);if(d3_geo_sphericalEqual(point0,point2)||d3_geo_sphericalEqual(point1,point2)){point1[0]+=ε;point1[1]+=ε;v=visible(point1[0],point1[1])}}if(v!==v0){clean=0;if(v){listener.lineStart();point2=intersect(point1,point0);listener.point(point2[0],point2[1])}else{point2=intersect(point0,point1);listener.point(point2[0],point2[1]);listener.lineEnd()}point0=point2}else if(notHemisphere&&point0&&smallRadius^v){var t;if(!(c&c0)&&(t=intersect(point1,point0,true))){clean=0;if(smallRadius){listener.lineStart();listener.point(t[0][0],t[0][1]);listener.point(t[1][0],t[1][1]);listener.lineEnd()}else{listener.point(t[1][0],t[1][1]);listener.lineEnd();listener.lineStart();listener.point(t[0][0],t[0][1])}}}if(v&&(!point0||!d3_geo_sphericalEqual(point0,point1))){listener.point(point1[0],point1[1])}point0=point1,v0=v,c0=c},lineEnd:function(){if(v0)listener.lineEnd();point0=null},clean:function(){return clean|(v00&&v0)<<1}}}function intersect(a,b,two){var pa=d3_geo_cartesian(a),pb=d3_geo_cartesian(b);var n1=[1,0,0],n2=d3_geo_cartesianCross(pa,pb),n2n2=d3_geo_cartesianDot(n2,n2),n1n2=n2[0],determinant=n2n2-n1n2*n1n2;if(!determinant)return!two&&a;var c1=cr*n2n2/determinant,c2=-cr*n1n2/determinant,n1xn2=d3_geo_cartesianCross(n1,n2),A=d3_geo_cartesianScale(n1,c1),B=d3_geo_cartesianScale(n2,c2);d3_geo_cartesianAdd(A,B);var u=n1xn2,w=d3_geo_cartesianDot(A,u),uu=d3_geo_cartesianDot(u,u),t2=w*w-uu*(d3_geo_cartesianDot(A,A)-1);if(t2<0)return;var t=Math.sqrt(t2),q=d3_geo_cartesianScale(u,(-w-t)/uu);d3_geo_cartesianAdd(q,A);q=d3_geo_spherical(q);if(!two)return q;var λ0=a[0],λ1=b[0],φ0=a[1],φ1=b[1],z;if(λ1<λ0)z=λ0,λ0=λ1,λ1=z;var δλ=λ1-λ0,polar=abs(δλ-π)<ε,meridian=polar||δλ<ε;if(!polar&&φ1<φ0)z=φ0,φ0=φ1,φ1=z;if(meridian?polar?φ0+φ1>0^q[1]<(abs(q[0]-λ0)<ε?φ0:φ1):φ0<=q[1]&&q[1]<=φ1:δλ>π^(λ0<=q[0]&&q[0]<=λ1)){var q1=d3_geo_cartesianScale(u,(-w+t)/uu);d3_geo_cartesianAdd(q1,A);return[q,d3_geo_spherical(q1)]}}function code(λ,φ){var r=smallRadius?radius:π-radius,code=0;if(λ<-r)code|=1;else if(λ>r)code|=2;if(φ<-r)code|=4;else if(φ>r)code|=8;return code}}function d3_geom_clipLine(x0,y0,x1,y1){return function(line){var a=line.a,b=line.b,ax=a.x,ay=a.y,bx=b.x,by=b.y,t0=0,t1=1,dx=bx-ax,dy=by-ay,r;r=x0-ax;if(!dx&&r>0)return;r/=dx;if(dx<0){if(r0){if(r>t1)return;if(r>t0)t0=r}r=x1-ax;if(!dx&&r<0)return;r/=dx;if(dx<0){if(r>t1)return;if(r>t0)t0=r}else if(dx>0){if(r0)return;r/=dy;if(dy<0){if(r0){if(r>t1)return;if(r>t0)t0=r}r=y1-ay;if(!dy&&r<0)return;r/=dy;if(dy<0){if(r>t1)return;if(r>t0)t0=r}else if(dy>0){if(r0)line.a={x:ax+t0*dx,y:ay+t0*dy};if(t1<1)line.b={x:ax+t1*dx,y:ay+t1*dy};return line}}var d3_geo_clipExtentMAX=1e9;d3.geo.clipExtent=function(){var x0,y0,x1,y1,stream,clip,clipExtent={stream:function(output){if(stream)stream.valid=false;stream=clip(output);stream.valid=true;return stream},extent:function(_){if(!arguments.length)return[[x0,y0],[x1,y1]];clip=d3_geo_clipExtent(x0=+_[0][0],y0=+_[0][1],x1=+_[1][0],y1=+_[1][1]);if(stream)stream.valid=false,stream=null;return clipExtent}};return clipExtent.extent([[0,0],[960,500]])};function d3_geo_clipExtent(x0,y0,x1,y1){return function(listener){var listener_=listener,bufferListener=d3_geo_clipBufferListener(),clipLine=d3_geom_clipLine(x0,y0,x1,y1),segments,polygon,ring;var clip={point:point,lineStart:lineStart,lineEnd:lineEnd,polygonStart:function(){listener=bufferListener;segments=[];polygon=[];clean=true},polygonEnd:function(){listener=listener_;segments=d3.merge(segments);var clipStartInside=insidePolygon([x0,y1]),inside=clean&&clipStartInside,visible=segments.length;if(inside||visible){listener.polygonStart();if(inside){listener.lineStart();interpolate(null,null,1,listener);listener.lineEnd()}if(visible){d3_geo_clipPolygon(segments,compare,clipStartInside,interpolate,listener)}listener.polygonEnd()}segments=polygon=ring=null}};function insidePolygon(p){var wn=0,n=polygon.length,y=p[1];for(var i=0;iy&&d3_cross2d(a,b,p)>0)++wn}else{if(b[1]<=y&&d3_cross2d(a,b,p)<0)--wn}a=b}}return wn!==0}function interpolate(from,to,direction,listener){var a=0,a1=0;if(from==null||(a=corner(from,direction))!==(a1=corner(to,direction))||comparePoints(from,to)<0^direction>0){do{listener.point(a===0||a===3?x0:x1,a>1?y1:y0)}while((a=(a+direction+4)%4)!==a1)}else{listener.point(to[0],to[1])}}function pointVisible(x,y){return x0<=x&&x<=x1&&y0<=y&&y<=y1}function point(x,y){if(pointVisible(x,y))listener.point(x,y)}var x__,y__,v__,x_,y_,v_,first,clean;function lineStart(){clip.point=linePoint;if(polygon)polygon.push(ring=[]);first=true;v_=false;x_=y_=NaN}function lineEnd(){if(segments){linePoint(x__,y__);if(v__&&v_)bufferListener.rejoin();segments.push(bufferListener.buffer())}clip.point=point;if(v_)listener.lineEnd()}function linePoint(x,y){x=Math.max(-d3_geo_clipExtentMAX,Math.min(d3_geo_clipExtentMAX,x));y=Math.max(-d3_geo_clipExtentMAX,Math.min(d3_geo_clipExtentMAX,y));var v=pointVisible(x,y);if(polygon)ring.push([x,y]);if(first){x__=x,y__=y,v__=v;first=false;if(v){listener.lineStart();listener.point(x,y)}}else{if(v&&v_)listener.point(x,y);else{var l={a:{x:x_,y:y_},b:{x:x,y:y}};if(clipLine(l)){if(!v_){listener.lineStart();listener.point(l.a.x,l.a.y)}listener.point(l.b.x,l.b.y);if(!v)listener.lineEnd();clean=false}else if(v){listener.lineStart();listener.point(x,y);clean=false}}}x_=x,y_=y,v_=v}return clip};function corner(p,direction){return abs(p[0]-x0)<ε?direction>0?0:3:abs(p[0]-x1)<ε?direction>0?2:1:abs(p[1]-y0)<ε?direction>0?1:0:direction>0?3:2}function compare(a,b){return comparePoints(a.x,b.x)}function comparePoints(a,b){var ca=corner(a,1),cb=corner(b,1);return ca!==cb?ca-cb:ca===0?b[1]-a[1]:ca===1?a[0]-b[0]:ca===2?a[1]-b[1]:b[0]-a[0]}}function d3_geo_conic(projectAt){var φ0=0,φ1=π/3,m=d3_geo_projectionMutator(projectAt),p=m(φ0,φ1);p.parallels=function(_){if(!arguments.length)return[φ0/π*180,φ1/π*180];return m(φ0=_[0]*π/180,φ1=_[1]*π/180)};return p}function d3_geo_conicEqualArea(φ0,φ1){var sinφ0=Math.sin(φ0),n=(sinφ0+Math.sin(φ1))/2,C=1+sinφ0*(2*n-sinφ0),ρ0=Math.sqrt(C)/n;function forward(λ,φ){var ρ=Math.sqrt(C-2*n*Math.sin(φ))/n;return[ρ*Math.sin(λ*=n),ρ0-ρ*Math.cos(λ)]}forward.invert=function(x,y){var ρ0_y=ρ0-y;return[Math.atan2(x,ρ0_y)/n,d3_asin((C-(x*x+ρ0_y*ρ0_y)*n*n)/(2*n))]};return forward}(d3.geo.conicEqualArea=function(){return d3_geo_conic(d3_geo_conicEqualArea)}).raw=d3_geo_conicEqualArea;d3.geo.albers=function(){return d3.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)};d3.geo.albersUsa=function(){var lower48=d3.geo.albers();var alaska=d3.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]);var hawaii=d3.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]);var point,pointStream={point:function(x,y){point=[x,y]}},lower48Point,alaskaPoint,hawaiiPoint;function albersUsa(coordinates){var x=coordinates[0],y=coordinates[1];point=null;(lower48Point(x,y),point)||(alaskaPoint(x,y),point)||hawaiiPoint(x,y);return point}albersUsa.invert=function(coordinates){var k=lower48.scale(),t=lower48.translate(),x=(coordinates[0]-t[0])/k,y=(coordinates[1]-t[1])/k;return(y>=.12&&y<.234&&x>=-.425&&x<-.214?alaska:y>=.166&&y<.234&&x>=-.214&&x<-.115?hawaii:lower48).invert(coordinates)};albersUsa.stream=function(stream){var lower48Stream=lower48.stream(stream),alaskaStream=alaska.stream(stream),hawaiiStream=hawaii.stream(stream);return{point:function(x,y){lower48Stream.point(x,y);alaskaStream.point(x,y);hawaiiStream.point(x,y)},sphere:function(){lower48Stream.sphere();alaskaStream.sphere();hawaiiStream.sphere()},lineStart:function(){lower48Stream.lineStart();alaskaStream.lineStart();hawaiiStream.lineStart()},lineEnd:function(){lower48Stream.lineEnd();alaskaStream.lineEnd();hawaiiStream.lineEnd()},polygonStart:function(){lower48Stream.polygonStart();alaskaStream.polygonStart();hawaiiStream.polygonStart()},polygonEnd:function(){lower48Stream.polygonEnd();alaskaStream.polygonEnd();hawaiiStream.polygonEnd()}}};albersUsa.precision=function(_){if(!arguments.length)return lower48.precision();lower48.precision(_);alaska.precision(_);hawaii.precision(_);return albersUsa};albersUsa.scale=function(_){if(!arguments.length)return lower48.scale();lower48.scale(_);alaska.scale(_*.35);hawaii.scale(_);return albersUsa.translate(lower48.translate())};albersUsa.translate=function(_){if(!arguments.length)return lower48.translate();var k=lower48.scale(),x=+_[0],y=+_[1];lower48Point=lower48.translate(_).clipExtent([[x-.455*k,y-.238*k],[x+.455*k,y+.238*k]]).stream(pointStream).point;alaskaPoint=alaska.translate([x-.307*k,y+.201*k]).clipExtent([[x-.425*k+ε,y+.12*k+ε],[x-.214*k-ε,y+.234*k-ε]]).stream(pointStream).point;hawaiiPoint=hawaii.translate([x-.205*k,y+.212*k]).clipExtent([[x-.214*k+ε,y+.166*k+ε],[x-.115*k-ε,y+.234*k-ε]]).stream(pointStream).point;return albersUsa};return albersUsa.scale(1070)};var d3_geo_pathAreaSum,d3_geo_pathAreaPolygon,d3_geo_pathArea={point:d3_noop,lineStart:d3_noop,lineEnd:d3_noop,polygonStart:function(){d3_geo_pathAreaPolygon=0;d3_geo_pathArea.lineStart=d3_geo_pathAreaRingStart},polygonEnd:function(){d3_geo_pathArea.lineStart=d3_geo_pathArea.lineEnd=d3_geo_pathArea.point=d3_noop;d3_geo_pathAreaSum+=abs(d3_geo_pathAreaPolygon/2)}};function d3_geo_pathAreaRingStart(){var x00,y00,x0,y0;d3_geo_pathArea.point=function(x,y){d3_geo_pathArea.point=nextPoint;x00=x0=x,y00=y0=y};function nextPoint(x,y){d3_geo_pathAreaPolygon+=y0*x-x0*y;x0=x,y0=y}d3_geo_pathArea.lineEnd=function(){nextPoint(x00,y00)}}var d3_geo_pathBoundsX0,d3_geo_pathBoundsY0,d3_geo_pathBoundsX1,d3_geo_pathBoundsY1;var d3_geo_pathBounds={point:d3_geo_pathBoundsPoint,lineStart:d3_noop,lineEnd:d3_noop,polygonStart:d3_noop,polygonEnd:d3_noop};function d3_geo_pathBoundsPoint(x,y){if(xd3_geo_pathBoundsX1)d3_geo_pathBoundsX1=x;if(yd3_geo_pathBoundsY1)d3_geo_pathBoundsY1=y}function d3_geo_pathBuffer(){var pointCircle=d3_geo_pathBufferCircle(4.5),buffer=[];var stream={point:point,lineStart:function(){stream.point=pointLineStart},lineEnd:lineEnd,polygonStart:function(){stream.lineEnd=lineEndPolygon},polygonEnd:function(){stream.lineEnd=lineEnd;stream.point=point},pointRadius:function(_){pointCircle=d3_geo_pathBufferCircle(_);return stream},result:function(){if(buffer.length){var result=buffer.join("");buffer=[];return result}}};function point(x,y){buffer.push("M",x,",",y,pointCircle)}function pointLineStart(x,y){buffer.push("M",x,",",y);stream.point=pointLine}function pointLine(x,y){buffer.push("L",x,",",y)}function lineEnd(){stream.point=point}function lineEndPolygon(){buffer.push("Z")}return stream}function d3_geo_pathBufferCircle(radius){return"m0,"+radius+"a"+radius+","+radius+" 0 1,1 0,"+-2*radius+"a"+radius+","+radius+" 0 1,1 0,"+2*radius+"z"}var d3_geo_pathCentroid={point:d3_geo_pathCentroidPoint,lineStart:d3_geo_pathCentroidLineStart,lineEnd:d3_geo_pathCentroidLineEnd,polygonStart:function(){d3_geo_pathCentroid.lineStart=d3_geo_pathCentroidRingStart},polygonEnd:function(){d3_geo_pathCentroid.point=d3_geo_pathCentroidPoint;d3_geo_pathCentroid.lineStart=d3_geo_pathCentroidLineStart;d3_geo_pathCentroid.lineEnd=d3_geo_pathCentroidLineEnd}};function d3_geo_pathCentroidPoint(x,y){d3_geo_centroidX0+=x;d3_geo_centroidY0+=y;++d3_geo_centroidZ0}function d3_geo_pathCentroidLineStart(){var x0,y0;d3_geo_pathCentroid.point=function(x,y){d3_geo_pathCentroid.point=nextPoint;d3_geo_pathCentroidPoint(x0=x,y0=y)};function nextPoint(x,y){var dx=x-x0,dy=y-y0,z=Math.sqrt(dx*dx+dy*dy);d3_geo_centroidX1+=z*(x0+x)/2;d3_geo_centroidY1+=z*(y0+y)/2;d3_geo_centroidZ1+=z;d3_geo_pathCentroidPoint(x0=x,y0=y)}}function d3_geo_pathCentroidLineEnd(){d3_geo_pathCentroid.point=d3_geo_pathCentroidPoint}function d3_geo_pathCentroidRingStart(){var x00,y00,x0,y0;d3_geo_pathCentroid.point=function(x,y){d3_geo_pathCentroid.point=nextPoint;d3_geo_pathCentroidPoint(x00=x0=x,y00=y0=y)};function nextPoint(x,y){var dx=x-x0,dy=y-y0,z=Math.sqrt(dx*dx+dy*dy);d3_geo_centroidX1+=z*(x0+x)/2;d3_geo_centroidY1+=z*(y0+y)/2;d3_geo_centroidZ1+=z;z=y0*x-x0*y;d3_geo_centroidX2+=z*(x0+x);d3_geo_centroidY2+=z*(y0+y);d3_geo_centroidZ2+=z*3;d3_geo_pathCentroidPoint(x0=x,y0=y)}d3_geo_pathCentroid.lineEnd=function(){nextPoint(x00,y00)}}function d3_geo_pathContext(context){var pointRadius=4.5;var stream={point:point,lineStart:function(){stream.point=pointLineStart},lineEnd:lineEnd,polygonStart:function(){stream.lineEnd=lineEndPolygon},polygonEnd:function(){stream.lineEnd=lineEnd;stream.point=point},pointRadius:function(_){pointRadius=_;return stream},result:d3_noop};function point(x,y){context.moveTo(x+pointRadius,y);context.arc(x,y,pointRadius,0,τ)}function pointLineStart(x,y){context.moveTo(x,y);stream.point=pointLine}function pointLine(x,y){context.lineTo(x,y)}function lineEnd(){stream.point=point}function lineEndPolygon(){context.closePath()}return stream}function d3_geo_resample(project){var δ2=.5,cosMinDistance=Math.cos(30*d3_radians),maxDepth=16;function resample(stream){return(maxDepth?resampleRecursive:resampleNone)(stream)}function resampleNone(stream){return d3_geo_transformPoint(stream,function(x,y){x=project(x,y);stream.point(x[0],x[1])})}function resampleRecursive(stream){var λ00,φ00,x00,y00,a00,b00,c00,λ0,x0,y0,a0,b0,c0;var resample={point:point,lineStart:lineStart,lineEnd:lineEnd,polygonStart:function(){stream.polygonStart();resample.lineStart=ringStart},polygonEnd:function(){stream.polygonEnd();resample.lineStart=lineStart}};function point(x,y){x=project(x,y);stream.point(x[0],x[1])}function lineStart(){x0=NaN;resample.point=linePoint;stream.lineStart()}function linePoint(λ,φ){var c=d3_geo_cartesian([λ,φ]),p=project(λ,φ);resampleLineTo(x0,y0,λ0,a0,b0,c0,x0=p[0],y0=p[1],λ0=λ,a0=c[0],b0=c[1],c0=c[2],maxDepth,stream);stream.point(x0,y0)}function lineEnd(){resample.point=point;stream.lineEnd()}function ringStart(){lineStart();resample.point=ringPoint;resample.lineEnd=ringEnd}function ringPoint(λ,φ){linePoint(λ00=λ,φ00=φ),x00=x0,y00=y0,a00=a0,b00=b0,c00=c0;resample.point=linePoint}function ringEnd(){resampleLineTo(x0,y0,λ0,a0,b0,c0,x00,y00,λ00,a00,b00,c00,maxDepth,stream);resample.lineEnd=lineEnd;lineEnd()}return resample}function resampleLineTo(x0,y0,λ0,a0,b0,c0,x1,y1,λ1,a1,b1,c1,depth,stream){var dx=x1-x0,dy=y1-y0,d2=dx*dx+dy*dy;if(d2>4*δ2&&depth--){var a=a0+a1,b=b0+b1,c=c0+c1,m=Math.sqrt(a*a+b*b+c*c),φ2=Math.asin(c/=m),λ2=abs(abs(c)-1)<ε||abs(λ0-λ1)<ε?(λ0+λ1)/2:Math.atan2(b,a),p=project(λ2,φ2),x2=p[0],y2=p[1],dx2=x2-x0,dy2=y2-y0,dz=dy*dx2-dx*dy2;if(dz*dz/d2>δ2||abs((dx*dx2+dy*dy2)/d2-.5)>.3||a0*a1+b0*b1+c0*c10&&16;return resample};return resample}d3.geo.path=function(){var pointRadius=4.5,projection,context,projectStream,contextStream,cacheStream;function path(object){if(object){if(typeof pointRadius==="function")contextStream.pointRadius(+pointRadius.apply(this,arguments));if(!cacheStream||!cacheStream.valid)cacheStream=projectStream(contextStream);d3.geo.stream(object,cacheStream)}return contextStream.result()}path.area=function(object){d3_geo_pathAreaSum=0;d3.geo.stream(object,projectStream(d3_geo_pathArea));return d3_geo_pathAreaSum};path.centroid=function(object){d3_geo_centroidX0=d3_geo_centroidY0=d3_geo_centroidZ0=d3_geo_centroidX1=d3_geo_centroidY1=d3_geo_centroidZ1=d3_geo_centroidX2=d3_geo_centroidY2=d3_geo_centroidZ2=0;d3.geo.stream(object,projectStream(d3_geo_pathCentroid));return d3_geo_centroidZ2?[d3_geo_centroidX2/d3_geo_centroidZ2,d3_geo_centroidY2/d3_geo_centroidZ2]:d3_geo_centroidZ1?[d3_geo_centroidX1/d3_geo_centroidZ1,d3_geo_centroidY1/d3_geo_centroidZ1]:d3_geo_centroidZ0?[d3_geo_centroidX0/d3_geo_centroidZ0,d3_geo_centroidY0/d3_geo_centroidZ0]:[NaN,NaN]};path.bounds=function(object){d3_geo_pathBoundsX1=d3_geo_pathBoundsY1=-(d3_geo_pathBoundsX0=d3_geo_pathBoundsY0=Infinity);d3.geo.stream(object,projectStream(d3_geo_pathBounds));return[[d3_geo_pathBoundsX0,d3_geo_pathBoundsY0],[d3_geo_pathBoundsX1,d3_geo_pathBoundsY1]]};path.projection=function(_){if(!arguments.length)return projection;projectStream=(projection=_)?_.stream||d3_geo_pathProjectStream(_):d3_identity;return reset()};path.context=function(_){if(!arguments.length)return context;contextStream=(context=_)==null?new d3_geo_pathBuffer:new d3_geo_pathContext(_);if(typeof pointRadius!=="function")contextStream.pointRadius(pointRadius);return reset()};path.pointRadius=function(_){if(!arguments.length)return pointRadius;pointRadius=typeof _==="function"?_:(contextStream.pointRadius(+_),+_);return path};function reset(){cacheStream=null;return path}return path.projection(d3.geo.albersUsa()).context(null)};function d3_geo_pathProjectStream(project){var resample=d3_geo_resample(function(x,y){return project([x*d3_degrees,y*d3_degrees])});return function(stream){return d3_geo_projectionRadians(resample(stream))}}d3.geo.transform=function(methods){return{stream:function(stream){var transform=new d3_geo_transform(stream);for(var k in methods)transform[k]=methods[k];return transform}}};function d3_geo_transform(stream){this.stream=stream}d3_geo_transform.prototype={point:function(x,y){this.stream.point(x,y)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}};function d3_geo_transformPoint(stream,point){return{point:point,sphere:function(){stream.sphere()},lineStart:function(){stream.lineStart()},lineEnd:function(){stream.lineEnd()},polygonStart:function(){stream.polygonStart()},polygonEnd:function(){stream.polygonEnd()}}}d3.geo.projection=d3_geo_projection;d3.geo.projectionMutator=d3_geo_projectionMutator;function d3_geo_projection(project){return d3_geo_projectionMutator(function(){return project})()}function d3_geo_projectionMutator(projectAt){var project,rotate,projectRotate,projectResample=d3_geo_resample(function(x,y){x=project(x,y);return[x[0]*k+δx,δy-x[1]*k]}),k=150,x=480,y=250,λ=0,φ=0,δλ=0,δφ=0,δγ=0,δx,δy,preclip=d3_geo_clipAntimeridian,postclip=d3_identity,clipAngle=null,clipExtent=null,stream;function projection(point){point=projectRotate(point[0]*d3_radians,point[1]*d3_radians);return[point[0]*k+δx,δy-point[1]*k]}function invert(point){point=projectRotate.invert((point[0]-δx)/k,(δy-point[1])/k);return point&&[point[0]*d3_degrees,point[1]*d3_degrees]}projection.stream=function(output){if(stream)stream.valid=false;stream=d3_geo_projectionRadians(preclip(rotate,projectResample(postclip(output))));stream.valid=true;return stream};projection.clipAngle=function(_){if(!arguments.length)return clipAngle;preclip=_==null?(clipAngle=_,d3_geo_clipAntimeridian):d3_geo_clipCircle((clipAngle=+_)*d3_radians);return invalidate()};projection.clipExtent=function(_){if(!arguments.length)return clipExtent;clipExtent=_;postclip=_?d3_geo_clipExtent(_[0][0],_[0][1],_[1][0],_[1][1]):d3_identity;return invalidate()};projection.scale=function(_){if(!arguments.length)return k;k=+_;return reset()};projection.translate=function(_){if(!arguments.length)return[x,y];x=+_[0];y=+_[1];return reset()};projection.center=function(_){if(!arguments.length)return[λ*d3_degrees,φ*d3_degrees];λ=_[0]%360*d3_radians;φ=_[1]%360*d3_radians;return reset()};projection.rotate=function(_){if(!arguments.length)return[δλ*d3_degrees,δφ*d3_degrees,δγ*d3_degrees];δλ=_[0]%360*d3_radians;δφ=_[1]%360*d3_radians;δγ=_.length>2?_[2]%360*d3_radians:0;return reset()};d3.rebind(projection,projectResample,"precision");function reset(){projectRotate=d3_geo_compose(rotate=d3_geo_rotation(δλ,δφ,δγ),project);var center=project(λ,φ);δx=x-center[0]*k;δy=y+center[1]*k;return invalidate()}function invalidate(){if(stream)stream.valid=false,stream=null;return projection}return function(){project=projectAt.apply(this,arguments);projection.invert=project.invert&&invert;return reset()}}function d3_geo_projectionRadians(stream){return d3_geo_transformPoint(stream,function(x,y){stream.point(x*d3_radians,y*d3_radians)})}function d3_geo_equirectangular(λ,φ){return[λ,φ]}(d3.geo.equirectangular=function(){return d3_geo_projection(d3_geo_equirectangular)}).raw=d3_geo_equirectangular.invert=d3_geo_equirectangular;d3.geo.rotation=function(rotate){rotate=d3_geo_rotation(rotate[0]%360*d3_radians,rotate[1]*d3_radians,rotate.length>2?rotate[2]*d3_radians:0);function forward(coordinates){coordinates=rotate(coordinates[0]*d3_radians,coordinates[1]*d3_radians);return coordinates[0]*=d3_degrees,coordinates[1]*=d3_degrees,coordinates}forward.invert=function(coordinates){coordinates=rotate.invert(coordinates[0]*d3_radians,coordinates[1]*d3_radians);return coordinates[0]*=d3_degrees,coordinates[1]*=d3_degrees,coordinates};return forward};function d3_geo_identityRotation(λ,φ){return[λ>π?λ-τ:λ<-π?λ+τ:λ,φ]}d3_geo_identityRotation.invert=d3_geo_equirectangular;function d3_geo_rotation(δλ,δφ,δγ){return δλ?δφ||δγ?d3_geo_compose(d3_geo_rotationλ(δλ),d3_geo_rotationφγ(δφ,δγ)):d3_geo_rotationλ(δλ):δφ||δγ?d3_geo_rotationφγ(δφ,δγ):d3_geo_identityRotation}function d3_geo_forwardRotationλ(δλ){return function(λ,φ){return λ+=δλ,[λ>π?λ-τ:λ<-π?λ+τ:λ,φ]}}function d3_geo_rotationλ(δλ){var rotation=d3_geo_forwardRotationλ(δλ);rotation.invert=d3_geo_forwardRotationλ(-δλ);return rotation}function d3_geo_rotationφγ(δφ,δγ){var cosδφ=Math.cos(δφ),sinδφ=Math.sin(δφ),cosδγ=Math.cos(δγ),sinδγ=Math.sin(δγ);function rotation(λ,φ){var cosφ=Math.cos(φ),x=Math.cos(λ)*cosφ,y=Math.sin(λ)*cosφ,z=Math.sin(φ),k=z*cosδφ+x*sinδφ;return[Math.atan2(y*cosδγ-k*sinδγ,x*cosδφ-z*sinδφ),d3_asin(k*cosδγ+y*sinδγ)]}rotation.invert=function(λ,φ){var cosφ=Math.cos(φ),x=Math.cos(λ)*cosφ,y=Math.sin(λ)*cosφ,z=Math.sin(φ),k=z*cosδγ-y*sinδγ;return[Math.atan2(y*cosδγ+z*sinδγ,x*cosδφ+k*sinδφ),d3_asin(k*cosδφ-x*sinδφ)]};return rotation}d3.geo.circle=function(){var origin=[0,0],angle,precision=6,interpolate;function circle(){var center=typeof origin==="function"?origin.apply(this,arguments):origin,rotate=d3_geo_rotation(-center[0]*d3_radians,-center[1]*d3_radians,0).invert,ring=[];interpolate(null,null,1,{point:function(x,y){ring.push(x=rotate(x,y));x[0]*=d3_degrees,x[1]*=d3_degrees}});return{type:"Polygon",coordinates:[ring]}}circle.origin=function(x){if(!arguments.length)return origin;origin=x;return circle};circle.angle=function(x){if(!arguments.length)return angle;interpolate=d3_geo_circleInterpolate((angle=+x)*d3_radians,precision*d3_radians);return circle};circle.precision=function(_){if(!arguments.length)return precision;interpolate=d3_geo_circleInterpolate(angle*d3_radians,(precision=+_)*d3_radians);return circle};return circle.angle(90)};function d3_geo_circleInterpolate(radius,precision){var cr=Math.cos(radius),sr=Math.sin(radius);return function(from,to,direction,listener){var step=direction*precision;if(from!=null){from=d3_geo_circleAngle(cr,from);to=d3_geo_circleAngle(cr,to);if(direction>0?fromto)from+=direction*τ}else{from=radius+direction*τ;to=radius-.5*step}for(var point,t=from;direction>0?t>to:tε}).map(x)).concat(d3.range(Math.ceil(y0/dy)*dy,y1,dy).filter(function(y){return abs(y%DY)>ε}).map(y))}graticule.lines=function(){return lines().map(function(coordinates){return{type:"LineString",coordinates:coordinates}})};graticule.outline=function(){return{type:"Polygon",coordinates:[X(X0).concat(Y(Y1).slice(1),X(X1).reverse().slice(1),Y(Y0).reverse().slice(1))]}};graticule.extent=function(_){if(!arguments.length)return graticule.minorExtent();return graticule.majorExtent(_).minorExtent(_)};graticule.majorExtent=function(_){if(!arguments.length)return[[X0,Y0],[X1,Y1]];X0=+_[0][0],X1=+_[1][0];Y0=+_[0][1],Y1=+_[1][1];if(X0>X1)_=X0,X0=X1,X1=_;if(Y0>Y1)_=Y0,Y0=Y1,Y1=_;return graticule.precision(precision)};graticule.minorExtent=function(_){if(!arguments.length)return[[x0,y0],[x1,y1]];x0=+_[0][0],x1=+_[1][0];y0=+_[0][1],y1=+_[1][1];if(x0>x1)_=x0,x0=x1,x1=_;if(y0>y1)_=y0,y0=y1,y1=_;return graticule.precision(precision)};graticule.step=function(_){if(!arguments.length)return graticule.minorStep();return graticule.majorStep(_).minorStep(_)};graticule.majorStep=function(_){if(!arguments.length)return[DX,DY];DX=+_[0],DY=+_[1];return graticule};graticule.minorStep=function(_){if(!arguments.length)return[dx,dy];dx=+_[0],dy=+_[1];return graticule};graticule.precision=function(_){if(!arguments.length)return precision;precision=+_;x=d3_geo_graticuleX(y0,y1,90);y=d3_geo_graticuleY(x0,x1,precision);X=d3_geo_graticuleX(Y0,Y1,90);Y=d3_geo_graticuleY(X0,X1,precision);return graticule};return graticule.majorExtent([[-180,-90+ε],[180,90-ε]]).minorExtent([[-180,-80-ε],[180,80+ε]])};function d3_geo_graticuleX(y0,y1,dy){var y=d3.range(y0,y1-ε,dy).concat(y1);return function(x){return y.map(function(y){return[x,y]})}}function d3_geo_graticuleY(x0,x1,dx){var x=d3.range(x0,x1-ε,dx).concat(x1);return function(y){return x.map(function(x){return[x,y]})}}function d3_source(d){return d.source}function d3_target(d){return d.target}d3.geo.greatArc=function(){var source=d3_source,source_,target=d3_target,target_;function greatArc(){return{type:"LineString",coordinates:[source_||source.apply(this,arguments),target_||target.apply(this,arguments)]}}greatArc.distance=function(){return d3.geo.distance(source_||source.apply(this,arguments),target_||target.apply(this,arguments))};greatArc.source=function(_){if(!arguments.length)return source;source=_,source_=typeof _==="function"?null:_;return greatArc};greatArc.target=function(_){if(!arguments.length)return target;target=_,target_=typeof _==="function"?null:_;return greatArc};greatArc.precision=function(){return arguments.length?greatArc:0};return greatArc};d3.geo.interpolate=function(source,target){return d3_geo_interpolate(source[0]*d3_radians,source[1]*d3_radians,target[0]*d3_radians,target[1]*d3_radians)};function d3_geo_interpolate(x0,y0,x1,y1){var cy0=Math.cos(y0),sy0=Math.sin(y0),cy1=Math.cos(y1),sy1=Math.sin(y1),kx0=cy0*Math.cos(x0),ky0=cy0*Math.sin(x0),kx1=cy1*Math.cos(x1),ky1=cy1*Math.sin(x1),d=2*Math.asin(Math.sqrt(d3_haversin(y1-y0)+cy0*cy1*d3_haversin(x1-x0))),k=1/Math.sin(d);var interpolate=d?function(t){var B=Math.sin(t*=d)*k,A=Math.sin(d-t)*k,x=A*kx0+B*kx1,y=A*ky0+B*ky1,z=A*sy0+B*sy1;return[Math.atan2(y,x)*d3_degrees,Math.atan2(z,Math.sqrt(x*x+y*y))*d3_degrees]}:function(){return[x0*d3_degrees,y0*d3_degrees]};interpolate.distance=d;return interpolate}d3.geo.length=function(object){d3_geo_lengthSum=0;d3.geo.stream(object,d3_geo_length);return d3_geo_lengthSum};var d3_geo_lengthSum;var d3_geo_length={sphere:d3_noop,point:d3_noop,lineStart:d3_geo_lengthLineStart,lineEnd:d3_noop,polygonStart:d3_noop,polygonEnd:d3_noop};function d3_geo_lengthLineStart(){var λ0,sinφ0,cosφ0;d3_geo_length.point=function(λ,φ){λ0=λ*d3_radians,sinφ0=Math.sin(φ*=d3_radians),cosφ0=Math.cos(φ);d3_geo_length.point=nextPoint};d3_geo_length.lineEnd=function(){d3_geo_length.point=d3_geo_length.lineEnd=d3_noop};function nextPoint(λ,φ){var sinφ=Math.sin(φ*=d3_radians),cosφ=Math.cos(φ),t=abs((λ*=d3_radians)-λ0),cosΔλ=Math.cos(t);d3_geo_lengthSum+=Math.atan2(Math.sqrt((t=cosφ*Math.sin(t))*t+(t=cosφ0*sinφ-sinφ0*cosφ*cosΔλ)*t),sinφ0*sinφ+cosφ0*cosφ*cosΔλ);λ0=λ,sinφ0=sinφ,cosφ0=cosφ}}function d3_geo_azimuthal(scale,angle){function azimuthal(λ,φ){var cosλ=Math.cos(λ),cosφ=Math.cos(φ),k=scale(cosλ*cosφ);return[k*cosφ*Math.sin(λ),k*Math.sin(φ)]}azimuthal.invert=function(x,y){var ρ=Math.sqrt(x*x+y*y),c=angle(ρ),sinc=Math.sin(c),cosc=Math.cos(c);return[Math.atan2(x*sinc,ρ*cosc),Math.asin(ρ&&y*sinc/ρ)]};return azimuthal}var d3_geo_azimuthalEqualArea=d3_geo_azimuthal(function(cosλcosφ){return Math.sqrt(2/(1+cosλcosφ))},function(ρ){return 2*Math.asin(ρ/2)});(d3.geo.azimuthalEqualArea=function(){return d3_geo_projection(d3_geo_azimuthalEqualArea)}).raw=d3_geo_azimuthalEqualArea;var d3_geo_azimuthalEquidistant=d3_geo_azimuthal(function(cosλcosφ){var c=Math.acos(cosλcosφ);return c&&c/Math.sin(c)},d3_identity);(d3.geo.azimuthalEquidistant=function(){return d3_geo_projection(d3_geo_azimuthalEquidistant)}).raw=d3_geo_azimuthalEquidistant;function d3_geo_conicConformal(φ0,φ1){var cosφ0=Math.cos(φ0),t=function(φ){return Math.tan(π/4+φ/2)},n=φ0===φ1?Math.sin(φ0):Math.log(cosφ0/Math.cos(φ1))/Math.log(t(φ1)/t(φ0)),F=cosφ0*Math.pow(t(φ0),n)/n;if(!n)return d3_geo_mercator;function forward(λ,φ){if(F>0){if(φ<-halfπ+ε)φ=-halfπ+ε}else{if(φ>halfπ-ε)φ=halfπ-ε}var ρ=F/Math.pow(t(φ),n);return[ρ*Math.sin(n*λ),F-ρ*Math.cos(n*λ)]}forward.invert=function(x,y){var ρ0_y=F-y,ρ=d3_sgn(n)*Math.sqrt(x*x+ρ0_y*ρ0_y);return[Math.atan2(x,ρ0_y)/n,2*Math.atan(Math.pow(F/ρ,1/n))-halfπ]};return forward}(d3.geo.conicConformal=function(){return d3_geo_conic(d3_geo_conicConformal)}).raw=d3_geo_conicConformal;function d3_geo_conicEquidistant(φ0,φ1){var cosφ0=Math.cos(φ0),n=φ0===φ1?Math.sin(φ0):(cosφ0-Math.cos(φ1))/(φ1-φ0),G=cosφ0/n+φ0;if(abs(n)<ε)return d3_geo_equirectangular;function forward(λ,φ){var ρ=G-φ;return[ρ*Math.sin(n*λ),G-ρ*Math.cos(n*λ)]}forward.invert=function(x,y){var ρ0_y=G-y;return[Math.atan2(x,ρ0_y)/n,G-d3_sgn(n)*Math.sqrt(x*x+ρ0_y*ρ0_y)]};return forward}(d3.geo.conicEquidistant=function(){return d3_geo_conic(d3_geo_conicEquidistant)}).raw=d3_geo_conicEquidistant;var d3_geo_gnomonic=d3_geo_azimuthal(function(cosλcosφ){return 1/cosλcosφ},Math.atan);(d3.geo.gnomonic=function(){return d3_geo_projection(d3_geo_gnomonic)}).raw=d3_geo_gnomonic;function d3_geo_mercator(λ,φ){return[λ,Math.log(Math.tan(π/4+φ/2))]}d3_geo_mercator.invert=function(x,y){return[x,2*Math.atan(Math.exp(y))-halfπ]};function d3_geo_mercatorProjection(project){var m=d3_geo_projection(project),scale=m.scale,translate=m.translate,clipExtent=m.clipExtent,clipAuto;m.scale=function(){var v=scale.apply(m,arguments);return v===m?clipAuto?m.clipExtent(null):m:v};m.translate=function(){var v=translate.apply(m,arguments);return v===m?clipAuto?m.clipExtent(null):m:v};m.clipExtent=function(_){var v=clipExtent.apply(m,arguments);if(v===m){if(clipAuto=_==null){var k=π*scale(),t=translate();clipExtent([[t[0]-k,t[1]-k],[t[0]+k,t[1]+k]])}}else if(clipAuto){v=null}return v};return m.clipExtent(null)}(d3.geo.mercator=function(){return d3_geo_mercatorProjection(d3_geo_mercator)}).raw=d3_geo_mercator;var d3_geo_orthographic=d3_geo_azimuthal(function(){return 1},Math.asin);(d3.geo.orthographic=function(){return d3_geo_projection(d3_geo_orthographic)}).raw=d3_geo_orthographic;var d3_geo_stereographic=d3_geo_azimuthal(function(cosλcosφ){return 1/(1+cosλcosφ)},function(ρ){return 2*Math.atan(ρ)});(d3.geo.stereographic=function(){return d3_geo_projection(d3_geo_stereographic)}).raw=d3_geo_stereographic;function d3_geo_transverseMercator(λ,φ){return[Math.log(Math.tan(π/4+φ/2)),-λ]}d3_geo_transverseMercator.invert=function(x,y){return[-y,2*Math.atan(Math.exp(x))-halfπ]};(d3.geo.transverseMercator=function(){var projection=d3_geo_mercatorProjection(d3_geo_transverseMercator),center=projection.center,rotate=projection.rotate;projection.center=function(_){return _?center([-_[1],_[0]]):(_=center(),[_[1],-_[0]])};projection.rotate=function(_){return _?rotate([_[0],_[1],_.length>2?_[2]+90:90]):(_=rotate(),[_[0],_[1],_[2]-90])};return rotate([0,0,90])}).raw=d3_geo_transverseMercator;d3.geom={};function d3_geom_pointX(d){return d[0]}function d3_geom_pointY(d){return d[1]}d3.geom.hull=function(vertices){var x=d3_geom_pointX,y=d3_geom_pointY;if(arguments.length)return hull(vertices);function hull(data){if(data.length<3)return[];var fx=d3_functor(x),fy=d3_functor(y),i,n=data.length,points=[],flippedPoints=[];for(i=0;i=0;--i)polygon.push(data[points[upper[i]][2]]);for(i=+skipLeft;i1&&d3_cross2d(points[hull[hs-2]],points[hull[hs-1]],points[i])<=0)--hs;hull[hs++]=i}return hull.slice(0,hs)}function d3_geom_hullOrder(a,b){return a[0]-b[0]||a[1]-b[1]}d3.geom.polygon=function(coordinates){d3_subclass(coordinates,d3_geom_polygonPrototype);return coordinates};var d3_geom_polygonPrototype=d3.geom.polygon.prototype=[];d3_geom_polygonPrototype.area=function(){var i=-1,n=this.length,a,b=this[n-1],area=0;while(++iε)node=node.L;else{dxr=x-d3_geom_voronoiRightBreakPoint(node,directrix);if(dxr>ε){if(!node.R){lArc=node;break}node=node.R}else{if(dxl>-ε){lArc=node.P;rArc=node}else if(dxr>-ε){lArc=node;rArc=node.N}else{lArc=rArc=node}break}}}var newArc=d3_geom_voronoiCreateBeach(site);d3_geom_voronoiBeaches.insert(lArc,newArc);if(!lArc&&!rArc)return;if(lArc===rArc){d3_geom_voronoiDetachCircle(lArc);rArc=d3_geom_voronoiCreateBeach(lArc.site);d3_geom_voronoiBeaches.insert(newArc,rArc);newArc.edge=rArc.edge=d3_geom_voronoiCreateEdge(lArc.site,newArc.site);d3_geom_voronoiAttachCircle(lArc);d3_geom_voronoiAttachCircle(rArc);return}if(!rArc){newArc.edge=d3_geom_voronoiCreateEdge(lArc.site,newArc.site);return}d3_geom_voronoiDetachCircle(lArc);d3_geom_voronoiDetachCircle(rArc);var lSite=lArc.site,ax=lSite.x,ay=lSite.y,bx=site.x-ax,by=site.y-ay,rSite=rArc.site,cx=rSite.x-ax,cy=rSite.y-ay,d=2*(bx*cy-by*cx),hb=bx*bx+by*by,hc=cx*cx+cy*cy,vertex={x:(cy*hb-by*hc)/d+ax,y:(bx*hc-cx*hb)/d+ay};d3_geom_voronoiSetEdgeEnd(rArc.edge,lSite,rSite,vertex);newArc.edge=d3_geom_voronoiCreateEdge(lSite,site,null,vertex);rArc.edge=d3_geom_voronoiCreateEdge(site,rSite,null,vertex);d3_geom_voronoiAttachCircle(lArc);d3_geom_voronoiAttachCircle(rArc)}function d3_geom_voronoiLeftBreakPoint(arc,directrix){var site=arc.site,rfocx=site.x,rfocy=site.y,pby2=rfocy-directrix;if(!pby2)return rfocx;var lArc=arc.P;if(!lArc)return-Infinity;site=lArc.site;var lfocx=site.x,lfocy=site.y,plby2=lfocy-directrix;if(!plby2)return lfocx;var hl=lfocx-rfocx,aby2=1/pby2-1/plby2,b=hl/plby2;if(aby2)return(-b+Math.sqrt(b*b-2*aby2*(hl*hl/(-2*plby2)-lfocy+plby2/2+rfocy-pby2/2)))/aby2+rfocx;return(rfocx+lfocx)/2}function d3_geom_voronoiRightBreakPoint(arc,directrix){var rArc=arc.N;if(rArc)return d3_geom_voronoiLeftBreakPoint(rArc,directrix);var site=arc.site;return site.y===directrix?site.x:Infinity}function d3_geom_voronoiCell(site){this.site=site;this.edges=[]}d3_geom_voronoiCell.prototype.prepare=function(){var halfEdges=this.edges,iHalfEdge=halfEdges.length,edge;while(iHalfEdge--){edge=halfEdges[iHalfEdge].edge;if(!edge.b||!edge.a)halfEdges.splice(iHalfEdge,1)}halfEdges.sort(d3_geom_voronoiHalfEdgeOrder);return halfEdges.length};function d3_geom_voronoiCloseCells(extent){var x0=extent[0][0],x1=extent[1][0],y0=extent[0][1],y1=extent[1][1],x2,y2,x3,y3,cells=d3_geom_voronoiCells,iCell=cells.length,cell,iHalfEdge,halfEdges,nHalfEdges,start,end;while(iCell--){cell=cells[iCell];if(!cell||!cell.prepare())continue;halfEdges=cell.edges;nHalfEdges=halfEdges.length;iHalfEdge=0;while(iHalfEdgeε||abs(y3-y2)>ε){halfEdges.splice(iHalfEdge,0,new d3_geom_voronoiHalfEdge(d3_geom_voronoiCreateBorderEdge(cell.site,end,abs(x3-x0)<ε&&y1-y3>ε?{x:x0,y:abs(x2-x0)<ε?y2:y1}:abs(y3-y1)<ε&&x1-x3>ε?{x:abs(y2-y1)<ε?x2:x1,y:y1}:abs(x3-x1)<ε&&y3-y0>ε?{x:x1,y:abs(x2-x1)<ε?y2:y0}:abs(y3-y0)<ε&&x3-x0>ε?{x:abs(y2-y0)<ε?x2:x0,y:y0}:null),cell.site,null));++nHalfEdges}}}}function d3_geom_voronoiHalfEdgeOrder(a,b){return b.angle-a.angle}function d3_geom_voronoiCircle(){d3_geom_voronoiRedBlackNode(this);this.x=this.y=this.arc=this.site=this.cy=null}function d3_geom_voronoiAttachCircle(arc){var lArc=arc.P,rArc=arc.N;if(!lArc||!rArc)return;var lSite=lArc.site,cSite=arc.site,rSite=rArc.site;if(lSite===rSite)return;var bx=cSite.x,by=cSite.y,ax=lSite.x-bx,ay=lSite.y-by,cx=rSite.x-bx,cy=rSite.y-by;var d=2*(ax*cy-ay*cx);if(d>=-ε2)return;var ha=ax*ax+ay*ay,hc=cx*cx+cy*cy,x=(cy*ha-ay*hc)/d,y=(ax*hc-cx*ha)/d,cy=y+by;var circle=d3_geom_voronoiCirclePool.pop()||new d3_geom_voronoiCircle;circle.arc=arc;circle.site=cSite;circle.x=x+bx;circle.y=cy+Math.sqrt(x*x+y*y);circle.cy=cy;arc.circle=circle;var before=null,node=d3_geom_voronoiCircles._;while(node){if(circle.y=x1)return;if(lx>rx){if(!va)va={x:fx,y:y0};else if(va.y>=y1)return;vb={x:fx,y:y1}}else{if(!va)va={x:fx,y:y1};else if(va.y1){if(lx>rx){if(!va)va={x:(y0-fb)/fm,y:y0};else if(va.y>=y1)return;vb={x:(y1-fb)/fm,y:y1}}else{if(!va)va={x:(y1-fb)/fm,y:y1};else if(va.y=x1)return;vb={x:x1,y:fm*x1+fb}}else{if(!va)va={x:x1,y:fm*x1+fb};else if(va.x=x0&&site.x<=x1&&site.y>=y0&&site.y<=y1?[[x0,y1],[x1,y1],[x1,y0],[x0,y0]]:[];polygon.point=data[i]});return polygons}function sites(data){return data.map(function(d,i){return{x:Math.round(fx(d,i)/ε)*ε,y:Math.round(fy(d,i)/ε)*ε,i:i}})}voronoi.links=function(data){return d3_geom_voronoi(sites(data)).edges.filter(function(edge){return edge.l&&edge.r}).map(function(edge){return{source:data[edge.l.i],target:data[edge.r.i]}})};voronoi.triangles=function(data){var triangles=[];d3_geom_voronoi(sites(data)).cells.forEach(function(cell,i){var site=cell.site,edges=cell.edges.sort(d3_geom_voronoiHalfEdgeOrder),j=-1,m=edges.length,e0,s0,e1=edges[m-1].edge,s1=e1.l===site?e1.r:e1.l;while(++jx2_)x2_=d.x;if(d.y>y2_)y2_=d.y;xs.push(d.x);ys.push(d.y)}else for(i=0;ix2_)x2_=x_;if(y_>y2_)y2_=y_;xs.push(x_);ys.push(y_)}}var dx=x2_-x1_,dy=y2_-y1_;if(dx>dy)y2_=y1_+dx;else x2_=x1_+dy;function insert(n,d,x,y,x1,y1,x2,y2){if(isNaN(x)||isNaN(y))return;if(n.leaf){var nx=n.x,ny=n.y;if(nx!=null){if(abs(nx-x)+abs(ny-y)<.01){insertChild(n,d,x,y,x1,y1,x2,y2)}else{var nPoint=n.point;n.x=n.y=n.point=null;insertChild(n,nPoint,nx,ny,x1,y1,x2,y2);insertChild(n,d,x,y,x1,y1,x2,y2)}}else{n.x=x,n.y=y,n.point=d}}else{insertChild(n,d,x,y,x1,y1,x2,y2)}}function insertChild(n,d,x,y,x1,y1,x2,y2){var xm=(x1+x2)*.5,ym=(y1+y2)*.5,right=x>=xm,below=y>=ym,i=below<<1|right;n.leaf=false;n=n.nodes[i]||(n.nodes[i]=d3_geom_quadtreeNode());if(right)x1=xm;else x2=xm;if(below)y1=ym;else y2=ym;insert(n,d,x,y,x1,y1,x2,y2)}var root=d3_geom_quadtreeNode();root.add=function(d){insert(root,d,+fx(d,++i),+fy(d,i),x1_,y1_,x2_,y2_)};root.visit=function(f){d3_geom_quadtreeVisit(f,root,x1_,y1_,x2_,y2_)};root.find=function(point){return d3_geom_quadtreeFind(root,point[0],point[1],x1_,y1_,x2_,y2_)};i=-1;if(x1==null){while(++ix3||y1>y3||x2=xm,below=y>=ym;for(var i=below<<1|right,j=i+4;ibi){bs=b.slice(bi,bs);if(s[i])s[i]+=bs;else s[++i]=bs}if((am=am[0])===(bm=bm[0])){if(s[i])s[i]+=bm;else s[++i]=bm}else{s[++i]=null;q.push({i:i,x:d3_interpolateNumber(am,bm)})}bi=d3_interpolate_numberB.lastIndex}if(bi=0&&!(f=d3.interpolators[i](a,b)));return f}d3.interpolators=[function(a,b){var t=typeof b;return(t==="string"?d3_rgb_names.has(b.toLowerCase())||/^(#|rgb\(|hsl\()/i.test(b)?d3_interpolateRgb:d3_interpolateString:b instanceof d3_color?d3_interpolateRgb:Array.isArray(b)?d3_interpolateArray:t==="object"&&isNaN(b)?d3_interpolateObject:d3_interpolateNumber)(a,b)}];d3.interpolateArray=d3_interpolateArray;function d3_interpolateArray(a,b){var x=[],c=[],na=a.length,nb=b.length,n0=Math.min(a.length,b.length),i;for(i=0;i=0?name.slice(0,i):name,m=i>=0?name.slice(i+1):"in";t=d3_ease.get(t)||d3_ease_default;m=d3_ease_mode.get(m)||d3_identity;return d3_ease_clamp(m(t.apply(null,d3_arraySlice.call(arguments,1))))};function d3_ease_clamp(f){return function(t){return t<=0?0:t>=1?1:f(t)}}function d3_ease_reverse(f){return function(t){return 1-f(1-t)}}function d3_ease_reflect(f){return function(t){return.5*(t<.5?f(2*t):2-f(2-2*t))}}function d3_ease_quad(t){return t*t}function d3_ease_cubic(t){return t*t*t}function d3_ease_cubicInOut(t){if(t<=0)return 0;if(t>=1)return 1;var t2=t*t,t3=t2*t;return 4*(t<.5?t3:3*(t-t2)+t3-.75)}function d3_ease_poly(e){return function(t){return Math.pow(t,e)}}function d3_ease_sin(t){return 1-Math.cos(t*halfπ)}function d3_ease_exp(t){return Math.pow(2,10*(t-1))}function d3_ease_circle(t){return 1-Math.sqrt(1-t*t)}function d3_ease_elastic(a,p){var s;if(arguments.length<2)p=.45;if(arguments.length)s=p/τ*Math.asin(1/a);else a=1,s=p/4;return function(t){return 1+a*Math.pow(2,-10*t)*Math.sin((t-s)*τ/p)}}function d3_ease_back(s){if(!s)s=1.70158;return function(t){return t*t*((s+1)*t-s)}}function d3_ease_bounce(t){return t<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375}d3.interpolateHcl=d3_interpolateHcl;function d3_interpolateHcl(a,b){a=d3.hcl(a);b=d3.hcl(b);var ah=a.h,ac=a.c,al=a.l,bh=b.h-ah,bc=b.c-ac,bl=b.l-al;if(isNaN(bc))bc=0,ac=isNaN(ac)?b.c:ac;if(isNaN(bh))bh=0,ah=isNaN(ah)?b.h:ah;else if(bh>180)bh-=360;else if(bh<-180)bh+=360;return function(t){return d3_hcl_lab(ah+bh*t,ac+bc*t,al+bl*t)+""}}d3.interpolateHsl=d3_interpolateHsl;function d3_interpolateHsl(a,b){a=d3.hsl(a);b=d3.hsl(b);var ah=a.h,as=a.s,al=a.l,bh=b.h-ah,bs=b.s-as,bl=b.l-al;if(isNaN(bs))bs=0,as=isNaN(as)?b.s:as;if(isNaN(bh))bh=0,ah=isNaN(ah)?b.h:ah;else if(bh>180)bh-=360;else if(bh<-180)bh+=360;return function(t){return d3_hsl_rgb(ah+bh*t,as+bs*t,al+bl*t)+""}}d3.interpolateLab=d3_interpolateLab;function d3_interpolateLab(a,b){a=d3.lab(a);b=d3.lab(b);var al=a.l,aa=a.a,ab=a.b,bl=b.l-al,ba=b.a-aa,bb=b.b-ab;return function(t){return d3_lab_rgb(al+bl*t,aa+ba*t,ab+bb*t)+""}}d3.interpolateRound=d3_interpolateRound;function d3_interpolateRound(a,b){b-=a;return function(t){return Math.round(a+b*t)}}d3.transform=function(string){var g=d3_document.createElementNS(d3.ns.prefix.svg,"g");return(d3.transform=function(string){if(string!=null){g.setAttribute("transform",string);var t=g.transform.baseVal.consolidate()}return new d3_transform(t?t.matrix:d3_transformIdentity)})(string)};function d3_transform(m){var r0=[m.a,m.b],r1=[m.c,m.d],kx=d3_transformNormalize(r0),kz=d3_transformDot(r0,r1),ky=d3_transformNormalize(d3_transformCombine(r1,r0,-kz))||0;if(r0[0]*r1[1]180)rb+=360;else if(rb-ra>180)ra+=360;q.push({i:s.push(d3_interpolateTransformPop(s)+"rotate(",null,")")-2,x:d3_interpolateNumber(ra,rb)})}else if(rb){s.push(d3_interpolateTransformPop(s)+"rotate("+rb+")")}}function d3_interpolateSkew(wa,wb,s,q){if(wa!==wb){q.push({i:s.push(d3_interpolateTransformPop(s)+"skewX(",null,")")-2,x:d3_interpolateNumber(wa,wb)})}else if(wb){s.push(d3_interpolateTransformPop(s)+"skewX("+wb+")")}}function d3_interpolateScale(ka,kb,s,q){if(ka[0]!==kb[0]||ka[1]!==kb[1]){var i=s.push(d3_interpolateTransformPop(s)+"scale(",null,",",null,")");q.push({i:i-4,x:d3_interpolateNumber(ka[0],kb[0])},{i:i-2,x:d3_interpolateNumber(ka[1],kb[1])})}else if(kb[0]!==1||kb[1]!==1){s.push(d3_interpolateTransformPop(s)+"scale("+kb+")")}}function d3_interpolateTransform(a,b){var s=[],q=[];a=d3.transform(a),b=d3.transform(b);d3_interpolateTranslate(a.translate,b.translate,s,q);d3_interpolateRotate(a.rotate,b.rotate,s,q);d3_interpolateSkew(a.skew,b.skew,s,q);d3_interpolateScale(a.scale,b.scale,s,q);a=b=null;return function(t){var i=-1,n=q.length,o;while(++i0){alpha=x}else{timer.c=null,timer.t=NaN,timer=null;event.end({type:"end",alpha:alpha=0})}}else if(x>0){event.start({type:"start",alpha:alpha=x});timer=d3_timer(force.tick)}return force};force.start=function(){var i,n=nodes.length,m=links.length,w=size[0],h=size[1],neighbors,o;for(i=0;i=0){stack.push(child=childs[n]);child.parent=node;child.depth=node.depth+1}if(value)node.value=0;node.children=childs}else{if(value)node.value=+value.call(hierarchy,node,node.depth)||0;delete node.children}}d3_layout_hierarchyVisitAfter(root,function(node){var childs,parent;if(sort&&(childs=node.children))childs.sort(sort);if(value&&(parent=node.parent))parent.value+=node.value});return nodes}hierarchy.sort=function(x){if(!arguments.length)return sort;sort=x;return hierarchy};hierarchy.children=function(x){if(!arguments.length)return children;children=x;return hierarchy};hierarchy.value=function(x){if(!arguments.length)return value;value=x;return hierarchy};hierarchy.revalue=function(root){if(value){d3_layout_hierarchyVisitBefore(root,function(node){if(node.children)node.value=0});d3_layout_hierarchyVisitAfter(root,function(node){var parent;if(!node.children)node.value=+value.call(hierarchy,node,node.depth)||0;if(parent=node.parent)parent.value+=node.value})}return root};return hierarchy};function d3_layout_hierarchyRebind(object,hierarchy){d3.rebind(object,hierarchy,"sort","children","value");object.nodes=object;object.links=d3_layout_hierarchyLinks;return object}function d3_layout_hierarchyVisitBefore(node,callback){var nodes=[node];while((node=nodes.pop())!=null){callback(node);if((children=node.children)&&(n=children.length)){var n,children;while(--n>=0)nodes.push(children[n])}}}function d3_layout_hierarchyVisitAfter(node,callback){var nodes=[node],nodes2=[];while((node=nodes.pop())!=null){nodes2.push(node);if((children=node.children)&&(n=children.length)){var i=-1,n,children;while(++imax)max=o;sums.push(o)}for(j=0;jv){j=i;v=k}}return j}function d3_layout_stackReduceSum(d){return d.reduce(d3_layout_stackSum,0)}function d3_layout_stackSum(p,d){return p+d[1]}d3.layout.histogram=function(){var frequency=true,valuer=Number,ranger=d3_layout_histogramRange,binner=d3_layout_histogramBinSturges;function histogram(data,i){var bins=[],values=data.map(valuer,this),range=ranger.call(this,values,i),thresholds=binner.call(this,range,values,i),bin,i=-1,n=values.length,m=thresholds.length-1,k=frequency?1:1/n,x;while(++i0){i=-1;while(++i=range[0]&&x<=range[1]){bin=bins[d3.bisect(thresholds,x,1,m)-1];bin.y+=k;bin.push(data[i])}}}return bins}histogram.value=function(x){if(!arguments.length)return valuer;valuer=x;return histogram};histogram.range=function(x){if(!arguments.length)return ranger;ranger=d3_functor(x);return histogram};histogram.bins=function(x){if(!arguments.length)return binner;binner=typeof x==="number"?function(range){return d3_layout_histogramBinFixed(range,x)}:d3_functor(x);return histogram};histogram.frequency=function(x){if(!arguments.length)return frequency;frequency=!!x;return histogram};return histogram};function d3_layout_histogramBinSturges(range,values){return d3_layout_histogramBinFixed(range,Math.ceil(Math.log(values.length)/Math.LN2+1))}function d3_layout_histogramBinFixed(range,n){var x=-1,b=+range[0],m=(range[1]-b)/n,f=[];while(++x<=n)f[x]=m*x+b;return f}function d3_layout_histogramRange(values){return[d3.min(values),d3.max(values)]}d3.layout.pack=function(){var hierarchy=d3.layout.hierarchy().sort(d3_layout_packSort),padding=0,size=[1,1],radius;function pack(d,i){var nodes=hierarchy.call(this,d,i),root=nodes[0],w=size[0],h=size[1],r=radius==null?Math.sqrt:typeof radius==="function"?radius:function(){return radius};root.x=root.y=0;d3_layout_hierarchyVisitAfter(root,function(d){d.r=+r(d.value)});d3_layout_hierarchyVisitAfter(root,d3_layout_packSiblings);if(padding){var dr=padding*(radius?1:Math.max(2*root.r/w,2*root.r/h))/2;d3_layout_hierarchyVisitAfter(root,function(d){d.r+=dr});d3_layout_hierarchyVisitAfter(root,d3_layout_packSiblings);d3_layout_hierarchyVisitAfter(root,function(d){d.r-=dr})}d3_layout_packTransform(root,w/2,h/2,radius?1:1/Math.max(2*root.r/w,2*root.r/h));return nodes}pack.size=function(_){if(!arguments.length)return size;size=_;return pack};pack.radius=function(_){if(!arguments.length)return radius;radius=_==null||typeof _==="function"?_:+_;return pack};pack.padding=function(_){if(!arguments.length)return padding;padding=+_;return pack};return d3_layout_hierarchyRebind(pack,hierarchy)};function d3_layout_packSort(a,b){return a.value-b.value}function d3_layout_packInsert(a,b){var c=a._pack_next;a._pack_next=b;b._pack_prev=a;b._pack_next=c;c._pack_prev=b}function d3_layout_packSplice(a,b){a._pack_next=b;b._pack_prev=a}function d3_layout_packIntersects(a,b){var dx=b.x-a.x,dy=b.y-a.y,dr=a.r+b.r;return.999*dr*dr>dx*dx+dy*dy}function d3_layout_packSiblings(node){if(!(nodes=node.children)||!(n=nodes.length))return;var nodes,xMin=Infinity,xMax=-Infinity,yMin=Infinity,yMax=-Infinity,a,b,c,i,j,k,n;function bound(node){xMin=Math.min(node.x-node.r,xMin);xMax=Math.max(node.x+node.r,xMax);yMin=Math.min(node.y-node.r,yMin);yMax=Math.max(node.y+node.r,yMax)}nodes.forEach(d3_layout_packLink);a=nodes[0];a.x=-a.r;a.y=0;bound(a);if(n>1){b=nodes[1];b.x=b.r;b.y=0;bound(b);if(n>2){c=nodes[2];d3_layout_packPlace(a,b,c);bound(c);d3_layout_packInsert(a,c);a._pack_prev=c;d3_layout_packInsert(c,b);b=a._pack_next;for(i=3;iright.x)right=node;if(node.depth>bottom.depth)bottom=node});var tx=separation(left,right)/2-left.x,kx=size[0]/(right.x+separation(right,left)/2+tx),ky=size[1]/(bottom.depth||1);d3_layout_hierarchyVisitBefore(root0,function(node){node.x=(node.x+tx)*kx;node.y=node.depth*ky})}return nodes}function wrapTree(root0){var root1={A:null,children:[root0]},queue=[root1],node1;while((node1=queue.pop())!=null){for(var children=node1.children,child,i=0,n=children.length;i0){d3_layout_treeMove(d3_layout_treeAncestor(vim,v,ancestor),v,shift);sip+=shift;sop+=shift}sim+=vim.m;sip+=vip.m;som+=vom.m;sop+=vop.m}if(vim&&!d3_layout_treeRight(vop)){vop.t=vim;vop.m+=sim-sop}if(vip&&!d3_layout_treeLeft(vom)){vom.t=vip;vom.m+=sip-som;ancestor=v}}return ancestor}function sizeNode(node){node.x*=size[0];node.y=node.depth*size[1]}tree.separation=function(x){if(!arguments.length)return separation;separation=x;return tree};tree.size=function(x){if(!arguments.length)return nodeSize?null:size;nodeSize=(size=x)==null?sizeNode:null;return tree};tree.nodeSize=function(x){if(!arguments.length)return nodeSize?size:null;nodeSize=(size=x)==null?null:sizeNode;return tree};return d3_layout_hierarchyRebind(tree,hierarchy)};function d3_layout_treeSeparation(a,b){return a.parent==b.parent?1:2}function d3_layout_treeLeft(v){var children=v.children;return children.length?children[0]:v.t}function d3_layout_treeRight(v){var children=v.children,n;return(n=children.length)?children[n-1]:v.t}function d3_layout_treeMove(wm,wp,shift){var change=shift/(wp.i-wm.i);wp.c-=change;wp.s+=shift;wm.c+=change;wp.z+=shift;wp.m+=shift}function d3_layout_treeShift(v){var shift=0,change=0,children=v.children,i=children.length,w;while(--i>=0){w=children[i];w.z+=shift;w.m+=shift;shift+=w.s+(change+=w.c)}}function d3_layout_treeAncestor(vim,v,ancestor){return vim.a.parent===v.parent?vim.a:ancestor}d3.layout.cluster=function(){var hierarchy=d3.layout.hierarchy().sort(null).value(null),separation=d3_layout_treeSeparation,size=[1,1],nodeSize=false;function cluster(d,i){var nodes=hierarchy.call(this,d,i),root=nodes[0],previousNode,x=0;d3_layout_hierarchyVisitAfter(root,function(node){var children=node.children;if(children&&children.length){node.x=d3_layout_clusterX(children);node.y=d3_layout_clusterY(children)}else{node.x=previousNode?x+=separation(node,previousNode):0;node.y=0;previousNode=node}});var left=d3_layout_clusterLeft(root),right=d3_layout_clusterRight(root),x0=left.x-separation(left,right)/2,x1=right.x+separation(right,left)/2;d3_layout_hierarchyVisitAfter(root,nodeSize?function(node){node.x=(node.x-root.x)*size[0];node.y=(root.y-node.y)*size[1]}:function(node){node.x=(node.x-x0)/(x1-x0)*size[0];node.y=(1-(root.y?node.y/root.y:1))*size[1]});return nodes}cluster.separation=function(x){if(!arguments.length)return separation;separation=x;return cluster};cluster.size=function(x){if(!arguments.length)return nodeSize?null:size;nodeSize=(size=x)==null;return cluster};cluster.nodeSize=function(x){if(!arguments.length)return nodeSize?size:null;nodeSize=(size=x)!=null;return cluster};return d3_layout_hierarchyRebind(cluster,hierarchy)};function d3_layout_clusterY(children){return 1+d3.max(children,function(child){return child.y})}function d3_layout_clusterX(children){return children.reduce(function(x,child){return x+child.x},0)/children.length}function d3_layout_clusterLeft(node){var children=node.children;return children&&children.length?d3_layout_clusterLeft(children[0]):node}function d3_layout_clusterRight(node){var children=node.children,n;return children&&(n=children.length)?d3_layout_clusterRight(children[n-1]):node}d3.layout.treemap=function(){var hierarchy=d3.layout.hierarchy(),round=Math.round,size=[1,1],padding=null,pad=d3_layout_treemapPadNull,sticky=false,stickies,mode="squarify",ratio=.5*(1+Math.sqrt(5));function scale(children,k){var i=-1,n=children.length,child,area;while(++i0){row.push(child=remaining[n-1]);row.area+=child.area;if(mode!=="squarify"||(score=worst(row,u))<=best){remaining.pop();best=score}else{row.area-=row.pop().area;position(row,u,rect,false);u=Math.min(rect.dx,rect.dy);row.length=row.area=0;best=Infinity}}if(row.length){position(row,u,rect,true);row.length=row.area=0}children.forEach(squarify)}}function stickify(node){var children=node.children;if(children&&children.length){var rect=pad(node),remaining=children.slice(),child,row=[];scale(remaining,rect.dx*rect.dy/node.value);row.area=0;while(child=remaining.pop()){row.push(child);row.area+=child.area;if(child.z!=null){position(row,child.z?rect.dx:rect.dy,rect,!remaining.length);row.length=row.area=0}}children.forEach(stickify)}}function worst(row,u){var s=row.area,r,rmax=0,rmin=Infinity,i=-1,n=row.length;while(++irmax)rmax=r}s*=s;u*=u;return s?Math.max(u*rmax*ratio/s,s/(u*rmin*ratio)):Infinity}function position(row,u,rect,flush){var i=-1,n=row.length,x=rect.x,y=rect.y,v=u?round(row.area/u):0,o;if(u==rect.dx){if(flush||v>rect.dy)v=rect.dy;while(++irect.dx)v=rect.dx;while(++i1);return µ+σ*x*Math.sqrt(-2*Math.log(r)/r)}},logNormal:function(){var random=d3.random.normal.apply(d3,arguments);return function(){return Math.exp(random())}},bates:function(m){var random=d3.random.irwinHall(m);return function(){return random()/m}},irwinHall:function(m){return function(){for(var s=0,j=0;j2?d3_scale_polylinear:d3_scale_bilinear,uninterpolate=clamp?d3_uninterpolateClamp:d3_uninterpolateNumber;output=linear(domain,range,uninterpolate,interpolate);input=linear(range,domain,uninterpolate,d3_interpolate);return scale}function scale(x){return output(x)}scale.invert=function(y){return input(y)};scale.domain=function(x){if(!arguments.length)return domain;domain=x.map(Number);return rescale()};scale.range=function(x){if(!arguments.length)return range;range=x;return rescale()};scale.rangeRound=function(x){return scale.range(x).interpolate(d3_interpolateRound)};scale.clamp=function(x){if(!arguments.length)return clamp;clamp=x;return rescale()};scale.interpolate=function(x){if(!arguments.length)return interpolate;interpolate=x;return rescale()};scale.ticks=function(m){return d3_scale_linearTicks(domain,m)};scale.tickFormat=function(m,format){return d3_scale_linearTickFormat(domain,m,format)};scale.nice=function(m){d3_scale_linearNice(domain,m);return rescale()};scale.copy=function(){return d3_scale_linear(domain,range,interpolate,clamp)};return rescale()}function d3_scale_linearRebind(scale,linear){return d3.rebind(scale,linear,"range","rangeRound","interpolate","clamp")}function d3_scale_linearNice(domain,m){d3_scale_nice(domain,d3_scale_niceStep(d3_scale_linearTickRange(domain,m)[2]));d3_scale_nice(domain,d3_scale_niceStep(d3_scale_linearTickRange(domain,m)[2]));return domain}function d3_scale_linearTickRange(domain,m){if(m==null)m=10;var extent=d3_scaleExtent(domain),span=extent[1]-extent[0],step=Math.pow(10,Math.floor(Math.log(span/m)/Math.LN10)),err=m/span*step;if(err<=.15)step*=10;else if(err<=.35)step*=5;else if(err<=.75)step*=2;extent[0]=Math.ceil(extent[0]/step)*step;extent[1]=Math.floor(extent[1]/step)*step+step*.5;extent[2]=step;return extent}function d3_scale_linearTicks(domain,m){return d3.range.apply(d3,d3_scale_linearTickRange(domain,m))}function d3_scale_linearTickFormat(domain,m,format){var range=d3_scale_linearTickRange(domain,m);if(format){var match=d3_format_re.exec(format);match.shift();if(match[8]==="s"){var prefix=d3.formatPrefix(Math.max(abs(range[0]),abs(range[1])));if(!match[7])match[7]="."+d3_scale_linearPrecision(prefix.scale(range[2]));match[8]="f";format=d3.format(match.join(""));return function(d){return format(prefix.scale(d))+prefix.symbol}}if(!match[7])match[7]="."+d3_scale_linearFormatPrecision(match[8],range);format=match.join("")}else{format=",."+d3_scale_linearPrecision(range[2])+"f"}return d3.format(format)}var d3_scale_linearFormatSignificant={s:1,g:1,p:1,r:1,e:1};function d3_scale_linearPrecision(value){return-Math.floor(Math.log(value)/Math.LN10+.01)}function d3_scale_linearFormatPrecision(type,range){var p=d3_scale_linearPrecision(range[2]);return type in d3_scale_linearFormatSignificant?Math.abs(p-d3_scale_linearPrecision(Math.max(abs(range[0]),abs(range[1]))))+ +(type!=="e"):p-(type==="%")*2}d3.scale.log=function(){return d3_scale_log(d3.scale.linear().domain([0,1]),10,true,[1,10])};function d3_scale_log(linear,base,positive,domain){function log(x){return(positive?Math.log(x<0?0:x):-Math.log(x>0?0:-x))/Math.log(base)}function pow(x){return positive?Math.pow(base,x):-Math.pow(base,-x)}function scale(x){return linear(log(x))}scale.invert=function(x){return pow(linear.invert(x))};scale.domain=function(x){if(!arguments.length)return domain;positive=x[0]>=0;linear.domain((domain=x.map(Number)).map(log));return scale};scale.base=function(_){if(!arguments.length)return base;base=+_;linear.domain(domain.map(log));return scale};scale.nice=function(){var niced=d3_scale_nice(domain.map(log),positive?Math:d3_scale_logNiceNegative);linear.domain(niced);domain=niced.map(pow);return scale};scale.ticks=function(){var extent=d3_scaleExtent(domain),ticks=[],u=extent[0],v=extent[1],i=Math.floor(log(u)),j=Math.ceil(log(v)),n=base%1?2:base;if(isFinite(j-i)){if(positive){for(;i0;k--)ticks.push(pow(i)*k)}for(i=0;ticks[i]v;j--){}ticks=ticks.slice(i,j)}return ticks};scale.tickFormat=function(n,format){if(!arguments.length)return d3_scale_logFormat;if(arguments.length<2)format=d3_scale_logFormat;else if(typeof format!=="function")format=d3.format(format);var k=Math.max(1,base*n/scale.ticks().length);return function(d){var i=d/pow(Math.round(log(d)));if(i*base0?thresholds[y-1]:domain[0],ya1?0:1;if(r1=τε)return circleSegment(r1,cw)+(r0?circleSegment(r0,1-cw):"")+"Z";var rc,cr,rp,ap,p0=0,p1=0,x0,y0,x1,y1,x2,y2,x3,y3,path=[];if(ap=(+padAngle.apply(this,arguments)||0)/2){rp=padRadius===d3_svg_arcAuto?Math.sqrt(r0*r0+r1*r1):+padRadius.apply(this,arguments);if(!cw)p1*=-1;if(r1)p1=d3_asin(rp/r1*Math.sin(ap));if(r0)p0=d3_asin(rp/r0*Math.sin(ap))}if(r1){x0=r1*Math.cos(a0+p1);y0=r1*Math.sin(a0+p1);x1=r1*Math.cos(a1-p1);y1=r1*Math.sin(a1-p1);var l1=Math.abs(a1-a0-2*p1)<=π?0:1;if(p1&&d3_svg_arcSweep(x0,y0,x1,y1)===cw^l1){var h1=(a0+a1)/2;x0=r1*Math.cos(h1);y0=r1*Math.sin(h1);x1=y1=null}}else{x0=y0=0}if(r0){x2=r0*Math.cos(a1-p0);y2=r0*Math.sin(a1-p0);x3=r0*Math.cos(a0+p0);y3=r0*Math.sin(a0+p0);var l0=Math.abs(a0-a1+2*p0)<=π?0:1;if(p0&&d3_svg_arcSweep(x2,y2,x3,y3)===1-cw^l0){var h0=(a0+a1)/2;x2=r0*Math.cos(h0);y2=r0*Math.sin(h0);x3=y3=null}}else{x2=y2=0}if(da>ε&&(rc=Math.min(Math.abs(r1-r0)/2,+cornerRadius.apply(this,arguments)))>.001){cr=r00?0:1}function d3_svg_arcCornerTangents(p0,p1,r1,rc,cw){var x01=p0[0]-p1[0],y01=p0[1]-p1[1],lo=(cw?rc:-rc)/Math.sqrt(x01*x01+y01*y01),ox=lo*y01,oy=-lo*x01,x1=p0[0]+ox,y1=p0[1]+oy,x2=p1[0]+ox,y2=p1[1]+oy,x3=(x1+x2)/2,y3=(y1+y2)/2,dx=x2-x1,dy=y2-y1,d2=dx*dx+dy*dy,r=r1-rc,D=x1*y2-x2*y1,d=(dy<0?-1:1)*Math.sqrt(Math.max(0,r*r*d2-D*D)),cx0=(D*dy-dx*d)/d2,cy0=(-D*dx-dy*d)/d2,cx1=(D*dy+dx*d)/d2,cy1=(-D*dx+dy*d)/d2,dx0=cx0-x3,dy0=cy0-y3,dx1=cx1-x3,dy1=cy1-y3;if(dx0*dx0+dy0*dy0>dx1*dx1+dy1*dy1)cx0=cx1,cy0=cy1;return[[cx0-ox,cy0-oy],[cx0*r1/r,cy0*r1/r]]}function d3_svg_line(projection){var x=d3_geom_pointX,y=d3_geom_pointY,defined=d3_true,interpolate=d3_svg_lineLinear,interpolateKey=interpolate.key,tension=.7;function line(data){var segments=[],points=[],i=-1,n=data.length,d,fx=d3_functor(x),fy=d3_functor(y);function segment(){segments.push("M",interpolate(projection(points),tension))}while(++i1?points.join("L"):points+"Z"}function d3_svg_lineLinearClosed(points){return points.join("L")+"Z"}function d3_svg_lineStep(points){var i=0,n=points.length,p=points[0],path=[p[0],",",p[1]];while(++i1)path.push("H",p[0]);return path.join("")}function d3_svg_lineStepBefore(points){var i=0,n=points.length,p=points[0],path=[p[0],",",p[1]];while(++i1){t=tangents[1];p=points[pi];pi++;path+="C"+(p0[0]+t0[0])+","+(p0[1]+t0[1])+","+(p[0]-t[0])+","+(p[1]-t[1])+","+p[0]+","+p[1];for(var i=2;i9){s=d*3/Math.sqrt(s);m[i]=s*a;m[i+1]=s*b}}}i=-1;while(++i<=j){s=(points[Math.min(j,i+1)][0]-points[Math.max(0,i-1)][0])/(6*(1+m[i]*m[i]));tangents.push([s||0,m[i]*s||0])}return tangents}function d3_svg_lineMonotone(points){return points.length<3?d3_svg_lineLinear(points):points[0]+d3_svg_lineHermite(points,d3_svg_lineMonotoneTangents(points))}d3.svg.line.radial=function(){var line=d3_svg_line(d3_svg_lineRadial);line.radius=line.x,delete line.x;line.angle=line.y,delete line.y;return line};function d3_svg_lineRadial(points){var point,i=-1,n=points.length,r,a;while(++iπ)+",1 "+p}function curve(r0,p0,r1,p1){return"Q 0,0 "+p1}chord.radius=function(v){if(!arguments.length)return radius;radius=d3_functor(v);return chord};chord.source=function(v){if(!arguments.length)return source;source=d3_functor(v);return chord};chord.target=function(v){if(!arguments.length)return target;target=d3_functor(v);return chord};chord.startAngle=function(v){if(!arguments.length)return startAngle;startAngle=d3_functor(v);return chord};chord.endAngle=function(v){if(!arguments.length)return endAngle;endAngle=d3_functor(v);return chord};return chord};function d3_svg_chordRadius(d){return d.radius}d3.svg.diagonal=function(){var source=d3_source,target=d3_target,projection=d3_svg_diagonalProjection;function diagonal(d,i){var p0=source.call(this,d,i),p3=target.call(this,d,i),m=(p0.y+p3.y)/2,p=[p0,{x:p0.x,y:m},{x:p3.x,y:m},p3];p=p.map(projection);return"M"+p[0]+"C"+p[1]+" "+p[2]+" "+p[3]}diagonal.source=function(x){if(!arguments.length)return source;source=d3_functor(x);return diagonal};diagonal.target=function(x){if(!arguments.length)return target;target=d3_functor(x);return diagonal};diagonal.projection=function(x){if(!arguments.length)return projection;projection=x;return diagonal};return diagonal};function d3_svg_diagonalProjection(d){return[d.x,d.y]}d3.svg.diagonal.radial=function(){var diagonal=d3.svg.diagonal(),projection=d3_svg_diagonalProjection,projection_=diagonal.projection;diagonal.projection=function(x){return arguments.length?projection_(d3_svg_diagonalRadialProjection(projection=x)):projection};return diagonal};function d3_svg_diagonalRadialProjection(projection){return function(){var d=projection.apply(this,arguments),r=d[0],a=d[1]-halfπ;return[r*Math.cos(a),r*Math.sin(a)]}}d3.svg.symbol=function(){var type=d3_svg_symbolType,size=d3_svg_symbolSize;function symbol(d,i){return(d3_svg_symbols.get(type.call(this,d,i))||d3_svg_symbolCircle)(size.call(this,d,i))}symbol.type=function(x){if(!arguments.length)return type;type=d3_functor(x);return symbol};symbol.size=function(x){if(!arguments.length)return size;size=d3_functor(x);return symbol};return symbol};function d3_svg_symbolSize(){return 64}function d3_svg_symbolType(){return"circle"}function d3_svg_symbolCircle(size){var r=Math.sqrt(size/π);return"M0,"+r+"A"+r+","+r+" 0 1,1 0,"+-r+"A"+r+","+r+" 0 1,1 0,"+r+"Z"}var d3_svg_symbols=d3.map({circle:d3_svg_symbolCircle,cross:function(size){var r=Math.sqrt(size/5)/2;return"M"+-3*r+","+-r+"H"+-r+"V"+-3*r+"H"+r+"V"+-r+"H"+3*r+"V"+r+"H"+r+"V"+3*r+"H"+-r+"V"+r+"H"+-3*r+"Z"},diamond:function(size){var ry=Math.sqrt(size/(2*d3_svg_symbolTan30)),rx=ry*d3_svg_symbolTan30;return"M0,"+-ry+"L"+rx+",0"+" 0,"+ry+" "+-rx+",0"+"Z"},square:function(size){var r=Math.sqrt(size)/2;return"M"+-r+","+-r+"L"+r+","+-r+" "+r+","+r+" "+-r+","+r+"Z"},"triangle-down":function(size){var rx=Math.sqrt(size/d3_svg_symbolSqrt3),ry=rx*d3_svg_symbolSqrt3/2;return"M0,"+ry+"L"+rx+","+-ry+" "+-rx+","+-ry+"Z"},"triangle-up":function(size){var rx=Math.sqrt(size/d3_svg_symbolSqrt3),ry=rx*d3_svg_symbolSqrt3/2;return"M0,"+-ry+"L"+rx+","+ry+" "+-rx+","+ry+"Z"}});d3.svg.symbolTypes=d3_svg_symbols.keys();var d3_svg_symbolSqrt3=Math.sqrt(3),d3_svg_symbolTan30=Math.tan(30*d3_radians);d3_selectionPrototype.transition=function(name){var id=d3_transitionInheritId||++d3_transitionId,ns=d3_transitionNamespace(name),subgroups=[],subgroup,node,transition=d3_transitionInherit||{time:Date.now(),ease:d3_ease_cubicInOut,delay:0,duration:250};for(var j=-1,m=this.length;++j0){tweens[--n].call(node,e)}if(t>=1){transition.event&&transition.event.end.call(node,node.__data__,i);if(--lock.count)delete lock[id];else delete node[ns];return 1}}if(!transition){time=inherit.time;timer=d3_timer(schedule,0,time);transition=lock[id]={tween:new d3_Map,time:time,timer:timer,delay:inherit.delay,duration:inherit.duration,ease:inherit.ease,index:i};inherit=null;++lock.count}}d3.svg.axis=function(){var scale=d3.scale.linear(),orient=d3_svg_axisDefaultOrient,innerTickSize=6,outerTickSize=6,tickPadding=3,tickArguments_=[10],tickValues=null,tickFormat_;function axis(g){g.each(function(){var g=d3.select(this);var scale0=this.__chart__||scale,scale1=this.__chart__=scale.copy();var ticks=tickValues==null?scale1.ticks?scale1.ticks.apply(scale1,tickArguments_):scale1.domain():tickValues,tickFormat=tickFormat_==null?scale1.tickFormat?scale1.tickFormat.apply(scale1,tickArguments_):d3_identity:tickFormat_,tick=g.selectAll(".tick").data(ticks,scale1),tickEnter=tick.enter().insert("g",".domain").attr("class","tick").style("opacity",ε),tickExit=d3.transition(tick.exit()).style("opacity",ε).remove(),tickUpdate=d3.transition(tick.order()).style("opacity",1),tickSpacing=Math.max(innerTickSize,0)+tickPadding,tickTransform;var range=d3_scaleRange(scale1),path=g.selectAll(".domain").data([0]),pathUpdate=(path.enter().append("path").attr("class","domain"),d3.transition(path));tickEnter.append("line");tickEnter.append("text");var lineEnter=tickEnter.select("line"),lineUpdate=tickUpdate.select("line"),text=tick.select("text").text(tickFormat),textEnter=tickEnter.select("text"),textUpdate=tickUpdate.select("text"),sign=orient==="top"||orient==="left"?-1:1,x1,x2,y1,y2;if(orient==="bottom"||orient==="top"){tickTransform=d3_svg_axisX,x1="x",y1="y",x2="x2",y2="y2";text.attr("dy",sign<0?"0em":".71em").style("text-anchor","middle");pathUpdate.attr("d","M"+range[0]+","+sign*outerTickSize+"V0H"+range[1]+"V"+sign*outerTickSize)}else{tickTransform=d3_svg_axisY,x1="y",y1="x",x2="y2",y2="x2";text.attr("dy",".32em").style("text-anchor",sign<0?"end":"start");pathUpdate.attr("d","M"+sign*outerTickSize+","+range[0]+"H0V"+range[1]+"H"+sign*outerTickSize)}lineEnter.attr(y2,sign*innerTickSize);textEnter.attr(y1,sign*tickSpacing);lineUpdate.attr(x2,0).attr(y2,sign*innerTickSize);textUpdate.attr(x1,0).attr(y1,sign*tickSpacing);if(scale1.rangeBand){var x=scale1,dx=x.rangeBand()/2;scale0=scale1=function(d){return x(d)+dx}}else if(scale0.rangeBand){scale0=scale1}else{tickExit.call(tickTransform,scale1,scale0)}tickEnter.call(tickTransform,scale0,scale1);tickUpdate.call(tickTransform,scale1,scale1)})}axis.scale=function(x){if(!arguments.length)return scale;scale=x;return axis};axis.orient=function(x){if(!arguments.length)return orient;orient=x in d3_svg_axisOrients?x+"":d3_svg_axisDefaultOrient;return axis};axis.ticks=function(){if(!arguments.length)return tickArguments_;tickArguments_=d3_array(arguments);return axis};axis.tickValues=function(x){if(!arguments.length)return tickValues;tickValues=x;return axis};axis.tickFormat=function(x){if(!arguments.length)return tickFormat_;tickFormat_=x;return axis};axis.tickSize=function(x){var n=arguments.length;if(!n)return innerTickSize;innerTickSize=+x;outerTickSize=+arguments[n-1];return axis};axis.innerTickSize=function(x){if(!arguments.length)return innerTickSize;innerTickSize=+x;return axis};axis.outerTickSize=function(x){if(!arguments.length)return outerTickSize;outerTickSize=+x;return axis};axis.tickPadding=function(x){if(!arguments.length)return tickPadding;tickPadding=+x;return axis};axis.tickSubdivide=function(){return arguments.length&&axis};return axis};var d3_svg_axisDefaultOrient="bottom",d3_svg_axisOrients={top:1,right:1,bottom:1,left:1};function d3_svg_axisX(selection,x0,x1){selection.attr("transform",function(d){var v0=x0(d);return"translate("+(isFinite(v0)?v0:x1(d))+",0)"})}function d3_svg_axisY(selection,y0,y1){selection.attr("transform",function(d){var v0=y0(d);return"translate(0,"+(isFinite(v0)?v0:y1(d))+")"})}d3.svg.brush=function(){var event=d3_eventDispatch(brush,"brushstart","brush","brushend"),x=null,y=null,xExtent=[0,0],yExtent=[0,0],xExtentDomain,yExtentDomain,xClamp=true,yClamp=true,resizes=d3_svg_brushResizes[0];function brush(g){g.each(function(){var g=d3.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",brushstart).on("touchstart.brush",brushstart);var background=g.selectAll(".background").data([0]);background.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair");g.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var resize=g.selectAll(".resize").data(resizes,d3_identity);resize.exit().remove();resize.enter().append("g").attr("class",function(d){return"resize "+d}).style("cursor",function(d){return d3_svg_brushCursor[d]}).append("rect").attr("x",function(d){return/[ew]$/.test(d)?-3:null}).attr("y",function(d){return/^[ns]/.test(d)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden");resize.style("display",brush.empty()?"none":null);var gUpdate=d3.transition(g),backgroundUpdate=d3.transition(background),range;if(x){range=d3_scaleRange(x);backgroundUpdate.attr("x",range[0]).attr("width",range[1]-range[0]);redrawX(gUpdate)}if(y){range=d3_scaleRange(y);backgroundUpdate.attr("y",range[0]).attr("height",range[1]-range[0]);redrawY(gUpdate)}redraw(gUpdate)})}brush.event=function(g){g.each(function(){var event_=event.of(this,arguments),extent1={x:xExtent,y:yExtent,i:xExtentDomain,j:yExtentDomain},extent0=this.__chart__||extent1;this.__chart__=extent1;if(d3_transitionInheritId){d3.select(this).transition().each("start.brush",function(){xExtentDomain=extent0.i;yExtentDomain=extent0.j;xExtent=extent0.x;yExtent=extent0.y;event_({type:"brushstart"})}).tween("brush:brush",function(){var xi=d3_interpolateArray(xExtent,extent1.x),yi=d3_interpolateArray(yExtent,extent1.y);xExtentDomain=yExtentDomain=null;return function(t){xExtent=extent1.x=xi(t);yExtent=extent1.y=yi(t);event_({type:"brush",mode:"resize"})}}).each("end.brush",function(){xExtentDomain=extent1.i;yExtentDomain=extent1.j;event_({type:"brush",mode:"resize"});event_({type:"brushend"})})}else{event_({type:"brushstart"});event_({type:"brush",mode:"resize"});event_({type:"brushend"})}})};function redraw(g){g.selectAll(".resize").attr("transform",function(d){return"translate("+xExtent[+/e$/.test(d)]+","+yExtent[+/^s/.test(d)]+")"})}function redrawX(g){g.select(".extent").attr("x",xExtent[0]);g.selectAll(".extent,.n>rect,.s>rect").attr("width",xExtent[1]-xExtent[0])}function redrawY(g){g.select(".extent").attr("y",yExtent[0]);g.selectAll(".extent,.e>rect,.w>rect").attr("height",yExtent[1]-yExtent[0])}function brushstart(){var target=this,eventTarget=d3.select(d3.event.target),event_=event.of(target,arguments),g=d3.select(target),resizing=eventTarget.datum(),resizingX=!/^(n|s)$/.test(resizing)&&x,resizingY=!/^(e|w)$/.test(resizing)&&y,dragging=eventTarget.classed("extent"),dragRestore=d3_event_dragSuppress(target),center,origin=d3.mouse(target),offset;var w=d3.select(d3_window(target)).on("keydown.brush",keydown).on("keyup.brush",keyup);if(d3.event.changedTouches){w.on("touchmove.brush",brushmove).on("touchend.brush",brushend)}else{w.on("mousemove.brush",brushmove).on("mouseup.brush",brushend)}g.interrupt().selectAll("*").interrupt();if(dragging){origin[0]=xExtent[0]-origin[0];origin[1]=yExtent[0]-origin[1]}else if(resizing){var ex=+/w$/.test(resizing),ey=+/^n/.test(resizing);offset=[xExtent[1-ex]-origin[0],yExtent[1-ey]-origin[1]];origin[0]=xExtent[ex];origin[1]=yExtent[ey]}else if(d3.event.altKey)center=origin.slice();g.style("pointer-events","none").selectAll(".resize").style("display",null);d3.select("body").style("cursor",eventTarget.style("cursor"));event_({type:"brushstart"});brushmove();function keydown(){if(d3.event.keyCode==32){if(!dragging){center=null;origin[0]-=xExtent[1];origin[1]-=yExtent[1];dragging=2}d3_eventPreventDefault()}}function keyup(){if(d3.event.keyCode==32&&dragging==2){origin[0]+=xExtent[1];origin[1]+=yExtent[1];dragging=0;d3_eventPreventDefault()}}function brushmove(){var point=d3.mouse(target),moved=false;if(offset){point[0]+=offset[0];point[1]+=offset[1]}if(!dragging){if(d3.event.altKey){if(!center)center=[(xExtent[0]+xExtent[1])/2,(yExtent[0]+yExtent[1])/2];origin[0]=xExtent[+(point[0]1?{floor:function(date){while(skipped(date=interval.floor(date)))date=d3_time_scaleDate(date-1);return date},ceil:function(date){while(skipped(date=interval.ceil(date)))date=d3_time_scaleDate(+date+1);return date}}:interval))};scale.ticks=function(interval,skip){var extent=d3_scaleExtent(scale.domain()),method=interval==null?tickMethod(extent,10):typeof interval==="number"?tickMethod(extent,interval):!interval.range&&[{range:interval},skip];if(method)interval=method[0],skip=method[1];return interval.range(extent[0],d3_time_scaleDate(+extent[1]+1),skip<1?1:skip)};scale.tickFormat=function(){return format};scale.copy=function(){return d3_time_scale(linear.copy(),methods,format)};return d3_scale_linearRebind(scale,linear)}function d3_time_scaleDate(t){return new Date(t)}var d3_time_scaleSteps=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6];var d3_time_scaleLocalMethods=[[d3_time.second,1],[d3_time.second,5],[d3_time.second,15],[d3_time.second,30],[d3_time.minute,1],[d3_time.minute,5],[d3_time.minute,15],[d3_time.minute,30],[d3_time.hour,1],[d3_time.hour,3],[d3_time.hour,6],[d3_time.hour,12],[d3_time.day,1],[d3_time.day,2],[d3_time.week,1],[d3_time.month,1],[d3_time.month,3],[d3_time.year,1]];var d3_time_scaleLocalFormat=d3_time_format.multi([[".%L",function(d){return d.getMilliseconds()}],[":%S",function(d){return d.getSeconds()}],["%I:%M",function(d){return d.getMinutes()}],["%I %p",function(d){return d.getHours()}],["%a %d",function(d){return d.getDay()&&d.getDate()!=1}],["%b %d",function(d){return d.getDate()!=1}],["%B",function(d){return d.getMonth()}],["%Y",d3_true]]);var d3_time_scaleMilliseconds={range:function(start,stop,step){return d3.range(Math.ceil(start/step)*step,+stop,step).map(d3_time_scaleDate)},floor:d3_identity,ceil:d3_identity};d3_time_scaleLocalMethods.year=d3_time.year;d3_time.scale=function(){return d3_time_scale(d3.scale.linear(),d3_time_scaleLocalMethods,d3_time_scaleLocalFormat)};var d3_time_scaleUtcMethods=d3_time_scaleLocalMethods.map(function(m){return[m[0].utc,m[1]]});var d3_time_scaleUtcFormat=d3_time_formatUtc.multi([[".%L",function(d){return d.getUTCMilliseconds()}],[":%S",function(d){return d.getUTCSeconds()}],["%I:%M",function(d){return d.getUTCMinutes()}],["%I %p",function(d){return d.getUTCHours()}],["%a %d",function(d){return d.getUTCDay()&&d.getUTCDate()!=1}],["%b %d",function(d){return d.getUTCDate()!=1}],["%B",function(d){return d.getUTCMonth()}],["%Y",d3_true]]);d3_time_scaleUtcMethods.year=d3_time.year.utc;d3_time.scale.utc=function(){return d3_time_scale(d3.scale.linear(),d3_time_scaleUtcMethods,d3_time_scaleUtcFormat)};d3.text=d3_xhrType(function(request){return request.responseText});d3.json=function(url,callback){return d3_xhr(url,"application/json",d3_json,callback)};function d3_json(request){return JSON.parse(request.responseText)}d3.html=function(url,callback){return d3_xhr(url,"text/html",d3_html,callback)};function d3_html(request){var range=d3_document.createRange();range.selectNode(d3_document.body);return range.createContextualFragment(request.responseText)}d3.xml=d3_xhrType(function(request){return request.responseXML});if(typeof define==="function"&&define.amd)this.d3=d3,define(d3);else if(typeof module==="object"&&module.exports)module.exports=d3;else this.d3=d3}(); \ No newline at end of file diff --git a/config/plugins/visualizations/aequatus/templates/aequatus.mako b/config/plugins/visualizations/aequatus/templates/aequatus.mako index 9233b9dc912..2b8e394788b 100755 --- a/config/plugins/visualizations/aequatus/templates/aequatus.mako +++ b/config/plugins/visualizations/aequatus/templates/aequatus.mako @@ -20,10 +20,7 @@ ${h.javascript_link( app_root + "aequatus-vis/scripts/jquery/js/jquery.svg.js" )} ${h.javascript_link( app_root + "aequatus-vis/scripts/jquery/js/jquery-migrate-1.4.1.min.js" )} ${h.javascript_link( app_root + "aequatus-vis/scripts/scriptaculous/prototype.js" )} - - -## install shared libraries - ${h.js( 'libs/d3')} + ${h.javascript_link( app_root + "scripts/d3.min.js" )} ## aequatus-vis ${h.javascript_link( app_root + "aequatus-vis/scripts/init.js" )} diff --git a/config/plugins/visualizations/nvd3/nvd3_bar/package.json b/config/plugins/visualizations/nvd3/nvd3_bar/package.json index d06fb73112b..9fdf7567c94 100644 --- a/config/plugins/visualizations/nvd3/nvd3_bar/package.json +++ b/config/plugins/visualizations/nvd3/nvd3_bar/package.json @@ -20,5 +20,8 @@ }, "scripts": { "build": "parcel build src/nvd3.js -d static" + }, + "devDependencies": { + "babel-core": "^6.26.3" } } diff --git a/config/plugins/visualizations/nvd3/nvd3_bar/src/nvd3.js b/config/plugins/visualizations/nvd3/nvd3_bar/src/nvd3.js index a3e227cf3cf..aa33cfa2f3c 100644 --- a/config/plugins/visualizations/nvd3/nvd3_bar/src/nvd3.js +++ b/config/plugins/visualizations/nvd3/nvd3_bar/src/nvd3.js @@ -109,7 +109,6 @@ var PieWrapper = Backbone.View.extend({ dataset_id: chart.get("dataset_id"), dataset_groups: chart.groups, success: function(groups) { - window.console.log(groups); for (var group_index in groups) { var group = groups[group_index]; self._drawGroup(chart, group, targets[group_index]); @@ -132,9 +131,9 @@ var PieWrapper = Backbone.View.extend({ pie_data.push({ y: value.y, x: value.label }); }); nv.addGraph(function() { - var legend_visible = chart.settings.get("show_legend") == "true"; - var label_outside = chart.settings.get("label_outside") == "true"; - var label_type = chart.settings.get("label_type"); + var legend_visible = chart.settings.get("legend_visible") == "true"; + var label_outside = chart.settings.get("label|outside") == "true"; + var label_type = chart.settings.get("label|type"); var donut_ratio = parseFloat(chart.settings.get("donut_ratio")); var chart_3d = nv.models .pieChart() diff --git a/config/plugins/visualizations/nvd3/nvd3_bar/static/nvd3.css b/config/plugins/visualizations/nvd3/nvd3_bar/static/nvd3.css index 08dbaafea5b..c3597c37781 100644 --- a/config/plugins/visualizations/nvd3/nvd3_bar/static/nvd3.css +++ b/config/plugins/visualizations/nvd3/nvd3_bar/static/nvd3.css @@ -1 +1 @@ -.nvd3 .nv-axis{pointer-events:none;opacity:1}.nvd3 .nv-axis path{fill:none;stroke:#000;stroke-opacity:.75;shape-rendering:crispEdges}.nvd3 .nv-axis path.domain{stroke-opacity:.75}.nvd3 .nv-axis.nv-x path.domain{stroke-opacity:0}.nvd3 .nv-axis line{fill:none;stroke:#e5e5e5;shape-rendering:crispEdges}.nvd3 .nv-axis .zero line,.nvd3 .nv-axis line.zero{stroke-opacity:.75}.nvd3 .nv-axis .nv-axisMaxMin text{font-weight:700}.nvd3 .x2 .nv-axis .nv-axisMaxMin text,.nvd3 .x3 .nv-axis .nv-axisMaxMin text,.nvd3 .x .nv-axis .nv-axisMaxMin text{text-anchor:middle}.nvd3 .nv-axis.nv-disabled{opacity:0}.nvd3 .nv-bars rect{fill-opacity:.75;transition:fill-opacity .25s linear}.nvd3 .nv-bars rect.hover{fill-opacity:1}.nvd3 .nv-bars .hover rect{fill:#add8e6}.nvd3 .nv-bars text{fill:transparent}.nvd3 .nv-bars .hover text{fill:#000}.nvd3 .nv-discretebar .nv-groups rect,.nvd3 .nv-multibar .nv-groups rect,.nvd3 .nv-multibarHorizontal .nv-groups rect{stroke-opacity:0;transition:fill-opacity .25s linear}.nvd3 .nv-candlestickBar .nv-ticks rect:hover,.nvd3 .nv-discretebar .nv-groups rect:hover,.nvd3 .nv-multibar .nv-groups rect:hover,.nvd3 .nv-multibarHorizontal .nv-groups rect:hover{fill-opacity:1}.nvd3 .nv-discretebar .nv-groups text,.nvd3 .nv-multibarHorizontal .nv-groups text{font-weight:700;fill:#000;stroke:transparent}.nvd3 .nv-boxplot circle{fill-opacity:.5}.nvd3 .nv-boxplot circle:hover,.nvd3 .nv-boxplot rect:hover{fill-opacity:1}.nvd3 line.nv-boxplot-median{stroke:#000}.nv-boxplot-tick:hover{stroke-width:2.5px}.nvd3.nv-bullet{font:10px sans-serif}.nvd3.nv-bullet .nv-measure{fill-opacity:.8}.nvd3.nv-bullet .nv-measure:hover{fill-opacity:1}.nvd3.nv-bullet .nv-marker{stroke:#000;stroke-width:2px}.nvd3.nv-bullet .nv-markerTriangle{stroke:#000;fill:#fff;stroke-width:1.5px}.nvd3.nv-bullet .nv-markerLine{stroke:#000;stroke-width:1.5px}.nvd3.nv-bullet .nv-tick line{stroke:#666;stroke-width:.5px}.nvd3.nv-bullet .nv-range.nv-s0{fill:#eee}.nvd3.nv-bullet .nv-range.nv-s1{fill:#ddd}.nvd3.nv-bullet .nv-range.nv-s2{fill:#ccc}.nvd3.nv-bullet .nv-title{font-size:14px;font-weight:700}.nvd3.nv-bullet .nv-subtitle{fill:#999}.nvd3.nv-bullet .nv-range{fill:#bababa;fill-opacity:.4}.nvd3.nv-bullet .nv-range:hover{fill-opacity:.7}.nvd3.nv-candlestickBar .nv-ticks .nv-tick{stroke-width:1px}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.hover{stroke-width:2px}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.positive rect{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.negative rect{stroke:#d62728;fill:#d62728}.with-transitions .nv-candlestickBar .nv-ticks .nv-tick{transition:stroke-width .25s linear,stroke-opacity .25s linear}.nvd3.nv-candlestickBar .nv-ticks line{stroke:#333}.nv-force-node{stroke:#fff;stroke-width:1.5px}.nv-force-link{stroke:#999;stroke-opacity:.6}.nv-force-node text{stroke-width:0px}.nvd3 .nv-check-box .nv-box{fill-opacity:0;stroke-width:2}.nvd3 .nv-check-box .nv-check{fill-opacity:0;stroke-width:4}.nvd3 .nv-series.nv-disabled .nv-check-box .nv-check{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-controlsWrap .nv-legend .nv-check-box .nv-check{opacity:0}.nvd3.nv-linePlusBar .nv-bar rect{fill-opacity:.75}.nvd3.nv-linePlusBar .nv-bar rect:hover{fill-opacity:1}.nvd3 .nv-groups path.nv-line{fill:none}.nvd3 .nv-groups path.nv-area{stroke:none}.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point{fill-opacity:0;stroke-opacity:0}.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point{fill-opacity:.5!important;stroke-opacity:.5!important}.with-transitions .nvd3 .nv-groups .nv-point{transition:stroke-width .25s linear,stroke-opacity .25s linear}.nvd3 .nv-groups .nv-point.hover,.nvd3.nv-scatter .nv-groups .nv-point.hover{stroke-width:7px;fill-opacity:.95!important;stroke-opacity:.95!important}.nvd3 .nv-point-paths path{stroke:#aaa;stroke-opacity:0;fill:#eee;fill-opacity:0}.nvd3 .nv-indexLine{cursor:ew-resize}svg.nvd3-svg{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;display:block;width:100%;height:100%}.nvtooltip.with-3d-shadow,.with-3d-shadow .nvtooltip{box-shadow:0 5px 10px rgba(0,0,0,.2);border-radius:5px}.nvd3 text{font:normal 12px Arial,sans-serif}.nvd3 .title{font:700 14px Arial,sans-serif}.nvd3 .nv-background{fill:#fff;fill-opacity:0}.nvd3.nv-noData{font-size:18px;font-weight:700}.nv-brush .extent{fill-opacity:.125;shape-rendering:crispEdges}.nv-brush .resize path{fill:#eee;stroke:#666}.nvd3 .nv-legend .nv-series{cursor:pointer}.nvd3 .nv-legend .nv-disabled circle{fill-opacity:0}.nvd3 .nv-brush .extent{fill-opacity:0!important}.nvd3 .nv-brushBackground rect{stroke:#000;stroke-width:.4;fill:#fff;fill-opacity:.7}@media print{.nvd3 text{stroke-width:0;fill-opacity:1}}.nvd3.nv-ohlcBar .nv-ticks .nv-tick{stroke-width:1px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover{stroke-width:2px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive{stroke:#2ca02c}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative{stroke:#d62728}.nvd3 .background path{fill:none;stroke:#eee;stroke-opacity:.4;shape-rendering:crispEdges}.nvd3 .foreground path{fill:none;stroke-opacity:.7}.nvd3 .nv-parallelCoordinates-brush .extent{fill:#fff;fill-opacity:.6;stroke:gray;shape-rendering:crispEdges}.nvd3 .nv-parallelCoordinates .hover{fill-opacity:1;stroke-width:3px}.nvd3 .missingValuesline line{fill:none;stroke:#000;stroke-width:1;stroke-opacity:1;stroke-dasharray:5,5}.nvd3.nv-pie path{stroke-opacity:0;transition:fill-opacity .25s linear,stroke-width .25s linear,stroke-opacity .25s linear}.nvd3.nv-pie .nv-pie-title{font-size:24px;fill:rgba(19,196,249,.59)}.nvd3.nv-pie .nv-slice text{stroke:#000;stroke-width:0}.nvd3.nv-pie path{stroke:#fff;stroke-width:1px;stroke-opacity:1;fill-opacity:.7}.nvd3.nv-pie .hover path{fill-opacity:1}.nvd3.nv-pie .nv-label{pointer-events:none}.nvd3.nv-pie .nv-label rect{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-groups .nv-point.hover{stroke-width:20px;stroke-opacity:.5}.nvd3 .nv-scatter .nv-point.hover{fill-opacity:1}.nv-distx,.nv-disty,.nv-noninteractive{pointer-events:none}.nvd3.nv-sparkline path{fill:none}.nvd3.nv-sparklineplus g.nv-hoverValue{pointer-events:none}.nvd3.nv-sparklineplus .nv-hoverValue line{stroke:#333;stroke-width:1.5px}.nvd3.nv-sparklineplus,.nvd3.nv-sparklineplus g{pointer-events:all}.nvd3 .nv-hoverArea{fill-opacity:0;stroke-opacity:0}.nvd3.nv-sparklineplus .nv-xValue,.nvd3.nv-sparklineplus .nv-yValue{stroke-width:0;font-size:.9em;font-weight:400}.nvd3.nv-sparklineplus .nv-yValue{stroke:#f66}.nvd3.nv-sparklineplus .nv-maxValue{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-sparklineplus .nv-minValue{stroke:#d62728;fill:#d62728}.nvd3.nv-sparklineplus .nv-currentValue{font-weight:700;font-size:1.1em}.nvd3.nv-stackedarea path.nv-area{fill-opacity:.7;stroke-opacity:0;transition:fill-opacity .25s linear,stroke-opacity .25s linear}.nvd3.nv-stackedarea path.nv-area.hover{fill-opacity:.9}.nvd3.nv-stackedarea .nv-groups .nv-point{stroke-opacity:0;fill-opacity:0}.nvtooltip{position:absolute;background-color:#fff;color:#000;padding:1px;border:1px solid rgba(0,0,0,.2);z-index:1;display:block;font-family:Arial,sans-serif;font-size:13px;text-align:left;pointer-events:none;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background:hsla(0,0%,100%,.8);border:1px solid rgba(0,0,0,.5);border-radius:4px}.nvtooltip.with-transitions,.with-transitions .nvtooltip{transition:opacity 50ms linear;transition-delay:.2s}.nvtooltip.x-nvtooltip,.nvtooltip.y-nvtooltip{padding:8px}.nvtooltip h3{margin:0;padding:4px 14px;line-height:18px;font-weight:400;background-color:hsla(0,0%,97%,.75);color:#000;text-align:center;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.nvtooltip p{margin:0;padding:5px 14px;text-align:center}.nvtooltip span{display:inline-block;margin:2px 0}.nvtooltip table{margin:6px;border-spacing:0}.nvtooltip table td{padding:2px 9px 2px 0;vertical-align:middle}.nvtooltip table td.key{font-weight:400}.nvtooltip table td.key.total{font-weight:700}.nvtooltip table td.value{text-align:right;font-weight:700}.nvtooltip table td.percent{color:#a9a9a9}.nvtooltip table tr.highlight td{padding:1px 9px 1px 0;border-bottom-style:solid;border-bottom-width:1px;border-top-style:solid;border-top-width:1px}.nvtooltip table td.legend-color-guide div{width:8px;height:8px;vertical-align:middle;width:12px;height:12px;border:1px solid #999}.nvtooltip .footer{padding:3px;text-align:center}.nvtooltip-pending-removal{pointer-events:none;display:none}.nvd3 .nv-interactiveGuideLine{pointer-events:none}.nvd3 line.nv-guideline{stroke:#ccc} \ No newline at end of file +.nvd3 .nv-axis{pointer-events:none;opacity:1}.nvd3 .nv-axis path{fill:none;stroke:#000;stroke-opacity:.75;shape-rendering:crispEdges}.nvd3 .nv-axis path.domain{stroke-opacity:.75}.nvd3 .nv-axis.nv-x path.domain{stroke-opacity:0}.nvd3 .nv-axis line{fill:none;stroke:#e5e5e5;shape-rendering:crispEdges}.nvd3 .nv-axis .zero line,.nvd3 .nv-axis line.zero{stroke-opacity:.75}.nvd3 .nv-axis .nv-axisMaxMin text{font-weight:700}.nvd3 .x2 .nv-axis .nv-axisMaxMin text,.nvd3 .x3 .nv-axis .nv-axisMaxMin text,.nvd3 .x .nv-axis .nv-axisMaxMin text{text-anchor:middle}.nvd3 .nv-axis.nv-disabled{opacity:0}.nvd3 .nv-bars rect{fill-opacity:.75;transition:fill-opacity .25s linear}.nvd3 .nv-bars rect.hover{fill-opacity:1}.nvd3 .nv-bars .hover rect{fill:#add8e6}.nvd3 .nv-bars text{fill:transparent}.nvd3 .nv-bars .hover text{fill:#000}.nvd3 .nv-discretebar .nv-groups rect,.nvd3 .nv-multibar .nv-groups rect,.nvd3 .nv-multibarHorizontal .nv-groups rect{stroke-opacity:0;transition:fill-opacity .25s linear}.nvd3 .nv-candlestickBar .nv-ticks rect:hover,.nvd3 .nv-discretebar .nv-groups rect:hover,.nvd3 .nv-multibar .nv-groups rect:hover,.nvd3 .nv-multibarHorizontal .nv-groups rect:hover{fill-opacity:1}.nvd3 .nv-discretebar .nv-groups text,.nvd3 .nv-multibarHorizontal .nv-groups text{font-weight:700;fill:#000;stroke:transparent}.nvd3 .nv-boxplot circle{fill-opacity:.5}.nvd3 .nv-boxplot circle:hover,.nvd3 .nv-boxplot rect:hover{fill-opacity:1}.nvd3 line.nv-boxplot-median{stroke:#000}.nv-boxplot-tick:hover{stroke-width:2.5px}.nvd3.nv-bullet{font:10px sans-serif}.nvd3.nv-bullet .nv-measure{fill-opacity:.8}.nvd3.nv-bullet .nv-measure:hover{fill-opacity:1}.nvd3.nv-bullet .nv-marker{stroke:#000;stroke-width:2px}.nvd3.nv-bullet .nv-markerTriangle{stroke:#000;fill:#fff;stroke-width:1.5px}.nvd3.nv-bullet .nv-markerLine{stroke:#000;stroke-width:1.5px}.nvd3.nv-bullet .nv-tick line{stroke:#666;stroke-width:.5px}.nvd3.nv-bullet .nv-range.nv-s0{fill:#eee}.nvd3.nv-bullet .nv-range.nv-s1{fill:#ddd}.nvd3.nv-bullet .nv-range.nv-s2{fill:#ccc}.nvd3.nv-bullet .nv-title{font-size:14px;font-weight:700}.nvd3.nv-bullet .nv-subtitle{fill:#999}.nvd3.nv-bullet .nv-range{fill:#bababa;fill-opacity:.4}.nvd3.nv-bullet .nv-range:hover{fill-opacity:.7}.nvd3.nv-candlestickBar .nv-ticks .nv-tick{stroke-width:1px}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.hover{stroke-width:2px}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.positive rect{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.negative rect{stroke:#d62728;fill:#d62728}.with-transitions .nv-candlestickBar .nv-ticks .nv-tick{transition:stroke-width .25s linear,stroke-opacity .25s linear}.nvd3.nv-candlestickBar .nv-ticks line{stroke:#333}.nv-force-node{stroke:#fff;stroke-width:1.5px}.nv-force-link{stroke:#999;stroke-opacity:.6}.nv-force-node text{stroke-width:0px}.nvd3 .nv-check-box .nv-box{fill-opacity:0;stroke-width:2}.nvd3 .nv-check-box .nv-check{fill-opacity:0;stroke-width:4}.nvd3 .nv-series.nv-disabled .nv-check-box .nv-check{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-controlsWrap .nv-legend .nv-check-box .nv-check{opacity:0}.nvd3.nv-linePlusBar .nv-bar rect{fill-opacity:.75}.nvd3.nv-linePlusBar .nv-bar rect:hover{fill-opacity:1}.nvd3 .nv-groups path.nv-line{fill:none}.nvd3 .nv-groups path.nv-area{stroke:none}.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point{fill-opacity:0;stroke-opacity:0}.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point{fill-opacity:.5!important;stroke-opacity:.5!important}.with-transitions .nvd3 .nv-groups .nv-point{transition:stroke-width .25s linear,stroke-opacity .25s linear}.nvd3 .nv-groups .nv-point.hover,.nvd3.nv-scatter .nv-groups .nv-point.hover{stroke-width:7px;fill-opacity:.95!important;stroke-opacity:.95!important}.nvd3 .nv-point-paths path{stroke:#aaa;stroke-opacity:0;fill:#eee;fill-opacity:0}.nvd3 .nv-indexLine{cursor:ew-resize}svg.nvd3-svg{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;display:block;width:100%;height:100%}.nvtooltip.with-3d-shadow,.with-3d-shadow .nvtooltip{box-shadow:0 5px 10px rgba(0,0,0,.2);border-radius:5px}.nvd3 text{font:normal 12px Arial,sans-serif}.nvd3 .title{font:700 14px Arial,sans-serif}.nvd3 .nv-background{fill:#fff;fill-opacity:0}.nvd3.nv-noData{font-size:18px;font-weight:700}.nv-brush .extent{fill-opacity:.125;shape-rendering:crispEdges}.nv-brush .resize path{fill:#eee;stroke:#666}.nvd3 .nv-legend .nv-series{cursor:pointer}.nvd3 .nv-legend .nv-disabled circle{fill-opacity:0}.nvd3 .nv-brush .extent{fill-opacity:0!important}.nvd3 .nv-brushBackground rect{stroke:#000;stroke-width:.4;fill:#fff;fill-opacity:.7}@media print{.nvd3 text{stroke-width:0;fill-opacity:1}}.nvd3.nv-ohlcBar .nv-ticks .nv-tick{stroke-width:1px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover{stroke-width:2px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive{stroke:#2ca02c}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative{stroke:#d62728}.nvd3 .background path{fill:none;stroke:#eee;stroke-opacity:.4;shape-rendering:crispEdges}.nvd3 .foreground path{fill:none;stroke-opacity:.7}.nvd3 .nv-parallelCoordinates-brush .extent{fill:#fff;fill-opacity:.6;stroke:grey;shape-rendering:crispEdges}.nvd3 .nv-parallelCoordinates .hover{fill-opacity:1;stroke-width:3px}.nvd3 .missingValuesline line{fill:none;stroke:#000;stroke-width:1;stroke-opacity:1;stroke-dasharray:5,5}.nvd3.nv-pie path{stroke-opacity:0;transition:fill-opacity .25s linear,stroke-width .25s linear,stroke-opacity .25s linear}.nvd3.nv-pie .nv-pie-title{font-size:24px;fill:rgba(19,196,249,.59)}.nvd3.nv-pie .nv-slice text{stroke:#000;stroke-width:0}.nvd3.nv-pie path{stroke:#fff;stroke-width:1px;stroke-opacity:1;fill-opacity:.7}.nvd3.nv-pie .hover path{fill-opacity:1}.nvd3.nv-pie .nv-label{pointer-events:none}.nvd3.nv-pie .nv-label rect{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-groups .nv-point.hover{stroke-width:20px;stroke-opacity:.5}.nvd3 .nv-scatter .nv-point.hover{fill-opacity:1}.nv-distx,.nv-disty,.nv-noninteractive{pointer-events:none}.nvd3.nv-sparkline path{fill:none}.nvd3.nv-sparklineplus g.nv-hoverValue{pointer-events:none}.nvd3.nv-sparklineplus .nv-hoverValue line{stroke:#333;stroke-width:1.5px}.nvd3.nv-sparklineplus,.nvd3.nv-sparklineplus g{pointer-events:all}.nvd3 .nv-hoverArea{fill-opacity:0;stroke-opacity:0}.nvd3.nv-sparklineplus .nv-xValue,.nvd3.nv-sparklineplus .nv-yValue{stroke-width:0;font-size:.9em;font-weight:400}.nvd3.nv-sparklineplus .nv-yValue{stroke:#f66}.nvd3.nv-sparklineplus .nv-maxValue{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-sparklineplus .nv-minValue{stroke:#d62728;fill:#d62728}.nvd3.nv-sparklineplus .nv-currentValue{font-weight:700;font-size:1.1em}.nvd3.nv-stackedarea path.nv-area{fill-opacity:.7;stroke-opacity:0;transition:fill-opacity .25s linear,stroke-opacity .25s linear}.nvd3.nv-stackedarea path.nv-area.hover{fill-opacity:.9}.nvd3.nv-stackedarea .nv-groups .nv-point{stroke-opacity:0;fill-opacity:0}.nvtooltip{position:absolute;background-color:#fff;color:#000;padding:1px;border:1px solid rgba(0,0,0,.2);z-index:10000;display:block;font-family:Arial,sans-serif;font-size:13px;text-align:left;pointer-events:none;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background:hsla(0,0%,100%,.8);border:1px solid rgba(0,0,0,.5);border-radius:4px}.nvtooltip.with-transitions,.with-transitions .nvtooltip{transition:opacity 50ms linear;transition-delay:.2s}.nvtooltip.x-nvtooltip,.nvtooltip.y-nvtooltip{padding:8px}.nvtooltip h3{margin:0;padding:4px 14px;line-height:18px;font-weight:400;background-color:hsla(0,0%,96.9%,.75);color:#000;text-align:center;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.nvtooltip p{margin:0;padding:5px 14px;text-align:center}.nvtooltip span{display:inline-block;margin:2px 0}.nvtooltip table{margin:6px;border-spacing:0}.nvtooltip table td{padding:2px 9px 2px 0;vertical-align:middle}.nvtooltip table td.key{font-weight:400}.nvtooltip table td.key.total{font-weight:700}.nvtooltip table td.value{text-align:right;font-weight:700}.nvtooltip table td.percent{color:#a9a9a9}.nvtooltip table tr.highlight td{padding:1px 9px 1px 0;border-bottom-style:solid;border-bottom-width:1px;border-top-style:solid;border-top-width:1px}.nvtooltip table td.legend-color-guide div{width:8px;height:8px;vertical-align:middle;width:12px;height:12px;border:1px solid #999}.nvtooltip .footer{padding:3px;text-align:center}.nvtooltip-pending-removal{pointer-events:none;display:none}.nvd3 .nv-interactiveGuideLine{pointer-events:none}.nvd3 line.nv-guideline{stroke:#ccc} \ No newline at end of file diff --git a/config/plugins/visualizations/nvd3/nvd3_bar/static/nvd3.js b/config/plugins/visualizations/nvd3/nvd3_bar/static/nvd3.js index 8b80072f2ae..2717890579c 100644 --- a/config/plugins/visualizations/nvd3/nvd3_bar/static/nvd3.js +++ b/config/plugins/visualizations/nvd3/nvd3_bar/static/nvd3.js @@ -1,22 +1,28 @@ -require=function(r,e,n){function t(n,o){function i(r){return t(i.resolve(r))}function f(e){return r[n][1][e]||e}if(!e[n]){if(!r[n]){var c="function"==typeof require&&require;if(!o&&c)return c(n,!0);if(u)return u(n,!0);var l=new Error("Cannot find module '"+n+"'");throw l.code="MODULE_NOT_FOUND",l}i.resolve=f;var a=e[n]=new t.Module;r[n][0].call(a.exports,i,a,a.exports)}return e[n].exports}function o(){this.bundle=t,this.exports={}}var u="function"==typeof require&&require;t.Module=o,t.modules=r,t.cache=e,t.parent=u;for(var i=0;it?1:n>=t?0:NaN}function h(n){return null===n?NaN:+n}function p(n){return!isNaN(n)}function g(n){return{left:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);r>>1;n(t[u],e)<0?r=u+1:i=u}return r},right:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);r>>1;n(t[u],e)>0?i=u:r=u+1}return r}}}n.ascending=s,n.descending=function(n,t){return tn?1:t>=n?0:NaN},n.min=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ir&&(e=r)}else{for(;++i=r){e=r;break}for(;++ir&&(e=r)}return e},n.max=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ie&&(e=r)}else{for(;++i=r){e=r;break}for(;++ie&&(e=r)}return e},n.extent=function(n,t){var e,r,i,u=-1,o=n.length;if(1===arguments.length){for(;++u=r){e=i=r;break}for(;++ur&&(e=r),i=r){e=i=r;break}for(;++ur&&(e=r),i1)return o/(l-1)},n.deviation=function(){var t=n.variance.apply(this,arguments);return t?Math.sqrt(t):t};var v=g(s);function d(n){return n.length}n.bisectLeft=v.left,n.bisect=n.bisectRight=v.right,n.bisector=function(n){return g(1===n.length?function(t,e){return s(n(t),e)}:n)},n.shuffle=function(n,t,e){(u=arguments.length)<3&&(e=n.length,u<2&&(t=0));for(var r,i,u=e-t;u;)i=Math.random()*u--|0,r=n[u+t],n[u+t]=n[i+t],n[i+t]=r;return n},n.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},n.pairs=function(n){for(var t=0,e=n.length-1,r=n[0],i=new Array(e<0?0:e);t=0;)for(t=(r=n[i]).length;--t>=0;)e[--o]=r[t];return e};var y=Math.abs;function m(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function M(){this._=Object.create(null)}n.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),(t-n)/e==1/0)throw new Error("infinite range");var r,i=[],u=function(n){var t=1;for(;n*t%1;)t*=10;return t}(y(e)),o=-1;if(n*=u,t*=u,(e*=u)<0)for(;(r=n+e*++o)>t;)i.push(r/u);else for(;(r=n+e*++o)=i.length)return e?e.call(r,u):t?u.sort(t):u;for(var l,c,f,s,h=-1,p=u.length,g=i[a++],v=new M;++h=i.length)return t;var r=[],o=u[e++];return t.forEach(function(t,i){r.push({key:t,values:n(i,e)})}),o?r.sort(function(n,t){return o(n.key,t.key)}):r}(o(n.map,t,0),0)},r.key=function(n){return i.push(n),r},r.sortKeys=function(n){return u[i.length-1]=n,r},r.sortValues=function(n){return t=n,r},r.rollup=function(n){return e=n,r},r},n.set=function(n){var t=new C;if(n)for(var e=0,r=n.length;e=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},n.event=null,n.requote=function(n){return n.replace(H,"\\$&")};var H=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,O={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]};function I(n){return O(n,X),n}var Y=function(n,t){return t.querySelector(n)},Z=function(n,t){return t.querySelectorAll(n)},V=function(n,t){var e=n.matches||n[q(n,"matchesSelector")];return(V=function(n,t){return e.call(n,t)})(n,t)};"function"==typeof Sizzle&&(Y=function(n,t){return Sizzle(n,t)[0]||null},Z=Sizzle,V=Sizzle.matchesSelector),n.selection=function(){return n.select(r.documentElement)};var X=n.selection.prototype=[];function $(n){return"function"==typeof n?n:function(){return Y(n,this)}}function B(n){return"function"==typeof n?n:function(){return Z(n,this)}}X.select=function(n){var t,e,r,i,u=[];n=$(n);for(var o=-1,a=this.length;++o=0&&"xmlns"!==(e=n.slice(0,t))&&(n=n.slice(t+1)),J.hasOwnProperty(e)?{space:J[e],local:n}:n}},X.attr=function(t,e){if(arguments.length<2){if("string"==typeof t){var r=this.node();return(t=n.ns.qualify(t)).local?r.getAttributeNS(t.space,t.local):r.getAttribute(t)}for(e in t)this.each(G(e,t[e]));return this}return this.each(G(t,e))},X.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=nn(n)).length,i=-1;if(t=e.classList){for(;++i=0;)(e=r[i])&&(u&&u!==e.nextSibling&&u.parentNode.insertBefore(e,u),u=e);return this},X.sort=function(n){n=function(n){arguments.length||(n=s);return function(t,e){return t&&e?n(t.__data__,e.__data__):!t-!e}}.apply(this,arguments);for(var t=-1,e=this.length;++t0&&(t=t.slice(0,o));var l=gn.get(t);function c(){var n=this[u];n&&(this.removeEventListener(t,n,n.$),delete this[u])}return l&&(t=l,a=dn),o?r?function(){var n=a(r,e(arguments));c.call(this),this.addEventListener(t,this[u]=n,n.$=i),n._=r}:c:r?R:function(){var e,r=new RegExp("^__on([^.]+)"+n.requote(t)+"$");for(var i in this)if(e=i.match(r)){var u=this[i];this.removeEventListener(e[1],u,u.$),delete this[i]}}}n.selection.enter=sn,n.selection.enter.prototype=hn,hn.append=X.append,hn.empty=X.empty,hn.node=X.node,hn.call=X.call,hn.size=X.size,hn.select=function(n){for(var t,e,r,i,u,o=[],a=-1,l=this.length;++a=r&&(r=t+1);!(o=a[r])&&++r0?1:n<0?-1:0}function qn(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function Tn(n){return n>1?0:n<-1?kn:Math.acos(n)}function Rn(n){return n>1?An:n<-1?-An:Math.asin(n)}function Dn(n){return((n=Math.exp(n))+1/n)/2}function Pn(n){return(n=Math.sin(n/2))*n}var Un=Math.SQRT2;n.interpolateZoom=function(n,t){var e,r,i=n[0],u=n[1],o=n[2],a=t[0],l=t[1],c=t[2],f=a-i,s=l-u,h=f*f+s*s;if(h0&&(t=t.transition().duration(v)),t.call(_.event)}function E(){c&&c.domain(l.range().map(function(n){return(n-h.x)/h.k}).map(l.invert)),s&&s.domain(f.range().map(function(n){return(n-h.y)/h.k}).map(f.invert))}function A(n){d++||n({type:"zoomstart"})}function C(n){E(),n({type:"zoom",scale:h.k,translate:[h.x,h.y]})}function z(n){--d||(n({type:"zoomend"}),e=null)}function L(){var t=this,e=b.of(t,arguments),r=0,i=n.select(u(t)).on(m,function(){r=1,k(n.mouse(t),o),C(e)}).on(M,function(){i.on(m,null).on(M,null),a(r),z(e)}),o=w(n.mouse(t)),a=Mn(t);ua.call(t),A(e)}function q(){var t,e=this,r=b.of(e,arguments),i={},u=0,o=".zoom-"+n.event.changedTouches[0].identifier,l="touchmove"+o,c="touchend"+o,f=[],s=n.select(e),p=Mn(e);function g(){var r=n.touches(e);return t=h.k,r.forEach(function(n){n.identifier in i&&(i[n.identifier]=w(n))}),r}function v(){var t=n.event.target;n.select(t).on(l,d).on(c,m),f.push(t);for(var r=n.event.changedTouches,o=0,s=r.length;o1){y=p[0];var M=p[1],x=y[0]-M[0],b=y[1]-M[1];u=x*x+b*b}}function d(){var o,l,c,f,s=n.touches(e);ua.call(e);for(var h=0,p=s.length;h360?t-=360:t<0&&(t+=360),t<60?r+(i-r)*t/60:t<180?i:t<240?r+(i-r)*(240-t)/60:r));var t}return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:t<0?0:t>1?1:t,r=2*(e=e<0?0:e>1?1:e)-(i=e<=.5?e*(1+t):e+t-e*t),new ut(u(n+120),u(n),u(n-120))}function Vn(t,e,r){return this instanceof Vn?(this.h=+t,this.c=+e,void(this.l=+r)):arguments.length<2?t instanceof Vn?new Vn(t.h,t.c,t.l):tt(t instanceof Bn?t.l:(t=ht((t=n.rgb(t)).r,t.g,t.b)).l,t.a,t.b):new Vn(t,e,r)}Yn.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new In(this.h,this.s,this.l/n)},Yn.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new In(this.h,this.s,n*this.l)},Yn.rgb=function(){return Zn(this.h,this.s,this.l)},n.hcl=Vn;var Xn=Vn.prototype=new On;function $n(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new Bn(e,Math.cos(n*=Cn)*t,Math.sin(n)*t)}function Bn(n,t,e){return this instanceof Bn?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof Bn?new Bn(n.l,n.a,n.b):n instanceof Vn?$n(n.h,n.c,n.l):ht((n=ut(n)).r,n.g,n.b):new Bn(n,t,e)}Xn.brighter=function(n){return new Vn(this.h,this.c,Math.min(100,this.l+Wn*(arguments.length?n:1)))},Xn.darker=function(n){return new Vn(this.h,this.c,Math.max(0,this.l-Wn*(arguments.length?n:1)))},Xn.rgb=function(){return $n(this.h,this.c,this.l).rgb()},n.lab=Bn;var Wn=18,Jn=.95047,Gn=1,Kn=1.08883,Qn=Bn.prototype=new On;function nt(n,t,e){var r=(n+16)/116,i=r+t/500,u=r-e/200;return new ut(it(3.2404542*(i=et(i)*Jn)-1.5371385*(r=et(r)*Gn)-.4985314*(u=et(u)*Kn)),it(-.969266*i+1.8760108*r+.041556*u),it(.0556434*i-.2040259*r+1.0572252*u))}function tt(n,t,e){return n>0?new Vn(Math.atan2(e,t)*zn,Math.sqrt(t*t+e*e),n):new Vn(NaN,NaN,n)}function et(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function rt(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function it(n){return Math.round(255*(n<=.00304?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function ut(n,t,e){return this instanceof ut?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof ut?new ut(n.r,n.g,n.b):ft(""+n,ut,Zn):new ut(n,t,e)}function ot(n){return new ut(n>>16,n>>8&255,255&n)}function at(n){return ot(n)+""}Qn.brighter=function(n){return new Bn(Math.min(100,this.l+Wn*(arguments.length?n:1)),this.a,this.b)},Qn.darker=function(n){return new Bn(Math.max(0,this.l-Wn*(arguments.length?n:1)),this.a,this.b)},Qn.rgb=function(){return nt(this.l,this.a,this.b)},n.rgb=ut;var lt=ut.prototype=new On;function ct(n){return n<16?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function ft(n,t,e){var r,i,u,o=0,a=0,l=0;if(r=/([a-z]+)\((.*)\)/.exec(n=n.toLowerCase()))switch(i=r[2].split(","),r[1]){case"hsl":return e(parseFloat(i[0]),parseFloat(i[1])/100,parseFloat(i[2])/100);case"rgb":return t(gt(i[0]),gt(i[1]),gt(i[2]))}return(u=vt.get(n))?t(u.r,u.g,u.b):(null==n||"#"!==n.charAt(0)||isNaN(u=parseInt(n.slice(1),16))||(4===n.length?(o=(3840&u)>>4,o|=o>>4,a=240&u,a|=a>>4,l=15&u,l|=l<<4):7===n.length&&(o=(16711680&u)>>16,a=(65280&u)>>8,l=255&u)),t(o,a,l))}function st(n,t,e){var r,i,u=Math.min(n/=255,t/=255,e/=255),o=Math.max(n,t,e),a=o-u,l=(o+u)/2;return a?(i=l<.5?a/(o+u):a/(2-o-u),r=n==o?(t-e)/a+(t0&&l<1?0:r),new In(r,i,l)}function ht(n,t,e){var r=rt((.4124564*(n=pt(n))+.3575761*(t=pt(t))+.1804375*(e=pt(e)))/Jn),i=rt((.2126729*n+.7151522*t+.072175*e)/Gn);return Bn(116*i-16,500*(r-i),200*(i-rt((.0193339*n+.119192*t+.9503041*e)/Kn)))}function pt(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function gt(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}lt.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b;return t||e||r?(t&&t<30&&(t=30),e&&e<30&&(e=30),r&&r<30&&(r=30),new ut(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new ut(30,30,30)},lt.darker=function(n){return new ut((n=Math.pow(.7,arguments.length?n:1))*this.r,n*this.g,n*this.b)},lt.hsl=function(){return st(this.r,this.g,this.b)},lt.toString=function(){return"#"+ct(this.r)+ct(this.g)+ct(this.b)};var vt=n.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});function dt(n){return"function"==typeof n?n:function(){return n}}function yt(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),mt(t,e,n,r)}}function mt(t,r,i,u){var o,a={},l=n.dispatch("beforesend","progress","load","error"),c={},f=new XMLHttpRequest,s=null;function h(){var n,t,e,r=f.status;if(!r&&((e=(t=f).responseType)&&"text"!==e?t.response:t.responseText)||r>=200&&r<300||304===r){try{n=i.call(a,f)}catch(n){return void l.error.call(a,n)}l.load.call(a,n)}else l.error.call(a,f)}return!this.XDomainRequest||"withCredentials"in f||!/^(http(s)?:)?\/\//.test(t)||(f=new XDomainRequest),"onload"in f?f.onload=f.onerror=h:f.onreadystatechange=function(){f.readyState>3&&h()},f.onprogress=function(t){var e=n.event;n.event=t;try{l.progress.call(a,f)}finally{n.event=e}},a.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?c[n]:(null==t?delete c[n]:c[n]=t+"",a)},a.mimeType=function(n){return arguments.length?(r=null==n?null:n+"",a):r},a.responseType=function(n){return arguments.length?(s=n,a):s},a.response=function(n){return i=n,a},["get","post"].forEach(function(n){a[n]=function(){return a.send.apply(a,[n].concat(e(arguments)))}}),a.send=function(n,e,i){if(2===arguments.length&&"function"==typeof e&&(i=e,e=null),f.open(n,t,!0),null==r||"accept"in c||(c.accept=r+",*/*"),f.setRequestHeader)for(var u in c)f.setRequestHeader(u,c[u]);return null!=r&&f.overrideMimeType&&f.overrideMimeType(r),null!=s&&(f.responseType=s),null!=i&&a.on("error",i).on("load",function(n){i(null,n)}),l.beforesend.call(a,f),f.send(null==e?null:e),a},a.abort=function(){return f.abort(),a},n.rebind(a,l,"on"),null==u?a:a.get(1===(o=u).length?function(n,t){o(null==n?t:null)}:o)}vt.forEach(function(n,t){vt.set(n,ot(t))}),n.functor=dt,n.xhr=yt(z),n.dsv=function(n,t){var e=new RegExp('["'+n+"\n]"),r=n.charCodeAt(0);function i(n,e,r){arguments.length<3&&(r=e,e=null);var i=mt(n,t,null==e?u:o(e),r);return i.row=function(n){return arguments.length?i.response(null==(e=n)?u:o(n)):e},i}function u(n){return i.parse(n.responseText)}function o(n){return function(t){return i.parse(t.responseText,n)}}function a(t){return t.map(l).join(n)}function l(n){return e.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}return i.parse=function(n,t){var e;return i.parseRows(n,function(n,r){if(e)return e(n,r-1);var i=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");e=t?function(n,e){return t(i(n),e)}:i})},i.parseRows=function(n,t){var e,i,u={},o={},a=[],l=n.length,c=0,f=0;function s(){if(c>=l)return o;if(i)return i=!1,u;var t=c;if(34===n.charCodeAt(t)){for(var e=t;e++24?(isFinite(t)&&(clearTimeout(_t),_t=setTimeout(kt,t)),bt=0):(bt=1,wt(kt))}function Nt(){for(var n=Date.now(),t=Mt;t;)n>=t.t&&t.c(n-t.t)&&(t.c=null),t=t.n;return n}function Et(){for(var n,t=Mt,e=1/0;t;)t.c?(t.t8?function(n){return n/e}:function(n){return n*e},symbol:n}});n.formatPrefix=function(t,e){var r=0;return(t=+t)&&(t<0&&(t*=-1),e&&(t=n.round(t,At(t,e))),r=1+Math.floor(1e-12+Math.log(t)/Math.LN10),r=Math.max(-24,Math.min(24,3*Math.floor((r-1)/3)))),Ct[8+r/3]};var zt=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,Lt=n.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(t,e){return(t=n.round(t,At(t,e))).toFixed(Math.max(0,Math.min(20,At(t*(1+1e-15),e))))}});function qt(n){return n+""}var Tt=n.time={},Rt=Date;function Dt(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}Dt.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){Pt.setUTCDate.apply(this._,arguments)},setDay:function(){Pt.setUTCDay.apply(this._,arguments)},setFullYear:function(){Pt.setUTCFullYear.apply(this._,arguments)},setHours:function(){Pt.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){Pt.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){Pt.setUTCMinutes.apply(this._,arguments)},setMonth:function(){Pt.setUTCMonth.apply(this._,arguments)},setSeconds:function(){Pt.setUTCSeconds.apply(this._,arguments)},setTime:function(){Pt.setTime.apply(this._,arguments)}};var Pt=Date.prototype;function Ut(n,t,e){function r(t){var e=n(t),r=u(e,1);return t-e1)for(;o68?1900:2e3),e+i[0].length):-1}function Jt(n,t,e){return/^[+-]\d{4}$/.test(t=t.slice(e,e+5))?(n.Z=-t,e+5):-1}function Gt(n,t,e){Ht.lastIndex=0;var r=Ht.exec(t.slice(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function Kt(n,t,e){Ht.lastIndex=0;var r=Ht.exec(t.slice(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function Qt(n,t,e){Ht.lastIndex=0;var r=Ht.exec(t.slice(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function ne(n,t,e){Ht.lastIndex=0;var r=Ht.exec(t.slice(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function te(n,t,e){Ht.lastIndex=0;var r=Ht.exec(t.slice(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function ee(n,t,e){Ht.lastIndex=0;var r=Ht.exec(t.slice(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function re(n,t,e){Ht.lastIndex=0;var r=Ht.exec(t.slice(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function ie(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=y(t)/60|0,i=y(t)%60;return e+It(r,"0",2)+It(i,"0",2)}function ue(n,t,e){Ot.lastIndex=0;var r=Ot.exec(t.slice(e,e+1));return r?e+r[0].length:-1}function oe(n){for(var t=n.length,e=-1;++e0&&a>0&&(l+a+1>t&&(a=Math.max(1,t-l)),r.push(n.substring(e-=a,e+a)),!((l+=a+1)>t));)a=u[o=(o+1)%u.length];return r.reverse().join(i)}:z,function(t){var e=zt.exec(t),i=e[1]||" ",u=e[2]||">",l=e[3]||"-",c=e[4]||"",f=e[5],s=+e[6],h=e[7],p=e[8],g=e[9],v=1,d="",y="",m=!1,M=!0;switch(p&&(p=+p.substring(1)),(f||"0"===i&&"="===u)&&(f=i="0",u="="),g){case"n":h=!0,g="g";break;case"%":v=100,y="%",g="f";break;case"p":v=100,y="%",g="r";break;case"b":case"o":case"x":case"X":"#"===c&&(d="0"+g.toLowerCase());case"c":M=!1;case"d":m=!0,p=0;break;case"s":v=-1,g="r"}"$"===c&&(d=o[0],y=o[1]),"r"!=g||p||(g="g"),null!=p&&("g"==g?p=Math.max(1,Math.min(21,p)):"e"!=g&&"f"!=g||(p=Math.max(0,Math.min(20,p)))),g=Lt.get(g)||qt;var x=f&&h;return function(t){var e=y;if(m&&t%1)return"";var o=t<0||0===t&&1/t<0?(t=-t,"-"):"-"===l?"":l;if(v<0){var c=n.formatPrefix(t,p);t=c.scale(t),e=c.symbol+y}else t*=v;var b,_,w=(t=g(t,p)).lastIndexOf(".");if(w<0){var S=M?t.lastIndexOf("e"):-1;S<0?(b=t,_=""):(b=t.substring(0,S),_=t.substring(S))}else b=t.substring(0,w),_=r+t.substring(w+1);!f&&h&&(b=a(b,1/0));var k=d.length+b.length+_.length+(x?0:o.length),N=k"===u?N+o+t:"^"===u?N.substring(0,k>>=1)+o+t+N.substring(k):o+(x?t:N+t))+e}}),timeFormat:function(t){var e=t.dateTime,r=t.date,i=t.time,u=t.periods,o=t.days,a=t.shortDays,l=t.months,c=t.shortMonths;function f(n){var t=n.length;function e(e){for(var r,i,u,o=[],a=-1,l=0;++a=c)return-1;if(37===(i=t.charCodeAt(a++))){if(o=t.charAt(a++),!(u=_[o in Ft?t.charAt(a++):o])||(r=u(n,e,r))<0)return-1}else if(i!=e.charCodeAt(r++))return-1}return r}f.utc=function(n){var t=f(n);function e(n){try{var e=new(Rt=Dt);return e._=n,t(e)}finally{Rt=Date}}return e.parse=function(n){try{Rt=Dt;var e=t.parse(n);return e&&e._}finally{Rt=Date}},e.toString=t.toString,e},f.multi=f.utc.multi=oe;var h=n.map(),p=Yt(o),g=Zt(o),v=Yt(a),d=Zt(a),y=Yt(l),m=Zt(l),M=Yt(c),x=Zt(c);u.forEach(function(n,t){h.set(n.toLowerCase(),t)});var b={a:function(n){return a[n.getDay()]},A:function(n){return o[n.getDay()]},b:function(n){return c[n.getMonth()]},B:function(n){return l[n.getMonth()]},c:f(e),d:function(n,t){return It(n.getDate(),t,2)},e:function(n,t){return It(n.getDate(),t,2)},H:function(n,t){return It(n.getHours(),t,2)},I:function(n,t){return It(n.getHours()%12||12,t,2)},j:function(n,t){return It(1+Tt.dayOfYear(n),t,3)},L:function(n,t){return It(n.getMilliseconds(),t,3)},m:function(n,t){return It(n.getMonth()+1,t,2)},M:function(n,t){return It(n.getMinutes(),t,2)},p:function(n){return u[+(n.getHours()>=12)]},S:function(n,t){return It(n.getSeconds(),t,2)},U:function(n,t){return It(Tt.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return It(Tt.mondayOfYear(n),t,2)},x:f(r),X:f(i),y:function(n,t){return It(n.getFullYear()%100,t,2)},Y:function(n,t){return It(n.getFullYear()%1e4,t,4)},Z:ie,"%":function(){return"%"}},_={a:function(n,t,e){v.lastIndex=0;var r=v.exec(t.slice(e));return r?(n.w=d.get(r[0].toLowerCase()),e+r[0].length):-1},A:function(n,t,e){p.lastIndex=0;var r=p.exec(t.slice(e));return r?(n.w=g.get(r[0].toLowerCase()),e+r[0].length):-1},b:function(n,t,e){M.lastIndex=0;var r=M.exec(t.slice(e));return r?(n.m=x.get(r[0].toLowerCase()),e+r[0].length):-1},B:function(n,t,e){y.lastIndex=0;var r=y.exec(t.slice(e));return r?(n.m=m.get(r[0].toLowerCase()),e+r[0].length):-1},c:function(n,t,e){return s(n,b.c.toString(),t,e)},d:Kt,e:Kt,H:ne,I:ne,j:Qt,L:re,m:Gt,M:te,p:function(n,t,e){var r=h.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)},S:ee,U:Xt,w:Vt,W:$t,x:function(n,t,e){return s(n,b.x.toString(),t,e)},X:function(n,t,e){return s(n,b.X.toString(),t,e)},y:Wt,Y:Bt,Z:Jt,"%":ue};return f}(t)};var e,r,i,u,o,a};var ae=n.locale({decimal:".",thousands:",",grouping:[3],currency:["$",""],dateTime:"%a %b %e %X %Y",date:"%m/%d/%Y",time:"%H:%M:%S",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});function le(){}n.format=ae.numberFormat,n.geo={},le.prototype={s:0,t:0,add:function(n){fe(n,this.t,ce),fe(ce.s,this.s,this),this.s?this.t+=ce.t:this.s=ce.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var ce=new le;function fe(n,t,e){var r=e.s=n+t,i=r-n,u=r-i;e.t=n-u+(t-i)}function se(n,t){n&&pe.hasOwnProperty(n.type)&&pe[n.type](n,t)}n.geo.stream=function(n,t){n&&he.hasOwnProperty(n.type)?he[n.type](n,t):se(n,t)};var he={Feature:function(n,t){se(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,i=e.length;++r=0?1:-1,a=o*u,l=Math.cos(t),c=Math.sin(t),f=i*c,s=r*l+f*Math.cos(a),h=f*o*Math.sin(a);Ae.add(Math.atan2(h,s)),e=n,r=l,i=c}Ce.point=function(o,a){Ce.point=u,e=(n=o)*Cn,r=Math.cos(a=(t=a)*Cn/2+kn/4),i=Math.sin(a)},Ce.lineEnd=function(){u(n,t)}}function Le(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function qe(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function Te(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function Re(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function De(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function Pe(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function Ue(n){return[Math.atan2(n[1],n[0]),Rn(n[2])]}function je(n,t){return y(n[0]-t[0])wn?i=90:c<-wn&&(e=-90),s[0]=t,s[1]=r}};function p(n,u){f.push(s=[t=n,r=n]),ui&&(i=u)}function g(n,o){var a=Le([n*Cn,o*Cn]);if(l){var c=Te(l,a),f=Te([c[1],-c[0],0],c);Pe(f),f=Ue(f);var s=n-u,h=s>0?1:-1,g=f[0]*zn*h,v=y(s)>180;if(v^(h*ui&&(i=d);else if(g=(g+360)%360-180,v^(h*ui&&(i=o);v?nb(t,r)&&(r=n):b(n,r)>b(t,r)&&(t=n):r>=t?(nr&&(r=n)):n>u?b(t,n)>b(t,r)&&(r=n):b(n,r)>b(t,r)&&(t=n)}else p(n,o);l=a,u=n}function v(){h.point=g}function d(){s[0]=t,s[1]=r,h.point=p,l=null}function m(n,t){if(l){var e=n-u;c+=y(e)>180?e+(e>0?360:-360):e}else o=n,a=t;Ce.point(n,t),g(n,t)}function M(){Ce.lineStart()}function x(){m(o,a),Ce.lineEnd(),y(c)>wn&&(t=-(r=180)),s[0]=t,s[1]=r,l=null}function b(n,t){return(t-=n)<0?t+360:t}function _(n,t){return n[0]-t[0]}function w(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nb(v[0],v[1])&&(v[1]=p[1]),b(p[0],v[1])>b(v[0],v[1])&&(v[0]=p[0])):a.push(v=p);for(var l,c,p,g=-1/0,v=(o=0,a[c=a.length-1]);o<=c;v=p,++o)p=a[o],(l=b(v[1],p[0]))>g&&(g=l,t=p[0],r=v[1])}return f=s=null,t===1/0||e===1/0?[[NaN,NaN],[NaN,NaN]]:[[t,e],[r,i]]}}(),n.geo.centroid=function(t){ye=me=Me=xe=be=_e=we=Se=ke=Ne=Ee=0,n.geo.stream(t,Fe);var e=ke,r=Ne,i=Ee,u=e*e+r*r+i*i;return u=0;--a)i.point((s=f[a])[0],s[1]);else r(p.x,p.p.x,-1,i);p=p.p}f=(p=p.o).z,g=!g}while(!p.v);i.lineEnd()}}}function Be(n){if(t=n.length){for(var t,e,r=0,i=n[0];++r=0?1:-1,w=_*b,S=w>kn,k=g*M;if(Ae.add(Math.atan2(k*_*Math.sin(w),v*x+k*Math.cos(w))),u+=S?b+_*Nn:b,S^h>=e^y>=e){var N=Te(Le(s),Le(n));Pe(N);var E=Te(i,N);Pe(E);var A=(S^b>=0?-1:1)*Rn(E[2]);(r>A||r===A&&(N[0]||N[1]))&&(o+=S^b>=0?1:-1)}if(!d++)break;h=y,g=M,v=x,s=n}}return(u<-wn||u0){for(M||(o.polygonStart(),M=!0),o.lineStart();++u1&&2&t&&e.push(e.pop().concat(e.shift())),a.push(e.filter(Ge))}return f}}function Ge(n){return n.length>1}function Ke(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:R,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function Qe(n,t){return((n=n.x)[0]<0?n[1]-An-wn:An-n[1])-((t=t.x)[0]<0?t[1]-An-wn:An-t[1])}var nr=Je(Xe,function(n){var t,e=NaN,r=NaN,i=NaN;return{lineStart:function(){n.lineStart(),t=1},point:function(u,o){var a,l,c,f,s,h,p,g=u>0?kn:-kn,v=y(u-e);y(v-kn)0?An:-An),n.point(i,r),n.lineEnd(),n.lineStart(),n.point(g,r),n.point(u,r),t=0):i!==g&&v>=kn&&(y(e-i)wn?Math.atan((Math.sin(l)*(h=Math.cos(f))*Math.sin(c)-Math.sin(f)*(s=Math.cos(l))*Math.sin(a))/(s*h*p)):(l+f)/2,n.point(i,r),n.lineEnd(),n.lineStart(),n.point(g,r),t=0),n.point(e=u,r=o),i=g},lineEnd:function(){n.lineEnd(),e=r=NaN},clean:function(){return 2-t}}},function(n,t,e,r){var i;if(null==n)i=e*An,r.point(-kn,i),r.point(0,i),r.point(kn,i),r.point(kn,0),r.point(kn,-i),r.point(0,-i),r.point(-kn,-i),r.point(-kn,0),r.point(-kn,i);else if(y(n[0]-t[0])>wn){var u=n[0]0)){if(u/=h,h<0){if(u0){if(u>s)return;u>f&&(f=u)}if(u=e-l,h||!(u<0)){if(u/=h,h<0){if(u>s)return;u>f&&(f=u)}else if(h>0){if(u0)){if(u/=p,p<0){if(u0){if(u>s)return;u>f&&(f=u)}if(u=r-c,p||!(u<0)){if(u/=p,p<0){if(u>s)return;u>f&&(f=u)}else if(p>0){if(u0&&(i.a={x:l+f*h,y:c+f*p}),s<1&&(i.b={x:l+s*h,y:c+s*p}),i}}}}}}var er=1e9;function rr(t,e,r,i){return function(l){var c,f,s,h,p,g,v,d,y,m,M,x=l,b=Ke(),_=tr(t,e,r,i),w={point:N,lineStart:function(){w.point=E,f&&f.push(s=[]);m=!0,y=!1,v=d=NaN},lineEnd:function(){c&&(E(h,p),g&&y&&b.rejoin(),c.push(b.buffer()));w.point=N,y&&l.lineEnd()},polygonStart:function(){l=b,c=[],f=[],M=!0},polygonEnd:function(){l=x,c=n.merge(c);var e=function(n){for(var t=0,e=f.length,r=n[1],i=0;ir&&qn(c,u,n)>0&&++t:u[1]<=r&&qn(c,u,n)<0&&--t,c=u;return 0!==t}([t,i]),r=M&&e,u=c.length;(r||u)&&(l.polygonStart(),r&&(l.lineStart(),S(null,null,1,l),l.lineEnd()),u&&$e(c,o,e,S,l),l.polygonEnd()),c=f=s=null}};function S(n,o,l,c){var f=0,s=0;if(null==n||(f=u(n,l))!==(s=u(o,l))||a(n,o)<0^l>0)do{c.point(0===f||3===f?t:r,f>1?i:e)}while((f=(f+l+4)%4)!==s);else c.point(o[0],o[1])}function k(n,u){return t<=n&&n<=r&&e<=u&&u<=i}function N(n,t){k(n,t)&&l.point(n,t)}function E(n,t){var e=k(n=Math.max(-er,Math.min(er,n)),t=Math.max(-er,Math.min(er,t)));if(f&&s.push([n,t]),m)h=n,p=t,g=e,m=!1,e&&(l.lineStart(),l.point(n,t));else if(e&&y)l.point(n,t);else{var r={a:{x:v,y:d},b:{x:n,y:t}};_(r)?(y||(l.lineStart(),l.point(r.a.x,r.a.y)),l.point(r.b.x,r.b.y),e||l.lineEnd(),M=!1):e&&(l.lineStart(),l.point(n,t),M=!1)}v=n,d=t,y=e}return w};function u(n,i){return y(n[0]-t)0?0:3:y(n[0]-r)0?2:1:y(n[1]-e)0?1:0:i>0?3:2}function o(n,t){return a(n.x,t.x)}function a(n,t){var e=u(n,1),r=u(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}}function ir(n){var t=0,e=kn/3,r=Nr(n),i=r(t,e);return i.parallels=function(n){return arguments.length?r(t=n[0]*kn/180,e=n[1]*kn/180):[t/kn*180,e/kn*180]},i}function ur(n,t){var e=Math.sin(n),r=(e+Math.sin(t))/2,i=1+e*(2*r-e),u=Math.sqrt(i)/r;function o(n,t){var e=Math.sqrt(i-2*r*Math.sin(t))/r;return[e*Math.sin(n*=r),u-e*Math.cos(n)]}return o.invert=function(n,t){var e=u-t;return[Math.atan2(n,e)/r,Rn((i-(n*n+e*e)*r*r)/(2*r))]},o}n.geo.clipExtent=function(){var n,t,e,r,i,u,o={stream:function(n){return i&&(i.valid=!1),(i=u(n)).valid=!0,i},extent:function(a){return arguments.length?(u=rr(n=+a[0][0],t=+a[0][1],e=+a[1][0],r=+a[1][1]),i&&(i.valid=!1,i=null),o):[[n,t],[e,r]]}};return o.extent([[0,0],[960,500]])},(n.geo.conicEqualArea=function(){return ir(ur)}).raw=ur,n.geo.albers=function(){return n.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},n.geo.albersUsa=function(){var t,e,r,i,u=n.geo.albers(),o=n.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),a=n.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),l={point:function(n,e){t=[n,e]}};function c(n){var u=n[0],o=n[1];return t=null,e(u,o),t||(r(u,o),t)||i(u,o),t}return c.invert=function(n){var t=u.scale(),e=u.translate(),r=(n[0]-e[0])/t,i=(n[1]-e[1])/t;return(i>=.12&&i<.234&&r>=-.425&&r<-.214?o:i>=.166&&i<.234&&r>=-.214&&r<-.115?a:u).invert(n)},c.stream=function(n){var t=u.stream(n),e=o.stream(n),r=a.stream(n);return{point:function(n,i){t.point(n,i),e.point(n,i),r.point(n,i)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},c.precision=function(n){return arguments.length?(u.precision(n),o.precision(n),a.precision(n),c):u.precision()},c.scale=function(n){return arguments.length?(u.scale(n),o.scale(.35*n),a.scale(n),c.translate(u.translate())):u.scale()},c.translate=function(n){if(!arguments.length)return u.translate();var t=u.scale(),f=+n[0],s=+n[1];return e=u.translate(n).clipExtent([[f-.455*t,s-.238*t],[f+.455*t,s+.238*t]]).stream(l).point,r=o.translate([f-.307*t,s+.201*t]).clipExtent([[f-.425*t+wn,s+.12*t+wn],[f-.214*t-wn,s+.234*t-wn]]).stream(l).point,i=a.translate([f-.205*t,s+.212*t]).clipExtent([[f-.214*t+wn,s+.166*t+wn],[f-.115*t-wn,s+.234*t-wn]]).stream(l).point,c},c.scale(1070)};var or,ar,lr,cr,fr,sr,hr={point:R,lineStart:R,lineEnd:R,polygonStart:function(){ar=0,hr.lineStart=pr},polygonEnd:function(){hr.lineStart=hr.lineEnd=hr.point=R,or+=y(ar/2)}};function pr(){var n,t,e,r;function i(n,t){ar+=r*n-e*t,e=n,r=t}hr.point=function(u,o){hr.point=i,n=e=u,t=r=o},hr.lineEnd=function(){i(n,t)}}var gr={point:function(n,t){nfr&&(fr=n);tsr&&(sr=t)},lineStart:R,lineEnd:R,polygonStart:R,polygonEnd:R};function vr(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}var dr,yr={point:mr,lineStart:Mr,lineEnd:xr,polygonStart:function(){yr.lineStart=br},polygonEnd:function(){yr.point=mr,yr.lineStart=Mr,yr.lineEnd=xr}};function mr(n,t){Me+=n,xe+=t,++be}function Mr(){var n,t;function e(e,r){var i=e-n,u=r-t,o=Math.sqrt(i*i+u*u);_e+=o*(n+e)/2,we+=o*(t+r)/2,Se+=o,mr(n=e,t=r)}yr.point=function(r,i){yr.point=e,mr(n=r,t=i)}}function xr(){yr.point=mr}function br(){var n,t,e,r;function i(n,t){var i=n-e,u=t-r,o=Math.sqrt(i*i+u*u);_e+=o*(e+n)/2,we+=o*(r+t)/2,Se+=o,ke+=(o=r*n-e*t)*(e+n),Ne+=o*(r+t),Ee+=3*o,mr(e=n,r=t)}yr.point=function(u,o){yr.point=i,mr(n=e=u,t=r=o)},yr.lineEnd=function(){i(n,t)}}function _r(n){var t=.5,e=Math.cos(30*Cn),r=16;function i(t){return(r?function(t){var e,i,o,a,l,c,f,s,h,p,g,v,d={point:y,lineStart:m,lineEnd:x,polygonStart:function(){t.polygonStart(),d.lineStart=b},polygonEnd:function(){t.polygonEnd(),d.lineStart=m}};function y(e,r){e=n(e,r),t.point(e[0],e[1])}function m(){s=NaN,d.point=M,t.lineStart()}function M(e,i){var o=Le([e,i]),a=n(e,i);u(s,h,f,p,g,v,s=a[0],h=a[1],f=e,p=o[0],g=o[1],v=o[2],r,t),t.point(s,h)}function x(){d.point=y,t.lineEnd()}function b(){m(),d.point=_,d.lineEnd=w}function _(n,t){M(e=n,t),i=s,o=h,a=p,l=g,c=v,d.point=M}function w(){u(s,h,f,p,g,v,i,o,e,a,l,c,r,t),d.lineEnd=x,x()}return d}:function(t){return Sr(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})})(t)}function u(r,i,o,a,l,c,f,s,h,p,g,v,d,m){var M=f-r,x=s-i,b=M*M+x*x;if(b>4*t&&d--){var _=a+p,w=l+g,S=c+v,k=Math.sqrt(_*_+w*w+S*S),N=Math.asin(S/=k),E=y(y(S)-1)t||y((M*L+x*q)/b-.5)>.3||a*p+l*g+c*v0&&16,i):Math.sqrt(t)},i}function wr(n){this.stream=n}function Sr(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function kr(n){return Nr(function(){return n})()}function Nr(t){var e,r,i,u,o,a,l=_r(function(n,t){return[(n=e(n,t))[0]*c+u,o-n[1]*c]}),c=150,f=480,s=250,h=0,p=0,g=0,v=0,d=0,m=nr,M=z,x=null,b=null;function _(n){return[(n=i(n[0]*Cn,n[1]*Cn))[0]*c+u,o-n[1]*c]}function w(n){return(n=i.invert((n[0]-u)/c,(o-n[1])/c))&&[n[0]*zn,n[1]*zn]}function S(){i=Ve(r=zr(g,v,d),e);var n=e(h,p);return u=f-n[0]*c,o=s+n[1]*c,k()}function k(){return a&&(a.valid=!1,a=null),_}return _.stream=function(n){return a&&(a.valid=!1),(a=Er(m(r,l(M(n))))).valid=!0,a},_.clipAngle=function(n){return arguments.length?(m=null==n?(x=n,nr):function(n){var t=Math.cos(n),e=t>0,r=y(t)>wn;return Je(i,function(n){var t,a,l,c,f;return{lineStart:function(){c=l=!1,f=1},point:function(s,h){var p,g=[s,h],v=i(s,h),d=e?v?0:o(s,h):v?o(s+(s<0?kn:-kn),h):0;if(!t&&(c=l=v)&&n.lineStart(),v!==l&&(p=u(t,g),(je(t,p)||je(g,p))&&(g[0]+=wn,g[1]+=wn,v=i(g[0],g[1]))),v!==l)f=0,v?(n.lineStart(),p=u(g,t),n.point(p[0],p[1])):(p=u(t,g),n.point(p[0],p[1]),n.lineEnd()),t=p;else if(r&&t&&e^v){var y;d&a||!(y=u(g,t,!0))||(f=0,e?(n.lineStart(),n.point(y[0][0],y[0][1]),n.point(y[1][0],y[1][1]),n.lineEnd()):(n.point(y[1][0],y[1][1]),n.lineEnd(),n.lineStart(),n.point(y[0][0],y[0][1])))}!v||t&&je(t,g)||n.point(g[0],g[1]),t=g,l=v,a=d},lineEnd:function(){l&&n.lineEnd(),t=null},clean:function(){return f|(c&&l)<<1}}},Rr(n,6*Cn),e?[0,-n]:[-kn,n-kn]);function i(n,e){return Math.cos(n)*Math.cos(e)>t}function u(n,e,r){var i=[1,0,0],u=Te(Le(n),Le(e)),o=qe(u,u),a=u[0],l=o-a*a;if(!l)return!r&&n;var c=t*o/l,f=-t*a/l,s=Te(i,u),h=De(i,c);Re(h,De(u,f));var p=s,g=qe(h,p),v=qe(p,p),d=g*g-v*(qe(h,h)-1);if(!(d<0)){var m=Math.sqrt(d),M=De(p,(-g-m)/v);if(Re(M,h),M=Ue(M),!r)return M;var x,b=n[0],_=e[0],w=n[1],S=e[1];_0^M[1]<(y(M[0]-b)kn^(b<=M[0]&&M[0]<=_)){var E=De(p,(-g+m)/v);return Re(E,h),[M,Ue(E)]}}}function o(t,r){var i=e?n:kn-n,u=0;return t<-i?u|=1:t>i&&(u|=2),r<-i?u|=4:r>i&&(u|=8),u}}((x=+n)*Cn),k()):x},_.clipExtent=function(n){return arguments.length?(b=n,M=n?rr(n[0][0],n[0][1],n[1][0],n[1][1]):z,k()):b},_.scale=function(n){return arguments.length?(c=+n,S()):c},_.translate=function(n){return arguments.length?(f=+n[0],s=+n[1],S()):[f,s]},_.center=function(n){return arguments.length?(h=n[0]%360*Cn,p=n[1]%360*Cn,S()):[h*zn,p*zn]},_.rotate=function(n){return arguments.length?(g=n[0]%360*Cn,v=n[1]%360*Cn,d=n.length>2?n[2]%360*Cn:0,S()):[g*zn,v*zn,d*zn]},n.rebind(_,l,"precision"),function(){return e=t.apply(this,arguments),_.invert=e.invert&&w,S()}}function Er(n){return Sr(n,function(t,e){n.point(t*Cn,e*Cn)})}function Ar(n,t){return[n,t]}function Cr(n,t){return[n>kn?n-Nn:n<-kn?n+Nn:n,t]}function zr(n,t,e){return n?t||e?Ve(qr(n),Tr(t,e)):qr(n):t||e?Tr(t,e):Cr}function Lr(n){return function(t,e){return[(t+=n)>kn?t-Nn:t<-kn?t+Nn:t,e]}}function qr(n){var t=Lr(n);return t.invert=Lr(-n),t}function Tr(n,t){var e=Math.cos(n),r=Math.sin(n),i=Math.cos(t),u=Math.sin(t);function o(n,t){var o=Math.cos(t),a=Math.cos(n)*o,l=Math.sin(n)*o,c=Math.sin(t),f=c*e+a*r;return[Math.atan2(l*i-f*u,a*e-c*r),Rn(f*i+l*u)]}return o.invert=function(n,t){var o=Math.cos(t),a=Math.cos(n)*o,l=Math.sin(n)*o,c=Math.sin(t),f=c*i-l*u;return[Math.atan2(l*i+c*u,a*e+f*r),Rn(f*e-a*r)]},o}function Rr(n,t){var e=Math.cos(n),r=Math.sin(n);return function(i,u,o,a){var l=o*t;null!=i?(i=Dr(e,i),u=Dr(e,u),(o>0?iu)&&(i+=o*Nn)):(i=n+o*Nn,u=n-.5*l);for(var c,f=i;o>0?f>u:f2?n[2]*Cn:0),t.invert=function(t){return(t=n.invert(t[0]*Cn,t[1]*Cn))[0]*=zn,t[1]*=zn,t},t},Cr.invert=Ar,n.geo.circle=function(){var n,t,e=[0,0],r=6;function i(){var n="function"==typeof e?e.apply(this,arguments):e,r=zr(-n[0]*Cn,-n[1]*Cn,0).invert,i=[];return t(null,null,1,{point:function(n,t){i.push(n=r(n,t)),n[0]*=zn,n[1]*=zn}}),{type:"Polygon",coordinates:[i]}}return i.origin=function(n){return arguments.length?(e=n,i):e},i.angle=function(e){return arguments.length?(t=Rr((n=+e)*Cn,r*Cn),i):n},i.precision=function(e){return arguments.length?(t=Rr(n*Cn,(r=+e)*Cn),i):r},i.angle(90)},n.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Cn,i=n[1]*Cn,u=t[1]*Cn,o=Math.sin(r),a=Math.cos(r),l=Math.sin(i),c=Math.cos(i),f=Math.sin(u),s=Math.cos(u);return Math.atan2(Math.sqrt((e=s*o)*e+(e=c*f-l*s*a)*e),l*f+c*s*a)},n.geo.graticule=function(){var t,e,r,i,u,o,a,l,c,f,s,h,p=10,g=p,v=90,d=360,m=2.5;function M(){return{type:"MultiLineString",coordinates:x()}}function x(){return n.range(Math.ceil(i/v)*v,r,v).map(s).concat(n.range(Math.ceil(l/d)*d,a,d).map(h)).concat(n.range(Math.ceil(e/p)*p,t,p).filter(function(n){return y(n%v)>wn}).map(c)).concat(n.range(Math.ceil(o/g)*g,u,g).filter(function(n){return y(n%d)>wn}).map(f))}return M.lines=function(){return x().map(function(n){return{type:"LineString",coordinates:n}})},M.outline=function(){return{type:"Polygon",coordinates:[s(i).concat(h(a).slice(1),s(r).reverse().slice(1),h(l).reverse().slice(1))]}},M.extent=function(n){return arguments.length?M.majorExtent(n).minorExtent(n):M.minorExtent()},M.majorExtent=function(n){return arguments.length?(i=+n[0][0],r=+n[1][0],l=+n[0][1],a=+n[1][1],i>r&&(n=i,i=r,r=n),l>a&&(n=l,l=a,a=n),M.precision(m)):[[i,l],[r,a]]},M.minorExtent=function(n){return arguments.length?(e=+n[0][0],t=+n[1][0],o=+n[0][1],u=+n[1][1],e>t&&(n=e,e=t,t=n),o>u&&(n=o,o=u,u=n),M.precision(m)):[[e,o],[t,u]]},M.step=function(n){return arguments.length?M.majorStep(n).minorStep(n):M.minorStep()},M.majorStep=function(n){return arguments.length?(v=+n[0],d=+n[1],M):[v,d]},M.minorStep=function(n){return arguments.length?(p=+n[0],g=+n[1],M):[p,g]},M.precision=function(n){return arguments.length?(m=+n,c=Pr(o,u,90),f=Ur(e,t,m),s=Pr(l,a,90),h=Ur(i,r,m),M):m},M.majorExtent([[-180,-90+wn],[180,90-wn]]).minorExtent([[-180,-80-wn],[180,80+wn]])},n.geo.greatArc=function(){var t,e,r=jr,i=Fr;function u(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||i.apply(this,arguments)]}}return u.distance=function(){return n.geo.distance(t||r.apply(this,arguments),e||i.apply(this,arguments))},u.source=function(n){return arguments.length?(r=n,t="function"==typeof n?null:n,u):r},u.target=function(n){return arguments.length?(i=n,e="function"==typeof n?null:n,u):i},u.precision=function(){return arguments.length?u:0},u},n.geo.interpolate=function(n,t){return e=n[0]*Cn,r=n[1]*Cn,i=t[0]*Cn,u=t[1]*Cn,o=Math.cos(r),a=Math.sin(r),l=Math.cos(u),c=Math.sin(u),f=o*Math.cos(e),s=o*Math.sin(e),h=l*Math.cos(i),p=l*Math.sin(i),g=2*Math.asin(Math.sqrt(Pn(u-r)+o*l*Pn(i-e))),v=1/Math.sin(g),(d=g?function(n){var t=Math.sin(n*=g)*v,e=Math.sin(g-n)*v,r=e*f+t*h,i=e*s+t*p,u=e*a+t*c;return[Math.atan2(i,r)*zn,Math.atan2(u,Math.sqrt(r*r+i*i))*zn]}:function(){return[e*zn,r*zn]}).distance=g,d;var e,r,i,u,o,a,l,c,f,s,h,p,g,v,d},n.geo.length=function(t){return dr=0,n.geo.stream(t,Hr),dr};var Hr={sphere:R,point:R,lineStart:function(){var n,t,e;function r(r,i){var u=Math.sin(i*=Cn),o=Math.cos(i),a=y((r*=Cn)-n),l=Math.cos(a);dr+=Math.atan2(Math.sqrt((a=o*Math.sin(a))*a+(a=e*u-t*o*l)*a),t*u+e*o*l),n=r,t=u,e=o}Hr.point=function(i,u){n=i*Cn,t=Math.sin(u*=Cn),e=Math.cos(u),Hr.point=r},Hr.lineEnd=function(){Hr.point=Hr.lineEnd=R}},lineEnd:R,polygonStart:R,polygonEnd:R};function Or(n,t){function e(t,e){var r=Math.cos(t),i=Math.cos(e),u=n(r*i);return[u*i*Math.sin(t),u*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),i=t(r),u=Math.sin(i),o=Math.cos(i);return[Math.atan2(n*u,r*o),Math.asin(r&&e*u/r)]},e}var Ir=Or(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(n.geo.azimuthalEqualArea=function(){return kr(Ir)}).raw=Ir;var Yr=Or(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},z);function Zr(n,t){var e=Math.cos(n),r=function(n){return Math.tan(kn/4+n/2)},i=n===t?Math.sin(n):Math.log(e/Math.cos(t))/Math.log(r(t)/r(n)),u=e*Math.pow(r(n),i)/i;if(!i)return $r;function o(n,t){u>0?t<-An+wn&&(t=-An+wn):t>An-wn&&(t=An-wn);var e=u/Math.pow(r(t),i);return[e*Math.sin(i*n),u-e*Math.cos(i*n)]}return o.invert=function(n,t){var e=u-t,r=Ln(i)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/i,2*Math.atan(Math.pow(u/r,1/i))-An]},o}function Vr(n,t){var e=Math.cos(n),r=n===t?Math.sin(n):(e-Math.cos(t))/(t-n),i=e/r+n;if(y(r)1&&qn(n[e[r-2]],n[e[r-1]],n[i])<=0;)--r;e[r++]=i}return e.slice(0,r)}function ti(n,t){return n[0]-t[0]||n[1]-t[1]}(n.geo.stereographic=function(){return kr(Jr)}).raw=Jr,Gr.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-An]},(n.geo.transverseMercator=function(){var n=Br(Gr),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):[(n=t())[1],-n[0]]},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):[(n=e())[0],n[1],n[2]-90]},e([0,0,90])}).raw=Gr,n.geom={},n.geom.hull=function(n){var t=Kr,e=Qr;if(arguments.length)return r(n);function r(n){if(n.length<3)return[];var r,i=dt(t),u=dt(e),o=n.length,a=[],l=[];for(r=0;r=0;--r)p.push(n[a[c[r]][2]]);for(r=+s;rwn)a=a.L;else{if(!((i=u-mi(a,o))>wn)){r>-wn?(t=a.P,e=a):i>-wn?(t=a,e=a.N):t=e=a;break}if(!a.R){t=a;break}a=a.R}var l=pi(n);if(li.insert(t,l),t||e){if(t===e)return _i(t),e=pi(t.site),li.insert(l,e),l.edge=e.edge=ki(t.site,l.site),bi(t),void bi(e);if(e){_i(t),_i(e);var c=t.site,f=c.x,s=c.y,h=n.x-f,p=n.y-s,g=e.site,v=g.x-f,d=g.y-s,y=2*(h*d-p*v),m=h*h+p*p,M=v*v+d*d,x={x:(d*m-p*M)/y+f,y:(h*M-v*m)/y+s};Ni(e.edge,c,g,x),l.edge=ki(c,n,null,x),e.edge=ki(n,g,null,x),bi(t),bi(e)}else l.edge=ki(t.site,l.site)}}function yi(n,t){var e=n.site,r=e.x,i=e.y,u=i-t;if(!u)return r;var o=n.P;if(!o)return-1/0;var a=(e=o.site).x,l=e.y,c=l-t;if(!c)return a;var f=a-r,s=1/u-1/c,h=f/c;return s?(-h+Math.sqrt(h*h-2*s*(f*f/(-2*c)-l+c/2+i-u/2)))/s+r:(r+a)/2}function mi(n,t){var e=n.N;if(e)return yi(e,t);var r=n.site;return r.y===t?r.x:1/0}function Mi(n){this.site=n,this.edges=[]}function xi(n,t){return t.angle-n.angle}function bi(n){var t=n.P,e=n.N;if(t&&e){var r=t.site,i=n.site,u=e.site;if(r!==u){var o=i.x,a=i.y,l=r.x-o,c=r.y-a,f=u.x-o,s=2*(l*(d=u.y-a)-c*f);if(!(s>=-Sn)){var h=l*l+c*c,p=f*f+d*d,g=(d*h-c*p)/s,v=(l*p-f*h)/s,d=v+a,y=hi.pop()||new function(){Ci(this),this.x=this.y=this.arc=this.site=this.cy=null};y.arc=n,y.site=i,y.x=g+o,y.y=d+Math.sqrt(g*g+v*v),y.cy=d,n.circle=y;for(var m=null,M=fi._;M;)if(y.y=a)return;if(h>g){if(u){if(u.y>=c)return}else u={x:d,y:l};e={x:d,y:c}}else{if(u){if(u.y1)if(h>g){if(u){if(u.y>=c)return}else u={x:(l-i)/r,y:l};e={x:(c-i)/r,y:c}}else{if(u){if(u.y=a)return}else u={x:o,y:r*o+i};e={x:a,y:r*a+i}}else{if(u){if(u.xwn||y(i-e)>wn)&&(a.splice(o,0,new Ei((m=u.site,M=f,x=y(r-s)wn?{x:s,y:y(t-s)wn?{x:y(e-g)wn?{x:h,y:y(t-h)wn?{x:y(e-p)=e&&c.x<=i&&c.y>=r&&c.y<=o?[[e,o],[i,o],[i,r],[e,r]]:[]).point=n[a]}),t}function a(n){return n.map(function(n,t){return{x:Math.round(r(n,t)/wn)*wn,y:Math.round(i(n,t)/wn)*wn,i:t}})}return o.links=function(n){return Ti(a(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},o.triangles=function(n){var t=[];return Ti(a(n)).cells.forEach(function(e,r){for(var i,u,o,a,l=e.site,c=e.edges.sort(xi),f=-1,s=c.length,h=c[s-1].edge,p=h.l===l?h.r:h.l;++fu&&(i=t.slice(u,i),a[o]?a[o]+=i:a[++o]=i),(e=e[0])===(r=r[0])?a[o]?a[o]+=r:a[++o]=r:(a[++o]=null,l.push({i:o,x:Hi(e,r)})),u=Yi.lastIndex;return uv&&(v=l.x),l.y>d&&(d=l.y),c.push(l.x),f.push(l.y);else for(s=0;sv&&(v=x),b>d&&(d=b),c.push(x),f.push(b)}var _=v-p,w=d-g;function S(n,t,e,r,i,u,o,a){if(!isNaN(e)&&!isNaN(r))if(n.leaf){var l=n.x,c=n.y;if(null!=l)if(y(l-e)+y(c-r)<.01)k(n,t,e,r,i,u,o,a);else{var f=n.point;n.x=n.y=n.point=null,k(n,f,l,c,i,u,o,a),k(n,t,e,r,i,u,o,a)}else n.x=e,n.y=r,n.point=t}else k(n,t,e,r,i,u,o,a)}function k(n,t,e,r,i,u,o,a){var l=.5*(i+o),c=.5*(u+a),f=e>=l,s=r>=c,h=s<<1|f;n.leaf=!1,n=n.nodes[h]||(n.nodes[h]={leaf:!0,nodes:[],point:null,x:null,y:null}),f?i=l:o=l,s?u=c:a=c,S(n,t,e,r,i,u,o,a)}_>w?d=g+_:v=p+w;var N={leaf:!0,nodes:[],point:null,x:null,y:null};if(N.add=function(n){S(N,n,+m(n,++s),+M(n,s),p,g,v,d)},N.visit=function(n){!function n(t,e,r,i,u,o){if(!t(e,r,i,u,o)){var a=.5*(r+u),l=.5*(i+o),c=e.nodes;c[0]&&n(t,c[0],r,i,a,l),c[1]&&n(t,c[1],a,i,u,l),c[2]&&n(t,c[2],r,l,a,o),c[3]&&n(t,c[3],a,l,u,o)}}(n,N,p,g,v,d)},N.find=function(n){return t=N,e=n[0],r=n[1],c=1/0,function n(t,f,s,h,p){if(!(f>o||s>a||h=b)<<1|e>=x,w=_+4;_=0&&!(r=n.interpolators[i](t,e)););return r}function Vi(n,t){var e,r=[],i=[],u=n.length,o=t.length,a=Math.min(n.length,t.length);for(e=0;e=1)return 1;var t=n*n,e=t*n;return 4*(n<.5?e:3*(n-t)+e-.75)}function nu(n){return 1-Math.cos(n*An)}function tu(n){return Math.pow(2,10*(n-1))}function eu(n){return 1-Math.sqrt(1-n*n)}function ru(n){return n<1/2.75?7.5625*n*n:n<2/2.75?7.5625*(n-=1.5/2.75)*n+.75:n<2.5/2.75?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function iu(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function uu(n){var t,e,r,i=[n.a,n.b],u=[n.c,n.d],o=au(i),a=ou(i,u),l=au(((t=u)[0]+=(r=-a)*(e=i)[0],t[1]+=r*e[1],t))||0;i[0]*u[1]=0?n.slice(0,r):n,u=r>=0?n.slice(r+1):"in";return i=$i.get(i)||Xi,u=Bi.get(u)||z,e=u(i.apply(null,t.call(arguments,1))),function(n){return n<=0?0:n>=1?1:e(n)}},n.interpolateHcl=function(t,e){t=n.hcl(t),e=n.hcl(e);var r=t.h,i=t.c,u=t.l,o=e.h-r,a=e.c-i,l=e.l-u;isNaN(a)&&(a=0,i=isNaN(i)?e.c:i);isNaN(o)?(o=0,r=isNaN(r)?e.h:r):o>180?o-=360:o<-180&&(o+=360);return function(n){return $n(r+o*n,i+a*n,u+l*n)+""}},n.interpolateHsl=function(t,e){t=n.hsl(t),e=n.hsl(e);var r=t.h,i=t.s,u=t.l,o=e.h-r,a=e.s-i,l=e.l-u;isNaN(a)&&(a=0,i=isNaN(i)?e.s:i);isNaN(o)?(o=0,r=isNaN(r)?e.h:r):o>180?o-=360:o<-180&&(o+=360);return function(n){return Zn(r+o*n,i+a*n,u+l*n)+""}},n.interpolateLab=function(t,e){t=n.lab(t),e=n.lab(e);var r=t.l,i=t.a,u=t.b,o=e.l-r,a=e.a-i,l=e.b-u;return function(n){return nt(r+o*n,i+a*n,u+l*n)+""}},n.interpolateRound=iu,n.transform=function(t){var e=r.createElementNS(n.ns.prefix.svg,"g");return(n.transform=function(n){if(null!=n){e.setAttribute("transform",n);var t=e.transform.baseVal.consolidate()}return new uu(t?t.matrix:lu)})(t)},uu.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var lu={a:1,b:0,c:0,d:1,e:0,f:0};function cu(n){return n.length?n.pop()+",":""}function fu(t,e){var r,i,u,o,a,l,c=[],f=[];return t=n.transform(t),e=n.transform(e),function(n,t,e,r){if(n[0]!==t[0]||n[1]!==t[1]){var i=e.push("translate(",null,",",null,")");r.push({i:i-4,x:Hi(n[0],t[0])},{i:i-2,x:Hi(n[1],t[1])})}else(t[0]||t[1])&&e.push("translate("+t+")")}(t.translate,e.translate,c,f),r=t.rotate,i=e.rotate,u=c,r!==i?(r-i>180?i+=360:i-r>180&&(r+=360),f.push({i:u.push(cu(u)+"rotate(",null,")")-2,x:Hi(r,i)})):i&&u.push(cu(u)+"rotate("+i+")"),o=t.skew,a=e.skew,l=c,o!==a?f.push({i:l.push(cu(l)+"skewX(",null,")")-2,x:Hi(o,a)}):a&&l.push(cu(l)+"skewX("+a+")"),function(n,t,e,r){if(n[0]!==t[0]||n[1]!==t[1]){var i=e.push(cu(e)+"scale(",null,",",null,")");r.push({i:i-4,x:Hi(n[0],t[0])},{i:i-2,x:Hi(n[1],t[1])})}else 1===t[0]&&1===t[1]||e.push(cu(e)+"scale("+t+")")}(t.scale,e.scale,c,f),t=e=null,function(n){for(var t,e=-1,r=f.length;++e0?r=n:(t.c=null,t.t=NaN,t=null,l.end({type:"end",alpha:r=0})):n>0&&(l.start({type:"start",alpha:r=n}),t=St(a.tick)),a):r},a.start=function(){var n,t,e,r=y.length,l=m.length,f=c[0],g=c[1];for(n=0;n=0;)e.push(i[r])}function Su(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(u=n.children)&&(i=u.length))for(var i,u,o=-1;++o=0;)o.push(f=c[l]),f.parent=u,f.depth=u.depth+1;e&&(u.value=0),u.children=c}else e&&(u.value=+e.call(r,u,u.depth)||0),delete u.children;return Su(i,function(t){var r,i;n&&(r=t.children)&&r.sort(n),e&&(i=t.parent)&&(i.value+=t.value)}),a}return r.sort=function(t){return arguments.length?(n=t,r):n},r.children=function(n){return arguments.length?(t=n,r):t},r.value=function(n){return arguments.length?(e=n,r):e},r.revalue=function(n){return e&&(wu(n,function(n){n.children&&(n.value=0)}),Su(n,function(n){var t;n.children||(n.value=+e.call(r,n,n.depth)||0),(t=n.parent)&&(t.value+=n.value)})),n},r},n.layout.partition=function(){var t=n.layout.hierarchy(),e=[1,1];function r(n,r){var i=t.call(this,n,r);return function n(t,e,r,i){var u=t.children;if(t.x=e,t.y=t.depth*i,t.dx=r,t.dy=i,u&&(o=u.length)){var o,a,l,c=-1;for(r=t.value?r/t.value:0;++ca&&(a=r),o.push(r)}for(e=0;ei&&(r=e,i=t);return r}function ju(n){return n.reduce(Fu,0)}function Fu(n,t){return n+t[1]}function Hu(n,t){return Ou(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function Ou(n,t){for(var e=-1,r=+n[0],i=(n[1]-r)/t,u=[];++e<=t;)u[e]=i*e+r;return u}function Iu(t){return[n.min(t),n.max(t)]}function Yu(n,t){return n.value-t.value}function Zu(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function Vu(n,t){n._pack_next=t,t._pack_prev=n}function Xu(n,t){var e=t.x-n.x,r=t.y-n.y,i=n.r+t.r;return.999*i*i>e*e+r*r}function $u(n){if((t=n.children)&&(l=t.length)){var t,e,r,i,u,o,a,l,c=1/0,f=-1/0,s=1/0,h=-1/0;if(t.forEach(Bu),(e=t[0]).x=-e.r,e.y=0,M(e),l>1&&((r=t[1]).x=r.r,r.y=0,M(r),l>2))for(Ju(e,r,i=t[2]),M(i),Zu(e,i),e._pack_prev=i,Zu(i,r),r=e._pack_next,u=3;u0)for(o=-1;++o=s[0]&&l<=s[1]&&((a=c[n.bisect(h,l,1,g)-1]).y+=v,a.push(u[o]));return c}return u.value=function(n){return arguments.length?(e=n,u):e},u.range=function(n){return arguments.length?(r=dt(n),u):r},u.bins=function(n){return arguments.length?(i="number"==typeof n?function(t){return Ou(t,n)}:dt(n),u):i},u.frequency=function(n){return arguments.length?(t=!!n,u):t},u},n.layout.pack=function(){var t,e=n.layout.hierarchy().sort(Yu),r=0,i=[1,1];function u(n,u){var o=e.call(this,n,u),a=o[0],l=i[0],c=i[1],f=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(a.x=a.y=0,Su(a,function(n){n.r=+f(n.value)}),Su(a,$u),r){var s=r*(t?1:Math.max(2*a.r/l,2*a.r/c))/2;Su(a,function(n){n.r+=s}),Su(a,$u),Su(a,function(n){n.r-=s})}return function n(t,e,r,i){var u=t.children;t.x=e+=i*t.x;t.y=r+=i*t.y;t.r*=i;if(u)for(var o=-1,a=u.length;++op.x&&(p=n),n.depth>g.depth&&(g=n)});var v=e(h,p)/2-h.x,d=r[0]/(p.x+e(p,h)/2+v),y=r[1]/(g.depth||1);wu(f,function(n){n.x=(n.x+v)*d,n.y=n.depth*y})}return c}function o(n){var t=n.children,r=n.parent.children,i=n.i?r[n.i-1]:null;if(t.length){!function(n){var t,e=0,r=0,i=n.children,u=i.length;for(;--u>=0;)(t=i[u]).z+=e,t.m+=e,e+=t.s+(r+=t.c)}(n);var u=(t[0].z+t[t.length-1].z)/2;i?(n.z=i.z+e(n._,i._),n.m=n.z-u):n.z=u}else i&&(n.z=i.z+e(n._,i._));n.parent.A=function(n,t,r){if(t){for(var i,u=n,o=n,a=t,l=u.parent.children[0],c=u.m,f=o.m,s=a.m,h=l.m;a=Qu(a),u=Ku(u),a&&u;)l=Ku(l),(o=Qu(o)).a=n,(i=a.z+s-u.z-c+e(a._,u._))>0&&(no((g=n,v=r,(p=a).a.parent===g.parent?p.a:v),n,i),c+=i,f+=i),s+=a.m,c+=u.m,h+=l.m,f+=o.m;a&&!Qu(o)&&(o.t=a,o.m+=s-f),u&&!Ku(l)&&(l.t=u,l.m+=c-h,r=n)}var p,g,v;return r}(n,i,n.parent.A||r[0])}function a(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function l(n){n.x*=r[0],n.y=n.depth*r[1]}return u.separation=function(n){return arguments.length?(e=n,u):e},u.size=function(n){return arguments.length?(i=null==(r=n)?l:null,u):i?null:r},u.nodeSize=function(n){return arguments.length?(i=null==(r=n)?null:l,u):i?r:null},_u(u,t)},n.layout.cluster=function(){var t=n.layout.hierarchy().sort(null).value(null),e=Gu,r=[1,1],i=!1;function u(u,o){var a,l=t.call(this,u,o),c=l[0],f=0;Su(c,function(t){var r,i,u=t.children;u&&u.length?(t.x=(i=u).reduce(function(n,t){return n+t.x},0)/i.length,t.y=(r=u,1+n.max(r,function(n){return n.y}))):(t.x=a?f+=e(t,a):0,t.y=0,a=t)});var s=function n(t){var e=t.children;return e&&e.length?n(e[0]):t}(c),h=function n(t){var e,r=t.children;return r&&(e=r.length)?n(r[e-1]):t}(c),p=s.x-e(s,h)/2,g=h.x+e(h,s)/2;return Su(c,i?function(n){n.x=(n.x-c.x)*r[0],n.y=(c.y-n.y)*r[1]}:function(n){n.x=(n.x-p)/(g-p)*r[0],n.y=(1-(c.y?n.y/c.y:1))*r[1]}),l}return u.separation=function(n){return arguments.length?(e=n,u):e},u.size=function(n){return arguments.length?(i=null==(r=n),u):i?null:r},u.nodeSize=function(n){return arguments.length?(i=null!=(r=n),u):i?r:null},_u(u,t)},n.layout.treemap=function(){var t,e=n.layout.hierarchy(),r=Math.round,i=[1,1],u=null,o=to,a=!1,l="squarify",c=.5*(1+Math.sqrt(5));function f(n,t){for(var e,r,i=-1,u=n.length;++i0;)a.push(e=c[i-1]),a.area+=e.area,"squarify"!==l||(r=p(a,v))<=h?(c.pop(),h=r):(a.area-=a.pop().area,g(a,v,u,!1),v=Math.min(u.dx,u.dy),a.length=a.area=0,h=1/0);a.length&&(g(a,v,u,!0),a.length=a.area=0),t.forEach(s)}}function h(n){var t=n.children;if(t&&t.length){var e,r=o(n),i=t.slice(),u=[];for(f(i,r.dx*r.dy/n.value),u.area=0;e=i.pop();)u.push(e),u.area+=e.area,null!=e.z&&(g(u,e.z?r.dx:r.dy,r,!i.length),u.length=u.area=0);t.forEach(h)}}function p(n,t){for(var e,r=n.area,i=0,u=1/0,o=-1,a=n.length;++oi&&(i=e));return t*=t,(r*=r)?Math.max(t*i*c/r,r/(t*u*c)):1/0}function g(n,t,e,i){var u,o=-1,a=n.length,l=e.x,c=e.y,f=t?r(n.area/t):0;if(t==e.dx){for((i||f>e.dy)&&(f=e.dy);++oe.dx)&&(f=e.dx);++o1);return n+t*e*Math.sqrt(-2*Math.log(i)/i)}},logNormal:function(){var t=n.random.normal.apply(n,arguments);return function(){return Math.exp(t())}},bates:function(t){var e=n.random.irwinHall(t);return function(){return e()/t}},irwinHall:function(n){return function(){for(var t=0,e=0;e2?co:uo,a=i?hu:su;return u=n(t,e,a,r),o=n(e,t,a,Zi),l}function l(n){return u(n)}l.invert=function(n){return o(n)};l.domain=function(n){return arguments.length?(t=n.map(Number),a()):t};l.range=function(n){return arguments.length?(e=n,a()):e};l.rangeRound=function(n){return l.range(n).interpolate(iu)};l.clamp=function(n){return arguments.length?(i=n,a()):i};l.interpolate=function(n){return arguments.length?(r=n,a()):r};l.ticks=function(n){return po(t,n)};l.tickFormat=function(n,e){return go(t,n,e)};l.nice=function(n){return so(t,n),a()};l.copy=function(){return n(t,e,r,i)};return a()}([0,1],[0,1],Zi,!1)};var vo={s:1,g:1,p:1,r:1,e:1};function yo(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}n.scale.log=function(){return function t(e,r,i,u){function o(n){return(i?Math.log(n<0?0:n):-Math.log(n>0?0:-n))/Math.log(r)}function a(n){return i?Math.pow(r,n):-Math.pow(r,-n)}function l(n){return e(o(n))}l.invert=function(n){return a(e.invert(n))};l.domain=function(n){return arguments.length?(i=n[0]>=0,e.domain((u=n.map(Number)).map(o)),l):u};l.base=function(n){return arguments.length?(r=+n,e.domain(u.map(o)),l):r};l.nice=function(){var n=oo(u.map(o),i?Math:Mo);return e.domain(n),u=n.map(a),l};l.ticks=function(){var n=ro(u),t=[],e=n[0],l=n[1],c=Math.floor(o(e)),f=Math.ceil(o(l)),s=r%1?2:r;if(isFinite(f-c)){if(i){for(;c0;h--)t.push(a(c)*h);for(c=0;t[c]l;f--);t=t.slice(c,f)}return t};l.tickFormat=function(t,e){if(!arguments.length)return mo;arguments.length<2?e=mo:"function"!=typeof e&&(e=n.format(e));var i=Math.max(1,r*t/l.ticks().length);return function(n){var t=n/a(Math.round(o(n)));return t*r0?i[n-1]:e[0],ns?0:1;if(c=En)return l(c,p)+(a?l(a,1-p):"")+"Z";var g,v,d,y,m,M,x,b,_,w,S,k,N=0,E=0,A=[];if((y=(+o.apply(this,arguments)||0)/2)&&(d=r===No?Math.sqrt(a*a+c*c):+r.apply(this,arguments),p||(E*=-1),c&&(E=Rn(d/c*Math.sin(y))),a&&(N=Rn(d/a*Math.sin(y)))),c){m=c*Math.cos(f+E),M=c*Math.sin(f+E),x=c*Math.cos(s-E),b=c*Math.sin(s-E);var C=Math.abs(s-f-2*E)<=kn?0:1;if(E&&qo(m,M,x,b)===p^C){var z=(f+s)/2;m=c*Math.cos(z),M=c*Math.sin(z),x=b=null}}else m=M=0;if(a){_=a*Math.cos(s-N),w=a*Math.sin(s-N),S=a*Math.cos(f+N),k=a*Math.sin(f+N);var L=Math.abs(f-s+2*N)<=kn?0:1;if(N&&qo(_,w,S,k)===1-p^L){var q=(f+s)/2;_=a*Math.cos(q),w=a*Math.sin(q),S=k=null}}else _=w=0;if(h>wn&&(g=Math.min(Math.abs(c-a)/2,+e.apply(this,arguments)))>.001){v=a0?0:1}function To(n,t,e,r,i){var u=n[0]-t[0],o=n[1]-t[1],a=(i?r:-r)/Math.sqrt(u*u+o*o),l=a*o,c=-a*u,f=n[0]+l,s=n[1]+c,h=t[0]+l,p=t[1]+c,g=(f+h)/2,v=(s+p)/2,d=h-f,y=p-s,m=d*d+y*y,M=e-r,x=f*p-h*s,b=(y<0?-1:1)*Math.sqrt(Math.max(0,M*M*m-x*x)),_=(x*y-d*b)/m,w=(-x*d-y*b)/m,S=(x*y+d*b)/m,k=(-x*d+y*b)/m,N=_-g,E=w-v,A=S-g,C=k-v;return N*N+E*E>A*A+C*C&&(_=S,w=k),[[_-l,w-c],[_*e/M,w*e/M]]}function Ro(n){var t=Kr,e=Qr,r=Xe,i=Po,u=i.key,o=.7;function a(u){var a,l=[],c=[],f=-1,s=u.length,h=dt(t),p=dt(e);function g(){l.push("M",i(n(c),o))}for(;++f1&&i.push("H",r[0]);return i.join("")},"step-before":jo,"step-after":Fo,basis:Io,"basis-open":function(n){if(n.length<4)return Po(n);var t,e=[],r=-1,i=n.length,u=[0],o=[0];for(;++r<3;)t=n[r],u.push(t[0]),o.push(t[1]);e.push(Yo(Xo,u)+","+Yo(Xo,o)),--r;for(;++r9&&(i=3*t/Math.sqrt(i),o[a]=i*e,o[a+1]=i*r));a=-1;for(;++a<=l;)i=(n[Math.min(l,a+1)][0]-n[Math.max(0,a-1)][0])/(6*(1+o[a]*o[a])),u.push([i||0,o[a]*i||0]);return u}(n))}});function Po(n){return n.length>1?n.join("L"):n+"Z"}function Uo(n){return n.join("L")+"Z"}function jo(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1){a=t[1],u=n[l],l++,r+="C"+(i[0]+o[0])+","+(i[1]+o[1])+","+(u[0]-a[0])+","+(u[1]-a[1])+","+u[0]+","+u[1];for(var c=2;ckn)+",1 "+t}function l(n,t,e,r){return"Q 0,0 "+r}return u.radius=function(n){return arguments.length?(e=dt(n),u):e},u.source=function(t){return arguments.length?(n=dt(t),u):n},u.target=function(n){return arguments.length?(t=dt(n),u):t},u.startAngle=function(n){return arguments.length?(r=dt(n),u):r},u.endAngle=function(n){return arguments.length?(i=dt(n),u):i},u},n.svg.diagonal=function(){var n=jr,t=Fr,e=Ko;function r(r,i){var u=n.call(this,r,i),o=t.call(this,r,i),a=(u.y+o.y)/2,l=[u,{x:u.x,y:a},{x:o.x,y:a},o];return"M"+(l=l.map(e))[0]+"C"+l[1]+" "+l[2]+" "+l[3]}return r.source=function(t){return arguments.length?(n=dt(t),r):n},r.target=function(n){return arguments.length?(t=dt(n),r):t},r.projection=function(n){return arguments.length?(e=n,r):e},r},n.svg.diagonal.radial=function(){var t=n.svg.diagonal(),e=Ko,r=t.projection;return t.projection=function(n){return arguments.length?r((t=e=n,function(){var n=t.apply(this,arguments),e=n[0],r=n[1]-An;return[e*Math.cos(r),e*Math.sin(r)]})):e;var t},t},n.svg.symbol=function(){var n=na,t=Qo;function e(e,r){return(ea.get(n.call(this,e,r))||ta)(t.call(this,e,r))}return e.type=function(t){return arguments.length?(n=dt(t),e):n},e.size=function(n){return arguments.length?(t=dt(n),e):t},e};var ea=n.map({circle:ta,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*ia)),e=t*ia;return"M0,"+-t+"L"+e+",0 0,"+t+" "+-e+",0Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/ra),e=t*ra/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/ra),e=t*ra/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});n.svg.symbolTypes=ea.keys();var ra=Math.sqrt(3),ia=Math.tan(30*Cn);X.transition=function(n){for(var t,e,r=la||++sa,i=ga(n),u=[],o=ca||{time:Date.now(),ease:Qi,delay:0,duration:250},a=-1,l=this.length;++a0;)c[--h].call(n,o);if(u>=1)return s.event&&s.event.end.call(n,n.__data__,t),--f.count?delete f[r]:delete n[e],1}s||(u=i.time,o=St(function(n){var t=s.delay;if(o.t=t+u,t<=n)return h(n-t);o.c=h},0,u),s=f[r]={tween:new M,time:u,timer:o,delay:i.delay,duration:i.duration,ease:i.ease,index:t},i=null,++f.count)}fa.call=X.call,fa.empty=X.empty,fa.node=X.node,fa.size=X.size,n.transition=function(t,e){return t&&t.transition?la?t.transition(e):t:n.selection().transition(t)},n.transition.prototype=fa,fa.select=function(n){var t,e,r,i=this.id,u=this.namespace,o=[];n=$(n);for(var a=-1,l=this.length;++arect,.s>rect").attr("width",a[1]-a[0])}function v(n){n.select(".extent").attr("y",l[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",l[1]-l[0])}function d(){var s,d,y=this,m=n.select(n.event.target),M=r.of(y,arguments),x=n.select(y),b=m.datum(),_=!/^(n|s)$/.test(b)&&i,w=!/^(e|w)$/.test(b)&&o,S=m.classed("extent"),k=Mn(y),N=n.mouse(y),E=n.select(u(y)).on("keydown.brush",function(){32==n.event.keyCode&&(S||(s=null,N[0]-=a[1],N[1]-=l[1],S=2),U())}).on("keyup.brush",function(){32==n.event.keyCode&&2==S&&(N[0]+=a[1],N[1]+=l[1],S=0,U())});if(n.event.changedTouches?E.on("touchmove.brush",z).on("touchend.brush",q):E.on("mousemove.brush",z).on("mouseup.brush",q),x.interrupt().selectAll("*").interrupt(),S)N[0]=a[0]-N[0],N[1]=l[0]-N[1];else if(b){var A=+/w$/.test(b),C=+/^n/.test(b);d=[a[1-A]-N[0],l[1-C]-N[1]],N[0]=a[A],N[1]=l[C]}else n.event.altKey&&(s=N.slice());function z(){var t=n.mouse(y),e=!1;d&&(t[0]+=d[0],t[1]+=d[1]),S||(n.event.altKey?(s||(s=[(a[0]+a[1])/2,(l[0]+l[1])/2]),N[0]=a[+(t[0]1?{floor:function(t){for(;a(t=n.floor(t));)t=Ea(t-1);return t},ceil:function(t){for(;a(t=n.ceil(t));)t=Ea(+t+1);return t}}:n))},i.ticks=function(n,t){var e=ro(i.domain()),r=null==n?u(e,10):"number"==typeof n?u(e,n):!n.range&&[{range:n},t];return r&&(n=r[0],t=r[1]),n.range(e[0],Ea(+e[1]+1),t<1?1:t)},i.tickFormat=function(){return r},i.copy=function(){return Na(t.copy(),e,r)},fo(i,t)}function Ea(n){return new Date(n)}_a.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?ka:Sa,ka.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},ka.toString=Sa.toString,Tt.second=Ut(function(n){return new Rt(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),Tt.seconds=Tt.second.range,Tt.seconds.utc=Tt.second.utc.range,Tt.minute=Ut(function(n){return new Rt(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),Tt.minutes=Tt.minute.range,Tt.minutes.utc=Tt.minute.utc.range,Tt.hour=Ut(function(n){var t=n.getTimezoneOffset()/60;return new Rt(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),Tt.hours=Tt.hour.range,Tt.hours.utc=Tt.hour.utc.range,Tt.month=Ut(function(n){return(n=Tt.day(n)).setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),Tt.months=Tt.month.range,Tt.months.utc=Tt.month.utc.range;var Aa=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Ca=[[Tt.second,1],[Tt.second,5],[Tt.second,15],[Tt.second,30],[Tt.minute,1],[Tt.minute,5],[Tt.minute,15],[Tt.minute,30],[Tt.hour,1],[Tt.hour,3],[Tt.hour,6],[Tt.hour,12],[Tt.day,1],[Tt.day,2],[Tt.week,1],[Tt.month,1],[Tt.month,3],[Tt.year,1]],za=_a.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",Xe]]),La={range:function(t,e,r){return n.range(Math.ceil(t/r)*r,+e,r).map(Ea)},floor:z,ceil:z};Ca.year=Tt.year,Tt.scale=function(){return Na(n.scale.linear(),Ca,za)};var qa=Ca.map(function(n){return[n[0].utc,n[1]]}),Ta=wa.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",Xe]]);function Ra(n){return JSON.parse(n.responseText)}function Da(n){var t=r.createRange();return t.selectNode(r.body),t.createContextualFragment(n.responseText)}qa.year=Tt.year.utc,Tt.scale.utc=function(){return Na(n.scale.linear(),qa,Ta)},n.text=yt(function(n){return n.responseText}),n.json=function(n,t){return mt(n,"application/json",Ra,t)},n.html=function(n,t){return mt(n,"text/html",Da,t)},n.xml=yt(function(n){return n.responseXML}),"function"==typeof define&&define.amd?(this.d3=n,define(n)):"object"==typeof module&&module.exports?module.exports=n:this.d3=n}(); -},{}],5:[function(require,module,exports) { -!function(){var t={dev:!1};t.tooltip=t.tooltip||{},t.utils=t.utils||{},t.models=t.models||{},t.charts={},t.logs={},t.dom={},"undefined"!=typeof module&&"undefined"!=typeof exports&&"undefined"==typeof d3&&(d3=require("d3")),t.dispatch=d3.dispatch("render_start","render_end"),Function.prototype.bind||(Function.prototype.bind=function(t){if("function"!=typeof this)throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var e=Array.prototype.slice.call(arguments,1),n=this,r=function(){},i=function(){return n.apply(this instanceof r&&t?this:t,e.concat(Array.prototype.slice.call(arguments)))};return r.prototype=this.prototype,i.prototype=new r,i}),t.dev&&(t.dispatch.on("render_start",function(e){t.logs.startTime=+new Date}),t.dispatch.on("render_end",function(e){t.logs.endTime=+new Date,t.logs.totalTime=t.logs.endTime-t.logs.startTime,t.log("total",t.logs.totalTime)})),t.log=function(){if(t.dev&&window.console&&console.log&&console.log.apply);else if(t.dev&&window.console&&"function"==typeof console.log&&Function.prototype.bind){Function.prototype.bind.call(console.log,console).apply(console,arguments)}return arguments[arguments.length-1]},t.deprecated=function(t,e){console&&console.warn},t.render=function(e){e=e||1,t.render.active=!0,t.dispatch.render_start();var n=function(){for(var r,i,o=0;op||n>h||d3.event.relatedTarget&&void 0===d3.event.relatedTarget.ownerSVGElement||a){if(u&&d3.event.relatedTarget&&void 0===d3.event.relatedTarget.ownerSVGElement&&(void 0===d3.event.relatedTarget.className||d3.event.relatedTarget.className.match(l.nvPointerEventsClass)))return;return o.elementMouseout({mouseX:t,mouseY:n}),c.renderGuideLine(null),void l.hidden(!0)}l.hidden(!1);var s=void 0;if("function"==typeof i.rangeBands){var d=d3.bisect(i.range(),t)-1;if(!(i.range()[d]+i.rangeBand()>=t))return o.elementMouseout({mouseX:t,mouseY:n}),c.renderGuideLine(null),void l.hidden(!0);s=i.domain()[d3.bisect(i.range(),t)-1]}else s=i.invert(t);o.elementMousemove({mouseX:t,mouseY:n,pointXValue:s}),"dblclick"===d3.event.type&&o.elementDblclick({mouseX:t,mouseY:n,pointXValue:s}),"click"===d3.event.type&&o.elementClick({mouseX:t,mouseY:n,pointXValue:s}),"mousedown"===d3.event.type&&o.elementMouseDown({mouseX:t,mouseY:n,pointXValue:s}),"mouseup"===d3.event.type&&o.elementMouseUp({mouseX:t,mouseY:n,pointXValue:s})}g.enter().append("g").attr("class"," nv-wrap nv-interactiveLineLayer").append("g").attr("class","nv-interactiveGuideLine"),s&&(s.on("touchmove",v).on("mousemove",v,!0).on("mouseout",v,!0).on("mousedown",v,!0).on("mouseup",v,!0).on("dblclick",v).on("click",v),c.guideLine=null,c.renderGuideLine=function(e){a&&(c.guideLine&&c.guideLine.attr("x1")===e||t.dom.write(function(){var n=g.select(".nv-interactiveGuideLine").selectAll("line").data(null!=e?[t.utils.NaNtoZero(e)]:[],String);n.enter().append("line").attr("class","nv-guideline").attr("x1",function(t){return t}).attr("x2",function(t){return t}).attr("y1",h).attr("y2",0),n.exit().remove()}))})})}return l.duration(0).hideDelay(0).hidden(!1),c.dispatch=o,c.tooltip=l,c.margin=function(t){return arguments.length?(e.top=void 0!==t.top?t.top:e.top,e.left=void 0!==t.left?t.left:e.left,c):e},c.width=function(t){return arguments.length?(n=t,c):n},c.height=function(t){return arguments.length?(r=t,c):r},c.xScale=function(t){return arguments.length?(i=t,c):i},c.showGuideLine=function(t){return arguments.length?(a=t,c):a},c.svgContainer=function(t){return arguments.length?(s=t,c):s},c},t.interactiveBisect=function(t,e,n){"use strict";if(!(t instanceof Array))return null;var r;r="function"!=typeof n?function(t){return t.x}:n;var i=d3.bisector(function(t,e){return r(t)-e}).left,o=d3.max([0,i(t,e)-1]),a=r(t[o]);if(void 0===a&&(a=o),a===e)return o;var s=d3.min([o+1,t.length-1]),l=r(t[s]);return void 0===l&&(l=s),Math.abs(l-e)>=Math.abs(a-e)?o:s},t.nearestValueIndex=function(t,e,n){"use strict";var r=1/0,i=null;return t.forEach(function(t,o){var a=Math.abs(e-t);null!=t&&a<=r&&a"+t.footer+""),i},x=function(){var t={left:null!==d3.event?d3.event.clientX:0,top:null!==d3.event?d3.event.clientY:0};if("none"!=getComputedStyle(document.body).transform){var e=document.body.getBoundingClientRect();t.left-=e.left,t.top-=e.top}return t},b=function(e){if(e&&e.series){if(t.utils.isArray(e.series))return!0;if(t.utils.isObject(e.series))return e.series=[e.series],!0}return!1},k=function(){t.dom.read(function(){var t=x(),e=function(t){var e,n,o,a=u.node().offsetHeight,s=u.node().offsetWidth,l=document.documentElement.clientWidth,c=document.documentElement.clientHeight;switch(r){case"e":e=-s-i,n=-a/2,t.left+e<0&&(e=i),(o=t.top+n)<0&&(n-=o),(o=t.top+n+a)>c&&(n-=o-c);break;case"w":e=i,n=-a/2,t.left+e+s>l&&(e=-s-i),(o=t.top+n)<0&&(n-=o),(o=t.top+n+a)>c&&(n-=o-c);break;case"n":e=-s/2-5,n=i,t.top+n+a>c&&(n=-a-i),(o=t.left+e)<0&&(e-=o),(o=t.left+e+s)>l&&(e-=o-l);break;case"s":e=-s/2,n=-a-i,t.top+n<0&&(n=i),(o=t.left+e)<0&&(e-=o),(o=t.left+e+s)>l&&(e-=o-l);break;case"center":e=-s/2,n=-a/2;break;default:e=0,n=0}return{left:e,top:n}}(t),n=t.left+e.left,o=t.top+e.top;if(s)u.interrupt().transition().delay(l).duration(0).style("opacity",0);else{var a="translate("+c.left+"px, "+c.top+"px)",d="translate("+Math.round(n)+"px, "+Math.round(o)+"px)",p=d3.interpolateString(a,d),h=u.style("opacity")<.1;u.interrupt().transition().duration(h?0:f).styleTween("transform",function(t){return p},"important").styleTween("-webkit-transform",function(t){return p}).style("-ms-transform",d).style("opacity",1)}c.left=n,c.top=o})};function w(){if(d&&b(n))return t.dom.write(function(){u&&u.node()||((u=d3.select(document.body).select("#"+e).data([1])).enter().append("div").attr("class","nvtooltip "+(a||"xy-tooltip")).attr("id",e).style("top",0).style("left",0).style("opacity",0).style("position","fixed").selectAll("div, table, td, tr").classed(h,!0).classed(h,!0),u.exit().remove());var t=y(n,u.node());t&&(u.node().innerHTML=t),k()}),w}return w.nvPointerEventsClass=h,w.options=t.utils.optionsFunc.bind(w),w._options=Object.create({},{duration:{get:function(){return f},set:function(t){f=t}},gravity:{get:function(){return r},set:function(t){r=t}},distance:{get:function(){return i},set:function(t){i=t}},snapDistance:{get:function(){return o},set:function(t){o=t}},classes:{get:function(){return a},set:function(t){a=t}},enabled:{get:function(){return d},set:function(t){d=t}},hideDelay:{get:function(){return l},set:function(t){l=t}},contentGenerator:{get:function(){return y},set:function(t){y=t}},valueFormatter:{get:function(){return g},set:function(t){g=t}},headerFormatter:{get:function(){return v},set:function(t){v=t}},keyFormatter:{get:function(){return m},set:function(t){m=t}},headerEnabled:{get:function(){return p},set:function(t){p=t}},position:{get:function(){return x},set:function(t){x=t}},chartContainer:{get:function(){return document.body},set:function(e){t.deprecated("chartContainer","feature removed after 1.8.3")}},fixedTop:{get:function(){return null},set:function(e){t.deprecated("fixedTop","feature removed after 1.8.1")}},offset:{get:function(){return{left:0,top:0}},set:function(e){t.deprecated("offset","use chart.tooltip.distance() instead")}},hidden:{get:function(){return s},set:function(t){s!=t&&(s=!!t,w())}},data:{get:function(){return n},set:function(t){t.point&&(t.value=t.point.x,t.series=t.series||{},t.series.value=t.point.y,t.series.color=t.point.color||t.series.color),n=t}},node:{get:function(){return u.node()},set:function(t){}},id:{get:function(){return e},set:function(t){}}}),t.utils.initOptions(w),w},t.utils.windowSize=function(){var t={width:640,height:480};return window.innerWidth&&window.innerHeight?(t.width=window.innerWidth,t.height=window.innerHeight,t):"CSS1Compat"==document.compatMode&&document.documentElement&&document.documentElement.offsetWidth?(t.width=document.documentElement.offsetWidth,t.height=document.documentElement.offsetHeight,t):document.body&&document.body.offsetWidth?(t.width=document.body.offsetWidth,t.height=document.body.offsetHeight,t):t},t.utils.isArray=Array.isArray,t.utils.isObject=function(t){return null!==t&&"object"==typeof t},t.utils.isFunction=function(t){return"function"==typeof t},t.utils.isDate=function(t){return"[object Date]"===toString.call(t)},t.utils.isNumber=function(t){return!isNaN(t)&&"number"==typeof t},t.utils.windowResize=function(e){return window.addEventListener?window.addEventListener("resize",e):t.log("ERROR: Failed to bind to window.resize with: ",e),{callback:e,clear:function(){window.removeEventListener("resize",e)}}},t.utils.getColor=function(e){if(void 0===e)return t.utils.defaultColor();if(t.utils.isArray(e)){var n=d3.scale.ordinal().range(e);return function(t,e){var r=void 0===e?t:e;return t.color||n(r)}}return e},t.utils.defaultColor=function(){return t.utils.getColor(d3.scale.category20().range())},t.utils.customTheme=function(e,n,r){n=n||function(t){return t.key};var i=(r=r||d3.scale.category20().range()).length;return function(o,a){var s=n(o);return t.utils.isFunction(e[s])?e[s]():void 0!==e[s]?e[s]:(i||(i=r.length),r[i-=1])}},t.utils.pjax=function(e,n){var r=function(r){d3.html(r,function(r){var i=d3.select(n).node();i.parentNode.replaceChild(d3.select(r).select(n).node(),i),t.utils.pjax(e,n)})};d3.selectAll(e).on("click",function(){history.pushState(this.href,this.textContent,this.href),r(this.href),d3.event.preventDefault()}),d3.select(window).on("popstate",function(){d3.event.state&&r(d3.event.state)})},t.utils.calcApproxTextWidth=function(e){if(t.utils.isFunction(e.style)&&t.utils.isFunction(e.text)){var n=parseInt(e.style("font-size").replace("px",""),10),r=e.text().length;return t.utils.NaNtoZero(r*n*.5)}return 0},t.utils.NaNtoZero=function(e){return!t.utils.isNumber(e)||isNaN(e)||null===e||e===1/0||e===-1/0?0:e},d3.selection.prototype.watchTransition=function(t){var e=[this].concat([].slice.call(arguments,1));return t.transition.apply(t,e)},t.utils.renderWatch=function(e,n){if(!(this instanceof t.utils.renderWatch))return new t.utils.renderWatch(e,n);var r=void 0!==n?n:250,i=[],o=this;this.models=function(t){return[].slice.call(arguments,0).forEach(function(t){var e;t.__rendered=!1,(e=t).dispatch.on("renderEnd",function(t){e.__rendered=!0,o.renderEnd("model")}),i.indexOf(t)<0&&i.push(t)}),this},this.reset=function(t){void 0!==t&&(r=t),i=[]},this.transition=function(t,e,n){if(n=(e=arguments.length>1?[].slice.call(arguments,1):[]).length>1?e.pop():void 0!==r?r:250,t.__rendered=!1,i.indexOf(t)<0&&i.push(t),0===n)return t.__rendered=!0,t.delay=function(){return this},t.duration=function(){return this},t;0===t.length?t.__rendered=!0:t.every(function(t){return!t.length})?t.__rendered=!0:t.__rendered=!1;var a=0;return t.transition().duration(n).each(function(){++a}).each("end",function(n,r){0==--a&&(t.__rendered=!0,o.renderEnd.apply(this,e))})},this.renderEnd=function(){i.every(function(t){return t.__rendered})&&(i.forEach(function(t){t.__rendered=!1}),e.renderEnd.apply(this,arguments))}},t.utils.deepExtend=function(e){(arguments.length>1?[].slice.call(arguments,1):[]).forEach(function(n){for(var r in n){var i=t.utils.isArray(e[r]),o=t.utils.isObject(e[r]),a=t.utils.isObject(n[r]);o&&!i&&a?t.utils.deepExtend(e[r],n[r]):e[r]=n[r]}})},t.utils.state=function(){if(!(this instanceof t.utils.state))return new t.utils.state;var e={},n=function(){},r=function(){return{}},i=null;this.dispatch=d3.dispatch("change","set"),this.dispatch.on("set",function(t){n(t,!0)}),this.getter=function(t){return r=t,this},this.setter=function(t,e){return e||(e=function(){}),n=function(n,r){t(n),r&&e()},this},this.init=function(e){i=i||{},t.utils.deepExtend(i,e)};this.update=function(){i&&(n(i,!1),i=null),function(){var t=r();if(JSON.stringify(t)===JSON.stringify(e))return!1;for(var n in t)void 0===e[n]&&(e[n]={}),e[n]=t[n],!0;return!0}.call(this)&&this.dispatch.change(e)}},t.utils.optionsFunc=function(e){return e&&d3.map(e).forEach(function(e,n){t.utils.isFunction(this[e])&&this[e](n)}.bind(this)),this},t.utils.calcTicksX=function(e,n){for(var r=1,i=0;ir?o:r}return t.log("Requested number of ticks: ",e),t.log("Calculated max values to be: ",r),e=(e=e>r?e=r-1:e)<1?1:e,e=Math.floor(e),t.log("Calculating tick count as: ",e),e},t.utils.calcTicksY=function(e,n){return t.utils.calcTicksX(e,n)},t.utils.initOption=function(t,e){t._calls&&t._calls[e]?t[e]=t._calls[e]:(t[e]=function(n){return arguments.length?(t._overrides[e]=!0,t._options[e]=n,t):t._options[e]},t["_"+e]=function(n){return arguments.length?(t._overrides[e]||(t._options[e]=n),t):t._options[e]})},t.utils.initOptions=function(e){e._overrides=e._overrides||{};var n=Object.getOwnPropertyNames(e._options||{}),r=Object.getOwnPropertyNames(e._calls||{});n=n.concat(r);for(var i in n)t.utils.initOption(e,n[i])},t.utils.inheritOptionsD3=function(t,e,n){t._d3options=n.concat(t._d3options||[]),t._d3options=(t._d3options||[]).filter(function(t,e,n){return n.indexOf(t)===e}),n.unshift(e),n.unshift(t),d3.rebind.apply(this,n)},t.utils.arrayUnique=function(t){return t.sort().filter(function(e,n){return!n||e!=t[n-1]})},t.utils.symbolMap=d3.map(),t.utils.symbol=function(){var e,n=64;function r(r,i){var o=e.call(this,r,i),a=n.call(this,r,i);return-1!==d3.svg.symbolTypes.indexOf(o)?d3.svg.symbol().type(o).size(a)():t.utils.symbolMap.get(o)(a)}return r.type=function(t){return arguments.length?(e=d3.functor(t),r):e},r.size=function(t){return arguments.length?(n=d3.functor(t),r):n},r},t.utils.inheritOptions=function(e,n){var r=Object.getOwnPropertyNames(n._options||{}),i=Object.getOwnPropertyNames(n._calls||{}),o=n._inherited||[],a=n._d3options||[],s=r.concat(i).concat(o).concat(a);s.unshift(n),s.unshift(e),d3.rebind.apply(this,s),e._inherited=t.utils.arrayUnique(r.concat(i).concat(o).concat(r).concat(e._inherited||[])),e._d3options=t.utils.arrayUnique(a.concat(e._d3options||[]))},t.utils.initSVG=function(t){t.classed({"nvd3-svg":!0})},t.utils.sanitizeHeight=function(t,e){return t||parseInt(e.style("height"),10)||400},t.utils.sanitizeWidth=function(t,e){return t||parseInt(e.style("width"),10)||960},t.utils.availableHeight=function(e,n,r){return Math.max(0,t.utils.sanitizeHeight(e,n)-r.top-r.bottom)},t.utils.availableWidth=function(e,n,r){return Math.max(0,t.utils.sanitizeWidth(e,n)-r.left-r.right)},t.utils.noData=function(e,n){var r=e.options(),i=r.margin(),o=r.noData(),a=null==o?["No Data Available."]:[o],s=t.utils.availableHeight(null,n,i),l=t.utils.availableWidth(null,n,i),u=i.left+l/2,c=i.top+s/2;n.selectAll("g").remove();var d=n.selectAll(".nv-noData").data(a);d.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),d.attr("x",u).attr("y",c).text(function(t){return t})},t.utils.wrapTicks=function(t,e){t.each(function(){for(var t,n=d3.select(this),r=n.text().split(/\s+/).reverse(),i=[],o=0,a=n.attr("y"),s=parseFloat(n.attr("dy")),l=n.text(null).append("tspan").attr("x",0).attr("y",a).attr("dy",s+"em");t=r.pop();)i.push(t),l.text(i.join(" ")),l.node().getComputedTextLength()>e&&(i.pop(),l.text(i.join(" ")),i=[t],l=n.append("tspan").attr("x",0).attr("y",a).attr("dy",1.1*++o+s+"em").text(t))})},t.utils.arrayEquals=function(e,n){if(e===n)return!0;if(!e||!n)return!1;if(e.length!=n.length)return!1;for(var r=0,i=e.length;r2&&(A=i.range()[i.range().length-1]+(i.range()[1]-i.range()[0])),C.attr("text-anchor","middle").attr("y",0).attr("x",A/2),u&&((M=y.selectAll("g.nv-axisMaxMin").data(i.domain())).enter().append("g").attr("class",function(t,e){return["nv-axisMaxMin","nv-axisMaxMin-x",0==e?"nv-axisMin-x":"nv-axisMax-x"].join(" ")}).append("text"),M.exit().remove(),M.attr("transform",function(e,n){return"translate("+t.utils.NaNtoZero(i(e))+",0)"}).select("text").attr("dy","-0.5em").attr("y",-r.tickPadding()).attr("text-anchor","middle").text(function(t,n){var r=(e||k)(t);return(""+r).match("NaN")?"":r}),M.watchTransition(x,"min-max top").attr("transform",function(e,n){return"translate("+t.utils.NaNtoZero(i.range()[n])+",0)"}));break;case"bottom":w=g+36;var S=30,W=0,E=b.selectAll("g").select("text"),F="";if(c%360){E.attr("transform",""),E.each(function(t,e){var n=this.getBoundingClientRect(),r=n.width;W=n.height,r>S&&(S=r)}),F="rotate("+c+" 0,"+(W/2+r.tickPadding())+")";var N=Math.abs(Math.sin(c*Math.PI/180));w=(N?N*S:S)+30,E.attr("transform",F).style("text-anchor",c%360>0?"start":"end")}else f?E.attr("transform",function(t,e){return"translate(0,"+(e%2==0?"0":"12")+")"}):E.attr("transform","translate(0,0)");C.enter().append("text").attr("class","nv-axislabel"),A=0,1===i.range().length?A=p?2*i.range()[0]+i.rangeBand():0:2===i.range().length?A=p?i.range()[0]+i.range()[1]+i.rangeBand():i.range()[1]:i.range().length>2&&(A=i.range()[i.range().length-1]+(i.range()[1]-i.range()[0])),C.attr("text-anchor","middle").attr("y",w).attr("x",A/2),u&&((M=y.selectAll("g.nv-axisMaxMin").data([i.domain()[0],i.domain()[i.domain().length-1]])).enter().append("g").attr("class",function(t,e){return["nv-axisMaxMin","nv-axisMaxMin-x",0==e?"nv-axisMin-x":"nv-axisMax-x"].join(" ")}).append("text"),M.exit().remove(),M.attr("transform",function(e,n){return"translate("+t.utils.NaNtoZero(i(e)+(p?i.rangeBand()/2:0))+",0)"}).select("text").attr("dy",".71em").attr("y",r.tickPadding()).attr("transform",F).style("text-anchor",c?c%360>0?"start":"end":"middle").text(function(t,n){var r=(e||k)(t);return(""+r).match("NaN")?"":r}),M.watchTransition(x,"min-max bottom").attr("transform",function(e,n){return"translate("+t.utils.NaNtoZero(i(e)+(p?i.rangeBand()/2:0))+",0)"}));break;case"right":C.enter().append("text").attr("class","nv-axislabel"),C.style("text-anchor",d?"middle":"begin").attr("transform",d?"rotate(90)":"").attr("y",d?12-Math.max(o.right,a)-(g||0):-10).attr("x",d?d3.max(i.range())/2:r.tickPadding()),u&&((M=y.selectAll("g.nv-axisMaxMin").data(i.domain())).enter().append("g").attr("class",function(t,e){return["nv-axisMaxMin","nv-axisMaxMin-y",0==e?"nv-axisMin-y":"nv-axisMax-y"].join(" ")}).append("text").style("opacity",0),M.exit().remove(),M.attr("transform",function(e,n){return"translate(0,"+t.utils.NaNtoZero(i(e))+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",r.tickPadding()).style("text-anchor","start").text(function(t,n){var r=(e||k)(t);return(""+r).match("NaN")?"":r}),M.watchTransition(x,"min-max right").attr("transform",function(e,n){return"translate(0,"+t.utils.NaNtoZero(i.range()[n])+")"}).select("text").style("opacity",1));break;case"left":C.enter().append("text").attr("class","nv-axislabel"),C.style("text-anchor",d?"middle":"end").attr("transform",d?"rotate(-90)":"").attr("y",d?25-Math.max(o.left,a)-(g||0):-10).attr("x",d?-d3.max(i.range())/2:-r.tickPadding()),u&&((M=y.selectAll("g.nv-axisMaxMin").data(i.domain())).enter().append("g").attr("class",function(t,e){return["nv-axisMaxMin","nv-axisMaxMin-y",0==e?"nv-axisMin-y":"nv-axisMax-y"].join(" ")}).append("text").style("opacity",0),M.exit().remove(),M.attr("transform",function(e,r){return"translate(0,"+t.utils.NaNtoZero(n(e))+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",-r.tickPadding()).attr("text-anchor","end").text(function(t,n){var r=(e||k)(t);return(""+r).match("NaN")?"":r}),M.watchTransition(x,"min-max right").attr("transform",function(e,n){return"translate(0,"+t.utils.NaNtoZero(i.range()[n])+")"}).select("text").style("opacity",1))}if(C.text(function(t){return t}),!u||"left"!==r.orient()&&"right"!==r.orient()||(b.selectAll("g").each(function(t,e){d3.select(this).select("text").attr("opacity",1),(i(t)i.range()[0]-10)&&((t>1e-10||t<-1e-10)&&d3.select(this).attr("opacity",0),d3.select(this).select("text").attr("opacity",0))}),i.domain()[0]==i.domain()[1]&&0==i.domain()[0]&&y.selectAll("g.nv-axisMaxMin").style("opacity",function(t,e){return e?0:1})),u&&("top"===r.orient()||"bottom"===r.orient())){var D=[];y.selectAll("g.nv-axisMaxMin").each(function(t,e){try{e?D.push(i(t)-this.getBoundingClientRect().width-4):D.push(i(t)+this.getBoundingClientRect().width+4)}catch(n){e?D.push(i(t)-4):D.push(i(t)+4)}}),b.selectAll("g").each(function(t,e){(i(t)D[1])&&(t>1e-10||t<-1e-10?d3.select(this).remove():d3.select(this).select("text").remove())})}b.selectAll(".tick").filter(function(t){return!parseFloat(Math.round(1e5*t)/1e6)&&void 0!==t}).classed("zero",!0),n=i.copy()}),x.renderEnd("axis immediate"),b}return b.axis=r,b.dispatch=y,b.options=t.utils.optionsFunc.bind(b),b._options=Object.create({},{axisLabelDistance:{get:function(){return g},set:function(t){g=t}},staggerLabels:{get:function(){return f},set:function(t){f=t}},rotateLabels:{get:function(){return c},set:function(t){c=t}},rotateYLabel:{get:function(){return d},set:function(t){d=t}},showMaxMin:{get:function(){return u},set:function(t){u=t}},axisLabel:{get:function(){return l},set:function(t){l=t}},height:{get:function(){return s},set:function(t){s=t}},ticks:{get:function(){return h},set:function(t){h=t}},width:{get:function(){return a},set:function(t){a=t}},fontSize:{get:function(){return v},set:function(t){v=t}},tickFormatMaxMin:{get:function(){return e},set:function(t){e=t}},margin:{get:function(){return o},set:function(t){o.top=void 0!==t.top?t.top:o.top,o.right=void 0!==t.right?t.right:o.right,o.bottom=void 0!==t.bottom?t.bottom:o.bottom,o.left=void 0!==t.left?t.left:o.left}},duration:{get:function(){return m},set:function(t){m=t,x.reset(m)}},scale:{get:function(){return i},set:function(e){i=e,r.scale(i),p="function"==typeof i.rangeBands,t.utils.inheritOptionsD3(b,i,["domain","range","rangeBand","rangeBands"])}}}),t.utils.initOptions(b),t.utils.inheritOptionsD3(b,r,["orient","tickValues","tickSubdivide","tickSize","tickPadding","tickFormat"]),t.utils.inheritOptionsD3(b,i,["domain","range","rangeBand","rangeBands"]),b},t.models.boxPlot=function(){"use strict";var e,n,r,i,o,a,s={top:0,right:0,bottom:0,left:0},l=960,u=500,c=Math.floor(1e4*Math.random()),d=d3.scale.ordinal(),f=d3.scale.linear(),p=function(t){return t.label},h=function(t){return t.values.Q1},g=function(t){return t.values.Q2},v=function(t){return t.values.Q3},m=function(t){return t.values.whisker_low},y=function(t){return t.values.whisker_high},x=function(t){return t.color},b=function(t){return t.values.outliers},k=function(t,e,n){return t},w=function(t,e,n){return t},M=function(t,e,n){},A=t.utils.defaultColor(),C=null,S=d3.dispatch("elementMouseover","elementMouseout","elementMousemove","renderEnd"),W=250,E=null,F=t.utils.renderWatch(S,W);function N(c){return F.reset(),c.each(function(c){var N=l-s.left-s.right,D=u-s.top-s.bottom;C=d3.select(this),t.utils.initSVG(C),d.domain(e||c.map(function(t,e){return p(t,e)})).rangeBands(n||[0,N],.1);var O=[];if(!r){var _=[];c.forEach(function(t,e){var n=h(t),r=v(t),i=m(t),o=y(t),a=b(t);a&&a.forEach(function(t,e){_.push(k(t,e,void 0))}),i&&_.push(i),n&&_.push(n),r&&_.push(r),o&&_.push(o)}),O=[d3.min(_),d3.max(_)]}f.domain(r||O),f.range(i||[D,0]),o=o||d,a=a||f.copy().range([f(0),f(0)]);var L=C.selectAll("g.nv-wrap").data([c]);L.enter().append("g").attr("class","nvd3 nv-wrap");L.attr("transform","translate("+s.left+","+s.top+")");var B=L.selectAll(".nv-boxplot").data(function(t){return t}),P=B.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6);B.attr("class","nv-boxplot").attr("transform",function(t,e,n){return"translate("+(d(p(t,e))+.05*d.rangeBand())+", 0)"}).classed("hover",function(t){return t.hover}),B.watchTransition(F,"nv-boxplot: boxplots").style("stroke-opacity",1).style("fill-opacity",.75).delay(function(t,e){return e*W/c.length}).attr("transform",function(t,e){return"translate("+(d(p(t,e))+.05*d.rangeBand())+", 0)"}),B.exit().remove(),P.each(function(t,e){var n=d3.select(this);[m,y].forEach(function(r){if(void 0!==r(t)&&null!==r(t)){var i=r===m?"low":"high";n.append("line").style("stroke",x(t)||A(t,e)).attr("class","nv-boxplot-whisker nv-boxplot-"+i),n.append("line").style("stroke",x(t)||A(t,e)).attr("class","nv-boxplot-tick nv-boxplot-"+i)}})});var T=function(){return null===E?.9*d.rangeBand():Math.min(75,.9*d.rangeBand())},I=function(){return.45*d.rangeBand()-T()/2},z=function(){return.45*d.rangeBand()+T()/2};[m,y].forEach(function(t){var e=t===m?"low":"high",n=t===m?h:v;B.select("line.nv-boxplot-whisker.nv-boxplot-"+e).watchTransition(F,"nv-boxplot: boxplots").attr("x1",.45*d.rangeBand()).attr("y1",function(e,n){return f(t(e))}).attr("x2",.45*d.rangeBand()).attr("y2",function(t,e){return f(n(t))}),B.select("line.nv-boxplot-tick.nv-boxplot-"+e).watchTransition(F,"nv-boxplot: boxplots").attr("x1",I).attr("y1",function(e,n){return f(t(e))}).attr("x2",z).attr("y2",function(e,n){return f(t(e))})}),[m,y].forEach(function(t){var e=t===m?"low":"high";P.selectAll(".nv-boxplot-"+e).on("mouseover",function(e,n,r){d3.select(this).classed("hover",!0),S.elementMouseover({series:{key:t(e),color:x(e)||A(e,r)},e:d3.event})}).on("mouseout",function(e,n,r){d3.select(this).classed("hover",!1),S.elementMouseout({series:{key:t(e),color:x(e)||A(e,r)},e:d3.event})}).on("mousemove",function(t,e){S.elementMousemove({e:d3.event})})}),P.append("rect").attr("class","nv-boxplot-box").on("mouseover",function(t,e){d3.select(this).classed("hover",!0),S.elementMouseover({key:p(t),value:p(t),series:[{key:"Q3",value:v(t),color:x(t)||A(t,e)},{key:"Q2",value:g(t),color:x(t)||A(t,e)},{key:"Q1",value:h(t),color:x(t)||A(t,e)}],data:t,index:e,e:d3.event})}).on("mouseout",function(t,e){d3.select(this).classed("hover",!1),S.elementMouseout({key:p(t),value:p(t),series:[{key:"Q3",value:v(t),color:x(t)||A(t,e)},{key:"Q2",value:g(t),color:x(t)||A(t,e)},{key:"Q1",value:h(t),color:x(t)||A(t,e)}],data:t,index:e,e:d3.event})}).on("mousemove",function(t,e){S.elementMousemove({e:d3.event})}),B.select("rect.nv-boxplot-box").watchTransition(F,"nv-boxplot: boxes").attr("y",function(t,e){return f(v(t))}).attr("width",T).attr("x",I).attr("height",function(t,e){return Math.abs(f(v(t))-f(h(t)))||1}).style("fill",function(t,e){return x(t)||A(t,e)}).style("stroke",function(t,e){return x(t)||A(t,e)}),P.append("line").attr("class","nv-boxplot-median"),B.select("line.nv-boxplot-median").watchTransition(F,"nv-boxplot: boxplots line").attr("x1",I).attr("y1",function(t,e){return f(g(t))}).attr("x2",z).attr("y2",function(t,e){return f(g(t))});var V=B.selectAll(".nv-boxplot-outlier").data(function(t){return b(t)||[]});V.enter().append("circle").style("fill",function(t,e,n){return M(t,e,n)||A(t,n)}).style("stroke",function(t,e,n){return M(t,e,n)||A(t,n)}).style("z-index",9e3).on("mouseover",function(t,e,n){d3.select(this).classed("hover",!0),S.elementMouseover({series:{key:w(t,e,n),color:M(t,e,n)||A(t,n)},e:d3.event})}).on("mouseout",function(t,e,n){d3.select(this).classed("hover",!1),S.elementMouseout({series:{key:w(t,e,n),color:M(t,e,n)||A(t,n)},e:d3.event})}).on("mousemove",function(t,e){S.elementMousemove({e:d3.event})}),V.attr("class","nv-boxplot-outlier"),V.watchTransition(F,"nv-boxplot: nv-boxplot-outlier").attr("cx",.45*d.rangeBand()).attr("cy",function(t,e,n){return f(k(t,e,n))}).attr("r","3"),V.exit().remove(),o=d.copy(),a=f.copy()}),F.renderEnd("nv-boxplot immediate"),N}return N.dispatch=S,N.options=t.utils.optionsFunc.bind(N),N._options=Object.create({},{width:{get:function(){return l},set:function(t){l=t}},height:{get:function(){return u},set:function(t){u=t}},maxBoxWidth:{get:function(){return E},set:function(t){E=t}},x:{get:function(){return p},set:function(t){p=t}},q1:{get:function(){return h},set:function(t){h=t}},q2:{get:function(){return g},set:function(t){g=t}},q3:{get:function(){return v},set:function(t){v=t}},wl:{get:function(){return m},set:function(t){m=t}},wh:{get:function(){return y},set:function(t){y=t}},itemColor:{get:function(){return x},set:function(t){x=t}},outliers:{get:function(){return b},set:function(t){b=t}},outlierValue:{get:function(){return k},set:function(t){k=t}},outlierLabel:{get:function(){return w},set:function(t){w=t}},outlierColor:{get:function(){return M},set:function(t){M=t}},xScale:{get:function(){return d},set:function(t){d=t}},yScale:{get:function(){return f},set:function(t){f=t}},xDomain:{get:function(){return e},set:function(t){e=t}},yDomain:{get:function(){return r},set:function(t){r=t}},xRange:{get:function(){return n},set:function(t){n=t}},yRange:{get:function(){return i},set:function(t){i=t}},id:{get:function(){return c},set:function(t){c=t}},y:{get:function(){return{}},set:function(t){}},margin:{get:function(){return s},set:function(t){s.top=void 0!==t.top?t.top:s.top,s.right=void 0!==t.right?t.right:s.right,s.bottom=void 0!==t.bottom?t.bottom:s.bottom,s.left=void 0!==t.left?t.left:s.left}},color:{get:function(){return A},set:function(e){A=t.utils.getColor(e)}},duration:{get:function(){return W},set:function(t){W=t,F.reset(W)}}}),t.utils.initOptions(N),N},t.models.boxPlotChart=function(){"use strict";var e,n,r=t.models.boxPlot(),i=t.models.axis(),o=t.models.axis(),a={top:15,right:10,bottom:50,left:60},s=null,l=null,u=t.utils.getColor(),c=!0,d=!0,f=!1,p=!1,h=t.models.tooltip(),g="No Data Available.",v=d3.dispatch("beforeUpdate","renderEnd"),m=250;i.orient("bottom").showMaxMin(!1).tickFormat(function(t){return t}),o.orient(f?"right":"left").tickFormat(d3.format(",.1f")),h.duration(0);var y=t.utils.renderWatch(v,m);function x(u){return y.reset(),y.models(r),c&&y.models(i),d&&y.models(o),u.each(function(u){var h=d3.select(this);t.utils.initSVG(h);var y=(s||parseInt(h.style("width"))||960)-a.left-a.right,b=(l||parseInt(h.style("height"))||400)-a.top-a.bottom;if(x.update=function(){v.beforeUpdate(),h.transition().duration(m).call(x)},x.container=this,!u||!u.length){var k=h.selectAll(".nv-noData").data([g]);return k.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),k.attr("x",a.left+y/2).attr("y",a.top+b/2).text(function(t){return t}),x}h.selectAll(".nv-noData").remove(),e=r.xScale(),n=r.yScale().clamp(!0);var w=h.selectAll("g.nv-wrap.nv-boxPlotWithAxes").data([u]),M=w.enter().append("g").attr("class","nvd3 nv-wrap nv-boxPlotWithAxes").append("g"),A=M.append("defs"),C=w.select("g");if(M.append("g").attr("class","nv-x nv-axis"),M.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),M.append("g").attr("class","nv-barsWrap"),C.attr("transform","translate("+a.left+","+a.top+")"),f&&C.select(".nv-y.nv-axis").attr("transform","translate("+y+",0)"),r.width(y).height(b),C.select(".nv-barsWrap").datum(u.filter(function(t){return!t.disabled})).transition().call(r),A.append("clipPath").attr("id","nv-x-label-clip-"+r.id()).append("rect"),C.select("#nv-x-label-clip-"+r.id()+" rect").attr("width",e.rangeBand()*(p?2:1)).attr("height",16).attr("x",-e.rangeBand()/(p?1:2)),c){i.scale(e).ticks(t.utils.calcTicksX(y/100,u)).tickSize(-b,0),C.select(".nv-x.nv-axis").attr("transform","translate(0,"+n.range()[0]+")"),C.select(".nv-x.nv-axis").call(i);var S=C.select(".nv-x.nv-axis").selectAll("g");p&&S.selectAll("text").attr("transform",function(t,e,n){return"translate(0,"+(n%2==0?"5":"17")+")"})}d&&(o.scale(n).ticks(Math.floor(b/36)).tickSize(-y,0),C.select(".nv-y.nv-axis").call(o)),C.select(".nv-zeroLine line").attr("x1",0).attr("x2",y).attr("y1",n(0)).attr("y2",n(0))}),y.renderEnd("nv-boxplot chart immediate"),x}return r.dispatch.on("elementMouseover.tooltip",function(t){h.data(t).hidden(!1)}),r.dispatch.on("elementMouseout.tooltip",function(t){h.data(t).hidden(!0)}),r.dispatch.on("elementMousemove.tooltip",function(t){h()}),x.dispatch=v,x.boxplot=r,x.xAxis=i,x.yAxis=o,x.tooltip=h,x.options=t.utils.optionsFunc.bind(x),x._options=Object.create({},{width:{get:function(){return s},set:function(t){s=t}},height:{get:function(){return l},set:function(t){l=t}},staggerLabels:{get:function(){return p},set:function(t){p=t}},showXAxis:{get:function(){return c},set:function(t){c=t}},showYAxis:{get:function(){return d},set:function(t){d=t}},tooltipContent:{get:function(){return h},set:function(t){h=t}},noData:{get:function(){return g},set:function(t){g=t}},margin:{get:function(){return a},set:function(t){a.top=void 0!==t.top?t.top:a.top,a.right=void 0!==t.right?t.right:a.right,a.bottom=void 0!==t.bottom?t.bottom:a.bottom,a.left=void 0!==t.left?t.left:a.left}},duration:{get:function(){return m},set:function(t){m=t,y.reset(m),r.duration(m),i.duration(m),o.duration(m)}},color:{get:function(){return u},set:function(e){u=t.utils.getColor(e),r.color(u)}},rightAlignYAxis:{get:function(){return f},set:function(t){f=t,o.orient(t?"right":"left")}}}),t.utils.inheritOptions(x,r),t.utils.initOptions(x),x},t.models.bullet=function(){"use strict";var e={top:0,right:0,bottom:0,left:0},n="left",r=!1,i=function(t){return t.ranges},o=function(t){return t.markers?t.markers:[]},a=function(t){return t.markerLines?t.markerLines:[0]},s=function(t){return t.measures},l=function(t){return t.rangeLabels?t.rangeLabels:[]},u=function(t){return t.markerLabels?t.markerLabels:[]},c=function(t){return t.markerLineLabels?t.markerLineLabels:[]},d=function(t){return t.measureLabels?t.measureLabels:[]},f=[0],p=380,h=30,g=null,v=null,m=t.utils.getColor(["#1f77b4"]),y=d3.dispatch("elementMouseover","elementMouseout","elementMousemove"),x=["Maximum","Mean","Minimum"],b=["Max","Avg","Min"],k=1e3;function w(t,e){var n=t.slice();t.sort(function(t,r){var i=n.indexOf(t),o=n.indexOf(r);return d3.descending(e[i],e[o])})}function M(n){return n.each(function(n,v){var M=p-e.left-e.right,A=h-e.top-e.bottom;g=d3.select(this),t.utils.initSVG(g);var C=i.call(this,n,v).slice(),S=o.call(this,n,v).slice(),W=a.call(this,n,v).slice(),E=s.call(this,n,v).slice(),F=l.call(this,n,v).slice(),N=u.call(this,n,v).slice(),D=c.call(this,n,v).slice(),O=d.call(this,n,v).slice();w(F,C),w(N,S),w(D,W),w(O,E),C.sort(d3.descending),S.sort(d3.descending),W.sort(d3.descending),E.sort(d3.descending);var _=d3.scale.linear().domain(d3.extent(d3.merge([f,C]))).range(r?[M,0]:[0,M]);this.__chart__||d3.scale.linear().domain([0,1/0]).range(_.range());this.__chart__=_;d3.min(C),d3.max(C),C[1];for(var L=g.selectAll("g.nv-wrap.nv-bullet").data([n]),B=L.enter().append("g").attr("class","nvd3 nv-wrap nv-bullet").append("g"),P=L.select("g"),T=(v=0,C.length);vg(t,e)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+n+"-"+e});_.append("line").attr("class","nv-candlestick-lines").attr("transform",function(t,e){return"translate("+c(f(t,e))+",0)"}).attr("x1",0).attr("y1",function(t,e){return d(v(t,e))}).attr("x2",0).attr("y2",function(t,e){return d(m(t,e))}),_.append("rect").attr("class","nv-candlestick-rects nv-bars").attr("transform",function(t,e){return"translate("+(c(f(t,e))-S/2)+","+(d(p(t,e))-(h(t,e)>g(t,e)?d(g(t,e))-d(h(t,e)):0))+")"}).attr("x",0).attr("y",0).attr("width",S).attr("height",function(t,e){var n=h(t,e),r=g(t,e);return n>r?d(r)-d(n):d(n)-d(r)});O.select(".nv-candlestick-lines").transition().attr("transform",function(t,e){return"translate("+c(f(t,e))+",0)"}).attr("x1",0).attr("y1",function(t,e){return d(v(t,e))}).attr("x2",0).attr("y2",function(t,e){return d(m(t,e))}),O.select(".nv-candlestick-rects").transition().attr("transform",function(t,e){return"translate("+(c(f(t,e))-S/2)+","+(d(p(t,e))-(h(t,e)>g(t,e)?d(g(t,e))-d(h(t,e)):0))+")"}).attr("x",0).attr("y",0).attr("width",S).attr("height",function(t,e){var n=h(t,e),r=g(t,e);return n>r?d(r)-d(n):d(n)-d(r)})}),C}return C.highlightPoint=function(t,n){C.clearHighlights(),e.select(".nv-candlestickBar .nv-tick-0-"+t).classed("hover",n)},C.clearHighlights=function(){e.select(".nv-candlestickBar .nv-tick.hover").classed("hover",!1)},C.dispatch=A,C.options=t.utils.optionsFunc.bind(C),C._options=Object.create({},{width:{get:function(){return s},set:function(t){s=t}},height:{get:function(){return l},set:function(t){l=t}},xScale:{get:function(){return c},set:function(t){c=t}},yScale:{get:function(){return d},set:function(t){d=t}},xDomain:{get:function(){return n},set:function(t){n=t}},yDomain:{get:function(){return r},set:function(t){r=t}},xRange:{get:function(){return i},set:function(t){i=t}},yRange:{get:function(){return o},set:function(t){o=t}},forceX:{get:function(){return y},set:function(t){y=t}},forceY:{get:function(){return x},set:function(t){x=t}},padData:{get:function(){return b},set:function(t){b=t}},clipEdge:{get:function(){return k},set:function(t){k=t}},id:{get:function(){return u},set:function(t){u=t}},interactive:{get:function(){return M},set:function(t){M=t}},x:{get:function(){return f},set:function(t){f=t}},y:{get:function(){return p},set:function(t){p=t}},open:{get:function(){return h()},set:function(t){h=t}},close:{get:function(){return g()},set:function(t){g=t}},high:{get:function(){return v},set:function(t){v=t}},low:{get:function(){return m},set:function(t){m=t}},margin:{get:function(){return a},set:function(t){a.top=void 0!=t.top?t.top:a.top,a.right=void 0!=t.right?t.right:a.right,a.bottom=void 0!=t.bottom?t.bottom:a.bottom,a.left=void 0!=t.left?t.left:a.left}},color:{get:function(){return w},set:function(e){w=t.utils.getColor(e)}}}),t.utils.initOptions(C),C},t.models.cumulativeLineChart=function(){"use strict";var e,n,r=t.models.line(),i=t.models.axis(),o=t.models.axis(),a=t.models.legend(),s=t.models.legend(),l=t.interactiveGuideline(),u=t.models.tooltip(),c={top:30,right:30,bottom:50,left:60},d=null,f=t.utils.defaultColor(),p=null,h=null,g=!0,v=!0,m=!0,y=!1,x=!0,b=!1,k=!0,w=r.id(),M=t.utils.state(),A=null,C=null,S=function(t){return t.average},W=d3.dispatch("stateChange","changeState","renderEnd"),E=250,F=!1;M.index=0,M.rescaleY=k,i.orient("bottom").tickPadding(7),o.orient(y?"right":"left"),u.valueFormatter(function(t,e){return o.tickFormat()(t,e)}).headerFormatter(function(t,e){return i.tickFormat()(t,e)}),s.updateState(!1);var N,D=d3.scale.linear(),O={i:0,x:0},_=t.utils.renderWatch(W,E),L=function(t){return function(){return{active:t.map(function(t){return!t.disabled}),index:O.i,rescaleY:k}}},B=function(t){return function(e){void 0!==e.index&&(O.i=e.index),void 0!==e.rescaleY&&(k=e.rescaleY),void 0!==e.active&&t.forEach(function(t,n){t.disabled=!e.active[n]})}};function P(u){return _.reset(),_.models(r),v&&_.models(i),m&&_.models(o),u.each(function(u){var C=d3.select(this);t.utils.initSVG(C),C.classed("nv-chart-"+w,!0);var _=t.utils.availableWidth(p,C,c),z=t.utils.availableHeight(h,C,c);if(P.update=function(){0===E?C.call(P):C.transition().duration(E).call(P)},P.container=this,M.setter(B(u),P.update).getter(L(u)).update(),M.disabled=u.map(function(t){return!!t.disabled}),!A){var V;A={};for(V in M)M[V]instanceof Array?A[V]=M[V].slice(0):A[V]=M[V]}var H=d3.behavior.drag().on("dragstart",function(t,e){d3.select(P.container).style("cursor","ew-resize")}).on("drag",function(t,e){O.x=d3.event.x,O.i=Math.round(D.invert(O.x)),$()}).on("dragend",function(t,e){d3.select(P.container).style("cursor","auto"),M.index=O.i,W.stateChange(M)});if(!(u&&u.length&&u.filter(function(t){return t.values.length}).length))return t.utils.noData(P,C),P;C.selectAll(".nv-noData").remove(),e=r.xScale(),n=r.yScale(),D.domain([0,u[0].values.length-1]).range([0,_]).clamp(!0);u=function(t,e){T||(T=r.y());return e.map(function(e,n){if(!e.values)return e;var r=e.values[t];if(null==r)return e;var i=T(r,t);return Math.abs(i)<1e-5&&!F?(e.tempDisabled=!0,e):(e.tempDisabled=!1,e.values=e.values.map(function(t,e){return t.display={y:(T(t,e)-i)/i},t}),e)})}(O.i,u);void 0===N&&(N=I(u)),k?r.yDomain(null):(r.yDomain(N),r.clipEdge(!0));var Y=b?"none":"all",G=C.selectAll("g.nv-wrap.nv-cumulativeLine").data([u]),X=G.enter().append("g").attr("class","nvd3 nv-wrap nv-cumulativeLine").append("g"),j=G.select("g");if(X.append("g").attr("class","nv-interactive"),X.append("g").attr("class","nv-x nv-axis").style("pointer-events","none"),X.append("g").attr("class","nv-y nv-axis"),X.append("g").attr("class","nv-background"),X.append("g").attr("class","nv-linesWrap").style("pointer-events",Y),X.append("g").attr("class","nv-avgLinesWrap").style("pointer-events","none"),X.append("g").attr("class","nv-legendWrap"),X.append("g").attr("class","nv-controlsWrap"),g?(a.width(_),j.select(".nv-legendWrap").datum(u).call(a),d||a.height()===c.top||(c.top=a.height(),z=t.utils.availableHeight(h,C,c)),j.select(".nv-legendWrap").attr("transform","translate(0,"+-c.top+")")):j.select(".nv-legendWrap").selectAll("*").remove(),x){var R=[{key:"Re-scale y-axis",disabled:!k}];s.width(140).color(["#444","#444","#444"]).rightAlign(!1).margin({top:5,right:0,bottom:5,left:20}),j.select(".nv-controlsWrap").datum(R).attr("transform","translate(0,"+-c.top+")").call(s)}else j.select(".nv-controlsWrap").selectAll("*").remove();G.attr("transform","translate("+c.left+","+c.top+")"),y&&j.select(".nv-y.nv-axis").attr("transform","translate("+_+",0)");var Z=u.filter(function(t){return t.tempDisabled});G.select(".tempDisabled").remove(),Z.length&&G.append("text").attr("class","tempDisabled").attr("x",_/2).attr("y","-.71em").style("text-anchor","end").text(Z.map(function(t){return t.key}).join(", ")+" values cannot be calculated for this time period."),b&&(l.width(_).height(z).margin({left:c.left,top:c.top}).svgContainer(C).xScale(e),G.select(".nv-interactive").call(l)),X.select(".nv-background").append("rect"),j.select(".nv-background rect").attr("width",_).attr("height",z),r.y(function(t){return t.display.y}).width(_).height(z).color(u.map(function(t,e){return t.color||f(t,e)}).filter(function(t,e){return!u[e].disabled&&!u[e].tempDisabled}));var q=j.select(".nv-linesWrap").datum(u.filter(function(t){return!t.disabled&&!t.tempDisabled}));q.call(r),u.forEach(function(t,e){t.seriesIndex=e});var K=u.filter(function(t){return!t.disabled&&!!S(t)}),U=j.select(".nv-avgLinesWrap").selectAll("line").data(K,function(t){return t.key}),Q=function(t){var e=n(S(t));return e<0?0:e>z?z:e};U.enter().append("line").style("stroke-width",2).style("stroke-dasharray","10,10").style("stroke",function(t,e){return r.color()(t,t.seriesIndex)}).attr("x1",0).attr("x2",_).attr("y1",Q).attr("y2",Q),U.style("stroke-opacity",function(t){var e=n(S(t));return e<0||e>z?0:1}).attr("x1",0).attr("x2",_).attr("y1",Q).attr("y2",Q),U.exit().remove();var J=q.selectAll(".nv-indexLine").data([O]);function $(){J.data([O]);var t=P.duration();P.duration(0),P.update(),P.duration(t)}J.enter().append("rect").attr("class","nv-indexLine").attr("width",3).attr("x",-2).attr("fill","red").attr("fill-opacity",.5).style("pointer-events","all").call(H),J.attr("transform",function(t){return"translate("+D(t.i)+",0)"}).attr("height",z),v&&(i.scale(e)._ticks(t.utils.calcTicksX(_/70,u)).tickSize(-z,0),j.select(".nv-x.nv-axis").attr("transform","translate(0,"+n.range()[0]+")"),j.select(".nv-x.nv-axis").call(i)),m&&(o.scale(n)._ticks(t.utils.calcTicksY(z/36,u)).tickSize(-_,0),j.select(".nv-y.nv-axis").call(o)),j.select(".nv-background rect").on("click",function(){O.x=d3.mouse(this)[0],O.i=Math.round(D.invert(O.x)),M.index=O.i,W.stateChange(M),$()}),r.dispatch.on("elementClick",function(t){O.i=t.pointIndex,O.x=D(O.i),M.index=O.i,W.stateChange(M),$()}),s.dispatch.on("legendClick",function(t,e){t.disabled=!t.disabled,k=!t.disabled,M.rescaleY=k,k||(N=I(u)),W.stateChange(M),P.update()}),a.dispatch.on("stateChange",function(t){for(var e in t)M[e]=t[e];W.stateChange(M),P.update()}),l.dispatch.on("elementMousemove",function(e){r.clearHighlights();var n,a,s,c=[];if(u.filter(function(t,e){return t.seriesIndex=e,!(t.disabled||t.tempDisabled)}).forEach(function(i,o){a=t.interactiveBisect(i.values,e.pointXValue,P.x()),r.highlightPoint(o,a,!0);var l=i.values[a];void 0!==l&&(void 0===n&&(n=l),void 0===s&&(s=P.xScale()(P.x()(l,a))),c.push({key:i.key,value:P.y()(l,a),color:f(i,i.seriesIndex)}))}),c.length>2){var d=P.yScale().invert(e.mouseY),p=.03*Math.abs(P.yScale().domain()[0]-P.yScale().domain()[1]),h=t.nearestValueIndex(c.map(function(t){return t.value}),d,p);null!==h&&(c[h].highlight=!0)}var g=i.tickFormat()(P.x()(n,a),a);l.tooltip.valueFormatter(function(t,e){return o.tickFormat()(t)}).data({value:g,series:c})(),l.renderGuideLine(s)}),l.dispatch.on("elementMouseout",function(t){r.clearHighlights()}),W.on("changeState",function(t){void 0!==t.disabled&&(u.forEach(function(e,n){e.disabled=t.disabled[n]}),M.disabled=t.disabled),void 0!==t.index&&(O.i=t.index,O.x=D(O.i),M.index=t.index,J.data([O])),void 0!==t.rescaleY&&(k=t.rescaleY),P.update()})}),_.renderEnd("cumulativeLineChart immediate"),P}r.dispatch.on("elementMouseover.tooltip",function(t){var e={x:P.x()(t.point),y:P.y()(t.point),color:t.point.color};t.point=e,u.data(t).hidden(!1)}),r.dispatch.on("elementMouseout.tooltip",function(t){u.hidden(!0)});var T=null;function I(t){var e=t.filter(function(t){return!(t.disabled||t.tempDisabled)}).map(function(t,e){return d3.extent(t.values,function(t){return t.display.y})});return[d3.min(e,function(t){return t[0]}),d3.max(e,function(t){return t[1]})]}return P.dispatch=W,P.lines=r,P.legend=a,P.controls=s,P.xAxis=i,P.yAxis=o,P.interactiveLayer=l,P.state=M,P.tooltip=u,P.options=t.utils.optionsFunc.bind(P),P._options=Object.create({},{width:{get:function(){return p},set:function(t){p=t}},height:{get:function(){return h},set:function(t){h=t}},showControls:{get:function(){return x},set:function(t){x=t}},showLegend:{get:function(){return g},set:function(t){g=t}},average:{get:function(){return S},set:function(t){S=t}},defaultState:{get:function(){return A},set:function(t){A=t}},noData:{get:function(){return C},set:function(t){C=t}},showXAxis:{get:function(){return v},set:function(t){v=t}},showYAxis:{get:function(){return m},set:function(t){m=t}},noErrorCheck:{get:function(){return F},set:function(t){F=t}},rescaleY:{get:function(){return k},set:function(t){k=t,P.state.rescaleY=t}},margin:{get:function(){return c},set:function(t){void 0!==t.top&&(c.top=t.top,d=t.top),c.right=void 0!==t.right?t.right:c.right,c.bottom=void 0!==t.bottom?t.bottom:c.bottom,c.left=void 0!==t.left?t.left:c.left}},color:{get:function(){return f},set:function(e){f=t.utils.getColor(e),a.color(f)}},useInteractiveGuideline:{get:function(){return b},set:function(t){b=t,!0===t&&(P.interactive(!1),P.useVoronoi(!1))}},rightAlignYAxis:{get:function(){return y},set:function(t){y=t,o.orient(t?"right":"left")}},duration:{get:function(){return E},set:function(t){E=t,r.duration(E),i.duration(E),o.duration(E),_.reset(E)}}}),t.utils.inheritOptions(P,r),t.utils.initOptions(P),P},t.models.discreteBar=function(){"use strict";var e,n,r,i,o,a,s,l={top:0,right:0,bottom:0,left:0},u=960,c=500,d=Math.floor(1e4*Math.random()),f=d3.scale.ordinal(),p=d3.scale.linear(),h=function(t){return t.x},g=function(t){return t.y},v=[0],m=t.utils.defaultColor(),y=!1,x=d3.format(",.2f"),b=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),k="discreteBar",w=250,M=t.utils.renderWatch(b,w);function A(d){return M.reset(),d.each(function(d){var w=u-l.left-l.right,A=c-l.top-l.bottom;e=d3.select(this),t.utils.initSVG(e),d.forEach(function(t,e){t.values.forEach(function(t){t.series=e})});var C=n&&r?[]:d.map(function(t){return t.values.map(function(t,e){return{x:h(t,e),y:g(t,e),y0:t.y0}})});f.domain(n||d3.merge(C).map(function(t){return t.x})).rangeBands(i||[0,w],.1),p.domain(r||d3.extent(d3.merge(C).map(function(t){return t.y}).concat(v))),y?p.range(o||[A-(p.domain()[0]<0?12:0),p.domain()[1]>0?12:0]):p.range(o||[A,0]),a=a||f,s=s||p.copy().range([p(0),p(0)]);var S=e.selectAll("g.nv-wrap.nv-discretebar").data([d]),W=S.enter().append("g").attr("class","nvd3 nv-wrap nv-discretebar").append("g");S.select("g");W.append("g").attr("class","nv-groups"),S.attr("transform","translate("+l.left+","+l.top+")");var E=S.select(".nv-groups").selectAll(".nv-group").data(function(t){return t},function(t){return t.key});E.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),E.exit().watchTransition(M,"discreteBar: exit groups").style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),E.attr("class",function(t,e){return"nv-group nv-series-"+e}).classed("hover",function(t){return t.hover}),E.watchTransition(M,"discreteBar: groups").style("stroke-opacity",1).style("fill-opacity",.75);var F=E.selectAll("g.nv-bar").data(function(t){return t.values});F.exit().remove();var N=F.enter().append("g").attr("transform",function(t,e,n){return"translate("+(f(h(t,e))+.05*f.rangeBand())+", "+p(0)+")"}).on("mouseover",function(t,e){d3.select(this).classed("hover",!0),b.elementMouseover({data:t,index:e,color:d3.select(this).style("fill")})}).on("mouseout",function(t,e){d3.select(this).classed("hover",!1),b.elementMouseout({data:t,index:e,color:d3.select(this).style("fill")})}).on("mousemove",function(t,e){b.elementMousemove({data:t,index:e,color:d3.select(this).style("fill")})}).on("click",function(t,e){b.elementClick({data:t,index:e,color:d3.select(this).style("fill"),event:d3.event,element:this}),d3.event.stopPropagation()}).on("dblclick",function(t,e){b.elementDblClick({data:t,index:e,color:d3.select(this).style("fill")}),d3.event.stopPropagation()});N.append("rect").attr("height",0).attr("width",.9*f.rangeBand()/d.length),y?(N.append("text").attr("text-anchor","middle"),F.select("text").text(function(t,e){return x(g(t,e))}).watchTransition(M,"discreteBar: bars text").attr("x",.9*f.rangeBand()/2).attr("y",function(t,e){return g(t,e)<0?p(g(t,e))-p(0)+12:-4})):F.selectAll("text").remove(),F.attr("class",function(t,e){return g(t,e)<0?"nv-bar negative":"nv-bar positive"}).style("fill",function(t,e){return t.color||m(t,e)}).style("stroke",function(t,e){return t.color||m(t,e)}).select("rect").attr("class",k).watchTransition(M,"discreteBar: bars rect").attr("width",.9*f.rangeBand()/d.length),F.watchTransition(M,"discreteBar: bars").attr("transform",function(t,e){return"translate("+(f(h(t,e))+.05*f.rangeBand())+", "+(g(t,e)<0?p(0):p(0)-p(g(t,e))<1?p(0)-1:p(g(t,e)))+")"}).select("rect").attr("height",function(t,e){return Math.max(Math.abs(p(g(t,e))-p(0)),1)}),a=f.copy(),s=p.copy()}),M.renderEnd("discreteBar immediate"),A}return A.dispatch=b,A.options=t.utils.optionsFunc.bind(A),A._options=Object.create({},{width:{get:function(){return u},set:function(t){u=t}},height:{get:function(){return c},set:function(t){c=t}},forceY:{get:function(){return v},set:function(t){v=t}},showValues:{get:function(){return y},set:function(t){y=t}},x:{get:function(){return h},set:function(t){h=t}},y:{get:function(){return g},set:function(t){g=t}},xScale:{get:function(){return f},set:function(t){f=t}},yScale:{get:function(){return p},set:function(t){p=t}},xDomain:{get:function(){return n},set:function(t){n=t}},yDomain:{get:function(){return r},set:function(t){r=t}},xRange:{get:function(){return i},set:function(t){i=t}},yRange:{get:function(){return o},set:function(t){o=t}},valueFormat:{get:function(){return x},set:function(t){x=t}},id:{get:function(){return d},set:function(t){d=t}},rectClass:{get:function(){return k},set:function(t){k=t}},margin:{get:function(){return l},set:function(t){l.top=void 0!==t.top?t.top:l.top,l.right=void 0!==t.right?t.right:l.right,l.bottom=void 0!==t.bottom?t.bottom:l.bottom,l.left=void 0!==t.left?t.left:l.left}},color:{get:function(){return m},set:function(e){m=t.utils.getColor(e)}},duration:{get:function(){return w},set:function(t){w=t,M.reset(w)}}}),t.utils.initOptions(A),A},t.models.discreteBarChart=function(){"use strict";var e,n,r=t.models.discreteBar(),i=t.models.axis(),o=t.models.axis(),a=t.models.legend(),s=t.models.tooltip(),l={top:15,right:10,bottom:50,left:60},u=null,c=null,d=null,f=t.utils.getColor(),p=!1,h=!0,g=!0,v=!1,m=!1,y=!1,x=0,b=null,k=d3.dispatch("beforeUpdate","renderEnd"),w=250;i.orient("bottom").showMaxMin(!1).tickFormat(function(t){return t}),o.orient(v?"right":"left").tickFormat(d3.format(",.1f")),s.duration(0).headerEnabled(!1).valueFormatter(function(t,e){return o.tickFormat()(t,e)}).keyFormatter(function(t,e){return i.tickFormat()(t,e)});var M=t.utils.renderWatch(k,w);function A(s){return M.reset(),M.models(r),h&&M.models(i),g&&M.models(o),s.each(function(s){var f=d3.select(this);t.utils.initSVG(f);var b=t.utils.availableWidth(c,f,l),M=t.utils.availableHeight(d,f,l);if(A.update=function(){k.beforeUpdate(),f.transition().duration(w).call(A)},A.container=this,!(s&&s.length&&s.filter(function(t){return t.values.length}).length))return t.utils.noData(A,f),A;f.selectAll(".nv-noData").remove(),e=r.xScale(),n=r.yScale().clamp(!0);var C=f.selectAll("g.nv-wrap.nv-discreteBarWithAxes").data([s]),S=C.enter().append("g").attr("class","nvd3 nv-wrap nv-discreteBarWithAxes").append("g"),W=S.append("defs"),E=C.select("g");if(S.append("g").attr("class","nv-x nv-axis"),S.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),S.append("g").attr("class","nv-barsWrap"),S.append("g").attr("class","nv-legendWrap"),E.attr("transform","translate("+l.left+","+l.top+")"),p?(a.width(b),E.select(".nv-legendWrap").datum(s).call(a),u||a.height()===l.top||(l.top=a.height(),M=t.utils.availableHeight(d,f,l)),C.select(".nv-legendWrap").attr("transform","translate(0,"+-l.top+")")):E.select(".nv-legendWrap").selectAll("*").remove(),v&&E.select(".nv-y.nv-axis").attr("transform","translate("+b+",0)"),r.width(b).height(M),E.select(".nv-barsWrap").datum(s.filter(function(t){return!t.disabled})).transition().call(r),W.append("clipPath").attr("id","nv-x-label-clip-"+r.id()).append("rect"),E.select("#nv-x-label-clip-"+r.id()+" rect").attr("width",e.rangeBand()*(m?2:1)).attr("height",16).attr("x",-e.rangeBand()/(m?1:2)),h){i.scale(e)._ticks(t.utils.calcTicksX(b/100,s)).tickSize(-M,0),E.select(".nv-x.nv-axis").attr("transform","translate(0,"+(n.range()[0]+(r.showValues()&&n.domain()[0]<0?16:0))+")"),E.select(".nv-x.nv-axis").call(i);var F=E.select(".nv-x.nv-axis").selectAll("g");m&&F.selectAll("text").attr("transform",function(t,e,n){return"translate(0,"+(n%2==0?"5":"17")+")"}),x&&F.selectAll(".tick text").attr("transform","rotate("+x+" 0,0)").style("text-anchor",x>0?"start":"end"),y&&E.selectAll(".tick text").call(t.utils.wrapTicks,A.xAxis.rangeBand())}g&&(o.scale(n)._ticks(t.utils.calcTicksY(M/36,s)).tickSize(-b,0),E.select(".nv-y.nv-axis").call(o)),E.select(".nv-zeroLine line").attr("x1",0).attr("x2",v?-b:b).attr("y1",n(0)).attr("y2",n(0))}),M.renderEnd("discreteBar chart immediate"),A}return r.dispatch.on("elementMouseover.tooltip",function(t){t.series={key:A.x()(t.data),value:A.y()(t.data),color:t.color},s.data(t).hidden(!1)}),r.dispatch.on("elementMouseout.tooltip",function(t){s.hidden(!0)}),r.dispatch.on("elementMousemove.tooltip",function(t){s()}),A.dispatch=k,A.discretebar=r,A.legend=a,A.xAxis=i,A.yAxis=o,A.tooltip=s,A.options=t.utils.optionsFunc.bind(A),A._options=Object.create({},{width:{get:function(){return c},set:function(t){c=t}},height:{get:function(){return d},set:function(t){d=t}},showLegend:{get:function(){return p},set:function(t){p=t}},staggerLabels:{get:function(){return m},set:function(t){m=t}},rotateLabels:{get:function(){return x},set:function(t){x=t}},wrapLabels:{get:function(){return y},set:function(t){y=!!t}},showXAxis:{get:function(){return h},set:function(t){h=t}},showYAxis:{get:function(){return g},set:function(t){g=t}},noData:{get:function(){return b},set:function(t){b=t}},margin:{get:function(){return l},set:function(t){void 0!==t.top&&(l.top=t.top,u=t.top),l.right=void 0!==t.right?t.right:l.right,l.bottom=void 0!==t.bottom?t.bottom:l.bottom,l.left=void 0!==t.left?t.left:l.left}},duration:{get:function(){return w},set:function(t){w=t,M.reset(w),r.duration(w),i.duration(w),o.duration(w)}},color:{get:function(){return f},set:function(e){f=t.utils.getColor(e),r.color(f),a.color(f)}},rightAlignYAxis:{get:function(){return v},set:function(t){v=t,o.orient(t?"right":"left")}}}),t.utils.inheritOptions(A,r),t.utils.initOptions(A),A},t.models.distribution=function(){"use strict";var e,n={top:0,right:0,bottom:0,left:0},r=400,i=8,o="x",a=function(t){return t[o]},s=t.utils.defaultColor(),l=d3.scale.linear(),u=250,c=d3.dispatch("renderEnd"),d=t.utils.renderWatch(c,u);function f(r){return d.reset(),r.each(function(r){"x"===o?(n.left,n.right):(n.top,n.bottom);var u="x"==o?"y":"x",c=d3.select(this);t.utils.initSVG(c),e=e||l;var f=c.selectAll("g.nv-distribution").data([r]),p=(f.enter().append("g").attr("class","nvd3 nv-distribution").append("g"),f.select("g"));f.attr("transform","translate("+n.left+","+n.top+")");var h=p.selectAll("g.nv-dist").data(function(t){return t},function(t){return t.key});h.enter().append("g"),h.attr("class",function(t,e){return"nv-dist nv-series-"+e}).style("stroke",function(t,e){return s(t,e)});var g=h.selectAll("line.nv-dist"+o).data(function(t){return t.values});g.enter().append("line").attr(o+"1",function(t,n){return e(a(t,n))}).attr(o+"2",function(t,n){return e(a(t,n))}),d.transition(h.exit().selectAll("line.nv-dist"+o),"dist exit").attr(o+"1",function(t,e){return l(a(t,e))}).attr(o+"2",function(t,e){return l(a(t,e))}).style("stroke-opacity",0).remove(),g.attr("class",function(t,e){return"nv-dist"+o+" nv-dist"+o+"-"+e}).attr(u+"1",0).attr(u+"2",i),d.transition(g,"dist").attr(o+"1",function(t,e){return l(a(t,e))}).attr(o+"2",function(t,e){return l(a(t,e))}),e=l.copy()}),d.renderEnd("distribution immediate"),f}return f.options=t.utils.optionsFunc.bind(f),f.dispatch=c,f.margin=function(t){return arguments.length?(n.top=void 0!==t.top?t.top:n.top,n.right=void 0!==t.right?t.right:n.right,n.bottom=void 0!==t.bottom?t.bottom:n.bottom,n.left=void 0!==t.left?t.left:n.left,f):n},f.width=function(t){return arguments.length?(r=t,f):r},f.axis=function(t){return arguments.length?(o=t,f):o},f.size=function(t){return arguments.length?(i=t,f):i},f.getData=function(t){return arguments.length?(a=d3.functor(t),f):a},f.scale=function(t){return arguments.length?(l=t,f):l},f.color=function(e){return arguments.length?(s=t.utils.getColor(e),f):s},f.duration=function(t){return arguments.length?(u=t,d.reset(u),f):u},f},t.models.focus=function(e){"use strict";e=e||t.models.line();var n,r,i=t.models.axis(),o=t.models.axis(),a=d3.svg.brush(),s={top:10,right:0,bottom:30,left:0},l=t.utils.defaultColor(),u=null,c=70,d=!0,f=!1,p=!1,h=null,g=250,v=d3.dispatch("brush","onBrush","renderEnd"),m=!0;e.interactive(!1),e.pointActive(function(t){return!1});var y=t.utils.renderWatch(v,g);function x(b){return y.reset(),y.models(e),d&&y.models(i),f&&y.models(o),b.each(function(y){var b=d3.select(this);t.utils.initSVG(b);var k=t.utils.availableWidth(u,b,s),w=c-s.top-s.bottom;x.update=function(){0===g?b.call(x):b.transition().duration(g).call(x)},x.container=this,n=e.xScale(),r=e.yScale();var M=b.selectAll("g.nv-focus").data([y]),A=M.enter().append("g").attr("class","nvd3 nv-focus").append("g"),C=M.select("g");M.attr("transform","translate("+s.left+","+s.top+")"),A.append("g").attr("class","nv-background").append("rect"),A.append("g").attr("class","nv-x nv-axis"),A.append("g").attr("class","nv-y nv-axis"),A.append("g").attr("class","nv-contentWrap"),A.append("g").attr("class","nv-brushBackground"),A.append("g").attr("class","nv-x nv-brush"),p&&C.select(".nv-y.nv-axis").attr("transform","translate("+k+",0)"),C.select(".nv-background rect").attr("width",k).attr("height",w),e.width(k).height(w).color(y.map(function(t,e){return t.color||l(t,e)}).filter(function(t,e){return!y[e].disabled}));var S=C.select(".nv-contentWrap").datum(y.filter(function(t){return!t.disabled}));d3.transition(S).call(e),a.x(n).on("brush",function(){N(m)}),a.on("brushend",function(){m||v.onBrush(a.empty()?n.domain():a.extent())}),h&&a.extent(h);var W=C.select(".nv-brushBackground").selectAll("g").data([h||a.extent()]),E=W.enter().append("g");E.append("rect").attr("class","left").attr("x",0).attr("y",0).attr("height",w),E.append("rect").attr("class","right").attr("x",0).attr("y",0).attr("height",w);var F=C.select(".nv-x.nv-brush").call(a);function N(t){h=a.empty()?null:a.extent();var e=a.empty()?n.domain():a.extent();v.brush({extent:e,brush:a}),a.empty()||a.extent(h),W.data([a.empty()?n.domain():h]).each(function(t,e){var r=n(t[0])-n.range()[0],i=k-n(t[1]);d3.select(this).select(".left").attr("width",r<0?0:r),d3.select(this).select(".right").attr("x",n(t[1])).attr("width",i<0?0:i)}),t&&v.onBrush(e)}F.selectAll("rect").attr("height",w),F.selectAll(".resize").append("path").attr("d",function(t){var e=+("e"==t),n=e?1:-1,r=w/3;return"M"+.5*n+","+r+"A6,6 0 0 "+e+" "+6.5*n+","+(r+6)+"V"+(2*r-6)+"A6,6 0 0 "+e+" "+.5*n+","+2*r+"ZM"+2.5*n+","+(r+8)+"V"+(2*r-8)+"M"+4.5*n+","+(r+8)+"V"+(2*r-8)}),N(!0),C.select(".nv-background rect").attr("width",k).attr("height",w),d&&(i.scale(n)._ticks(t.utils.calcTicksX(k/100,y)).tickSize(-w,0),C.select(".nv-x.nv-axis").attr("transform","translate(0,"+r.range()[0]+")"),d3.transition(C.select(".nv-x.nv-axis")).call(i)),f&&(o.scale(r)._ticks(t.utils.calcTicksY(w/36,y)).tickSize(-k,0),d3.transition(C.select(".nv-y.nv-axis")).call(o)),C.select(".nv-x.nv-axis").attr("transform","translate(0,"+r.range()[0]+")")}),y.renderEnd("focus immediate"),x}return x.dispatch=v,x.content=e,x.brush=a,x.xAxis=i,x.yAxis=o,x.options=t.utils.optionsFunc.bind(x),x._options=Object.create({},{width:{get:function(){return u},set:function(t){u=t}},height:{get:function(){return c},set:function(t){c=t}},showXAxis:{get:function(){return d},set:function(t){d=t}},showYAxis:{get:function(){return f},set:function(t){f=t}},brushExtent:{get:function(){return h},set:function(t){h=t}},syncBrushing:{get:function(){return m},set:function(t){m=t}},margin:{get:function(){return s},set:function(t){s.top=void 0!==t.top?t.top:s.top,s.right=void 0!==t.right?t.right:s.right,s.bottom=void 0!==t.bottom?t.bottom:s.bottom,s.left=void 0!==t.left?t.left:s.left}},duration:{get:function(){return g},set:function(t){g=t,y.reset(g),e.duration(g),i.duration(g),o.duration(g)}},color:{get:function(){return l},set:function(n){l=t.utils.getColor(n),e.color(l)}},interpolate:{get:function(){return e.interpolate()},set:function(t){e.interpolate(t)}},xTickFormat:{get:function(){return i.tickFormat()},set:function(t){i.tickFormat(t)}},yTickFormat:{get:function(){return o.tickFormat()},set:function(t){o.tickFormat(t)}},x:{get:function(){return e.x()},set:function(t){e.x(t)}},y:{get:function(){return e.y()},set:function(t){e.y(t)}},rightAlignYAxis:{get:function(){return p},set:function(t){p=t,o.orient(p?"right":"left")}}}),t.utils.inheritOptions(x,e),t.utils.initOptions(x),x},t.models.forceDirectedGraph=function(){"use strict";var e={top:2,right:0,bottom:2,left:0},n=400,r=32,i=null,o=d3.dispatch("renderEnd"),a=t.utils.getColor(["#000"]),s=t.models.tooltip(),l=null,u=.1,c=.9,d=30,f=-120,p=.1,h=.8,g=.1,v=5,m=function(t){},y=function(t){},x=d3.functor(0),b=d3.functor(0),k=t.utils.renderWatch(o);function w(o){return k.reset(),o.each(function(o){i=d3.select(this),t.utils.initSVG(i);var l=t.utils.availableWidth(n,i,e),x=t.utils.availableHeight(r,i,e);if(i.attr("width",l).attr("height",x),!(o&&o.links&&o.nodes))return t.utils.noData(w,i),w;i.selectAll(".nv-noData").remove(),i.selectAll("*").remove();var b=new Set;o.nodes.forEach(function(t){Object.keys(t).forEach(function(t){b.add(t)})});var k=d3.layout.force().nodes(o.nodes).links(o.links).size([l,x]).linkStrength(u).friction(c).linkDistance(d).charge(f).gravity(p).theta(h).alpha(g).start(),M=i.selectAll(".link").data(o.links).enter().append("line").attr("class","nv-force-link").style("stroke-width",function(t){return Math.sqrt(t.value)}),A=i.selectAll(".node").data(o.nodes).enter().append("g").attr("class","nv-force-node").call(k.drag);A.append("circle").attr("r",v).style("fill",function(t){return a(t)}).on("mouseover",function(t){i.select(".nv-series-"+t.seriesIndex+" .nv-distx-"+t.pointIndex).attr("y1",t.py),i.select(".nv-series-"+t.seriesIndex+" .nv-disty-"+t.pointIndex).attr("x2",t.px);var e=a(t);t.series=[],b.forEach(function(n){t.series.push({color:e,key:n,value:t[n]})}),s.data(t).hidden(!1)}).on("mouseout",function(t){s.hidden(!0)}),s.headerFormatter(function(t){return"Node"}),y(M),m(A),k.on("tick",function(){M.attr("x1",function(t){return t.source.x}).attr("y1",function(t){return t.source.y}).attr("x2",function(t){return t.target.x}).attr("y2",function(t){return t.target.y}),A.attr("transform",function(t){return"translate("+t.x+", "+t.y+")"})})}),w}return w.options=t.utils.optionsFunc.bind(w),w._options=Object.create({},{width:{get:function(){return n},set:function(t){n=t}},height:{get:function(){return r},set:function(t){r=t}},linkStrength:{get:function(){return u},set:function(t){u=t}},friction:{get:function(){return c},set:function(t){c=t}},linkDist:{get:function(){return d},set:function(t){d=t}},charge:{get:function(){return f},set:function(t){f=t}},gravity:{get:function(){return p},set:function(t){p=t}},theta:{get:function(){return h},set:function(t){h=t}},alpha:{get:function(){return g},set:function(t){g=t}},radius:{get:function(){return v},set:function(t){v=t}},x:{get:function(){return x},set:function(t){x=d3.functor(t)}},y:{get:function(){return b},set:function(t){b=d3.functor(t)}},margin:{get:function(){return e},set:function(t){e.top=void 0!==t.top?t.top:e.top,e.right=void 0!==t.right?t.right:e.right,e.bottom=void 0!==t.bottom?t.bottom:e.bottom,e.left=void 0!==t.left?t.left:e.left}},color:{get:function(){return a},set:function(e){a=t.utils.getColor(e)}},noData:{get:function(){return l},set:function(t){l=t}},nodeExtras:{get:function(){return m},set:function(t){m=t}},linkExtras:{get:function(){return y},set:function(t){y=t}}}),w.dispatch=o,w.tooltip=s,t.utils.initOptions(w),w},t.models.furiousLegend=function(){"use strict";var e={top:5,right:0,bottom:5,left:0},n=400,r=20,i=function(t){return t.key},o=function(t){return t},a=t.utils.getColor(),s=20,l=!0,u=28,c=!0,d=!0,f=!1,p=!1,h=d3.dispatch("legendClick","legendDblclick","legendMouseover","legendMouseout","stateChange"),g="classic";function v(m){function y(t,e){return"furious"!=g?"#000":p?t.disengaged?a(t,e):"#fff":p?void 0:t.disabled?a(t,e):"#fff"}function x(t,e){return p&&"furious"==g?t.disengaged?"#fff":a(t,e):t.disabled?"#fff":a(t,e)}return m.each(function(v){var m=n-e.left-e.right,b=d3.select(this);t.utils.initSVG(b);var k=b.selectAll("g.nv-legend").data([v]),w=(k.enter().append("g").attr("class","nvd3 nv-legend").append("g"),k.select("g"));k.attr("transform","translate("+e.left+","+e.top+")");var M,A=w.selectAll(".nv-series").data(function(t){return"furious"!=g?t:t.filter(function(t){return!!p||!t.disengaged})}),C=A.enter().append("g").attr("class","nv-series");if("classic"==g)C.append("circle").style("stroke-width",2).attr("class","nv-legend-symbol").attr("r",5),M=A.select("circle");else if("furious"==g){C.append("rect").style("stroke-width",2).attr("class","nv-legend-symbol").attr("rx",3).attr("ry",3),M=A.select("rect"),C.append("g").attr("class","nv-check-box").property("innerHTML",'').attr("transform","translate(-10,-8)scale(0.5)"),A.select(".nv-check-box").each(function(t,e){d3.select(this).selectAll("path").attr("stroke",y(t,e))})}C.append("text").attr("text-anchor","start").attr("class","nv-legend-text").attr("dy",".32em").attr("dx","8");var S,W=A.select("text.nv-legend-text");switch(A.on("mouseover",function(t,e){h.legendMouseover(t,e)}).on("mouseout",function(t,e){h.legendMouseout(t,e)}).on("click",function(t,e){h.legendClick(t,e);var n=A.data();if(d){if("classic"==g)f?(n.forEach(function(t){t.disabled=!0}),t.disabled=!1):(t.disabled=!t.disabled,n.every(function(t){return t.disabled})&&n.forEach(function(t){t.disabled=!1}));else if("furious"==g)if(p)t.disengaged=!t.disengaged,t.userDisabled=void 0==t.userDisabled?!!t.disabled:t.userDisabled,t.disabled=t.disengaged||t.userDisabled;else if(!p){t.disabled=!t.disabled,t.userDisabled=t.disabled,n.filter(function(t){return!t.disengaged}).every(function(t){return t.userDisabled})&&n.forEach(function(t){t.disabled=t.userDisabled=!1})}h.stateChange({disabled:n.map(function(t){return!!t.disabled}),disengaged:n.map(function(t){return!!t.disengaged})})}}).on("dblclick",function(t,e){if(("furious"!=g||!p)&&(h.legendDblclick(t,e),d)){var n=A.data();n.forEach(function(t){t.disabled=!0,"furious"==g&&(t.userDisabled=t.disabled)}),t.disabled=!1,"furious"==g&&(t.userDisabled=t.disabled),h.stateChange({disabled:n.map(function(t){return!!t.disabled})})}}),A.classed("nv-disabled",function(t){return t.userDisabled}),A.exit().remove(),W.attr("fill",y).text(function(t){return o(i(t))}),g){case"furious":S=23;break;case"classic":S=20}if(l){var E=[];A.each(function(e,n){var r,a;if(o(i(e))&&o(i(e)).length>s){var l=o(i(e)).substring(0,s);r=d3.select(this).select("text").text(l+"..."),d3.select(this).append("svg:title").text(o(i(e)))}else r=d3.select(this).select("text");try{if((a=r.node().getComputedTextLength())<=0)throw Error()}catch(e){a=t.utils.calcApproxTextWidth(r)}E.push(a+u)});for(var F=0,N=0,D=[];Nm&&F>1;){D=[],F--;for(var O=0;O(D[O%F]||0)&&(D[O%F]=E[O]);N=D.reduce(function(t,e,n,r){return t+e})}for(var _=[],L=0,B=0;Lz&&(z=I),"translate("+P+","+T+")"}),w.attr("transform","translate("+(n-e.right-z)+","+e.top+")"),r=e.top+e.bottom+T+15}"furious"==g&&M.attr("width",function(t,e){return W[0][e].getComputedTextLength()+27}).attr("height",18).attr("y",-9).attr("x",-15),M.style("fill",x).style("stroke",function(t,e){return t.color||a(t,e)})}),v}return v.dispatch=h,v.options=t.utils.optionsFunc.bind(v),v._options=Object.create({},{width:{get:function(){return n},set:function(t){n=t}},height:{get:function(){return r},set:function(t){r=t}},key:{get:function(){return i},set:function(t){i=t}},keyFormatter:{get:function(){return o},set:function(t){o=t}},align:{get:function(){return l},set:function(t){l=t}},rightAlign:{get:function(){return c},set:function(t){c=t}},maxKeyLength:{get:function(){return s},set:function(t){s=t}},padding:{get:function(){return u},set:function(t){u=t}},updateState:{get:function(){return d},set:function(t){d=t}},radioButtonMode:{get:function(){return f},set:function(t){f=t}},expanded:{get:function(){return p},set:function(t){p=t}},vers:{get:function(){return g},set:function(t){g=t}},margin:{get:function(){return e},set:function(t){e.top=void 0!==t.top?t.top:e.top,e.right=void 0!==t.right?t.right:e.right,e.bottom=void 0!==t.bottom?t.bottom:e.bottom,e.left=void 0!==t.left?t.left:e.left}},color:{get:function(){return a},set:function(e){a=t.utils.getColor(e)}}}),t.utils.initOptions(v),v},t.models.historicalBar=function(){"use strict";var e,n,r,i,o={top:0,right:0,bottom:0,left:0},a=null,s=null,l=Math.floor(1e4*Math.random()),u=null,c=d3.scale.linear(),d=d3.scale.linear(),f=function(t){return t.x},p=function(t){return t.y},h=[],g=[0],v=!1,m=!0,y=t.utils.defaultColor(),x=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),b=!0,k=t.utils.renderWatch(x,0);function w(M){return M.each(function(w){k.reset(),u=d3.select(this);var M=t.utils.availableWidth(a,u,o),A=t.utils.availableHeight(s,u,o);t.utils.initSVG(u),c.domain(e||d3.extent(w[0].values.map(f).concat(h))),v?c.range(r||[.5*M/w[0].values.length,M*(w[0].values.length-.5)/w[0].values.length]):c.range(r||[0,M]),d.domain(n||d3.extent(w[0].values.map(p).concat(g))).range(i||[A,0]),c.domain()[0]===c.domain()[1]&&(c.domain()[0]?c.domain([c.domain()[0]-.01*c.domain()[0],c.domain()[1]+.01*c.domain()[1]]):c.domain([-1,1])),d.domain()[0]===d.domain()[1]&&(d.domain()[0]?d.domain([d.domain()[0]+.01*d.domain()[0],d.domain()[1]-.01*d.domain()[1]]):d.domain([-1,1]));var C=u.selectAll("g.nv-wrap.nv-historicalBar-"+l).data([w[0].values]),S=C.enter().append("g").attr("class","nvd3 nv-wrap nv-historicalBar-"+l),W=S.append("defs"),E=S.append("g"),F=C.select("g");E.append("g").attr("class","nv-bars"),C.attr("transform","translate("+o.left+","+o.top+")"),u.on("click",function(t,e){x.chartClick({data:t,index:e,pos:d3.event,id:l})}),W.append("clipPath").attr("id","nv-chart-clip-path-"+l).append("rect"),C.select("#nv-chart-clip-path-"+l+" rect").attr("width",M).attr("height",A),F.attr("clip-path",m?"url(#nv-chart-clip-path-"+l+")":"");var N=C.select(".nv-bars").selectAll(".nv-bar").data(function(t){return t},function(t,e){return f(t,e)});N.exit().remove(),N.enter().append("rect").attr("x",0).attr("y",function(e,n){return t.utils.NaNtoZero(d(Math.max(0,p(e,n))))}).attr("height",function(e,n){return t.utils.NaNtoZero(Math.abs(d(p(e,n))-d(0)))}).attr("transform",function(t,e){return"translate("+(c(f(t,e))-M/w[0].values.length*.45)+",0)"}).on("mouseover",function(t,e){b&&(d3.select(this).classed("hover",!0),x.elementMouseover({data:t,index:e,color:d3.select(this).style("fill")}))}).on("mouseout",function(t,e){b&&(d3.select(this).classed("hover",!1),x.elementMouseout({data:t,index:e,color:d3.select(this).style("fill")}))}).on("mousemove",function(t,e){b&&x.elementMousemove({data:t,index:e,color:d3.select(this).style("fill")})}).on("click",function(t,e){if(b){x.elementClick({data:t,index:e,color:d3.select(this).style("fill"),event:d3.event,element:this}),d3.event.stopPropagation()}}).on("dblclick",function(t,e){b&&(x.elementDblClick({data:t,index:e,color:d3.select(this).style("fill")}),d3.event.stopPropagation())}),N.attr("fill",function(t,e){return y(t,e)}).attr("class",function(t,e,n){return(p(t,e)<0?"nv-bar negative":"nv-bar positive")+" nv-bar-"+n+"-"+e}).watchTransition(k,"bars").attr("transform",function(t,e){return"translate("+(c(f(t,e))-M/w[0].values.length*.45)+",0)"}).attr("width",M/w[0].values.length*.9),N.watchTransition(k,"bars").attr("y",function(e,n){var r=p(e,n)<0?d(0):d(0)-d(p(e,n))<1?d(0)-1:d(p(e,n));return t.utils.NaNtoZero(r)}).attr("height",function(e,n){return t.utils.NaNtoZero(Math.max(Math.abs(d(p(e,n))-d(0)),1))})}),k.renderEnd("historicalBar immediate"),w}return w.highlightPoint=function(t,e){u.select(".nv-bars .nv-bar-0-"+t).classed("hover",e)},w.clearHighlights=function(){u.select(".nv-bars .nv-bar.hover").classed("hover",!1)},w.dispatch=x,w.options=t.utils.optionsFunc.bind(w),w._options=Object.create({},{width:{get:function(){return a},set:function(t){a=t}},height:{get:function(){return s},set:function(t){s=t}},forceX:{get:function(){return h},set:function(t){h=t}},forceY:{get:function(){return g},set:function(t){g=t}},padData:{get:function(){return v},set:function(t){v=t}},x:{get:function(){return f},set:function(t){f=t}},y:{get:function(){return p},set:function(t){p=t}},xScale:{get:function(){return c},set:function(t){c=t}},yScale:{get:function(){return d},set:function(t){d=t}},xDomain:{get:function(){return e},set:function(t){e=t}},yDomain:{get:function(){return n},set:function(t){n=t}},xRange:{get:function(){return r},set:function(t){r=t}},yRange:{get:function(){return i},set:function(t){i=t}},clipEdge:{get:function(){return m},set:function(t){m=t}},id:{get:function(){return l},set:function(t){l=t}},interactive:{get:function(){return b},set:function(t){b=t}},margin:{get:function(){return o},set:function(t){o.top=void 0!==t.top?t.top:o.top,o.right=void 0!==t.right?t.right:o.right,o.bottom=void 0!==t.bottom?t.bottom:o.bottom,o.left=void 0!==t.left?t.left:o.left}},color:{get:function(){return y},set:function(e){y=t.utils.getColor(e)}}}),t.utils.initOptions(w),w},t.models.historicalBarChart=function(e){"use strict";var n,r,i=e||t.models.historicalBar(),o=t.models.axis(),a=t.models.axis(),s=t.models.legend(),l=t.interactiveGuideline(),u=t.models.tooltip(),c={top:30,right:90,bottom:50,left:90},d=null,f=t.utils.defaultColor(),p=null,h=null,g=!1,v=!0,m=!0,y=!1,x=!1,b={},k=null,w=null,M=d3.dispatch("tooltipHide","stateChange","changeState","renderEnd"),A=250;o.orient("bottom").tickPadding(7),a.orient(y?"right":"left"),u.duration(0).headerEnabled(!1).valueFormatter(function(t,e){return a.tickFormat()(t,e)}).headerFormatter(function(t,e){return o.tickFormat()(t,e)});var C=t.utils.renderWatch(M,0);function S(e){return e.each(function(u){C.reset(),C.models(i),v&&C.models(o),m&&C.models(a);var w=d3.select(this);t.utils.initSVG(w);var W=t.utils.availableWidth(p,w,c),E=t.utils.availableHeight(h,w,c);if(S.update=function(){w.transition().duration(A).call(S)},S.container=this,b.disabled=u.map(function(t){return!!t.disabled}),!k){var F;k={};for(F in b)b[F]instanceof Array?k[F]=b[F].slice(0):k[F]=b[F]}if(!(u&&u.length&&u.filter(function(t){return t.values.length}).length))return t.utils.noData(S,w),S;w.selectAll(".nv-noData").remove(),n=i.xScale(),r=i.yScale();var N=w.selectAll("g.nv-wrap.nv-historicalBarChart").data([u]),D=N.enter().append("g").attr("class","nvd3 nv-wrap nv-historicalBarChart").append("g"),O=N.select("g");D.append("g").attr("class","nv-x nv-axis"),D.append("g").attr("class","nv-y nv-axis"),D.append("g").attr("class","nv-barsWrap"),D.append("g").attr("class","nv-legendWrap"),D.append("g").attr("class","nv-interactive"),g?(s.width(W),O.select(".nv-legendWrap").datum(u).call(s),d||s.height()===c.top||(c.top=s.height(),E=t.utils.availableHeight(h,w,c)),N.select(".nv-legendWrap").attr("transform","translate(0,"+-c.top+")")):O.select(".nv-legendWrap").selectAll("*").remove(),N.attr("transform","translate("+c.left+","+c.top+")"),y&&O.select(".nv-y.nv-axis").attr("transform","translate("+W+",0)"),x&&(l.width(W).height(E).margin({left:c.left,top:c.top}).svgContainer(w).xScale(n),N.select(".nv-interactive").call(l)),i.width(W).height(E).color(u.map(function(t,e){return t.color||f(t,e)}).filter(function(t,e){return!u[e].disabled})),O.select(".nv-barsWrap").datum(u.filter(function(t){return!t.disabled})).transition().call(i),v&&(o.scale(n)._ticks(t.utils.calcTicksX(W/100,u)).tickSize(-E,0),O.select(".nv-x.nv-axis").attr("transform","translate(0,"+r.range()[0]+")"),O.select(".nv-x.nv-axis").transition().call(o)),m&&(a.scale(r)._ticks(t.utils.calcTicksY(E/36,u)).tickSize(-W,0),O.select(".nv-y.nv-axis").transition().call(a)),l.dispatch.on("elementMousemove",function(e){i.clearHighlights();var n,r,s,c=[];u.filter(function(t,e){return t.seriesIndex=e,!t.disabled}).forEach(function(o,a){r=t.interactiveBisect(o.values,e.pointXValue,S.x()),i.highlightPoint(r,!0);var l=o.values[r];void 0!==l&&(void 0===n&&(n=l),void 0===s&&(s=S.xScale()(S.x()(l,r))),c.push({key:o.key,value:S.y()(l,r),color:f(o,o.seriesIndex),data:o.values[r]}))});var d=o.tickFormat()(S.x()(n,r));l.tooltip.valueFormatter(function(t,e){return a.tickFormat()(t)}).data({value:d,index:r,series:c})(),l.renderGuideLine(s)}),l.dispatch.on("elementMouseout",function(t){M.tooltipHide(),i.clearHighlights()}),s.dispatch.on("legendClick",function(t,n){t.disabled=!t.disabled,u.filter(function(t){return!t.disabled}).length||u.map(function(t){return t.disabled=!1,N.selectAll(".nv-series").classed("disabled",!1),t}),b.disabled=u.map(function(t){return!!t.disabled}),M.stateChange(b),e.transition().call(S)}),s.dispatch.on("legendDblclick",function(t){u.forEach(function(t){t.disabled=!0}),t.disabled=!1,b.disabled=u.map(function(t){return!!t.disabled}),M.stateChange(b),S.update()}),M.on("changeState",function(t){void 0!==t.disabled&&(u.forEach(function(e,n){e.disabled=t.disabled[n]}),b.disabled=t.disabled),S.update()})}),C.renderEnd("historicalBarChart immediate"),S}return i.dispatch.on("elementMouseover.tooltip",function(t){t.series={key:S.x()(t.data),value:S.y()(t.data),color:t.color},u.data(t).hidden(!1)}),i.dispatch.on("elementMouseout.tooltip",function(t){u.hidden(!0)}),i.dispatch.on("elementMousemove.tooltip",function(t){u()}),S.dispatch=M,S.bars=i,S.legend=s,S.xAxis=o,S.yAxis=a,S.interactiveLayer=l,S.tooltip=u,S.options=t.utils.optionsFunc.bind(S),S._options=Object.create({},{width:{get:function(){return p},set:function(t){p=t}},height:{get:function(){return h},set:function(t){h=t}},showLegend:{get:function(){return g},set:function(t){g=t}},showXAxis:{get:function(){return v},set:function(t){v=t}},showYAxis:{get:function(){return m},set:function(t){m=t}},defaultState:{get:function(){return k},set:function(t){k=t}},noData:{get:function(){return w},set:function(t){w=t}},margin:{get:function(){return c},set:function(t){void 0!==t.top&&(c.top=t.top,d=t.top),c.right=void 0!==t.right?t.right:c.right,c.bottom=void 0!==t.bottom?t.bottom:c.bottom,c.left=void 0!==t.left?t.left:c.left}},color:{get:function(){return f},set:function(e){f=t.utils.getColor(e),s.color(f),i.color(f)}},duration:{get:function(){return A},set:function(t){A=t,C.reset(A),a.duration(A),o.duration(A)}},rightAlignYAxis:{get:function(){return y},set:function(t){y=t,a.orient(t?"right":"left")}},useInteractiveGuideline:{get:function(){return x},set:function(t){x=t,!0===t&&S.interactive(!1)}}}),t.utils.inheritOptions(S,i),t.utils.initOptions(S),S},t.models.ohlcBarChart=function(){var e=t.models.historicalBarChart(t.models.ohlcBar());return e.useInteractiveGuideline(!0),e.interactiveLayer.tooltip.contentGenerator(function(t){var n=t.series[0].data;return'

'+t.value+"

open:"+e.yAxis.tickFormat()(n.open)+"
close:"+e.yAxis.tickFormat()(n.close)+"
high"+e.yAxis.tickFormat()(n.high)+"
low:"+e.yAxis.tickFormat()(n.low)+"
"}),e},t.models.candlestickBarChart=function(){var e=t.models.historicalBarChart(t.models.candlestickBar());return e.useInteractiveGuideline(!0),e.interactiveLayer.tooltip.contentGenerator(function(t){var n=t.series[0].data;return'

'+t.value+"

open:"+e.yAxis.tickFormat()(n.open)+"
close:"+e.yAxis.tickFormat()(n.close)+"
high"+e.yAxis.tickFormat()(n.high)+"
low:"+e.yAxis.tickFormat()(n.low)+"
"}),e},t.models.legend=function(){"use strict";var e={top:5,right:0,bottom:5,left:0},n=400,r=20,i=function(t){return t.key},o=function(t){return t},a=t.utils.getColor(),s=20,l=!0,u=32,c=!0,d=!0,f=!0,p=!1,h=!1,g=d3.dispatch("legendClick","legendDblclick","legendMouseover","legendMouseout","stateChange"),v="classic";function m(y){function x(t,e){return"furious"!=v?"#000":h?t.disengaged?"#000":"#fff":h?void 0:(t.color||(t.color=a(t,e)),t.disabled?t.color:"#fff")}function b(t,e){return h&&"furious"==v&&t.disengaged?"#eee":t.color||a(t,e)}function k(t,e){return h&&"furious"==v?1:t.disabled?0:1}return y.each(function(a){var m=n-e.left-e.right,y=d3.select(this);t.utils.initSVG(y);var w=y.selectAll("g.nv-legend").data([a]),M=w.enter().append("g").attr("class","nvd3 nv-legend").append("g"),A=w.select("g");c?w.attr("transform","translate("+-e.right+","+e.top+")"):w.attr("transform","translate("+e.left+","+e.top+")");var C,S,W=A.selectAll(".nv-series").data(function(t){return"furious"!=v?t:t.filter(function(t){return!!h||!t.disengaged})}),E=W.enter().append("g").attr("class","nv-series");switch(v){case"furious":S=23;break;case"classic":S=20}if("classic"==v)E.append("circle").style("stroke-width",2).attr("class","nv-legend-symbol").attr("r",5),C=W.select(".nv-legend-symbol");else if("furious"==v){E.append("rect").style("stroke-width",2).attr("class","nv-legend-symbol").attr("rx",3).attr("ry",3),C=W.select(".nv-legend-symbol"),E.append("g").attr("class","nv-check-box").property("innerHTML",'').attr("transform","translate(-10,-8)scale(0.5)"),W.select(".nv-check-box").each(function(t,e){d3.select(this).selectAll("path").attr("stroke",x(t,e))})}E.append("text").attr("text-anchor","start").attr("class","nv-legend-text").attr("dy",".32em").attr("dx","8");var F=W.select("text.nv-legend-text");W.on("mouseover",function(t,e){g.legendMouseover(t,e)}).on("mouseout",function(t,e){g.legendMouseout(t,e)}).on("click",function(t,e){g.legendClick(t,e);var n=W.data();if(d){if("classic"==v)p?(n.forEach(function(t){t.disabled=!0}),t.disabled=!1):(t.disabled=!t.disabled,n.every(function(t){return t.disabled})&&n.forEach(function(t){t.disabled=!1}));else if("furious"==v)if(h)t.disengaged=!t.disengaged,t.userDisabled=void 0==t.userDisabled?!!t.disabled:t.userDisabled,t.disabled=t.disengaged||t.userDisabled;else if(!h){t.disabled=!t.disabled,t.userDisabled=t.disabled,n.filter(function(t){return!t.disengaged}).every(function(t){return t.userDisabled})&&n.forEach(function(t){t.disabled=t.userDisabled=!1})}g.stateChange({disabled:n.map(function(t){return!!t.disabled}),disengaged:n.map(function(t){return!!t.disengaged})})}}).on("dblclick",function(t,e){if(f){if("furious"==v&&h)return;if(g.legendDblclick(t,e),d){var n=W.data();n.forEach(function(t){t.disabled=!0,"furious"==v&&(t.userDisabled=t.disabled)}),t.disabled=!1,"furious"==v&&(t.userDisabled=t.disabled),g.stateChange({disabled:n.map(function(t){return!!t.disabled})})}}}),W.classed("nv-disabled",function(t){return t.userDisabled}),W.exit().remove(),F.attr("fill",x).text(function(t){return o(i(t))});var N=0;if(l){var D=[];W.each(function(e,n){var r,a;if(o(i(e))&&o(i(e)).length>s){var l=o(i(e)).substring(0,s);r=d3.select(this).select("text").text(l+"..."),d3.select(this).append("svg:title").text(o(i(e)))}else r=d3.select(this).select("text");try{if((a=r.node().getComputedTextLength())<=0)throw Error()}catch(e){a=t.utils.calcApproxTextWidth(r)}D.push(a+u)});var O=0,_=[];for(N=0;Nm&&O>1;){_=[],O--;for(var L=0;L(_[L%O]||0)&&(_[L%O]=D[L]);N=_.reduce(function(t,e,n,r){return t+e})}for(var B=[],P=0,T=0;PH&&(H=V),N0?k:0),S.attr("clip-path",h?"url(#nv-edge-clip-"+r.id()+")":""),W.attr("clip-path",h?"url(#nv-edge-clip-"+r.id()+")":"");var E=w.select(".nv-groups").selectAll(".nv-group").data(function(t){return t},function(t){return t.key});E.enter().append("g").style("stroke-opacity",1e-6).style("stroke-width",function(t){return t.strokeWidth||l}).style("fill-opacity",1e-6),E.exit().remove(),E.attr("class",function(t,e){return(t.classed||"")+" nv-group nv-series-"+e}).classed("hover",function(t){return t.hover}).style("fill",function(t,e){return u(t,e)}).style("stroke",function(t,e){return u(t,e)}),E.watchTransition(b,"line: groups").style("stroke-opacity",1).style("fill-opacity",function(t){return t.fillOpacity||.5});var F=E.selectAll("path.nv-area").data(function(t){return p(t)?[t]:[]});F.enter().append("path").attr("class","nv-area").attr("d",function(e){return d3.svg.area().interpolate(g).defined(f).x(function(e,n){return t.utils.NaNtoZero(y(c(e,n)))}).y0(function(e,n){return t.utils.NaNtoZero(x(d(e,n)))}).y1(function(t,e){return x(n.domain()[0]<=0?n.domain()[1]>=0?0:n.domain()[1]:n.domain()[0])}).apply(this,[e.values])}),E.exit().selectAll("path.nv-area").remove(),F.watchTransition(b,"line: areaPaths").attr("d",function(r){return d3.svg.area().interpolate(g).defined(f).x(function(n,r){return t.utils.NaNtoZero(e(c(n,r)))}).y0(function(e,r){return t.utils.NaNtoZero(n(d(e,r)))}).y1(function(t,e){return n(n.domain()[0]<=0?n.domain()[1]>=0?0:n.domain()[1]:n.domain()[0])}).apply(this,[r.values])});var N=E.selectAll("path.nv-line").data(function(t){return[t.values]});N.enter().append("path").attr("class","nv-line").attr("d",d3.svg.line().interpolate(g).defined(f).x(function(e,n){return t.utils.NaNtoZero(y(c(e,n)))}).y(function(e,n){return t.utils.NaNtoZero(x(d(e,n)))})),N.watchTransition(b,"line: linePaths").attr("d",d3.svg.line().interpolate(g).defined(f).x(function(n,r){return t.utils.NaNtoZero(e(c(n,r)))}).y(function(e,r){return t.utils.NaNtoZero(n(d(e,r)))})),y=e.copy(),x=n.copy()}),b.renderEnd("line immediate"),k}return k.dispatch=m,k.scatter=r,r.dispatch.on("elementClick",function(){m.elementClick.apply(this,arguments)}),r.dispatch.on("elementMouseover",function(){m.elementMouseover.apply(this,arguments)}),r.dispatch.on("elementMouseout",function(){m.elementMouseout.apply(this,arguments)}),k.options=t.utils.optionsFunc.bind(k),k._options=Object.create({},{width:{get:function(){return o},set:function(t){o=t}},height:{get:function(){return a},set:function(t){a=t}},defined:{get:function(){return f},set:function(t){f=t}},interpolate:{get:function(){return g},set:function(t){g=t}},clipEdge:{get:function(){return h},set:function(t){h=t}},margin:{get:function(){return i},set:function(t){i.top=void 0!==t.top?t.top:i.top,i.right=void 0!==t.right?t.right:i.right,i.bottom=void 0!==t.bottom?t.bottom:i.bottom,i.left=void 0!==t.left?t.left:i.left}},duration:{get:function(){return v},set:function(t){v=t,b.reset(v),r.duration(v)}},isArea:{get:function(){return p},set:function(t){p=d3.functor(t)}},x:{get:function(){return c},set:function(t){c=t,r.x(t)}},y:{get:function(){return d},set:function(t){d=t,r.y(t)}},color:{get:function(){return u},set:function(e){u=t.utils.getColor(e),r.color(u)}}}),t.utils.inheritOptions(k,r),t.utils.initOptions(k),k},t.models.lineChart=function(){"use strict";var e,n,r=t.models.line(),i=t.models.axis(),o=t.models.axis(),a=t.models.legend(),s=t.interactiveGuideline(),l=t.models.tooltip(),u=t.models.focus(t.models.line()),c={top:30,right:20,bottom:50,left:60},d=null,f=t.utils.defaultColor(),p=null,h=null,g=!0,v="top",m=!0,y=!0,x=!1,b=!1,k=!1,w=t.utils.state(),M=null,A=null,C=d3.dispatch("stateChange","changeState","renderEnd"),S=250;i.orient("bottom").tickPadding(7),o.orient(x?"right":"left"),r.clipEdge(!0).duration(0),l.valueFormatter(function(t,e){return o.tickFormat()(t,e)}).headerFormatter(function(t,e){return i.tickFormat()(t,e)}),s.tooltip.valueFormatter(function(t,e){return o.tickFormat()(t,e)}).headerFormatter(function(t,e){return i.tickFormat()(t,e)});var W=t.utils.renderWatch(C,S),E=function(t){return function(){return{active:t.map(function(t){return!t.disabled})}}},F=function(t){return function(e){void 0!==e.active&&t.forEach(function(t,n){t.disabled=!e.active[n]})}};function N(l){return W.reset(),W.models(r),m&&W.models(i),y&&W.models(o),l.each(function(l){var A=d3.select(this);t.utils.initSVG(A);var W=t.utils.availableWidth(p,A,c),D=t.utils.availableHeight(h,A,c)-(k?u.height():0);if(N.update=function(){0===S?A.call(N):A.transition().duration(S).call(N)},N.container=this,w.setter(F(l),N.update).getter(E(l)).update(),w.disabled=l.map(function(t){return!!t.disabled}),!M){var O;M={};for(O in w)w[O]instanceof Array?M[O]=w[O].slice(0):M[O]=w[O]}if(!(l&&l.length&&l.filter(function(t){return t.values.length}).length))return t.utils.noData(N,A),N;A.selectAll(".nv-noData").remove(),u.dispatch.on("onBrush",function(t){H(t)}),e=r.xScale(),n=r.yScale();var _=A.selectAll("g.nv-wrap.nv-lineChart").data([l]),L=_.enter().append("g").attr("class","nvd3 nv-wrap nv-lineChart").append("g"),B=_.select("g");L.append("g").attr("class","nv-legendWrap");var P=L.append("g").attr("class","nv-focus");P.append("g").attr("class","nv-background").append("rect"),P.append("g").attr("class","nv-x nv-axis"),P.append("g").attr("class","nv-y nv-axis"),P.append("g").attr("class","nv-linesWrap"),P.append("g").attr("class","nv-interactive");L.append("g").attr("class","nv-focusWrap");g?(a.width(W),B.select(".nv-legendWrap").datum(l).call(a),"bottom"===v?(c.bottom=i.height()+a.height(),D=t.utils.availableHeight(h,A,c),B.select(".nv-legendWrap").attr("transform","translate(0,"+(D+i.height())+")")):"top"===v&&(d||a.height()===c.top||(c.top=a.height(),D=t.utils.availableHeight(h,A,c)-(k?u.height():0)),_.select(".nv-legendWrap").attr("transform","translate(0,"+-c.top+")"))):B.select(".nv-legendWrap").selectAll("*").remove(),_.attr("transform","translate("+c.left+","+c.top+")"),x&&B.select(".nv-y.nv-axis").attr("transform","translate("+W+",0)"),b&&(s.width(W).height(D).margin({left:c.left,top:c.top}).svgContainer(A).xScale(e),_.select(".nv-interactive").call(s)),B.select(".nv-focus .nv-background rect").attr("width",W).attr("height",D),r.width(W).height(D).color(l.map(function(t,e){return t.color||f(t,e)}).filter(function(t,e){return!l[e].disabled}));var T=B.select(".nv-linesWrap").datum(l.filter(function(t){return!t.disabled}));function I(){m&&B.select(".nv-focus .nv-x.nv-axis").transition().duration(S).call(i)}function z(){y&&B.select(".nv-focus .nv-y.nv-axis").transition().duration(S).call(o)}if(m&&i.scale(e)._ticks(t.utils.calcTicksX(W/100,l)).tickSize(-D,0),y&&o.scale(n)._ticks(t.utils.calcTicksY(D/36,l)).tickSize(-W,0),B.select(".nv-focus .nv-x.nv-axis").attr("transform","translate(0,"+D+")"),k||null!==u.brush.extent()){u.width(W),B.select(".nv-focusWrap").style("display",k?"initial":"none").attr("transform","translate(0,"+(D+c.bottom+u.margin().top)+")").call(u);var V=u.brush.empty()?u.xDomain():u.brush.extent();null!==V&&H(V)}else T.call(r),I(),z();function H(t){B.select(".nv-focus .nv-linesWrap").datum(l.filter(function(t){return!t.disabled}).map(function(e,n){return{key:e.key,area:e.area,classed:e.classed,values:e.values.filter(function(e,n){return r.x()(e,n)>=t[0]&&r.x()(e,n)<=t[1]}),disableTooltip:e.disableTooltip}})).transition().duration(S).call(r),I(),z()}a.dispatch.on("stateChange",function(t){for(var e in t)w[e]=t[e];C.stateChange(w),N.update()}),s.dispatch.on("elementMousemove",function(n){r.clearHighlights();var i,a,c,d=[];if(l.filter(function(t,e){return t.seriesIndex=e,!t.disabled&&!t.disableTooltip}).forEach(function(o,s){var l=null!==u.brush.extent()?u.brush.empty()?u.xScale().domain():u.brush.extent():e.domain(),p=o.values.filter(function(t,e){return l[0]<=l[1]?r.x()(t,e)>=l[0]&&r.x()(t,e)<=l[1]:r.x()(t,e)>=l[1]&&r.x()(t,e)<=l[0]}),h=p[a=t.interactiveBisect(p,n.pointXValue,r.x())],g=N.y()(h,a);null!==g&&r.highlightPoint(s,a,!0),void 0!==h&&(void 0===i&&(i=h),void 0===c&&(c=N.xScale()(N.x()(h,a))),d.push({key:o.key,value:g,color:f(o,o.seriesIndex),data:h}))}),d.length>2){var p=N.yScale().invert(n.mouseY),h=.03*Math.abs(N.yScale().domain()[0]-N.yScale().domain()[1]),g=t.nearestValueIndex(d.map(function(t){return t.value}),p,h);null!==g&&(d[g].highlight=!0)}s.tooltip.valueFormatter(s.tooltip.valueFormatter()||function(t,e){return null==t?"N/A":o.tickFormat()(t)}).data({value:N.x()(i,a),index:a,series:d})(),s.renderGuideLine(c)}),s.dispatch.on("elementClick",function(e){var n,i=[];l.filter(function(t,e){return t.seriesIndex=e,!t.disabled}).forEach(function(r){var o=t.interactiveBisect(r.values,e.pointXValue,N.x()),a=r.values[o];if(void 0!==a){void 0===n&&(n=N.xScale()(N.x()(a,o)));var s=N.yScale()(N.y()(a,o));i.push({point:a,pointIndex:o,pos:[n,s],seriesIndex:r.seriesIndex,series:r})}}),r.dispatch.elementClick(i)}),s.dispatch.on("elementMouseout",function(t){r.clearHighlights()}),C.on("changeState",function(t){void 0!==t.disabled&&l.length===t.disabled.length&&(l.forEach(function(e,n){e.disabled=t.disabled[n]}),w.disabled=t.disabled),N.update()})}),W.renderEnd("lineChart immediate"),N}return r.dispatch.on("elementMouseover.tooltip",function(t){t.series.disableTooltip||l.data(t).hidden(!1)}),r.dispatch.on("elementMouseout.tooltip",function(t){l.hidden(!0)}),N.dispatch=C,N.lines=r,N.legend=a,N.focus=u,N.xAxis=i,N.x2Axis=u.xAxis,N.yAxis=o,N.y2Axis=u.yAxis,N.interactiveLayer=s,N.tooltip=l,N.state=w,N.dispatch=C,N.options=t.utils.optionsFunc.bind(N),N._options=Object.create({},{width:{get:function(){return p},set:function(t){p=t}},height:{get:function(){return h},set:function(t){h=t}},showLegend:{get:function(){return g},set:function(t){g=t}},legendPosition:{get:function(){return v},set:function(t){v=t}},showXAxis:{get:function(){return m},set:function(t){m=t}},showYAxis:{get:function(){return y},set:function(t){y=t}},defaultState:{get:function(){return M},set:function(t){M=t}},noData:{get:function(){return A},set:function(t){A=t}},focusEnable:{get:function(){return k},set:function(t){k=t}},focusHeight:{get:function(){return u.height()},set:function(t){u.height(t)}},focusShowAxisX:{get:function(){return u.showXAxis()},set:function(t){u.showXAxis(t)}},focusShowAxisY:{get:function(){return u.showYAxis()},set:function(t){u.showYAxis(t)}},brushExtent:{get:function(){return u.brushExtent()},set:function(t){u.brushExtent(t)}},focusMargin:{get:function(){return u.margin},set:function(t){void 0!==t.top&&(c.top=t.top,d=t.top),u.margin.right=void 0!==t.right?t.right:u.margin.right,u.margin.bottom=void 0!==t.bottom?t.bottom:u.margin.bottom,u.margin.left=void 0!==t.left?t.left:u.margin.left}},margin:{get:function(){return c},set:function(t){c.top=void 0!==t.top?t.top:c.top,c.right=void 0!==t.right?t.right:c.right,c.bottom=void 0!==t.bottom?t.bottom:c.bottom,c.left=void 0!==t.left?t.left:c.left}},duration:{get:function(){return S},set:function(t){S=t,W.reset(S),r.duration(S),u.duration(S),i.duration(S),o.duration(S)}},color:{get:function(){return f},set:function(e){f=t.utils.getColor(e),a.color(f),r.color(f),u.color(f)}},interpolate:{get:function(){return r.interpolate()},set:function(t){r.interpolate(t),u.interpolate(t)}},xTickFormat:{get:function(){return i.tickFormat()},set:function(t){i.tickFormat(t),u.xTickFormat(t)}},yTickFormat:{get:function(){return o.tickFormat()},set:function(t){o.tickFormat(t),u.yTickFormat(t)}},x:{get:function(){return r.x()},set:function(t){r.x(t),u.x(t)}},y:{get:function(){return r.y()},set:function(t){r.y(t),u.y(t)}},rightAlignYAxis:{get:function(){return x},set:function(t){x=t,o.orient(x?"right":"left")}},useInteractiveGuideline:{get:function(){return b},set:function(t){(b=t)&&(r.interactive(!1),r.useVoronoi(!1))}}}),t.utils.inheritOptions(N,r),t.utils.initOptions(N),N},t.models.lineWithFocusChart=function(){return t.models.lineChart().margin({bottom:30}).focusEnable(!0)},t.models.linePlusBarChart=function(){"use strict";var e,n,r,i,o,a,s,l=t.models.line(),u=t.models.line(),c=t.models.historicalBar(),d=t.models.historicalBar(),f=t.models.axis(),p=t.models.axis(),h=t.models.axis(),g=t.models.axis(),v=t.models.axis(),m=t.models.axis(),y=t.models.legend(),x=d3.svg.brush(),b=t.models.tooltip(),k={top:30,right:30,bottom:30,left:60},w=null,M={top:0,right:30,bottom:20,left:60},A=null,C=null,S=function(t){return t.x},W=function(t){return t.y},E=t.utils.defaultColor(),F=!0,N=!0,D=!1,O=!0,_=50,L=null,B=null,P=d3.dispatch("brush","stateChange","changeState"),T=0,I=t.utils.state(),z=null,V=" (left axis)",H=" (right axis)",Y=!1;l.clipEdge(!0),u.interactive(!1),u.pointActive(function(t){return!1}),f.orient("bottom").tickPadding(5),h.orient("left"),g.orient("right"),p.orient("bottom").tickPadding(5),v.orient("left"),m.orient("right"),b.headerEnabled(!0).headerFormatter(function(t,e){return f.tickFormat()(t,e)});var G=function(t){return function(){return{active:t.map(function(t){return!t.disabled})}}},X=function(t){return function(e){void 0!==e.active&&t.forEach(function(t,n){t.disabled=!e.active[n]})}},j=function(t){return t.every(function(t){return t.disabled})};function R(b){return b.each(function(b){var B=d3.select(this);t.utils.initSVG(B);var Z=t.utils.availableWidth(A,B,k),q=t.utils.availableHeight(C,B,k)-(N?_:0),K=_-M.top-M.bottom;if(R.update=function(){B.transition().duration(T).call(R)},R.container=this,I.setter(X(b),R.update).getter(G(b)).update(),I.disabled=b.map(function(t){return!!t.disabled}),!z){var U;z={};for(U in I)I[U]instanceof Array?z[U]=I[U].slice(0):z[U]=I[U]}if(!(b&&b.length&&b.filter(function(t){return t.values.length}).length))return t.utils.noData(R,B),R;B.selectAll(".nv-noData").remove();var Q=b.filter(function(t){return!t.disabled&&t.bar}),J=b.filter(function(t){return!t.bar});n=Q.length&&!Y?c.xScale():l.xScale(),r=p.scale(),i=Y?l.yScale():c.yScale(),o=Y?c.yScale():l.yScale(),a=Y?u.yScale():d.yScale(),s=Y?d.yScale():u.yScale();var $=b.filter(function(t){return!t.disabled&&(Y?!t.bar:t.bar)}).map(function(t){return t.values.map(function(t,e){return{x:S(t,e),y:W(t,e)}})}),tt=b.filter(function(t){return!t.disabled&&(Y?t.bar:!t.bar)}).map(function(t){return t.values.map(function(t,e){return{x:S(t,e),y:W(t,e)}})});n.range([0,Z]),r.domain(d3.extent(d3.merge($.concat(tt)),function(t){return t.x})).range([0,Z]);var et=B.selectAll("g.nv-wrap.nv-linePlusBar").data([b]),nt=et.enter().append("g").attr("class","nvd3 nv-wrap nv-linePlusBar").append("g"),rt=et.select("g");nt.append("g").attr("class","nv-legendWrap");var it=nt.append("g").attr("class","nv-focus");it.append("g").attr("class","nv-x nv-axis"),it.append("g").attr("class","nv-y1 nv-axis"),it.append("g").attr("class","nv-y2 nv-axis"),it.append("g").attr("class","nv-barsWrap"),it.append("g").attr("class","nv-linesWrap");var ot=nt.append("g").attr("class","nv-context");if(ot.append("g").attr("class","nv-x nv-axis"),ot.append("g").attr("class","nv-y1 nv-axis"),ot.append("g").attr("class","nv-y2 nv-axis"),ot.append("g").attr("class","nv-barsWrap"),ot.append("g").attr("class","nv-linesWrap"),ot.append("g").attr("class","nv-brushBackground"),ot.append("g").attr("class","nv-x nv-brush"),F){var at=y.align()?Z/2:Z,st=y.align()?at:0;y.width(at),rt.select(".nv-legendWrap").datum(b.map(function(t){return t.originalKey=void 0===t.originalKey?t.key:t.originalKey,t.key=Y?t.originalKey+(t.bar?H:V):t.originalKey+(t.bar?V:H),t})).call(y),w||y.height()===k.top||(k.top=y.height(),q=t.utils.availableHeight(C,B,k)-_),rt.select(".nv-legendWrap").attr("transform","translate("+st+","+-k.top+")")}else rt.select(".nv-legendWrap").selectAll("*").remove();et.attr("transform","translate("+k.left+","+k.top+")"),rt.select(".nv-context").style("display",N?"initial":"none"),d.width(Z).height(K).color(b.map(function(t,e){return t.color||E(t,e)}).filter(function(t,e){return!b[e].disabled&&b[e].bar})),u.width(Z).height(K).color(b.map(function(t,e){return t.color||E(t,e)}).filter(function(t,e){return!b[e].disabled&&!b[e].bar}));var lt=rt.select(".nv-context .nv-barsWrap").datum(Q.length?Q:[{values:[]}]),ut=rt.select(".nv-context .nv-linesWrap").datum(j(J)?[{values:[]}]:J.filter(function(t){return!t.disabled}));rt.select(".nv-context").attr("transform","translate(0,"+(q+k.bottom+M.top)+")"),lt.transition().call(d),ut.transition().call(u),O&&(p._ticks(t.utils.calcTicksX(Z/100,b)).tickSize(-K,0),rt.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+a.range()[0]+")"),rt.select(".nv-context .nv-x.nv-axis").transition().call(p)),D&&(v.scale(a)._ticks(K/36).tickSize(-Z,0),m.scale(s)._ticks(K/36).tickSize(Q.length?0:-Z,0),rt.select(".nv-context .nv-y3.nv-axis").style("opacity",Q.length?1:0).attr("transform","translate(0,"+r.range()[0]+")"),rt.select(".nv-context .nv-y2.nv-axis").style("opacity",J.length?1:0).attr("transform","translate("+r.range()[1]+",0)"),rt.select(".nv-context .nv-y1.nv-axis").transition().call(v),rt.select(".nv-context .nv-y2.nv-axis").transition().call(m)),x.x(r).on("brush",pt),L&&x.extent(L);var ct=rt.select(".nv-brushBackground").selectAll("g").data([L||x.extent()]),dt=ct.enter().append("g");dt.append("rect").attr("class","left").attr("x",0).attr("y",0).attr("height",K),dt.append("rect").attr("class","right").attr("x",0).attr("y",0).attr("height",K);var ft=rt.select(".nv-x.nv-brush").call(x);function pt(){L=x.empty()?null:x.extent(),e=x.empty()?r.domain():x.extent(),P.brush({extent:e,brush:x}),x.empty()||x.extent(L),ct.data([x.empty()?r.domain():L]).each(function(t,e){var n=r(t[0])-r.range()[0],i=r.range()[1]-r(t[1]);d3.select(this).select(".left").attr("width",n<0?0:n),d3.select(this).select(".right").attr("x",r(t[1])).attr("width",i<0?0:i)}),c.width(Z).height(q).color(b.map(function(t,e){return t.color||E(t,e)}).filter(function(t,e){return!b[e].disabled&&b[e].bar})),l.width(Z).height(q).color(b.map(function(t,e){return t.color||E(t,e)}).filter(function(t,e){return!b[e].disabled&&!b[e].bar}));var a=rt.select(".nv-focus .nv-barsWrap").datum(Q.length?Q.map(function(t,n){return{key:t.key,values:t.values.filter(function(t,n){return c.x()(t,n)>=e[0]&&c.x()(t,n)<=e[1]})}}):[{values:[]}]),s=rt.select(".nv-focus .nv-linesWrap").datum(j(J)?[{values:[]}]:J.filter(function(t){return!t.disabled}).map(function(t,n){return{area:t.area,fillOpacity:t.fillOpacity,strokeWidth:t.strokeWidth,key:t.key,values:t.values.filter(function(t,n){return l.x()(t,n)>=e[0]&&l.x()(t,n)<=e[1]})}}));n=Q.length&&!Y?c.xScale():l.xScale(),f.scale(n)._ticks(t.utils.calcTicksX(Z/100,b)).tickSize(-q,0),f.domain([Math.ceil(e[0]),Math.floor(e[1])]),rt.select(".nv-x.nv-axis").transition().duration(T).call(f),a.transition().duration(T).call(c),s.transition().duration(T).call(l),rt.select(".nv-focus .nv-x.nv-axis").attr("transform","translate(0,"+i.range()[0]+")"),h.scale(i)._ticks(t.utils.calcTicksY(q/36,b)).tickSize(-Z,0),g.scale(o)._ticks(t.utils.calcTicksY(q/36,b)),Y?g.tickSize(J.length?0:-Z,0):g.tickSize(Q.length?0:-Z,0);var u=Q.length?1:0,d=J.length&&!j(J)?1:0,p=Y?d:u,v=Y?u:d;rt.select(".nv-focus .nv-y1.nv-axis").style("opacity",p),rt.select(".nv-focus .nv-y2.nv-axis").style("opacity",v).attr("transform","translate("+n.range()[1]+",0)"),rt.select(".nv-focus .nv-y1.nv-axis").transition().duration(T).call(h),rt.select(".nv-focus .nv-y2.nv-axis").transition().duration(T).call(g)}ft.selectAll("rect").attr("height",K),ft.selectAll(".resize").append("path").attr("d",function(t){var e=+("e"==t),n=e?1:-1,r=K/3;return"M"+.5*n+","+r+"A6,6 0 0 "+e+" "+6.5*n+","+(r+6)+"V"+(2*r-6)+"A6,6 0 0 "+e+" "+.5*n+","+2*r+"ZM"+2.5*n+","+(r+8)+"V"+(2*r-8)+"M"+4.5*n+","+(r+8)+"V"+(2*r-8)}),y.dispatch.on("stateChange",function(t){for(var e in t)I[e]=t[e];P.stateChange(I),R.update()}),P.on("changeState",function(t){void 0!==t.disabled&&(b.forEach(function(e,n){e.disabled=t.disabled[n]}),I.disabled=t.disabled),R.update()}),pt()}),R}return l.dispatch.on("elementMouseover.tooltip",function(t){b.duration(100).valueFormatter(function(t,e){return(Y?{main:h,focus:v}:{main:g,focus:m}).main.tickFormat()(t,e)}).data(t).hidden(!1)}),l.dispatch.on("elementMouseout.tooltip",function(t){b.hidden(!0)}),c.dispatch.on("elementMouseover.tooltip",function(t){t.value=R.x()(t.data),t.series={value:R.y()(t.data),color:t.color},b.duration(0).valueFormatter(function(t,e){return(Y?{main:g,focus:m}:{main:h,focus:v}).main.tickFormat()(t,e)}).data(t).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(t){b.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(t){b()}),R.dispatch=P,R.legend=y,R.lines=l,R.lines2=u,R.bars=c,R.bars2=d,R.xAxis=f,R.x2Axis=p,R.y1Axis=h,R.y2Axis=g,R.y3Axis=v,R.y4Axis=m,R.tooltip=b,R.options=t.utils.optionsFunc.bind(R),R._options=Object.create({},{width:{get:function(){return A},set:function(t){A=t}},height:{get:function(){return C},set:function(t){C=t}},showLegend:{get:function(){return F},set:function(t){F=t}},brushExtent:{get:function(){return L},set:function(t){L=t}},noData:{get:function(){return B},set:function(t){B=t}},focusEnable:{get:function(){return N},set:function(t){N=t}},focusHeight:{get:function(){return _},set:function(t){_=t}},focusShowAxisX:{get:function(){return O},set:function(t){O=t}},focusShowAxisY:{get:function(){return D},set:function(t){D=t}},legendLeftAxisHint:{get:function(){return V},set:function(t){V=t}},legendRightAxisHint:{get:function(){return H},set:function(t){H=t}},margin:{get:function(){return k},set:function(t){void 0!==t.top&&(k.top=t.top,w=t.top),k.right=void 0!==t.right?t.right:k.right,k.bottom=void 0!==t.bottom?t.bottom:k.bottom,k.left=void 0!==t.left?t.left:k.left}},focusMargin:{get:function(){return M},set:function(t){M.top=void 0!==t.top?t.top:M.top,M.right=void 0!==t.right?t.right:M.right,M.bottom=void 0!==t.bottom?t.bottom:M.bottom,M.left=void 0!==t.left?t.left:M.left}},duration:{get:function(){return T},set:function(t){T=t}},color:{get:function(){return E},set:function(e){E=t.utils.getColor(e),y.color(E)}},x:{get:function(){return S},set:function(t){S=t,l.x(t),u.x(t),c.x(t),d.x(t)}},y:{get:function(){return W},set:function(t){W=t,l.y(t),u.y(t),c.y(t),d.y(t)}},switchYAxisOrder:{get:function(){return Y},set:function(t){if(Y!==t){var e=h;h=g,g=e;var n=v;v=m,m=n}Y=t,h.orient("left"),g.orient("right"),v.orient("left"),m.orient("right")}}}),t.utils.inheritOptions(R,l),t.utils.initOptions(R),R},t.models.multiBar=function(){"use strict";var e,n,r,i,o,a,s,l={top:0,right:0,bottom:0,left:0},u=960,c=500,d=d3.scale.ordinal(),f=d3.scale.linear(),p=Math.floor(1e4*Math.random()),h=null,g=function(t){return t.x},v=function(t){return t.y},m=[0],y=!0,x=!1,b="zero",k=t.utils.defaultColor(),w=!1,M=null,A=500,C=.1,S=.75,W=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),E=t.utils.renderWatch(W,A),F=0;function N(D){return E.reset(),D.each(function(N){var D=u-l.left-l.right,O=c-l.top-l.bottom;h=d3.select(this),t.utils.initSVG(h);var _=0;if(w&&N.length&&(w=[{values:N[0].values.map(function(t){return{x:t.x,y:0,series:t.series,size:.01}})}]),x){var L=d3.layout.stack().offset(b).values(function(t){return t.values}).y(v)(!N.length&&w?w:N);L.forEach(function(t,e){t.nonStackable?(N[e].nonStackableSeries=_++,L[e]=N[e]):e>0&&L[e-1].nonStackable&&L[e].values.map(function(t,n){t.y0-=L[e-1].values[n].y,t.y1=t.y0+t.y})}),N=L}N.forEach(function(t,e){t.values.forEach(function(n){n.series=e,n.key=t.key})}),x&&N.length>0&&N[0].values.map(function(t,e){var n=0,r=0;N.map(function(t,i){if(!N[i].nonStackable){var o=t.values[e];o.size=Math.abs(o.y),o.y<0?(o.y1=r,r-=o.size):(o.y1=o.size+n,n+=o.size)}})});var B=n&&r?[]:N.map(function(t,e){return t.values.map(function(t,n){return{x:g(t,n),y:v(t,n),y0:t.y0,y1:t.y1,idx:e}})});d.domain(n||d3.merge(B).map(function(t){return t.x})).rangeBands(i||[0,D],C),f.domain(r||d3.extent(d3.merge(B).map(function(t){var e=t.y;return x&&!N[t.idx].nonStackable&&(e=t.y>0?t.y1:t.y1+t.y),e}).concat(m))).range(o||[O,0]),d.domain()[0]===d.domain()[1]&&(d.domain()[0]?d.domain([d.domain()[0]-.01*d.domain()[0],d.domain()[1]+.01*d.domain()[1]]):d.domain([-1,1])),f.domain()[0]===f.domain()[1]&&(f.domain()[0]?f.domain([f.domain()[0]+.01*f.domain()[0],f.domain()[1]-.01*f.domain()[1]]):f.domain([-1,1])),a=a||d,s=s||f;var P=h.selectAll("g.nv-wrap.nv-multibar").data([N]),T=P.enter().append("g").attr("class","nvd3 nv-wrap nv-multibar"),I=T.append("defs"),z=T.append("g"),V=P.select("g");z.append("g").attr("class","nv-groups"),P.attr("transform","translate("+l.left+","+l.top+")"),I.append("clipPath").attr("id","nv-edge-clip-"+p).append("rect"),P.select("#nv-edge-clip-"+p+" rect").attr("width",D).attr("height",O),V.attr("clip-path",y?"url(#nv-edge-clip-"+p+")":"");var H=P.select(".nv-groups").selectAll(".nv-group").data(function(t){return t},function(t,e){return e});H.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6);var Y=E.transition(H.exit().selectAll("rect.nv-bar"),"multibarExit",Math.min(100,A)).attr("y",function(t,e,n){var r=s(0)||0;return x&&N[t.series]&&!N[t.series].nonStackable&&(r=s(t.y0)),r}).attr("height",0).remove();Y.delay&&Y.delay(function(t,e){return e*(A/(F+1))-e}),H.attr("class",function(t,e){return"nv-group nv-series-"+e}).classed("hover",function(t){return t.hover}).style("fill",function(t,e){return k(t,e)}).style("stroke",function(t,e){return k(t,e)}),H.style("stroke-opacity",1).style("fill-opacity",S);var G=H.selectAll("rect.nv-bar").data(function(t){return w&&!N.length?w.values:t.values});G.exit().remove();G.enter().append("rect").attr("class",function(t,e){return v(t,e)<0?"nv-bar negative":"nv-bar positive"}).attr("x",function(t,e,n){return x&&!N[n].nonStackable?0:n*d.rangeBand()/N.length}).attr("y",function(t,e,n){return s(x&&!N[n].nonStackable?t.y0:0)||0}).attr("height",0).attr("width",function(t,e,n){return d.rangeBand()/(x&&!N[n].nonStackable?1:N.length)}).attr("transform",function(t,e){return"translate("+d(g(t,e))+",0)"});G.style("fill",function(t,e,n){return k(t,n,e)}).style("stroke",function(t,e,n){return k(t,n,e)}).on("mouseover",function(t,e,n){d3.select(this).classed("hover",!0),W.elementMouseover({data:t,index:e,series:N[n],color:d3.select(this).style("fill")})}).on("mouseout",function(t,e,n){d3.select(this).classed("hover",!1),W.elementMouseout({data:t,index:e,series:N[n],color:d3.select(this).style("fill")})}).on("mousemove",function(t,e,n){W.elementMousemove({data:t,index:e,series:N[n],color:d3.select(this).style("fill")})}).on("click",function(t,e,n){W.elementClick({data:t,index:e,series:N[n],color:d3.select(this).style("fill"),event:d3.event,element:this}),d3.event.stopPropagation()}).on("dblclick",function(t,e,n){W.elementDblClick({data:t,index:e,series:N[n],color:d3.select(this).style("fill")}),d3.event.stopPropagation()}),G.attr("class",function(t,e){return v(t,e)<0?"nv-bar negative":"nv-bar positive"}).attr("transform",function(t,e){return"translate("+d(g(t,e))+",0)"}),M&&(e||(e=N.map(function(){return!0})),G.style("fill",function(t,n,r){return d3.rgb(M(t,n)).darker(e.map(function(t,e){return e}).filter(function(t,n){return!e[n]})[r]).toString()}).style("stroke",function(t,n,r){return d3.rgb(M(t,n)).darker(e.map(function(t,e){return e}).filter(function(t,n){return!e[n]})[r]).toString()}));var X=G.watchTransition(E,"multibar",Math.min(250,A)).delay(function(t,e){return e*A/N[0].values.length});x?X.attr("y",function(t,e,n){return N[n].nonStackable?v(t,e)<0?f(0):f(0)-f(v(t,e))<-1?f(0)-1:f(v(t,e))||0:f(t.y1)}).attr("height",function(t,e,n){return N[n].nonStackable?Math.max(Math.abs(f(v(t,e))-f(0)),0)||0:Math.max(Math.abs(f(t.y+t.y0)-f(t.y0)),0)}).attr("x",function(t,e,n){var r=0;return N[n].nonStackable&&(r=t.series*d.rangeBand()/N.length,N.length!==_&&(r=N[n].nonStackableSeries*d.rangeBand()/(2*_))),r}).attr("width",function(t,e,n){if(N[n].nonStackable){var r=d.rangeBand()/_;return N.length!==_&&(r=d.rangeBand()/(2*_)),r}return d.rangeBand()}):X.attr("x",function(t,e){return t.series*d.rangeBand()/N.length}).attr("width",d.rangeBand()/N.length).attr("y",function(t,e){return v(t,e)<0?f(0):f(0)-f(v(t,e))<1?f(0)-1:f(v(t,e))||0}).attr("height",function(t,e){return Math.max(Math.abs(f(v(t,e))-f(0)),1)||0}),a=d.copy(),s=f.copy(),N[0]&&N[0].values&&(F=N[0].values.length)}),E.renderEnd("multibar immediate"),N}return N.dispatch=W,N.options=t.utils.optionsFunc.bind(N),N._options=Object.create({},{width:{get:function(){return u},set:function(t){u=t}},height:{get:function(){return c},set:function(t){c=t}},x:{get:function(){return g},set:function(t){g=t}},y:{get:function(){return v},set:function(t){v=t}},xScale:{get:function(){return d},set:function(t){d=t}},yScale:{get:function(){return f},set:function(t){f=t}},xDomain:{get:function(){return n},set:function(t){n=t}},yDomain:{get:function(){return r},set:function(t){r=t}},xRange:{get:function(){return i},set:function(t){i=t}},yRange:{get:function(){return o},set:function(t){o=t}},forceY:{get:function(){return m},set:function(t){m=t}},stacked:{get:function(){return x},set:function(t){x=t}},stackOffset:{get:function(){return b},set:function(t){b=t}},clipEdge:{get:function(){return y},set:function(t){y=t}},disabled:{get:function(){return e},set:function(t){e=t}},id:{get:function(){return p},set:function(t){p=t}},hideable:{get:function(){return w},set:function(t){w=t}},groupSpacing:{get:function(){return C},set:function(t){C=t}},fillOpacity:{get:function(){return S},set:function(t){S=t}},margin:{get:function(){return l},set:function(t){l.top=void 0!==t.top?t.top:l.top,l.right=void 0!==t.right?t.right:l.right,l.bottom=void 0!==t.bottom?t.bottom:l.bottom,l.left=void 0!==t.left?t.left:l.left}},duration:{get:function(){return A},set:function(t){A=t,E.reset(A)}},color:{get:function(){return k},set:function(e){k=t.utils.getColor(e)}},barColor:{get:function(){return M},set:function(e){M=e?t.utils.getColor(e):null}}}),t.utils.initOptions(N),N},t.models.multiBarChart=function(){"use strict";var e,n,r=t.models.multiBar(),i=t.models.axis(),o=t.models.axis(),a=t.interactiveGuideline(),s=t.models.legend(),l=t.models.legend(),u=t.models.tooltip(),c={top:30,right:20,bottom:50,left:60},d=null,f=null,p=null,h=t.utils.defaultColor(),g=!0,v={},m=!0,y=null,x=!0,b=!0,k=!1,w=!0,M=!1,A=!1,C=0,S=t.utils.state(),W=null,E=null,F=d3.dispatch("stateChange","changeState","renderEnd"),N=function(){return g?180:0},D=250,O=!1;S.stacked=!1,r.stacked(!1),i.orient("bottom").tickPadding(7).showMaxMin(!1).tickFormat(function(t){return t}),o.orient(k?"right":"left").tickFormat(d3.format(",.1f")),u.duration(0).valueFormatter(function(t,e){return o.tickFormat()(t,e)}).headerFormatter(function(t,e){return i.tickFormat()(t,e)}),a.tooltip.valueFormatter(function(t,e){return null==t?"N/A":o.tickFormat()(t,e)}).headerFormatter(function(t,e){return i.tickFormat()(t,e)}),a.tooltip.valueFormatter(function(t,e){return null==t?"N/A":o.tickFormat()(t,e)}).headerFormatter(function(t,e){return i.tickFormat()(t,e)}),a.tooltip.duration(0).valueFormatter(function(t,e){return o.tickFormat()(t,e)}).headerFormatter(function(t,e){return i.tickFormat()(t,e)}),l.updateState(!1);var _=t.utils.renderWatch(F),L=!1,B=function(t){return function(){return{active:t.map(function(t){return!t.disabled}),stacked:L}}},P=function(t){return function(e){void 0!==e.stacked&&(L=e.stacked),void 0!==e.active&&t.forEach(function(t,n){t.disabled=!e.active[n]})}};function T(E){return _.reset(),_.models(r),x&&_.models(i),b&&_.models(o),E.each(function(E){var _=d3.select(this);t.utils.initSVG(_);var I=t.utils.availableWidth(f,_,c),z=t.utils.availableHeight(p,_,c);if(T.update=function(){0===D?_.call(T):_.transition().duration(D).call(T)},T.container=this,S.setter(P(E),T.update).getter(B(E)).update(),S.disabled=E.map(function(t){return!!t.disabled}),!W){var V;W={};for(V in S)S[V]instanceof Array?W[V]=S[V].slice(0):W[V]=S[V]}if(!(E&&E.length&&E.filter(function(t){return t.values.length}).length))return t.utils.noData(T,_),T;_.selectAll(".nv-noData").remove(),e=r.xScale(),n=r.yScale();var H=_.selectAll("g.nv-wrap.nv-multiBarWithLegend").data([E]),Y=H.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarWithLegend").append("g"),G=H.select("g");if(Y.append("g").attr("class","nv-x nv-axis"),Y.append("g").attr("class","nv-y nv-axis"),Y.append("g").attr("class","nv-barsWrap"),Y.append("g").attr("class","nv-legendWrap"),Y.append("g").attr("class","nv-controlsWrap"),Y.append("g").attr("class","nv-interactive"),m?"bottom"===y?(s.width(I-c.right),G.select(".nv-legendWrap").datum(E).call(s),c.bottom=i.height()+s.height(),z=t.utils.availableHeight(p,_,c),G.select(".nv-legendWrap").attr("transform","translate(0,"+(z+i.height())+")")):(s.width(I-N()),G.select(".nv-legendWrap").datum(E).call(s),d||s.height()===c.top||(c.top=s.height(),z=t.utils.availableHeight(p,_,c)),G.select(".nv-legendWrap").attr("transform","translate("+N()+","+-c.top+")")):G.select(".nv-legendWrap").selectAll("*").remove(),g){var X=[{key:v.grouped||"Grouped",disabled:r.stacked()},{key:v.stacked||"Stacked",disabled:!r.stacked()}];l.width(N()).color(["#444","#444","#444"]),G.select(".nv-controlsWrap").datum(X).attr("transform","translate(0,"+-c.top+")").call(l)}else G.select(".nv-controlsWrap").selectAll("*").remove();if(H.attr("transform","translate("+c.left+","+c.top+")"),k&&G.select(".nv-y.nv-axis").attr("transform","translate("+I+",0)"),r.disabled(E.map(function(t){return t.disabled})).width(I).height(z).color(E.map(function(t,e){return t.color||h(t,e)}).filter(function(t,e){return!E[e].disabled})),G.select(".nv-barsWrap").datum(E.filter(function(t){return!t.disabled})).call(r),x){i.scale(e)._ticks(t.utils.calcTicksX(I/100,E)).tickSize(-z,0),G.select(".nv-x.nv-axis").attr("transform","translate(0,"+n.range()[0]+")"),G.select(".nv-x.nv-axis").call(i);var j=G.select(".nv-x.nv-axis > g").selectAll("g");if(j.selectAll("line, text").style("opacity",1),M){var R=function(t,e){return"translate("+t+","+e+")"};j.selectAll("text").attr("transform",function(t,e,n){return R(0,n%2==0?5:17)});var Z=d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;G.selectAll(".nv-x.nv-axis .nv-axisMaxMin text").attr("transform",function(t,e){return R(0,0===e||Z%2!=0?17:5)})}A&&G.selectAll(".tick text").call(t.utils.wrapTicks,T.xAxis.rangeBand()),w&&j.filter(function(t,e){return e%Math.ceil(E[0].values.length/(I/100))!=0}).selectAll("text, line").style("opacity",0),C&&j.selectAll(".tick text").attr("transform","rotate("+C+" 0,0)").style("text-anchor",C>0?"start":"end"),G.select(".nv-x.nv-axis").selectAll("g.nv-axisMaxMin text").style("opacity",1)}b&&(o.scale(n)._ticks(t.utils.calcTicksY(z/36,E)).tickSize(-I,0),G.select(".nv-y.nv-axis").call(o)),O&&(a.width(I).height(z).margin({left:c.left,top:c.top}).svgContainer(_).xScale(e),H.select(".nv-interactive").call(a)),s.dispatch.on("stateChange",function(t){for(var e in t)S[e]=t[e];F.stateChange(S),T.update()}),l.dispatch.on("legendClick",function(t,e){if(t.disabled){switch(X=X.map(function(t){return t.disabled=!0,t}),t.disabled=!1,t.key){case"Grouped":case v.grouped:r.stacked(!1);break;case"Stacked":case v.stacked:r.stacked(!0)}S.stacked=r.stacked(),F.stateChange(S),T.update()}}),F.on("changeState",function(t){void 0!==t.disabled&&(E.forEach(function(e,n){e.disabled=t.disabled[n]}),S.disabled=t.disabled),void 0!==t.stacked&&(r.stacked(t.stacked),S.stacked=t.stacked,L=t.stacked),T.update()}),O?(a.dispatch.on("elementMousemove",function(t){if(void 0!=t.pointXValue){var n,r,i,o,s=[];E.filter(function(t,e){return t.seriesIndex=e,!t.disabled}).forEach(function(a,l){r=e.domain().indexOf(t.pointXValue);var u=a.values[r];void 0!==u&&(o=u.x,void 0===n&&(n=u),void 0===i&&(i=t.mouseX),s.push({key:a.key,value:T.y()(u,r),color:h(a,a.seriesIndex),data:a.values[r]}))}),a.tooltip.data({value:o,index:r,series:s})(),a.renderGuideLine(i)}}),a.dispatch.on("elementMouseout",function(t){a.tooltip.hidden(!0)})):(r.dispatch.on("elementMouseover.tooltip",function(t){t.value=T.x()(t.data),t.series={key:t.data.key,value:T.y()(t.data),color:t.color},u.data(t).hidden(!1)}),r.dispatch.on("elementMouseout.tooltip",function(t){u.hidden(!0)}),r.dispatch.on("elementMousemove.tooltip",function(t){u()}))}),_.renderEnd("multibarchart immediate"),T}return T.dispatch=F,T.multibar=r,T.legend=s,T.controls=l,T.xAxis=i,T.yAxis=o,T.state=S,T.tooltip=u,T.interactiveLayer=a,T.options=t.utils.optionsFunc.bind(T),T._options=Object.create({},{width:{get:function(){return f},set:function(t){f=t}},height:{get:function(){return p},set:function(t){p=t}},showLegend:{get:function(){return m},set:function(t){m=t}},legendPosition:{get:function(){return y},set:function(t){y=t}},showControls:{get:function(){return g},set:function(t){g=t}},controlLabels:{get:function(){return v},set:function(t){v=t}},showXAxis:{get:function(){return x},set:function(t){x=t}},showYAxis:{get:function(){return b},set:function(t){b=t}},defaultState:{get:function(){return W},set:function(t){W=t}},noData:{get:function(){return E},set:function(t){E=t}},reduceXTicks:{get:function(){return w},set:function(t){w=t}},rotateLabels:{get:function(){return C},set:function(t){C=t}},staggerLabels:{get:function(){return M},set:function(t){M=t}},wrapLabels:{get:function(){return A},set:function(t){A=!!t}},margin:{get:function(){return c},set:function(t){void 0!==t.top&&(c.top=t.top,d=t.top),c.right=void 0!==t.right?t.right:c.right,c.bottom=void 0!==t.bottom?t.bottom:c.bottom,c.left=void 0!==t.left?t.left:c.left}},duration:{get:function(){return D},set:function(t){D=t,r.duration(D),i.duration(D),o.duration(D),_.reset(D)}},color:{get:function(){return h},set:function(e){h=t.utils.getColor(e),s.color(h)}},rightAlignYAxis:{get:function(){return k},set:function(t){k=t,o.orient(k?"right":"left")}},useInteractiveGuideline:{get:function(){return O},set:function(t){O=t}},barColor:{get:function(){return r.barColor},set:function(t){r.barColor(t),s.color(function(t,e){return d3.rgb("#ccc").darker(1.5*e).toString()})}}}),t.utils.inheritOptions(T,r),t.utils.initOptions(T),T},t.models.multiBarHorizontal=function(){"use strict";var e,n,r,i,o,a,s,l={top:0,right:0,bottom:0,left:0},u=960,c=500,d=Math.floor(1e4*Math.random()),f=null,p=d3.scale.ordinal(),h=d3.scale.linear(),g=function(t){return t.x},v=function(t){return t.y},m=function(t){return t.yErr},y=[0],x=t.utils.defaultColor(),b=null,k=!1,w=!1,M=!1,A=60,C=.1,S=.75,W=d3.format(",.2f"),E=250,F=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),N=t.utils.renderWatch(F,E);function D(d){return N.reset(),d.each(function(d){var E=u-l.left-l.right,D=c-l.top-l.bottom;f=d3.select(this),t.utils.initSVG(f),k&&(d=d3.layout.stack().offset("zero").values(function(t){return t.values}).y(v)(d)),d.forEach(function(t,e){t.values.forEach(function(n){n.series=e,n.key=t.key})}),k&&d[0].values.map(function(t,e){var n=0,r=0;d.map(function(t){var i=t.values[e];i.size=Math.abs(i.y),i.y<0?(i.y1=r-i.size,r-=i.size):(i.y1=n,n+=i.size)})});var O=n&&r?[]:d.map(function(t){return t.values.map(function(t,e){return{x:g(t,e),y:v(t,e),y0:t.y0,y1:t.y1}})});p.domain(n||d3.merge(O).map(function(t){return t.x})).rangeBands(i||[0,D],C),h.domain(r||d3.extent(d3.merge(O).map(function(t){return k?t.y>0?t.y1+t.y:t.y1:t.y}).concat(y))),w&&!k?h.range(o||[h.domain()[0]<0?A:0,E-(h.domain()[1]>0?A:0)]):h.range(o||[0,E]),a=a||p,s=s||d3.scale.linear().domain(h.domain()).range([h(0),h(0)]);var _=d3.select(this).selectAll("g.nv-wrap.nv-multibarHorizontal").data([d]),L=_.enter().append("g").attr("class","nvd3 nv-wrap nv-multibarHorizontal"),B=(L.append("defs"),L.append("g"));_.select("g");B.append("g").attr("class","nv-groups"),_.attr("transform","translate("+l.left+","+l.top+")");var P=_.select(".nv-groups").selectAll(".nv-group").data(function(t){return t},function(t,e){return e});P.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),P.exit().watchTransition(N,"multibarhorizontal: exit groups").style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),P.attr("class",function(t,e){return"nv-group nv-series-"+e}).classed("hover",function(t){return t.hover}).style("fill",function(t,e){return x(t,e)}).style("stroke",function(t,e){return x(t,e)}),P.watchTransition(N,"multibarhorizontal: groups").style("stroke-opacity",1).style("fill-opacity",S);var T=P.selectAll("g.nv-bar").data(function(t){return t.values});T.exit().remove();var I=T.enter().append("g").attr("transform",function(t,e,n){return"translate("+s(k?t.y0:0)+","+(k?0:n*p.rangeBand()/d.length+p(g(t,e)))+")"});I.append("rect").attr("width",0).attr("height",p.rangeBand()/(k?1:d.length)),T.on("mouseover",function(t,e){d3.select(this).classed("hover",!0),F.elementMouseover({data:t,index:e,color:d3.select(this).style("fill")})}).on("mouseout",function(t,e){d3.select(this).classed("hover",!1),F.elementMouseout({data:t,index:e,color:d3.select(this).style("fill")})}).on("mouseout",function(t,e){F.elementMouseout({data:t,index:e,color:d3.select(this).style("fill")})}).on("mousemove",function(t,e){F.elementMousemove({data:t,index:e,color:d3.select(this).style("fill")})}).on("click",function(t,e){F.elementClick({data:t,index:e,color:d3.select(this).style("fill"),event:d3.event,element:this}),d3.event.stopPropagation()}).on("dblclick",function(t,e){F.elementDblClick({data:t,index:e,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}),m(d[0],0)&&(I.append("polyline"),T.select("polyline").attr("fill","none").attr("points",function(t,e){var n=m(t,e),r=.8*p.rangeBand()/(2*(k?1:d.length));return[[(n=(n=n.length?n:[-Math.abs(n),Math.abs(n)]).map(function(n){return h(n+(v(t,e)<0?0:v(t,e)))-h(0)}))[0],-r],[n[0],r],[n[0],0],[n[1],0],[n[1],-r],[n[1],r]].map(function(t){return t.join(",")}).join(" ")}).attr("transform",function(t,e){return"translate(0, "+p.rangeBand()/(2*(k?1:d.length))+")"})),I.append("text"),w&&!k?(T.select("text").attr("text-anchor",function(t,e){return v(t,e)<0?"end":"start"}).attr("y",p.rangeBand()/(2*d.length)).attr("dy",".32em").text(function(t,e){var n=W(v(t,e)),r=m(t,e);return void 0===r?n:r.length?n+"+"+W(Math.abs(r[1]))+"-"+W(Math.abs(r[0])):n+"±"+W(Math.abs(r))}),T.watchTransition(N,"multibarhorizontal: bars").select("text").attr("x",function(t,e){return v(t,e)<0?-4:h(v(t,e))-h(0)+4})):T.selectAll("text").text(""),M&&!k?(I.append("text").classed("nv-bar-label",!0),T.select("text.nv-bar-label").attr("text-anchor",function(t,e){return v(t,e)<0?"start":"end"}).attr("y",p.rangeBand()/(2*d.length)).attr("dy",".32em").text(function(t,e){return g(t,e)}),T.watchTransition(N,"multibarhorizontal: bars").select("text.nv-bar-label").attr("x",function(t,e){return v(t,e)<0?h(0)-h(v(t,e))+4:-4})):T.selectAll("text.nv-bar-label").text(""),T.attr("class",function(t,e){return v(t,e)<0?"nv-bar negative":"nv-bar positive"}),b&&(e||(e=d.map(function(){return!0})),T.style("fill",function(t,n,r){return d3.rgb(b(t,n)).darker(e.map(function(t,e){return e}).filter(function(t,n){return!e[n]})[r]).toString()}).style("stroke",function(t,n,r){return d3.rgb(b(t,n)).darker(e.map(function(t,e){return e}).filter(function(t,n){return!e[n]})[r]).toString()})),k?T.watchTransition(N,"multibarhorizontal: bars").attr("transform",function(t,e){return"translate("+h(t.y1)+","+p(g(t,e))+")"}).select("rect").attr("width",function(t,e){return Math.abs(h(v(t,e)+t.y0)-h(t.y0))||0}).attr("height",p.rangeBand()):T.watchTransition(N,"multibarhorizontal: bars").attr("transform",function(t,e){return"translate("+(v(t,e)<0?h(v(t,e)):h(0))+","+(t.series*p.rangeBand()/d.length+p(g(t,e)))+")"}).select("rect").attr("height",p.rangeBand()/d.length).attr("width",function(t,e){return Math.max(Math.abs(h(v(t,e))-h(0)),1)||0}),a=p.copy(),s=h.copy()}),N.renderEnd("multibarHorizontal immediate"),D}return D.dispatch=F,D.options=t.utils.optionsFunc.bind(D),D._options=Object.create({},{width:{get:function(){return u},set:function(t){u=t}},height:{get:function(){return c},set:function(t){c=t}},x:{get:function(){return g},set:function(t){g=t}},y:{get:function(){return v},set:function(t){v=t}},yErr:{get:function(){return m},set:function(t){m=t}},xScale:{get:function(){return p},set:function(t){p=t}},yScale:{get:function(){return h},set:function(t){h=t}},xDomain:{get:function(){return n},set:function(t){n=t}},yDomain:{get:function(){return r},set:function(t){r=t}},xRange:{get:function(){return i},set:function(t){i=t}},yRange:{get:function(){return o},set:function(t){o=t}},forceY:{get:function(){return y},set:function(t){y=t}},stacked:{get:function(){return k},set:function(t){k=t}},showValues:{get:function(){return w},set:function(t){w=t}},disabled:{get:function(){return e},set:function(t){e=t}},id:{get:function(){return d},set:function(t){d=t}},valueFormat:{get:function(){return W},set:function(t){W=t}},valuePadding:{get:function(){return A},set:function(t){A=t}},groupSpacing:{get:function(){return C},set:function(t){C=t}},fillOpacity:{get:function(){return S},set:function(t){S=t}},margin:{get:function(){return l},set:function(t){l.top=void 0!==t.top?t.top:l.top,l.right=void 0!==t.right?t.right:l.right,l.bottom=void 0!==t.bottom?t.bottom:l.bottom,l.left=void 0!==t.left?t.left:l.left}},duration:{get:function(){return E},set:function(t){E=t,N.reset(E)}},color:{get:function(){return x},set:function(e){x=t.utils.getColor(e)}},barColor:{get:function(){return b},set:function(e){b=e?t.utils.getColor(e):null}}}),t.utils.initOptions(D),D},t.models.multiBarHorizontalChart=function(){"use strict";var e,n,r=t.models.multiBarHorizontal(),i=t.models.axis(),o=t.models.axis(),a=t.models.legend().height(30),s=t.models.legend().height(30),l=t.models.tooltip(),u={top:30,right:20,bottom:50,left:60},c=null,d=null,f=null,p=t.utils.defaultColor(),h=!0,g="top",v={},m=!0,y="top",x=!0,b=!0,k=!1,w=t.utils.state(),M=null,A=null,C=d3.dispatch("stateChange","changeState","renderEnd"),S=function(){return h?180:0},W=250;w.stacked=!1,r.stacked(k),i.orient("left").tickPadding(5).showMaxMin(!1).tickFormat(function(t){return t}),o.orient("bottom").tickFormat(d3.format(",.1f")),l.duration(0).valueFormatter(function(t,e){return o.tickFormat()(t,e)}).headerFormatter(function(t,e){return i.tickFormat()(t,e)}),s.updateState(!1);var E=function(t){return function(){return{active:t.map(function(t){return!t.disabled}),stacked:k}}},F=function(t){return function(e){void 0!==e.stacked&&(k=e.stacked),void 0!==e.active&&t.forEach(function(t,n){t.disabled=!e.active[n]})}},N=t.utils.renderWatch(C,W);function D(l){return N.reset(),N.models(r),x&&N.models(i),b&&N.models(o),l.each(function(l){var A=d3.select(this);t.utils.initSVG(A);var N=t.utils.availableWidth(d,A,u),O=t.utils.availableHeight(f,A,u);if(D.update=function(){A.transition().duration(W).call(D)},D.container=this,k=r.stacked(),w.setter(F(l),D.update).getter(E(l)).update(),w.disabled=l.map(function(t){return!!t.disabled}),!M){var _;M={};for(_ in w)w[_]instanceof Array?M[_]=w[_].slice(0):M[_]=w[_]}if(!(l&&l.length&&l.filter(function(t){return t.values.length}).length))return t.utils.noData(D,A),D;A.selectAll(".nv-noData").remove(),e=r.xScale(),n=r.yScale().clamp(!0);var L=A.selectAll("g.nv-wrap.nv-multiBarHorizontalChart").data([l]),B=L.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarHorizontalChart").append("g"),P=L.select("g");if(B.append("g").attr("class","nv-x nv-axis"),B.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),B.append("g").attr("class","nv-barsWrap"),B.append("g").attr("class","nv-legendWrap"),B.append("g").attr("class","nv-controlsWrap"),m?(a.width(N-S()),P.select(".nv-legendWrap").datum(l).call(a),"bottom"===y?(u.bottom=i.height()+a.height(),O=t.utils.availableHeight(f,A,u),P.select(".nv-legendWrap").attr("transform","translate("+S()+","+(O+i.height())+")")):"top"===y&&(c||a.height()===u.top||(u.top=a.height(),O=t.utils.availableHeight(f,A,u)),P.select(".nv-legendWrap").attr("transform","translate("+S()+","+-u.top+")"))):P.select(".nv-legendWrap").selectAll("*").remove(),h){var T=[{key:v.grouped||"Grouped",disabled:r.stacked()},{key:v.stacked||"Stacked",disabled:!r.stacked()}];s.width(S()).color(["#444","#444","#444"]),"bottom"===g?(u.bottom=i.height()+a.height(),O=t.utils.availableHeight(f,A,u),P.select(".nv-controlsWrap").datum(T).attr("transform","translate(0,"+(O+i.height())+")").call(s)):"top"===g&&P.select(".nv-controlsWrap").datum(T).attr("transform","translate(0,"+-u.top+")").call(s)}else P.select(".nv-controlsWrap").selectAll("*").remove();(L.attr("transform","translate("+u.left+","+u.top+")"),r.disabled(l.map(function(t){return t.disabled})).width(N).height(O).color(l.map(function(t,e){return t.color||p(t,e)}).filter(function(t,e){return!l[e].disabled})),P.select(".nv-barsWrap").datum(l.filter(function(t){return!t.disabled})).transition().call(r),x)&&(i.scale(e)._ticks(t.utils.calcTicksY(O/24,l)).tickSize(-N,0),P.select(".nv-x.nv-axis").call(i),P.select(".nv-x.nv-axis").selectAll("g").selectAll("line, text"));b&&(o.scale(n)._ticks(t.utils.calcTicksX(N/100,l)).tickSize(-O,0),P.select(".nv-y.nv-axis").attr("transform","translate(0,"+O+")"),P.select(".nv-y.nv-axis").call(o)),P.select(".nv-zeroLine line").attr("x1",n(0)).attr("x2",n(0)).attr("y1",0).attr("y2",-O),a.dispatch.on("stateChange",function(t){for(var e in t)w[e]=t[e];C.stateChange(w),D.update()}),s.dispatch.on("legendClick",function(t,e){if(t.disabled){switch(T=T.map(function(t){return t.disabled=!0,t}),t.disabled=!1,t.key){case"Grouped":case v.grouped:r.stacked(!1);break;case"Stacked":case v.stacked:r.stacked(!0)}w.stacked=r.stacked(),C.stateChange(w),k=r.stacked(),D.update()}}),C.on("changeState",function(t){void 0!==t.disabled&&(l.forEach(function(e,n){e.disabled=t.disabled[n]}),w.disabled=t.disabled),void 0!==t.stacked&&(r.stacked(t.stacked),w.stacked=t.stacked,k=t.stacked),D.update()})}),N.renderEnd("multibar horizontal chart immediate"),D}return r.dispatch.on("elementMouseover.tooltip",function(t){t.value=D.x()(t.data),t.series={key:t.data.key,value:D.y()(t.data),color:t.color},l.data(t).hidden(!1)}),r.dispatch.on("elementMouseout.tooltip",function(t){l.hidden(!0)}),r.dispatch.on("elementMousemove.tooltip",function(t){l()}),D.dispatch=C,D.multibar=r,D.legend=a,D.controls=s,D.xAxis=i,D.yAxis=o,D.state=w,D.tooltip=l,D.options=t.utils.optionsFunc.bind(D),D._options=Object.create({},{width:{get:function(){return d},set:function(t){d=t}},height:{get:function(){return f},set:function(t){f=t}},showLegend:{get:function(){return m},set:function(t){m=t}},legendPosition:{get:function(){return y},set:function(t){y=t}},controlsPosition:{get:function(){return g},set:function(t){g=t}},showControls:{get:function(){return h},set:function(t){h=t}},controlLabels:{get:function(){return v},set:function(t){v=t}},showXAxis:{get:function(){return x},set:function(t){x=t}},showYAxis:{get:function(){return b},set:function(t){b=t}},defaultState:{get:function(){return M},set:function(t){M=t}},noData:{get:function(){return A},set:function(t){A=t}},margin:{get:function(){return u},set:function(t){void 0!==t.top&&(u.top=t.top,c=t.top),u.right=void 0!==t.right?t.right:u.right,u.bottom=void 0!==t.bottom?t.bottom:u.bottom,u.left=void 0!==t.left?t.left:u.left}},duration:{get:function(){return W},set:function(t){W=t,N.reset(W),r.duration(W),i.duration(W),o.duration(W)}},color:{get:function(){return p},set:function(e){p=t.utils.getColor(e),a.color(p)}},barColor:{get:function(){return r.barColor},set:function(t){r.barColor(t),a.color(function(t,e){return d3.rgb("#ccc").darker(1.5*e).toString()})}}}),t.utils.inheritOptions(D,r),t.utils.initOptions(D),D},t.models.multiChart=function(){"use strict";var e,n,r={top:30,right:20,bottom:50,left:60},i=null,o=t.utils.defaultColor(),a=null,s=null,l=!0,u=null,c=function(t){return t.x},d=function(t){return t.y},f="linear",p=!0,h=t.interactiveGuideline(),g=!1,v=" (right axis)",m=250,y=d3.scale.linear(),x=d3.scale.linear(),b=d3.scale.linear(),k=t.models.line().yScale(x).duration(m),w=t.models.line().yScale(b).duration(m),M=t.models.scatter().yScale(x).duration(m),A=t.models.scatter().yScale(b).duration(m),C=t.models.multiBar().stacked(!1).yScale(x).duration(m),S=t.models.multiBar().stacked(!1).yScale(b).duration(m),W=t.models.stackedArea().yScale(x).duration(m),E=t.models.stackedArea().yScale(b).duration(m),F=t.models.axis().scale(y).orient("bottom").tickPadding(5).duration(m),N=t.models.axis().scale(x).orient("left").duration(m),D=t.models.axis().scale(b).orient("right").duration(m),O=t.models.legend().height(30),_=t.models.tooltip(),L=d3.dispatch(),B=[k,w,M,A,C,S,W,E];function P(u){return u.each(function(u){var p=d3.select(this);t.utils.initSVG(p),P.update=function(){p.transition().call(P)},P.container=this;var m=t.utils.availableWidth(a,p,r),L=t.utils.availableHeight(s,p,r),T=u.filter(function(t){return"line"==t.type&&1==t.yAxis}),I=u.filter(function(t){return"line"==t.type&&2==t.yAxis}),z=u.filter(function(t){return"scatter"==t.type&&1==t.yAxis}),V=u.filter(function(t){return"scatter"==t.type&&2==t.yAxis}),H=u.filter(function(t){return"bar"==t.type&&1==t.yAxis}),Y=u.filter(function(t){return"bar"==t.type&&2==t.yAxis}),G=u.filter(function(t){return"area"==t.type&&1==t.yAxis}),X=u.filter(function(t){return"area"==t.type&&2==t.yAxis});if(!(u&&u.length&&u.filter(function(t){return t.values.length}).length))return t.utils.noData(P,p),P;p.selectAll(".nv-noData").remove();var j=u.filter(function(t){return!t.disabled&&1==t.yAxis}).map(function(t){return t.values.map(function(t,e){return{x:c(t),y:d(t)}})}),R=u.filter(function(t){return!t.disabled&&2==t.yAxis}).map(function(t){return t.values.map(function(t,e){return{x:c(t),y:d(t)}})});y.domain(d3.extent(d3.merge(j.concat(R)),function(t){return t.x})).range([0,m]);var Z=p.selectAll("g.wrap.multiChart").data([u]),q=Z.enter().append("g").attr("class","wrap nvd3 multiChart").append("g");q.append("g").attr("class","nv-x nv-axis"),q.append("g").attr("class","nv-y1 nv-axis"),q.append("g").attr("class","nv-y2 nv-axis"),q.append("g").attr("class","stack1Wrap"),q.append("g").attr("class","stack2Wrap"),q.append("g").attr("class","bars1Wrap"),q.append("g").attr("class","bars2Wrap"),q.append("g").attr("class","scatters1Wrap"),q.append("g").attr("class","scatters2Wrap"),q.append("g").attr("class","lines1Wrap"),q.append("g").attr("class","lines2Wrap"),q.append("g").attr("class","legendWrap"),q.append("g").attr("class","nv-interactive");var K=Z.select("g"),U=u.map(function(t,e){return u[e].color||o(t,e)});if(l){var Q=O.align()?m/2:m,J=O.align()?Q:0;O.width(Q),O.color(U),K.select(".legendWrap").datum(u.map(function(t){return t.originalKey=void 0===t.originalKey?t.key:t.originalKey,t.key=t.originalKey+(1==t.yAxis?"":v),t})).call(O),i||O.height()===r.top||(r.top=O.height(),L=t.utils.availableHeight(s,p,r)),K.select(".legendWrap").attr("transform","translate("+J+","+-r.top+")")}else K.select(".legendWrap").selectAll("*").remove();k.width(m).height(L).interpolate(f).color(U.filter(function(t,e){return!u[e].disabled&&1==u[e].yAxis&&"line"==u[e].type})),w.width(m).height(L).interpolate(f).color(U.filter(function(t,e){return!u[e].disabled&&2==u[e].yAxis&&"line"==u[e].type})),M.width(m).height(L).color(U.filter(function(t,e){return!u[e].disabled&&1==u[e].yAxis&&"scatter"==u[e].type})),A.width(m).height(L).color(U.filter(function(t,e){return!u[e].disabled&&2==u[e].yAxis&&"scatter"==u[e].type})),C.width(m).height(L).color(U.filter(function(t,e){return!u[e].disabled&&1==u[e].yAxis&&"bar"==u[e].type})),S.width(m).height(L).color(U.filter(function(t,e){return!u[e].disabled&&2==u[e].yAxis&&"bar"==u[e].type})),W.width(m).height(L).interpolate(f).color(U.filter(function(t,e){return!u[e].disabled&&1==u[e].yAxis&&"area"==u[e].type})),E.width(m).height(L).interpolate(f).color(U.filter(function(t,e){return!u[e].disabled&&2==u[e].yAxis&&"area"==u[e].type})),K.attr("transform","translate("+r.left+","+r.top+")");var $=K.select(".lines1Wrap").datum(T.filter(function(t){return!t.disabled})),tt=K.select(".scatters1Wrap").datum(z.filter(function(t){return!t.disabled})),et=K.select(".bars1Wrap").datum(H.filter(function(t){return!t.disabled})),nt=K.select(".stack1Wrap").datum(G.filter(function(t){return!t.disabled})),rt=K.select(".lines2Wrap").datum(I.filter(function(t){return!t.disabled})),it=K.select(".scatters2Wrap").datum(V.filter(function(t){return!t.disabled})),ot=K.select(".bars2Wrap").datum(Y.filter(function(t){return!t.disabled})),at=K.select(".stack2Wrap").datum(X.filter(function(t){return!t.disabled})),st=[];C.stacked()&&H.length&&((st=H.filter(function(t){return!t.disabled}).map(function(t){return t.values})).length>0&&(st=st.reduce(function(t,e){return t.map(function(t,n){return{x:t.x,y:t.y+e[n].y}})})));H.length&&st.push({x:0,y:0});var lt=[];S.stacked()&&Y.length&&((lt=Y.filter(function(t){return!t.disabled}).map(function(t){return t.values})).length>0&&(lt=lt.reduce(function(t,e){return t.map(function(t,n){return{x:t.x,y:t.y+e[n].y}})})));function ut(t){var e=2===t.series.yAxis?D:N;t.value=t.point.x,t.series={value:t.point.y,color:t.point.color,key:t.series.key},_.duration(0).headerFormatter(function(t,e){return F.tickFormat()(t,e)}).valueFormatter(function(t,n){return e.tickFormat()(t,n)}).data(t).hidden(!1)}function ct(t){var e=2===t.series.yAxis?D:N;t.value=t.point.x,t.series={value:t.point.y,color:t.point.color,key:t.series.key},_.duration(100).headerFormatter(function(t,e){return F.tickFormat()(t,e)}).valueFormatter(function(t,n){return e.tickFormat()(t,n)}).data(t).hidden(!1)}function dt(t){var e=2===t.series.yAxis?D:N;t.point.x=W.x()(t.point),t.point.y=W.y()(t.point),_.duration(0).headerFormatter(function(t,e){return F.tickFormat()(t,e)}).valueFormatter(function(t,n){return e.tickFormat()(t,n)}).data(t).hidden(!1)}function ft(t){var e=2===t.series.yAxis?D:N;t.value=C.x()(t.data),t.series={value:C.y()(t.data),color:t.color,key:t.data.key},_.duration(0).headerFormatter(function(t,e){return F.tickFormat()(t,e)}).valueFormatter(function(t,n){return e.tickFormat()(t,n)}).data(t).hidden(!1)}function pt(){for(var t=0,e=B.length;t=u[0]&&P.x()(t,e)<=u[1]}),d=c[r=t.interactiveBisect(c,e.pointXValue,P.x())],f=P.y()(d,r);null!==f&&function(t,e,n){for(var r=0,i=B.length;rg(t,e)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+n+"-"+e}).attr("d",function(t,e){return"m0,0l0,"+(d(h(t,e))-d(v(t,e)))+"l"+-W/2+",0l"+W/2+",0l0,"+(d(m(t,e))-d(h(t,e)))+"l0,"+(d(g(t,e))-d(m(t,e)))+"l"+W/2+",0l"+-W/2+",0z"}).attr("transform",function(t,e){return"translate("+c(f(t,e))+","+d(v(t,e))+")"}).attr("fill",function(t,e){return w[0]}).attr("stroke",function(t,e){return w[0]}).attr("x",0).attr("y",function(t,e){return d(Math.max(0,p(t,e)))}).attr("height",function(t,e){return Math.abs(d(p(t,e))-d(0))}),_.attr("class",function(t,e,n){return(h(t,e)>g(t,e)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+n+"-"+e}),d3.transition(_).attr("transform",function(t,e){return"translate("+c(f(t,e))+","+d(v(t,e))+")"}).attr("d",function(t,e){var n=C/M[0].values.length*.9;return"m0,0l0,"+(d(h(t,e))-d(v(t,e)))+"l"+-n/2+",0l"+n/2+",0l0,"+(d(m(t,e))-d(h(t,e)))+"l0,"+(d(g(t,e))-d(m(t,e)))+"l"+n/2+",0l"+-n/2+",0z"})}),C}return C.highlightPoint=function(t,e){C.clearHighlights(),u.select(".nv-ohlcBar .nv-tick-0-"+t).classed("hover",e)},C.clearHighlights=function(){u.select(".nv-ohlcBar .nv-tick.hover").classed("hover",!1)},C.dispatch=A,C.options=t.utils.optionsFunc.bind(C),C._options=Object.create({},{width:{get:function(){return a},set:function(t){a=t}},height:{get:function(){return s},set:function(t){s=t}},xScale:{get:function(){return c},set:function(t){c=t}},yScale:{get:function(){return d},set:function(t){d=t}},xDomain:{get:function(){return e},set:function(t){e=t}},yDomain:{get:function(){return n},set:function(t){n=t}},xRange:{get:function(){return r},set:function(t){r=t}},yRange:{get:function(){return i},set:function(t){i=t}},forceX:{get:function(){return y},set:function(t){y=t}},forceY:{get:function(){return x},set:function(t){x=t}},padData:{get:function(){return b},set:function(t){b=t}},clipEdge:{get:function(){return k},set:function(t){k=t}},id:{get:function(){return l},set:function(t){l=t}},interactive:{get:function(){return M},set:function(t){M=t}},x:{get:function(){return f},set:function(t){f=t}},y:{get:function(){return p},set:function(t){p=t}},open:{get:function(){return h()},set:function(t){h=t}},close:{get:function(){return g()},set:function(t){g=t}},high:{get:function(){return v},set:function(t){v=t}},low:{get:function(){return m},set:function(t){m=t}},margin:{get:function(){return o},set:function(t){o.top=void 0!=t.top?t.top:o.top,o.right=void 0!=t.right?t.right:o.right,o.bottom=void 0!=t.bottom?t.bottom:o.bottom,o.left=void 0!=t.left?t.left:o.left}},color:{get:function(){return w},set:function(e){w=t.utils.getColor(e)}}}),t.utils.initOptions(C),C},t.models.parallelCoordinates=function(){"use strict";var e,n,r,i={top:30,right:0,bottom:10,left:0},o=null,a=null,s=null,l=null,u=d3.scale.ordinal(),c={},d="undefined values",f=[],p=[],h=[],g=!0,v=t.utils.defaultColor(),m=[],y=[],x=[],b=[],k=1,w=d3.svg.line(),M=d3.svg.axis(),A=d3.dispatch("brushstart","brush","brushEnd","dimensionsOrder","stateChange","elementClick","elementMouseover","elementMouseout","elementMousemove","renderEnd","activeChanged"),C=t.utils.renderWatch(A);function S(W){return C.reset(),W.each(function(C){var S=d3.select(this);if(s=t.utils.availableWidth(o,S,i),l=t.utils.availableHeight(a,S,i),t.utils.initSVG(S),void 0===C[0].values){var W=[];C.forEach(function(t){var e={};Object.keys(t).forEach(function(n){"name"!==n&&(e[n]=t[n])}),W.push({key:t.name,values:e})}),C=W}var E=C.map(function(t){return t.values});0===y.length&&(y=C),h=f.sort(function(t,e){return t.currentPosition-e.currentPosition}).map(function(t){return t.key}),p=f.filter(function(t){return!t.disabled}),u.rangePoints([0,s],1).domain(p.map(function(t){return t.key}));var F={},N=!1,D=[];h.forEach(function(t){var e=d3.extent(E,function(e){return+e[t]}),n=e[0],r=e[1],i=!1;(isNaN(n)||isNaN(r))&&(i=!0,n=0,r=0),n===r&&(n-=1,r+=1);var o=m.filter(function(e){return e.dimension==t});0!==o.length&&(i?(n=c[t].domain()[0],r=c[t].domain()[1]):!o[0].hasOnlyNaN&&g?(n=n>o[0].extent[0]?o[0].extent[0]:n,r=r0||N?(B.style("display","inline"),P.style("display","inline")):(B.style("display","none"),P.style("display","none"))),[u(e.key),c[e.key](t.values[e.key])]}))}function j(t){m.forEach(function(e){var n=c[e.dimension].brush.y().domain();e.hasOnlyNaN&&(e.extent[1]=(c[e.dimension].domain()[1]-n[0])*(e.extent[1]-e.extent[0])/(F[e.dimension]-e.extent[0])+n[0]),e.hasNaN&&(e.extent[0]=n[0]),t&&c[e.dimension].brush.extent(e.extent)}),r.select(".nv-brushBackground").each(function(t){d3.select(this).call(c[t.key].brush)}).selectAll("rect").attr("x",-8).attr("width",16),K()}function R(){!1===g&&(g=!0,j(!0))}function Z(){H=h.filter(function(t){return!c[t].brush.empty()}),Y=H.map(function(t){return c[t].brush.extent()}),m=[],H.forEach(function(t,e){m[e]={dimension:t,extent:Y[e],hasNaN:!1,hasOnlyNaN:!1}}),y=[],e.style("display",function(t){var e=H.every(function(e,n){return!(!isNaN(t.values[e])&&!isNaN(parseFloat(t.values[e]))||Y[n][0]!=c[e].brush.y().domain()[0])||Y[n][0]<=t.values[e]&&t.values[e]<=Y[n][1]&&!isNaN(parseFloat(t.values[e]))});return e&&y.push(t),e?null:"none"}),K(),A.brush({filters:m,active:y})}function q(){var t=H.length>0;m.forEach(function(t){t.extent[0]===c[t.dimension].brush.y().domain()[0]&&b.indexOf(t.dimension)>=0&&(t.hasNaN=!0),t.extent[1]c[t.key].domain()[0]&&(D[t.key]=[n[0].extent[1]]),n[0].extent[0]>=c[t.key].domain()[0]&&D[t.key].push(n[0].extent[0])),d3.select(this).call(M.scale(c[t.key]).tickFormat(t.format).tickValues(D[t.key]))})}function U(t){var e=x[t];return null==e?u(t):e}y=[],e.style("display",function(t){var e=H.every(function(e,n){return!(!isNaN(t.values[e])&&!isNaN(parseFloat(t.values[e]))||Y[n][0]!=c[e].brush.y().domain()[0])||Y[n][0]<=t.values[e]&&t.values[e]<=Y[n][1]&&!isNaN(parseFloat(t.values[e]))});return e&&y.push(t),e?null:"none"}),(m.length>0||!t.utils.arrayEquals(y,G))&&A.activeChanged(y)}),S}return S.dispatch=A,S.options=t.utils.optionsFunc.bind(S),S._options=Object.create({},{width:{get:function(){return o},set:function(t){o=t}},height:{get:function(){return a},set:function(t){a=t}},dimensionData:{get:function(){return f},set:function(t){f=t}},displayBrush:{get:function(){return g},set:function(t){g=t}},filters:{get:function(){return m},set:function(t){m=t}},active:{get:function(){return y},set:function(t){y=t}},lineTension:{get:function(){return k},set:function(t){k=t}},undefinedValuesLabel:{get:function(){return d},set:function(t){d=t}},dimensions:{get:function(){return f.map(function(t){return t.key})},set:function(e){t.deprecated("dimensions","use dimensionData instead"),0===f.length?e.forEach(function(t){f.push({key:t})}):e.forEach(function(t,e){f[e].key=t})}},dimensionNames:{get:function(){return f.map(function(t){return t.key})},set:function(e){t.deprecated("dimensionNames","use dimensionData instead"),h=[],0===f.length?e.forEach(function(t){f.push({key:t})}):e.forEach(function(t,e){f[e].key=t})}},dimensionFormats:{get:function(){return f.map(function(t){return t.format})},set:function(e){t.deprecated("dimensionFormats","use dimensionData instead"),0===f.length?e.forEach(function(t){f.push({format:t})}):e.forEach(function(t,e){f[e].format=t})}},margin:{get:function(){return i},set:function(t){i.top=void 0!==t.top?t.top:i.top,i.right=void 0!==t.right?t.right:i.right,i.bottom=void 0!==t.bottom?t.bottom:i.bottom,i.left=void 0!==t.left?t.left:i.left}},color:{get:function(){return v},set:function(e){v=t.utils.getColor(e)}}}),t.utils.initOptions(S),S},t.models.parallelCoordinatesChart=function(){"use strict";var e=t.models.parallelCoordinates(),n=t.models.legend(),r=t.models.tooltip(),i=(t.models.tooltip(),{top:0,right:0,bottom:0,left:0}),o=null,a=null,s=null,l=!0,u=t.utils.defaultColor(),c=t.utils.state(),d=[],f=!0,p=null,h=null,g="undefined",v=d3.dispatch("dimensionsOrder","brushEnd","stateChange","changeState","renderEnd"),m=t.utils.renderWatch(v),y=function(t){return function(){return{active:t.map(function(t){return!t.disabled})}}},x=function(t){return function(e){void 0!==e.active&&t.forEach(function(t,n){t.disabled=!e.active[n]})}};function b(r){return m.reset(),m.models(e),r.each(function(r){var u=d3.select(this);t.utils.initSVG(u);var h=t.utils.availableWidth(a,u,i),g=t.utils.availableHeight(s,u,i);if(b.update=function(){u.call(b)},b.container=this,c.setter(x(d),b.update).getter(y(d)).update(),c.disabled=d.map(function(t){return!!t.disabled}),(d=d.map(function(t){return t.disabled=!!t.disabled,t})).forEach(function(t,e){t.originalPosition=isNaN(t.originalPosition)?e:t.originalPosition,t.currentPosition=isNaN(t.currentPosition)?e:t.currentPosition}),!p){var m;p={};for(m in c)c[m]instanceof Array?p[m]=c[m].slice(0):p[m]=c[m]}if(!r||!r.length)return t.utils.noData(b,u),b;u.selectAll(".nv-noData").remove();var k=u.selectAll("g.nv-wrap.nv-parallelCoordinatesChart").data([r]),w=k.enter().append("g").attr("class","nvd3 nv-wrap nv-parallelCoordinatesChart").append("g"),M=k.select("g");w.append("g").attr("class","nv-parallelCoordinatesWrap"),w.append("g").attr("class","nv-legendWrap"),M.select("rect").attr("width",h).attr("height",g>0?g:0),l?(n.width(h).color(function(t){return"rgb(188,190,192)"}),M.select(".nv-legendWrap").datum(d.sort(function(t,e){return t.originalPosition-e.originalPosition})).call(n),o||n.height()===i.top||(i.top=n.height(),g=t.utils.availableHeight(s,u,i)),k.select(".nv-legendWrap").attr("transform","translate( 0 ,"+-i.top+")")):M.select(".nv-legendWrap").selectAll("*").remove(),k.attr("transform","translate("+i.left+","+i.top+")"),e.width(h).height(g).dimensionData(d).displayBrush(f),M.select(".nv-parallelCoordinatesWrap ").datum(r).transition().call(e),e.dispatch.on("brushEnd",function(t,e){e?(f=!0,v.brushEnd(t)):f=!1}),n.dispatch.on("stateChange",function(t){for(var e in t)c[e]=t[e];v.stateChange(c),b.update()}),e.dispatch.on("dimensionsOrder",function(t){d.sort(function(t,e){return t.currentPosition-e.currentPosition});var e=!1;d.forEach(function(t,n){t.currentPosition=n,t.currentPosition!==t.originalPosition&&(e=!0)}),v.dimensionsOrder(d,e)}),v.on("changeState",function(t){void 0!==t.disabled&&(d.forEach(function(e,n){e.disabled=t.disabled[n]}),c.disabled=t.disabled),b.update()})}),m.renderEnd("parraleleCoordinateChart immediate"),b}return r.contentGenerator(function(t){var e='";return 0!==t.series.length&&(e+='',t.series.forEach(function(t){e=e+'"}),e+=""),e+="
'+t.key+"
'+t.key+''+t.value+"
"}),e.dispatch.on("elementMouseover.tooltip",function(t){var e={key:t.label,color:t.color,series:[]};t.values&&(Object.keys(t.values).forEach(function(n){var r,i=t.dimensions.filter(function(t){return t.key===n})[0];i&&(r=isNaN(t.values[n])||isNaN(parseFloat(t.values[n]))?g:i.format(t.values[n]),e.series.push({idx:i.currentPosition,key:n,value:r,color:i.color}))}),e.series.sort(function(t,e){return t.idx-e.idx})),r.data(e).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(t){r.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){r()}),b.dispatch=v,b.parallelCoordinates=e,b.legend=n,b.tooltip=r,b.options=t.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return a},set:function(t){a=t}},height:{get:function(){return s},set:function(t){s=t}},showLegend:{get:function(){return l},set:function(t){l=t}},defaultState:{get:function(){return p},set:function(t){p=t}},dimensionData:{get:function(){return d},set:function(t){d=t}},displayBrush:{get:function(){return f},set:function(t){f=t}},noData:{get:function(){return h},set:function(t){h=t}},nanValue:{get:function(){return g},set:function(t){g=t}},margin:{get:function(){return i},set:function(t){void 0!==t.top&&(i.top=t.top,o=t.top),i.right=void 0!==t.right?t.right:i.right,i.bottom=void 0!==t.bottom?t.bottom:i.bottom,i.left=void 0!==t.left?t.left:i.left}},color:{get:function(){return u},set:function(r){u=t.utils.getColor(r),n.color(u),e.color(u)}}}),t.utils.inheritOptions(b,e),t.utils.initOptions(b),b},t.models.pie=function(){"use strict";var e={top:0,right:0,bottom:0,left:0},n=500,r=500,i=function(t){return t.x},o=function(t){return t.y},a=Math.floor(1e4*Math.random()),s=null,l=t.utils.defaultColor(),u=d3.format(",.2f"),c=!0,d=!1,f="key",p=.02,h=!1,g=!1,v=!1,m=!0,y=0,x=!1,b=!1,k=!1,w=!1,M=0,A=.5,C=250,S=[],W=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),E=[],F=[],N=t.utils.renderWatch(W);function D(O){return N.reset(),O.each(function(D){var O=n-e.left-e.right,_=r-e.top-e.bottom,L=Math.min(O,_)/2,B=[],P=[];if(s=d3.select(this),0===S.length)for(var T=L-L/10,I=A*L,z=0;z=p){var o=J(r);Q[o]&&(r[1]-=14),Q[J(r)]=!0}return"translate("+r+")"}),q.select(".nv-label text").style("text-anchor",function(t,e){return x?(t.startAngle+t.endAngle)/20;--t)u(s*=.99),p(),d(),l(s),p(),d();function l(t){function n(t){return(t.source.y+t.sy+t.dy/2)*t.value}e.forEach(function(e,r){e.forEach(function(e){if(e.targetLinks.length){var r=d3.sum(e.targetLinks,n)/d3.sum(e.targetLinks,f);e.y+=(r-c(e))*t}})})}function u(t){function n(t){return(t.target.y+t.ty+t.dy/2)*t.value}e.slice().reverse().forEach(function(e){e.forEach(function(e){if(e.sourceLinks.length){var r=d3.sum(e.sourceLinks,n)/d3.sum(e.sourceLinks,f);e.y+=(r-c(e))*t}})})}function p(){e.forEach(function(t){var e,n,o,a=0,s=t.length;for(t.sort(h),o=0;o0&&(e.y+=n),a=e.y+e.dy+r;if((n=a-r-i[1])>0)for(a=e.y-=n,o=s-2;o>=0;--o)e=t[o],(n=e.y+e.dy+r-a)>0&&(e.y-=n),a=e.y})}function h(t,e){return t.y-e.y}}(t)},u=function(){var t=.5;function e(e){var n=e.source.x+e.source.dx,r=e.target.x,i=d3.interpolateNumber(n,r),o=i(t),a=i(1-t),s=e.source.y+e.sy+e.dy/2,l=e.target.y+e.ty+e.dy/2;return"M"+n+","+s+"C"+o+","+s+" "+a+","+l+" "+r+","+l}return e.curvature=function(n){return arguments.length?(t=+n,e):t},e},c=function(t){return t.y+t.dy/2};function d(){function t(t,e){return t.source.y-e.source.y}function e(t,e){return t.target.y-e.target.y}o.forEach(function(n){n.sourceLinks.sort(e),n.targetLinks.sort(t)}),o.forEach(function(t){var e=0,n=0;t.sourceLinks.forEach(function(t){t.sy=e,e+=t.dy}),t.targetLinks.forEach(function(t){t.ty=n,n+=t.dy})})}function f(t){return t.value}return e.options=t.utils.optionsFunc.bind(e),e._options=Object.create({},{nodeWidth:{get:function(){return n},set:function(t){n=+t}},nodePadding:{get:function(){return r},set:function(t){r=t}},nodes:{get:function(){return o},set:function(t){o=t}},links:{get:function(){return a},set:function(t){a=t}},size:{get:function(){return i},set:function(t){i=t}},sinksRight:{get:function(){return s},set:function(t){s=t}},layout:{get:function(){l(32)},set:function(t){l(t)}},relayout:{get:function(){d()},set:function(t){}},center:{get:function(){return c()},set:function(t){"function"==typeof t&&(c=t)}},link:{get:function(){return u()},set:function(t){return"function"==typeof t&&(u=t),u()}}}),t.utils.initOptions(e),e},t.models.sankeyChart=function(){"use strict";var e={top:5,right:0,bottom:5,left:0},n=t.models.sankey(),r=600,i=400,o=36,a=40,s="units",l=void 0,u=d3.format(",.0f"),c=function(t){return u(t)+" "+s},d=d3.scale.category20(),f=function(t){return t.source.name+" → "+t.target.name+"\n"+c(t.value)},p=function(t){return t.color=d(t.name.replace(/ .*/,""))},h=function(t){return d3.rgb(t.color).darker(2)},g=function(t){return t.name+"\n"+c(t.value)},v=function(t,e){t.append("text").attr("x",0).attr("y",0).attr("class","nvd3-sankey-chart-error").attr("text-anchor","middle").text(e)};function m(t){return t.each(function(e){var s=!1,u=!1;if(("object"==typeof e.nodes&&e.nodes.length)>=0&&("object"==typeof e.links&&e.links.length)>=0&&(s=!0),e.nodes&&e.nodes.length>0&&e.links&&e.links.length>0&&(u=!0),!s)return v(t,"Error loading chart, data is invalid"),!1;if(!u)return v(t,"No data available"),!1;var c=t.append("svg").attr("width",r).attr("height",i).append("g").attr("class","nvd3 nv-wrap nv-sankeyChart");n.nodeWidth(o).nodePadding(a).size([r,i]);var d=n.link();n.nodes(e.nodes).links(e.links).layout(32).center(l);var m=c.append("g").selectAll(".link").data(e.links).enter().append("path").attr("class","link").attr("d",d).style("stroke-width",function(t){return Math.max(1,t.dy)}).sort(function(t,e){return e.dy-t.dy});m.append("title").text(f);var y=c.append("g").selectAll(".node").data(e.nodes).enter().append("g").attr("class","node").attr("transform",function(t){return"translate("+t.x+","+t.y+")"}).call(d3.behavior.drag().origin(function(t){return t}).on("dragstart",function(){this.parentNode.appendChild(this)}).on("drag",function(t){d3.select(this).attr("transform","translate("+t.x+","+(t.y=Math.max(0,Math.min(i-t.dy,d3.event.y)))+")"),n.relayout(),m.attr("d",d)}));y.append("rect").attr("height",function(t){return t.dy}).attr("width",n.nodeWidth()).style("fill",p).style("stroke",h).append("title").text(g),y.append("text").attr("x",-6).attr("y",function(t){return t.dy/2}).attr("dy",".35em").attr("text-anchor","end").attr("transform",null).text(function(t){return t.name}).filter(function(t){return t.x0?$+20:0),ct.attr("clip-path",N?"url(#nv-edge-clip-"+h+")":""),R=!0;var ft=at.select(".nv-groups").selectAll(".nv-group").data(function(t){return t},function(t){return t.key});ft.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),ft.exit().remove(),ft.attr("class",function(t,e){return(t.classed||"")+" nv-group nv-series-"+e}).classed("nv-noninteractive",!S).classed("hover",function(t){return t.hover}),ft.watchTransition(Z,"scatter: groups").style("fill",function(t,e){return f(t,e)}).style("stroke",function(t,e){return t.pointBorderColor||p||f(t,e)}).style("stroke-opacity",1).style("fill-opacity",.5);var pt=ft.selectAll("path.nv-point").data(function(t){return t.values.map(function(t,e){return[t,e]}).filter(function(t,e){return W(t[0],e)})});if(pt.enter().append("path").attr("class",function(t){return"nv-point nv-point-"+t[1]}).style("fill",function(t){return t.color}).style("stroke",function(t){return t.color}).attr("transform",function(r){return"translate("+t.utils.NaNtoZero(e(x(r[0],r[1])))+","+t.utils.NaNtoZero(n(b(r[0],r[1])))+")"}).attr("d",t.utils.symbol().type(function(t){return w(t[0])}).size(function(t){return y(k(t[0],t[1]))})),pt.exit().each(U).remove(),ft.exit().selectAll("path.nv-point").watchTransition(Z,"scatter exit").attr("transform",function(e){return"translate("+t.utils.NaNtoZero(v(x(e[0],e[1])))+","+t.utils.NaNtoZero(m(b(e[0],e[1])))+")"}).remove(),pt.filter(function(t){return Q(t,"x",x,"y",b)||rt||it||ot}).watchTransition(Z,"scatter points").attr("transform",function(e){return"translate("+t.utils.NaNtoZero(v(x(e[0],e[1])))+","+t.utils.NaNtoZero(m(b(e[0],e[1])))+")"}),pt.filter(function(t){return Q(t,"shape",w,"size",k)||rt||it||ot}).watchTransition(Z,"scatter points").attr("d",t.utils.symbol().type(function(t){return w(t[0])}).size(function(t){return y(k(t[0],t[1]))})),j){var ht=ft.selectAll(".nv-label").data(function(t){return t.values.map(function(t,e){return[t,e]}).filter(function(t,e){return W(t[0],e)})});ht.enter().append("text").style("fill",function(t,e){return t.color}).style("stroke-opacity",0).style("fill-opacity",1).attr("transform",function(r){return"translate("+(t.utils.NaNtoZero(e(x(r[0],r[1])))+Math.sqrt(y(k(r[0],r[1]))/Math.PI)+2)+","+t.utils.NaNtoZero(n(b(r[0],r[1])))+")"}).text(function(t,e){return t[0].label}),ht.exit().remove(),ft.exit().selectAll("path.nv-label").watchTransition(Z,"scatter exit").attr("transform",function(e){return"translate("+(t.utils.NaNtoZero(v(x(e[0],e[1])))+Math.sqrt(y(k(e[0],e[1]))/Math.PI)+2)+","+t.utils.NaNtoZero(m(b(e[0],e[1])))+")"}).remove(),ht.each(function(t){d3.select(this).classed("nv-label",!0).classed("nv-label-"+t[1],!1).classed("hover",!1)}),ht.watchTransition(Z,"scatter labels").attr("transform",function(e){return"translate("+(t.utils.NaNtoZero(v(x(e[0],e[1])))+Math.sqrt(y(k(e[0],e[1]))/Math.PI)+2)+","+t.utils.NaNtoZero(m(b(e[0],e[1])))+")"})}X?(clearTimeout(l),l=setTimeout(dt,X)):dt(),e=v.copy(),n=m.copy(),r=y.copy(),a=c,s=d}),Z.renderEnd("scatter immediate"),J}return J.dispatch=H,J.options=t.utils.optionsFunc.bind(J),J._calls=new function(){this.clearHighlights=function(){return t.dom.write(function(){g.selectAll(".nv-point.hover").classed("hover",!1)}),null},this.highlightPoint=function(e,n,r){t.dom.write(function(){g.select(".nv-groups").selectAll(".nv-series-"+e).selectAll(".nv-point-"+n).classed("hover",r)})}},H.on("elementMouseover.point",function(t){S&&J._calls.highlightPoint(t.seriesIndex,t.pointIndex,!0)}),H.on("elementMouseout.point",function(t){S&&J._calls.highlightPoint(t.seriesIndex,t.pointIndex,!1)}),J._options=Object.create({},{width:{get:function(){return c},set:function(t){c=t}},height:{get:function(){return d},set:function(t){d=t}},xScale:{get:function(){return v},set:function(t){v=t}},yScale:{get:function(){return m},set:function(t){m=t}},pointScale:{get:function(){return y},set:function(t){y=t}},xDomain:{get:function(){return L},set:function(t){L=t}},yDomain:{get:function(){return B},set:function(t){B=t}},pointDomain:{get:function(){return I},set:function(t){I=t}},xRange:{get:function(){return P},set:function(t){P=t}},yRange:{get:function(){return T},set:function(t){T=t}},pointRange:{get:function(){return z},set:function(t){z=t}},forceX:{get:function(){return M},set:function(t){M=t}},forceY:{get:function(){return A},set:function(t){A=t}},forcePoint:{get:function(){return C},set:function(t){C=t}},interactive:{get:function(){return S},set:function(t){S=t}},pointActive:{get:function(){return W},set:function(t){W=t}},padDataOuter:{get:function(){return F},set:function(t){F=t}},padData:{get:function(){return E},set:function(t){E=t}},clipEdge:{get:function(){return N},set:function(t){N=t}},clipVoronoi:{get:function(){return D},set:function(t){D=t}},clipRadius:{get:function(){return _},set:function(t){_=t}},showVoronoi:{get:function(){return O},set:function(t){O=t}},id:{get:function(){return h},set:function(t){h=t}},interactiveUpdateDelay:{get:function(){return X},set:function(t){X=t}},showLabels:{get:function(){return j},set:function(t){j=t}},pointBorderColor:{get:function(){return p},set:function(t){p=t}},x:{get:function(){return x},set:function(t){x=d3.functor(t)}},y:{get:function(){return b},set:function(t){b=d3.functor(t)}},pointSize:{get:function(){return k},set:function(t){k=d3.functor(t)}},pointShape:{get:function(){return w},set:function(t){w=d3.functor(t)}},margin:{get:function(){return u},set:function(t){u.top=void 0!==t.top?t.top:u.top,u.right=void 0!==t.right?t.right:u.right,u.bottom=void 0!==t.bottom?t.bottom:u.bottom,u.left=void 0!==t.left?t.left:u.left}},duration:{get:function(){return G},set:function(t){G=t,Z.reset(G)}},color:{get:function(){return f},set:function(e){f=t.utils.getColor(e)}},useVoronoi:{get:function(){return Y},set:function(t){!1===(Y=t)&&(D=!1)}}}),t.utils.initOptions(J),J},t.models.scatterChart=function(){"use strict";var e=t.models.scatter(),n=t.models.axis(),r=t.models.axis(),i=t.models.legend(),o=t.models.distribution(),a=t.models.distribution(),s=t.models.tooltip(),l={top:30,right:20,bottom:50,left:75},u=null,c=null,d=null,f=null,p=t.utils.defaultColor(),h=e.xScale(),g=e.yScale(),v=!1,m=!1,y=!0,x=!0,b=!0,k=!1,w=t.utils.state(),M=null,A=d3.dispatch("stateChange","changeState","renderEnd"),C=null,S=250,W=!1;e.xScale(h).yScale(g),n.orient("bottom").tickPadding(10),r.orient(k?"right":"left").tickPadding(10),o.axis("x"),a.axis("y"),s.headerFormatter(function(t,e){return n.tickFormat()(t,e)}).valueFormatter(function(t,e){return r.tickFormat()(t,e)});var E=t.utils.renderWatch(A,S),F=function(t){return function(){return{active:t.map(function(t){return!t.disabled})}}},N=function(t){return function(e){void 0!==e.active&&t.forEach(function(t,n){t.disabled=!e.active[n]})}};function D(C){return E.reset(),E.models(e),x&&E.models(n),b&&E.models(r),v&&E.models(o),m&&E.models(a),C.each(function(C){f=d3.select(this),t.utils.initSVG(f);var O=t.utils.availableWidth(c,f,l),_=t.utils.availableHeight(d,f,l);if(D.update=function(){0===S?f.call(D):f.transition().duration(S).call(D)},D.container=this,w.setter(N(C),D.update).getter(F(C)).update(),w.disabled=C.map(function(t){return!!t.disabled}),!M){var L;M={};for(L in w)w[L]instanceof Array?M[L]=w[L].slice(0):M[L]=w[L]}if(!(C&&C.length&&C.filter(function(t){return t.values.length}).length))return t.utils.noData(D,f),E.renderEnd("scatter immediate"),D;f.selectAll(".nv-noData").remove(),h=e.xScale(),g=e.yScale();var B=f.selectAll("g.nv-wrap.nv-scatterChart").data([C]),P=B.enter().append("g").attr("class","nvd3 nv-wrap nv-scatterChart nv-chart-"+e.id()).append("g"),T=B.select("g");if(P.append("rect").attr("class","nvd3 nv-background").style("pointer-events","none"),P.append("g").attr("class","nv-x nv-axis"),P.append("g").attr("class","nv-y nv-axis"),P.append("g").attr("class","nv-scatterWrap"),P.append("g").attr("class","nv-regressionLinesWrap"),P.append("g").attr("class","nv-distWrap"),P.append("g").attr("class","nv-legendWrap"),k&&T.select(".nv-y.nv-axis").attr("transform","translate("+O+",0)"),y){var I=O;i.width(I),B.select(".nv-legendWrap").datum(C).call(i),u||i.height()===l.top||(l.top=i.height(),_=t.utils.availableHeight(d,f,l)),B.select(".nv-legendWrap").attr("transform","translate(0,"+-l.top+")")}else T.select(".nv-legendWrap").selectAll("*").remove();B.attr("transform","translate("+l.left+","+l.top+")"),e.width(O).height(_).color(C.map(function(t,e){return t.color=t.color||p(t,e),t.color}).filter(function(t,e){return!C[e].disabled})).showLabels(W),B.select(".nv-scatterWrap").datum(C.filter(function(t){return!t.disabled})).call(e),B.select(".nv-regressionLinesWrap").attr("clip-path","url(#nv-edge-clip-"+e.id()+")");var z=B.select(".nv-regressionLinesWrap").selectAll(".nv-regLines").data(function(t){return t});z.enter().append("g").attr("class","nv-regLines");var V=z.selectAll(".nv-regLine").data(function(t){return[t]});V.enter().append("line").attr("class","nv-regLine").style("stroke-opacity",0),V.filter(function(t){return t.intercept&&t.slope}).watchTransition(E,"scatterPlusLineChart: regline").attr("x1",h.range()[0]).attr("x2",h.range()[1]).attr("y1",function(t,e){return g(h.domain()[0]*t.slope+t.intercept)}).attr("y2",function(t,e){return g(h.domain()[1]*t.slope+t.intercept)}).style("stroke",function(t,e,n){return p(t,n)}).style("stroke-opacity",function(t,e){return t.disabled||void 0===t.slope||void 0===t.intercept?0:1}),x&&(n.scale(h)._ticks(t.utils.calcTicksX(O/100,C)).tickSize(-_,0),T.select(".nv-x.nv-axis").attr("transform","translate(0,"+g.range()[0]+")").call(n)),b&&(r.scale(g)._ticks(t.utils.calcTicksY(_/36,C)).tickSize(-O,0),T.select(".nv-y.nv-axis").call(r)),v&&(o.getData(e.x()).scale(h).width(O).color(C.map(function(t,e){return t.color||p(t,e)}).filter(function(t,e){return!C[e].disabled})),P.select(".nv-distWrap").append("g").attr("class","nv-distributionX"),T.select(".nv-distributionX").attr("transform","translate(0,"+g.range()[0]+")").datum(C.filter(function(t){return!t.disabled})).call(o)),m&&(a.getData(e.y()).scale(g).width(_).color(C.map(function(t,e){return t.color||p(t,e)}).filter(function(t,e){return!C[e].disabled})),P.select(".nv-distWrap").append("g").attr("class","nv-distributionY"),T.select(".nv-distributionY").attr("transform","translate("+(k?O:-a.size())+",0)").datum(C.filter(function(t){return!t.disabled})).call(a)),i.dispatch.on("stateChange",function(t){for(var e in t)w[e]=t[e];A.stateChange(w),D.update()}),A.on("changeState",function(t){void 0!==t.disabled&&(C.forEach(function(e,n){e.disabled=t.disabled[n]}),w.disabled=t.disabled),D.update()}),e.dispatch.on("elementMouseout.tooltip",function(t){s.hidden(!0),f.select(".nv-chart-"+e.id()+" .nv-series-"+t.seriesIndex+" .nv-distx-"+t.pointIndex).attr("y1",0),f.select(".nv-chart-"+e.id()+" .nv-series-"+t.seriesIndex+" .nv-disty-"+t.pointIndex).attr("x2",a.size())}),e.dispatch.on("elementMouseover.tooltip",function(t){f.select(".nv-series-"+t.seriesIndex+" .nv-distx-"+t.pointIndex).attr("y1",t.relativePos[1]-_),f.select(".nv-series-"+t.seriesIndex+" .nv-disty-"+t.pointIndex).attr("x2",t.relativePos[0]+o.size()),s.data(t).hidden(!1)}),h.copy(),g.copy()}),E.renderEnd("scatter with line immediate"),D}return D.dispatch=A,D.scatter=e,D.legend=i,D.xAxis=n,D.yAxis=r,D.distX=o,D.distY=a,D.tooltip=s,D.options=t.utils.optionsFunc.bind(D),D._options=Object.create({},{width:{get:function(){return c},set:function(t){c=t}},height:{get:function(){return d},set:function(t){d=t}},container:{get:function(){return f},set:function(t){f=t}},showDistX:{get:function(){return v},set:function(t){v=t}},showDistY:{get:function(){return m},set:function(t){m=t}},showLegend:{get:function(){return y},set:function(t){y=t}},showXAxis:{get:function(){return x},set:function(t){x=t}},showYAxis:{get:function(){return b},set:function(t){b=t}},defaultState:{get:function(){return M},set:function(t){M=t}},noData:{get:function(){return C},set:function(t){C=t}},duration:{get:function(){return S},set:function(t){S=t}},showLabels:{get:function(){return W},set:function(t){W=t}},margin:{get:function(){return l},set:function(t){void 0!==t.top&&(l.top=t.top,u=t.top),l.right=void 0!==t.right?t.right:l.right,l.bottom=void 0!==t.bottom?t.bottom:l.bottom,l.left=void 0!==t.left?t.left:l.left}},rightAlignYAxis:{get:function(){return k},set:function(t){k=t,r.orient(t?"right":"left")}},color:{get:function(){return p},set:function(e){p=t.utils.getColor(e),i.color(p),o.color(p),a.color(p)}}}),t.utils.inheritOptions(D,e),t.utils.initOptions(D),D},t.models.sparkline=function(){"use strict";var e,n,r,i,o={top:2,right:0,bottom:2,left:0},a=400,s=32,l=null,u=!0,c=d3.scale.linear(),d=d3.scale.linear(),f=function(t){return t.x},p=function(t){return t.y},h=t.utils.getColor(["#000"]),g=!0,v=!0,m=d3.dispatch("renderEnd"),y=t.utils.renderWatch(m);function x(u){return y.reset(),u.each(function(u){var m=a-o.left-o.right,y=s-o.top-o.bottom;l=d3.select(this),t.utils.initSVG(l),c.domain(e||d3.extent(u,f)).range(r||[0,m]),d.domain(n||d3.extent(u,p)).range(i||[y,0]);var x=l.selectAll("g.nv-wrap.nv-sparkline").data([u]);x.enter().append("g").attr("class","nvd3 nv-wrap nv-sparkline").append("g"),x.select("g");x.attr("transform","translate("+o.left+","+o.top+")");var b=x.selectAll("path").data(function(t){return[t]});b.enter().append("path"),b.exit().remove(),b.style("stroke",function(t,e){return t.color||h(t,e)}).attr("d",d3.svg.line().x(function(t,e){return c(f(t,e))}).y(function(t,e){return d(p(t,e))}));var k=x.selectAll("circle.nv-point").data(function(t){var e=t.map(function(t,e){return p(t,e)});function n(e){if(-1!=e){var n=t[e];return n.pointIndex=e,n}return null}var r=n(e.lastIndexOf(d.domain()[1])),i=n(e.indexOf(d.domain()[0])),o=n(e.length-1);return[g?i:null,g?r:null,v?o:null].filter(function(t){return null!=t})});k.enter().append("circle"),k.exit().remove(),k.attr("cx",function(t,e){return c(f(t,t.pointIndex))}).attr("cy",function(t,e){return d(p(t,t.pointIndex))}).attr("r",2).attr("class",function(t,e){return f(t,t.pointIndex)==c.domain()[1]?"nv-point nv-currentValue":p(t,t.pointIndex)==d.domain()[0]?"nv-point nv-minValue":"nv-point nv-maxValue"})}),y.renderEnd("sparkline immediate"),x}return x.options=t.utils.optionsFunc.bind(x),x._options=Object.create({},{width:{get:function(){return a},set:function(t){a=t}},height:{get:function(){return s},set:function(t){s=t}},xDomain:{get:function(){return e},set:function(t){e=t}},yDomain:{get:function(){return n},set:function(t){n=t}},xRange:{get:function(){return r},set:function(t){r=t}},yRange:{get:function(){return i},set:function(t){i=t}},xScale:{get:function(){return c},set:function(t){c=t}},yScale:{get:function(){return d},set:function(t){d=t}},animate:{get:function(){return u},set:function(t){u=t}},showMinMaxPoints:{get:function(){return g},set:function(t){g=t}},showCurrentPoint:{get:function(){return v},set:function(t){v=t}},x:{get:function(){return f},set:function(t){f=d3.functor(t)}},y:{get:function(){return p},set:function(t){p=d3.functor(t)}},margin:{get:function(){return o},set:function(t){o.top=void 0!==t.top?t.top:o.top,o.right=void 0!==t.right?t.right:o.right,o.bottom=void 0!==t.bottom?t.bottom:o.bottom,o.left=void 0!==t.left?t.left:o.left}},color:{get:function(){return h},set:function(e){h=t.utils.getColor(e)}}}),x.dispatch=m,t.utils.initOptions(x),x},t.models.sparklinePlus=function(){"use strict";var e,n,r=t.models.sparkline(),i={top:15,right:100,bottom:10,left:50},o=null,a=null,s=[],l=!1,u=d3.format(",r"),c=d3.format(",.2f"),d=!0,f=!0,p=!1,h=null,g=d3.dispatch("renderEnd"),v=t.utils.renderWatch(g);function m(h){return v.reset(),v.models(r),h.each(function(h){var g=d3.select(this);t.utils.initSVG(g);var v=t.utils.availableWidth(o,g,i),y=t.utils.availableHeight(a,g,i);if(m.update=function(){g.call(m)},m.container=this,!h||!h.length)return t.utils.noData(m,g),m;g.selectAll(".nv-noData").remove();var x=r.y()(h[h.length-1],h.length-1);e=r.xScale(),n=r.yScale();var b=g.selectAll("g.nv-wrap.nv-sparklineplus").data([h]),k=b.enter().append("g").attr("class","nvd3 nv-wrap nv-sparklineplus").append("g"),w=b.select("g");k.append("g").attr("class","nv-sparklineWrap"),k.append("g").attr("class","nv-valueWrap"),k.append("g").attr("class","nv-hoverArea"),b.attr("transform","translate("+i.left+","+i.top+")");var M=w.select(".nv-sparklineWrap");if(r.width(v).height(y),M.call(r),d){var A=w.select(".nv-valueWrap").selectAll(".nv-currentValue").data([x]);A.enter().append("text").attr("class","nv-currentValue").attr("dx",p?-8:8).attr("dy",".9em").style("text-anchor",p?"end":"start"),A.attr("x",v+(p?i.right:0)).attr("y",f?function(t){return n(t)}:0).style("fill",r.color()(h[h.length-1],h.length-1)).text(c(x))}function C(){if(!l){var t=w.selectAll(".nv-hoverValue").data(s),n=t.enter().append("g").attr("class","nv-hoverValue").style("stroke-opacity",0).style("fill-opacity",0);t.exit().transition().duration(250).style("stroke-opacity",0).style("fill-opacity",0).remove(),t.attr("transform",function(t){return"translate("+e(r.x()(h[t],t))+",0)"}).transition().duration(250).style("stroke-opacity",1).style("fill-opacity",1),s.length&&(n.append("line").attr("x1",0).attr("y1",-i.top).attr("x2",0).attr("y2",y),n.append("text").attr("class","nv-xValue").attr("x",-6).attr("y",-i.top).attr("text-anchor","end").attr("dy",".9em"),w.select(".nv-hoverValue .nv-xValue").text(u(r.x()(h[s[0]],s[0]))),n.append("text").attr("class","nv-yValue").attr("x",6).attr("y",-i.top).attr("text-anchor","start").attr("dy",".9em"),w.select(".nv-hoverValue .nv-yValue").text(c(r.y()(h[s[0]],s[0]))))}}k.select(".nv-hoverArea").append("rect").on("mousemove",function(){if(l)return;var t=d3.mouse(this)[0]-i.left;s=[function(t,e){for(var n=Math.abs(r.x()(t[0],0)-e),i=0,o=0;o=t[0]&&r.x()(e,n)<=t[1]}),disableTooltip:e.disableTooltip}})).transition().duration(_).call(r),tt(),et()}r.dispatch.on("areaClick.toggle",function(t){1===u.filter(function(t){return!t.disabled}).length?u.forEach(function(t){t.disabled=!1}):u.forEach(function(e,n){e.disabled=n!=t.seriesIndex}),S.disabled=u.map(function(t){return!!t.disabled}),F.stateChange(S),V.update()}),a.dispatch.on("stateChange",function(t){for(var e in t)S[e]=t[e];F.stateChange(S),V.update()}),s.dispatch.on("legendClick",function(t,e){t.disabled&&(U=U.map(function(t){return t.disabled=!0,t}),t.disabled=!1,r.style(t.style),S.style=r.style(),F.stateChange(S),V.update())}),l.dispatch.on("elementMousemove",function(e){r.clearHighlights();var n,i,o,a=[],s=0,c=!0,d=!1;if(u.filter(function(t,e){return t.seriesIndex=e,!t.disabled}).forEach(function(l,f){i=t.interactiveBisect(l.values,e.pointXValue,V.x());var p=l.values[i],h=V.y()(p,i);if(null!=h&&h>0&&(r.highlightPoint(f,i,!0),d=!0),f!==u.length-1||d||r.highlightPoint(f,i,!0),void 0!==p){void 0===n&&(n=p),void 0===o&&(o=V.xScale()(V.x()(p,i)));var v="expand"==r.style()?p.display.y:V.y()(p,i);a.push({key:l.key,value:v,color:g(l,l.seriesIndex),point:p}),A&&"expand"!=r.style()&&null!=v&&(s+=v,c=!1)}}),a.reverse(),a.length>2){var f=V.yScale().invert(e.mouseY),p=null;a.forEach(function(t,e){f=Math.abs(f);var n=Math.abs(t.point.display.y0),r=Math.abs(t.point.display.y);f>=n&&f<=r+n&&(p=e)}),null!=p&&(a[p].highlight=!0)}A&&"expand"!=r.style()&&a.length>=2&&!c&&a.push({key:C,value:s,total:!0});var h=V.x()(n,i),v=l.tooltip.valueFormatter();"expand"===r.style()||"stack_percent"===r.style()?(B||(B=v),v=d3.format(".1%")):B&&(v=B,B=null),l.tooltip.valueFormatter(v).data({value:h,series:a})(),l.renderGuideLine(o)}),l.dispatch.on("elementMouseout",function(t){r.clearHighlights()}),c.dispatch.on("onBrush",function(t){rt(t)}),F.on("changeState",function(t){void 0!==t.disabled&&u.length===t.disabled.length&&(u.forEach(function(e,n){e.disabled=t.disabled[n]}),S.disabled=t.disabled),void 0!==t.style&&(r.style(t.style),t.style),V.update()})}),P.renderEnd("stacked Area chart immediate"),V}return r.dispatch.on("elementMouseover.tooltip",function(t){t.point.x=r.x()(t.point),t.point.y=r.y()(t.point),u.data(t).hidden(!1)}),r.dispatch.on("elementMouseout.tooltip",function(t){u.hidden(!0)}),V.dispatch=F,V.stacked=r,V.legend=a,V.controls=s,V.xAxis=i,V.x2Axis=c.xAxis,V.yAxis=o,V.y2Axis=c.yAxis,V.interactiveLayer=l,V.tooltip=u,V.focus=c,V.dispatch=F,V.options=t.utils.optionsFunc.bind(V),V._options=Object.create({},{width:{get:function(){return p},set:function(t){p=t}},height:{get:function(){return h},set:function(t){h=t}},showLegend:{get:function(){return m},set:function(t){m=t}},legendPosition:{get:function(){return y},set:function(t){y=t}},showXAxis:{get:function(){return x},set:function(t){x=t}},showYAxis:{get:function(){return b},set:function(t){b=t}},defaultState:{get:function(){return W},set:function(t){W=t}},noData:{get:function(){return E},set:function(t){E=t}},showControls:{get:function(){return v},set:function(t){v=t}},controlLabels:{get:function(){return O},set:function(t){O=t}},controlOptions:{get:function(){return D},set:function(t){D=t}},showTotalInTooltip:{get:function(){return A},set:function(t){A=t}},totalLabel:{get:function(){return C},set:function(t){C=t}},focusEnable:{get:function(){return w},set:function(t){w=t}},focusHeight:{get:function(){return c.height()},set:function(t){c.height(t)}},brushExtent:{get:function(){return c.brushExtent()},set:function(t){c.brushExtent(t)}},margin:{get:function(){return d},set:function(t){void 0!==t.top&&(d.top=t.top,f=t.top),d.right=void 0!==t.right?t.right:d.right,d.bottom=void 0!==t.bottom?t.bottom:d.bottom,d.left=void 0!==t.left?t.left:d.left}},focusMargin:{get:function(){return c.margin},set:function(t){c.margin.top=void 0!==t.top?t.top:c.margin.top,c.margin.right=void 0!==t.right?t.right:c.margin.right,c.margin.bottom=void 0!==t.bottom?t.bottom:c.margin.bottom,c.margin.left=void 0!==t.left?t.left:c.margin.left}},duration:{get:function(){return _},set:function(t){_=t,P.reset(_),r.duration(_),i.duration(_),o.duration(_)}},color:{get:function(){return g},set:function(e){g=t.utils.getColor(e),a.color(g),r.color(g),c.color(g)}},x:{get:function(){return r.x()},set:function(t){r.x(t),c.x(t)}},y:{get:function(){return r.y()},set:function(t){r.y(t),c.y(t)}},rightAlignYAxis:{get:function(){return k},set:function(t){k=t,o.orient(k?"right":"left")}},useInteractiveGuideline:{get:function(){return M},set:function(t){M=!!t,V.interactive(!t),V.useVoronoi(!t),r.scatter.interactive(!t)}}}),t.utils.inheritOptions(V,r),t.utils.initOptions(V),V},t.models.stackedAreaWithFocusChart=function(){return t.models.stackedAreaChart().margin({bottom:30}).focusEnable(!0)},t.models.sunburst=function(){"use strict";var e,n,r,i,o={top:0,right:0,bottom:0,left:0},a=600,s=600,l="count",u={count:function(t){return 1},value:function(t){return t.value||t.size},size:function(t){return t.value||t.size}},c=Math.floor(1e4*Math.random()),d=null,f=t.utils.defaultColor(),p=!1,h=function(t){return"count"===l?t.name+" #"+t.value:t.name+" "+(t.value||t.size)},g=.02,v=function(t,e){return t.name>e.name},m=function(t,e){return void 0!==t.parent?t.name+"-"+t.parent.name+"-"+e:t.name},y=!0,x=500,b=d3.dispatch("chartClick","elementClick","elementDblClick","elementMousemove","elementMouseover","elementMouseout","renderEnd"),k=d3.scale.linear().range([0,2*Math.PI]),w=d3.scale.sqrt(),M=d3.layout.partition().sort(v),A={},C=d3.svg.arc().startAngle(function(t){return Math.max(0,Math.min(2*Math.PI,k(t.x)))}).endAngle(function(t){return Math.max(0,Math.min(2*Math.PI,k(t.x+t.dx)))}).innerRadius(function(t){return Math.max(0,w(t.y))}).outerRadius(function(t){return Math.max(0,w(t.y+t.dy))});function S(t){return W(t)>90?180:0}function W(t){return(Math.max(0,Math.min(2*Math.PI,k(t.x)))+Math.max(0,Math.min(2*Math.PI,k(t.x+t.dx))))/2*(180/Math.PI)-90}function E(t){var e=Math.max(0,Math.min(2*Math.PI,k(t.x)));return Math.max(0,Math.min(2*Math.PI,k(t.x+t.dx)))-e>g}function F(t,n){var r=d3.interpolate(k.domain(),[e.x,e.x+e.dx]),o=d3.interpolate(w.domain(),[e.y,1]),a=d3.interpolate(w.range(),[e.y?20:0,i]);return 0===n?function(){return C(t)}:function(e){return k.domain(r(e)),w.domain(o(e)).range(a(e)),C(t)}}function N(t){var e=d3.interpolate({x:t.x0,dx:t.dx0,y:t.y0,dy:t.dy0},t);return function(n){var r=e(n);return t.x0=r.x,t.dx0=r.dx,t.y0=r.y,t.dy0=r.dy,C(r)}}function D(t){t.forEach(function(t){var e=m(t),n=A[e];n?(t.dx0=n.dx,t.x0=n.x,t.dy0=n.dy,t.y0=n.y):(t.dx0=t.dx,t.x0=t.x,t.dy0=t.dy,t.y0=t.y),function(t){var e=m(t);A[e]||(A[e]={});var n=A[e];n.dx=t.dx,n.x=t.x,n.dy=t.dy,n.y=t.y}(t)})}function O(t){var n=d.selectAll("text"),r=d.selectAll("path");n.transition().attr("opacity",0),e=t,r.transition().duration(x).attrTween("d",F).each("end",function(e){e.x>=t.x&&e.x=t.depth&&d3.select(this.parentNode).select("text").transition().duration(x).text(function(t){return h(t)}).attr("opacity",function(t){return E(t)?1:0}).attr("transform",function(){var n=this.getBBox().width;if(0===e.depth)return"translate("+n/2*-1+",0)";if(e.depth===t.depth)return"translate("+(w(e.y)+5)+",0)";var r=W(e),i=S(e);return 0===i?"rotate("+r+")translate("+(w(e.y)+5)+",0)":"rotate("+r+")translate("+(w(e.y)+n+5)+",0)rotate("+i+")"}))})}var _=t.utils.renderWatch(b);function L(e){return _.reset(),e.each(function(e){d=d3.select(this),n=t.utils.availableWidth(a,d,o),r=t.utils.availableHeight(s,d,o),i=Math.min(n,r)/2,w.range([0,i]);var g=d.select("g.nvd3.nv-wrap.nv-sunburst");g[0][0]?g.attr("transform","translate("+(n/2+o.left+o.right)+","+(r/2+o.top+o.bottom)+")"):g=d.append("g").attr("class","nvd3 nv-wrap nv-sunburst nv-chart-"+c).attr("transform","translate("+(n/2+o.left+o.right)+","+(r/2+o.top+o.bottom)+")"),d.on("click",function(t,e){b.chartClick({data:t,index:e,pos:d3.event,id:c})}),M.value(u[l]||u.count);var v=M.nodes(e[0]).reverse();D(v);var F=g.selectAll(".arc-container").data(v,m);F.enter().append("g").attr("class","arc-container").append("path").attr("d",C).style("fill",function(t){return t.color?t.color:f(y?(t.children?t:t.parent).name:t.name)}).style("stroke","#FFF").on("click",function(t,e){O(t),b.elementClick({data:t,index:e})}).on("mouseover",function(t,e){var n,r;d3.select(this).classed("hover",!0).style("opacity",.8),b.elementMouseover({data:t,color:d3.select(this).style("fill"),percent:(n=t,r=Math.max(0,Math.min(2*Math.PI,k(n.x))),(Math.max(0,Math.min(2*Math.PI,k(n.x+n.dx)))-r)/(2*Math.PI))})}).on("mouseout",function(t,e){d3.select(this).classed("hover",!1).style("opacity",1),b.elementMouseout({data:t})}).on("mousemove",function(t,e){b.elementMousemove({data:t})}),F.each(function(t){d3.select(this).select("path").transition().duration(x).attrTween("d",N)}),p&&(F.selectAll("text").remove(),F.append("text").text(function(t){return h(t)}).transition().duration(x).attr("opacity",function(t){return E(t)?1:0}).attr("transform",function(t){var e=this.getBBox().width;if(0===t.depth)return"rotate(0)translate("+e/2*-1+",0)";var n=W(t),r=S(t);return 0===r?"rotate("+n+")translate("+(w(t.y)+5)+",0)":"rotate("+n+")translate("+(w(t.y)+e+5)+",0)rotate("+r+")"})),O(v[v.length-1]),F.exit().transition().duration(x).attr("opacity",0).each("end",function(t){var e=m(t);A[e]=void 0}).remove()}),_.renderEnd("sunburst immediate"),L}return L.dispatch=b,L.options=t.utils.optionsFunc.bind(L),L._options=Object.create({},{width:{get:function(){return a},set:function(t){a=t}},height:{get:function(){return s},set:function(t){s=t}},mode:{get:function(){return l},set:function(t){l=t}},id:{get:function(){return c},set:function(t){c=t}},duration:{get:function(){return x},set:function(t){x=t}},groupColorByParent:{get:function(){return y},set:function(t){y=!!t}},showLabels:{get:function(){return p},set:function(t){p=!!t}},labelFormat:{get:function(){return h},set:function(t){h=t}},labelThreshold:{get:function(){return g},set:function(t){g=t}},sort:{get:function(){return v},set:function(t){v=t}},key:{get:function(){return m},set:function(t){m=t}},margin:{get:function(){return o},set:function(t){o.top=void 0!=t.top?t.top:o.top,o.right=void 0!=t.right?t.right:o.right,o.bottom=void 0!=t.bottom?t.bottom:o.bottom,o.left=void 0!=t.left?t.left:o.left}},color:{get:function(){return f},set:function(e){f=t.utils.getColor(e)}}}),t.utils.initOptions(L),L},t.models.sunburstChart=function(){"use strict";var e=t.models.sunburst(),n=t.models.tooltip(),r={top:30,right:20,bottom:20,left:20},i=null,o=null,a=t.utils.defaultColor(),s=!1,l=(Math.round(1e5*Math.random()),null),u=null,c=250,d=d3.dispatch("stateChange","changeState","renderEnd"),f=t.utils.renderWatch(d);function p(n){return f.reset(),f.models(e),n.each(function(n){var a=d3.select(this);t.utils.initSVG(a);var s=t.utils.availableWidth(i,a,r),l=t.utils.availableHeight(o,a,r);if(p.update=function(){0===c?a.call(p):a.transition().duration(c).call(p)},p.container=a,!n||!n.length)return t.utils.noData(p,a),p;a.selectAll(".nv-noData").remove(),e.width(s).height(l).margin(r),a.call(e)}),f.renderEnd("sunburstChart immediate"),p}return n.duration(0).headerEnabled(!1).valueFormatter(function(t){return t}),e.dispatch.on("elementMouseover.tooltip",function(t){t.series={key:t.data.name,value:t.data.value||t.data.size,color:t.color,percent:t.percent},s||(delete t.percent,delete t.series.percent),n.data(t).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(t){n.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(t){n()}),p.dispatch=d,p.sunburst=e,p.tooltip=n,p.options=t.utils.optionsFunc.bind(p),p._options=Object.create({},{noData:{get:function(){return u},set:function(t){u=t}},defaultState:{get:function(){return l},set:function(t){l=t}},showTooltipPercent:{get:function(){return s},set:function(t){s=t}},color:{get:function(){return a},set:function(t){a=t,e.color(a)}},duration:{get:function(){return c},set:function(t){c=t,f.reset(c),e.duration(c)}},margin:{get:function(){return r},set:function(t){r.top=void 0!==t.top?t.top:r.top,r.right=void 0!==t.right?t.right:r.right,r.bottom=void 0!==t.bottom?t.bottom:r.bottom,r.left=void 0!==t.left?t.left:r.left,e.margin(r)}}}),t.utils.inheritOptions(p,e),t.utils.initOptions(p),p},t.version="1.8.6"}(); -},{"d3":4}],7:[function(require,module,exports) { -(function(){var n=this,r=n._,t=Array.prototype,e=Object.prototype,u=Function.prototype,i=t.push,o=t.slice,a=e.toString,c=e.hasOwnProperty,f=Array.isArray,l=Object.keys,s=u.bind,p=Object.create,h=function(){},v=function(n){return n instanceof v?n:this instanceof v?void(this._wrapped=n):new v(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=v),exports._=v):n._=v,v.VERSION="1.8.3";var y=function(n,r,t){if(void 0===r)return n;switch(null==t?3:t){case 1:return function(t){return n.call(r,t)};case 2:return function(t,e){return n.call(r,t,e)};case 3:return function(t,e,u){return n.call(r,t,e,u)};case 4:return function(t,e,u,i){return n.call(r,t,e,u,i)}}return function(){return n.apply(r,arguments)}},d=function(n,r,t){return null==n?v.identity:v.isFunction(n)?y(n,r,t):v.isObject(n)?v.matcher(n):v.property(n)};v.iteratee=function(n,r){return d(n,r,1/0)};var g=function(n,r){return function(t){var e=arguments.length;if(e<2||null==t)return t;for(var u=1;u=0&&r<=x};function w(n){return function(r,t,e,u){t=y(t,u,4);var i=!j(r)&&v.keys(r),o=(i||r).length,a=n>0?0:o-1;return arguments.length<3&&(e=r[i?i[a]:a],a+=n),function(r,t,e,u,i,o){for(;i>=0&&i=0},v.invoke=function(n,r){var t=o.call(arguments,2),e=v.isFunction(r);return v.map(n,function(n){var u=e?r:n[r];return null==u?u:u.apply(n,t)})},v.pluck=function(n,r){return v.map(n,v.property(r))},v.where=function(n,r){return v.filter(n,v.matcher(r))},v.findWhere=function(n,r){return v.find(n,v.matcher(r))},v.max=function(n,r,t){var e,u,i=-1/0,o=-1/0;if(null==r&&null!=n)for(var a=0,c=(n=j(n)?n:v.values(n)).length;ai&&(i=e);else r=d(r,t),v.each(n,function(n,t,e){((u=r(n,t,e))>o||u===-1/0&&i===-1/0)&&(i=n,o=u)});return i},v.min=function(n,r,t){var e,u,i=1/0,o=1/0;if(null==r&&null!=n)for(var a=0,c=(n=j(n)?n:v.values(n)).length;ae||void 0===t)return 1;if(t0?0:u-1;i>=0&&i0?a=i>=0?i:Math.max(i+c,a):c=i>=0?Math.min(i+1,c):i+c+1;else if(t&&i&&c)return e[i=t(e,u)]===u?i:-1;if(u!=u)return(i=r(o.call(e,a,c),v.isNaN))>=0?i+a:-1;for(i=n>0?a:c-1;i>=0&&ir?(o&&(clearTimeout(o),o=null),a=f,i=n.apply(e,u),o||(e=u=null)):o||!1===t.trailing||(o=setTimeout(c,l)),i}},v.debounce=function(n,r,t){var e,u,i,o,a,c=function(){var f=v.now()-o;f=0?e=setTimeout(c,r-f):(e=null,t||(a=n.apply(i,u),e||(i=u=null)))};return function(){i=this,u=arguments,o=v.now();var f=t&&!e;return e||(e=setTimeout(c,r)),f&&(a=n.apply(i,u),i=u=null),a}},v.wrap=function(n,r){return v.partial(r,n)},v.negate=function(n){return function(){return!n.apply(this,arguments)}},v.compose=function(){var n=arguments,r=n.length-1;return function(){for(var t=r,e=n[r].apply(this,arguments);t--;)e=n[t].call(this,e);return e}},v.after=function(n,r){return function(){if(--n<1)return r.apply(this,arguments)}},v.before=function(n,r){var t;return function(){return--n>0&&(t=r.apply(this,arguments)),n<=1&&(r=null),t}},v.once=v.partial(v.before,2);var E=!{toString:null}.propertyIsEnumerable("toString"),M=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"];function I(n,r){var t=M.length,u=n.constructor,i=v.isFunction(u)&&u.prototype||e,o="constructor";for(v.has(n,o)&&!v.contains(r,o)&&r.push(o);t--;)(o=M[t])in n&&n[o]!==i[o]&&!v.contains(r,o)&&r.push(o)}v.keys=function(n){if(!v.isObject(n))return[];if(l)return l(n);var r=[];for(var t in n)v.has(n,t)&&r.push(t);return E&&I(n,r),r},v.allKeys=function(n){if(!v.isObject(n))return[];var r=[];for(var t in n)r.push(t);return E&&I(n,r),r},v.values=function(n){for(var r=v.keys(n),t=r.length,e=Array(t),u=0;u":">",'"':""","'":"'","`":"`"},T=v.invert(B),R=function(n){var r=function(r){return n[r]},t="(?:"+v.keys(n).join("|")+")",e=RegExp(t),u=RegExp(t,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,r):n}};v.escape=R(B),v.unescape=R(T),v.result=function(n,r,t){var e=null==n?void 0:n[r];return void 0===e&&(e=t),v.isFunction(e)?e.call(n):e};var q=0;v.uniqueId=function(n){var r=++q+"";return n?n+r:r},v.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var K=/(.)^/,z={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\u2028|\u2029/g,L=function(n){return"\\"+z[n]};v.template=function(n,r,t){!r&&t&&(r=t),r=v.defaults({},r,v.templateSettings);var e=RegExp([(r.escape||K).source,(r.interpolate||K).source,(r.evaluate||K).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(r,t,e,o,a){return i+=n.slice(u,a).replace(D,L),u=a+r.length,t?i+="'+\n((__t=("+t+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":o&&(i+="';\n"+o+"\n__p+='"),r}),i+="';\n",r.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var o=new Function(r.variable||"obj","_",i)}catch(n){throw n.source=i,n}var a=function(n){return o.call(this,n,v)},c=r.variable||"obj";return a.source="function("+c+"){\n"+i+"}",a},v.chain=function(n){var r=v(n);return r._chain=!0,r};var P=function(n,r){return n._chain?v(r).chain():r};v.mixin=function(n){v.each(v.functions(n),function(r){var t=v[r]=n[r];v.prototype[r]=function(){var n=[this._wrapped];return i.apply(n,arguments),P(this,t.apply(v,n))}})},v.mixin(v),v.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var r=t[n];v.prototype[n]=function(){var t=this._wrapped;return r.apply(t,arguments),"shift"!==n&&"splice"!==n||0!==t.length||delete t[0],P(this,t)}}),v.each(["concat","join","slice"],function(n){var r=t[n];v.prototype[n]=function(){return P(this,r.apply(this._wrapped,arguments))}}),v.prototype.value=function(){return this._wrapped},v.prototype.valueOf=v.prototype.toJSON=v.prototype.value,v.prototype.toString=function(){return""+this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return v})}).call(this); -},{}],2:[function(require,module,exports) { +parcelRequire=function(e,r,n,t){var i="function"==typeof parcelRequire&&parcelRequire,o="function"==typeof require&&require;function u(n,t){if(!r[n]){if(!e[n]){var f="function"==typeof parcelRequire&&parcelRequire;if(!t&&f)return f(n,!0);if(i)return i(n,!0);if(o&&"string"==typeof n)return o(n);var c=new Error("Cannot find module '"+n+"'");throw c.code="MODULE_NOT_FOUND",c}p.resolve=function(r){return e[n][1][r]||r},p.cache={};var l=r[n]=new u.Module(n);e[n][0].call(l.exports,p,l,l.exports,this)}return r[n].exports;function p(e){return u(p.resolve(e))}}u.isParcelRequire=!0,u.Module=function(e){this.id=e,this.bundle=u,this.exports={}},u.modules=e,u.cache=r,u.parent=i,u.register=function(r,n){e[r]=[function(e,r){r.exports=n},{}]};for(var f=0;ft?1:n>=t?0:NaN}function p(n){return null===n?NaN:+n}function g(n){return!isNaN(n)}function v(n){return{left:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);r>>1;n(t[u],e)<0?r=u+1:i=u}return r},right:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);r>>1;n(t[u],e)>0?i=u:r=u+1}return r}}}t.ascending=h,t.descending=function(n,t){return tn?1:t>=n?0:NaN},t.min=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ir&&(e=r)}else{for(;++i=r){e=r;break}for(;++ir&&(e=r)}return e},t.max=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ie&&(e=r)}else{for(;++i=r){e=r;break}for(;++ie&&(e=r)}return e},t.extent=function(n,t){var e,r,i,u=-1,o=n.length;if(1===arguments.length){for(;++u=r){e=i=r;break}for(;++ur&&(e=r),i=r){e=i=r;break}for(;++ur&&(e=r),i1)return o/(l-1)},t.deviation=function(){var n=t.variance.apply(this,arguments);return n?Math.sqrt(n):n};var d=v(h);function y(n){return n.length}t.bisectLeft=d.left,t.bisect=t.bisectRight=d.right,t.bisector=function(n){return v(1===n.length?function(t,e){return h(n(t),e)}:n)},t.shuffle=function(n,t,e){(u=arguments.length)<3&&(e=n.length,u<2&&(t=0));for(var r,i,u=e-t;u;)i=Math.random()*u--|0,r=n[u+t],n[u+t]=n[i+t],n[i+t]=r;return n},t.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},t.pairs=function(n){for(var t=0,e=n.length-1,r=n[0],i=new Array(e<0?0:e);t=0;)for(t=(r=n[i]).length;--t>=0;)e[--o]=r[t];return e};var m=Math.abs;function M(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function x(){this._=Object.create(null)}t.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),(t-n)/e==1/0)throw new Error("infinite range");var r,i=[],u=function(n){var t=1;for(;n*t%1;)t*=10;return t}(m(e)),o=-1;if(n*=u,t*=u,(e*=u)<0)for(;(r=n+e*++o)>t;)i.push(r/u);else for(;(r=n+e*++o)=i.length)return e?e.call(r,u):n?u.sort(n):u;for(var l,c,f,s,h=-1,p=u.length,g=i[a++],v=new x;++h=i.length)return t;var r=[],o=u[e++];return t.forEach(function(t,i){r.push({key:t,values:n(i,e)})}),o?r.sort(function(n,t){return o(n.key,t.key)}):r}(o(t.map,n,0),0)},r.key=function(n){return i.push(n),r},r.sortKeys=function(n){return u[i.length-1]=n,r},r.sortValues=function(t){return n=t,r},r.rollup=function(n){return e=n,r},r},t.set=function(n){var t=new z;if(n)for(var e=0,r=n.length;e=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},t.event=null,t.requote=function(n){return n.replace(O,"\\$&")};var O=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,I={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]};function Y(n){return I(n,$),n}var Z=function(n,t){return t.querySelector(n)},V=function(n,t){return t.querySelectorAll(n)},X=function(n,t){var e=n.matches||n[T(n,"matchesSelector")];return(X=function(n,t){return e.call(n,t)})(n,t)};"function"==typeof Sizzle&&(Z=function(n,t){return Sizzle(n,t)[0]||null},V=Sizzle,X=Sizzle.matchesSelector),t.selection=function(){return t.select(i.documentElement)};var $=t.selection.prototype=[];function B(n){return"function"==typeof n?n:function(){return Z(n,this)}}function W(n){return"function"==typeof n?n:function(){return V(n,this)}}$.select=function(n){var t,e,r,i,u=[];n=B(n);for(var o=-1,a=this.length;++o=0&&"xmlns"!==(e=n.slice(0,t))&&(n=n.slice(t+1)),G.hasOwnProperty(e)?{space:G[e],local:n}:n}},$.attr=function(n,e){if(arguments.length<2){if("string"==typeof n){var r=this.node();return(n=t.ns.qualify(n)).local?r.getAttributeNS(n.space,n.local):r.getAttribute(n)}for(e in n)this.each(K(e,n[e]));return this}return this.each(K(n,e))},$.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=tn(n)).length,i=-1;if(t=e.classList){for(;++i=0;)(e=r[i])&&(u&&u!==e.nextSibling&&u.parentNode.insertBefore(e,u),u=e);return this},$.sort=function(n){n=function(n){arguments.length||(n=h);return function(t,e){return t&&e?n(t.__data__,e.__data__):!t-!e}}.apply(this,arguments);for(var t=-1,e=this.length;++t0&&(n=n.slice(0,o));var l=vn.get(n);function c(){var t=this[u];t&&(this.removeEventListener(n,t,t.$),delete this[u])}return l&&(n=l,a=yn),o?e?function(){var t=a(e,r(arguments));c.call(this),this.addEventListener(n,this[u]=t,t.$=i),t._=e}:c:e?D:function(){var e,r=new RegExp("^__on([^.]+)"+t.requote(n)+"$");for(var i in this)if(e=i.match(r)){var u=this[i];this.removeEventListener(e[1],u,u.$),delete this[i]}}}t.selection.enter=hn,t.selection.enter.prototype=pn,pn.append=$.append,pn.empty=$.empty,pn.node=$.node,pn.call=$.call,pn.size=$.size,pn.select=function(n){for(var t,e,r,i,u,o=[],a=-1,l=this.length;++a=r&&(r=t+1);!(o=a[r])&&++r0?1:n<0?-1:0}function Tn(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function Rn(n){return n>1?0:n<-1?Nn:Math.acos(n)}function Dn(n){return n>1?Cn:n<-1?-Cn:Math.asin(n)}function Pn(n){return((n=Math.exp(n))+1/n)/2}function Un(n){return(n=Math.sin(n/2))*n}var jn=Math.SQRT2;t.interpolateZoom=function(n,t){var e,r,i=n[0],u=n[1],o=n[2],a=t[0],l=t[1],c=t[2],f=a-i,s=l-u,h=f*f+s*s;if(h0&&(n=n.transition().duration(v)),n.call(_.event)}function E(){c&&c.domain(l.range().map(function(n){return(n-h.x)/h.k}).map(l.invert)),s&&s.domain(f.range().map(function(n){return(n-h.y)/h.k}).map(f.invert))}function A(n){d++||n({type:"zoomstart"})}function C(n){E(),n({type:"zoom",scale:h.k,translate:[h.x,h.y]})}function z(n){--d||(n({type:"zoomend"}),e=null)}function L(){var n=this,e=b.of(n,arguments),r=0,i=t.select(o(n)).on(m,function(){r=1,k(t.mouse(n),u),C(e)}).on(M,function(){i.on(m,null).on(M,null),a(r),z(e)}),u=w(t.mouse(n)),a=xn(n);pa.call(n),A(e)}function q(){var n,e=this,r=b.of(e,arguments),i={},u=0,o=".zoom-"+t.event.changedTouches[0].identifier,l="touchmove"+o,c="touchend"+o,f=[],s=t.select(e),p=xn(e);function g(){var r=t.touches(e);return n=h.k,r.forEach(function(n){n.identifier in i&&(i[n.identifier]=w(n))}),r}function v(){var n=t.event.target;t.select(n).on(l,d).on(c,m),f.push(n);for(var r=t.event.changedTouches,o=0,s=r.length;o1){y=p[0];var M=p[1],x=y[0]-M[0],b=y[1]-M[1];u=x*x+b*b}}function d(){var o,l,c,f,s=t.touches(e);pa.call(e);for(var h=0,p=s.length;h360?n-=360:n<0&&(n+=360),n<60?r+(i-r)*n/60:n<180?i:n<240?r+(i-r)*(240-n)/60:r}(n))}return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:t<0?0:t>1?1:t,r=2*(e=e<0?0:e>1?1:e)-(i=e<=.5?e*(1+t):e+t-e*t),new ot(u(n+120),u(n),u(n-120))}function Xn(n,e,r){return this instanceof Xn?(this.h=+n,this.c=+e,void(this.l=+r)):arguments.length<2?n instanceof Xn?new Xn(n.h,n.c,n.l):et(n instanceof Wn?n.l:(n=pt((n=t.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new Xn(n,e,r)}Zn.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new Yn(this.h,this.s,this.l/n)},Zn.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new Yn(this.h,this.s,n*this.l)},Zn.rgb=function(){return Vn(this.h,this.s,this.l)},t.hcl=Xn;var $n=Xn.prototype=new In;function Bn(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new Wn(e,Math.cos(n*=zn)*t,Math.sin(n)*t)}function Wn(n,t,e){return this instanceof Wn?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof Wn?new Wn(n.l,n.a,n.b):n instanceof Xn?Bn(n.h,n.c,n.l):pt((n=ot(n)).r,n.g,n.b):new Wn(n,t,e)}$n.brighter=function(n){return new Xn(this.h,this.c,Math.min(100,this.l+Jn*(arguments.length?n:1)))},$n.darker=function(n){return new Xn(this.h,this.c,Math.max(0,this.l-Jn*(arguments.length?n:1)))},$n.rgb=function(){return Bn(this.h,this.c,this.l).rgb()},t.lab=Wn;var Jn=18,Gn=.95047,Kn=1,Qn=1.08883,nt=Wn.prototype=new In;function tt(n,t,e){var r=(n+16)/116,i=r+t/500,u=r-e/200;return new ot(ut(3.2404542*(i=rt(i)*Gn)-1.5371385*(r=rt(r)*Kn)-.4985314*(u=rt(u)*Qn)),ut(-.969266*i+1.8760108*r+.041556*u),ut(.0556434*i-.2040259*r+1.0572252*u))}function et(n,t,e){return n>0?new Xn(Math.atan2(e,t)*Ln,Math.sqrt(t*t+e*e),n):new Xn(NaN,NaN,n)}function rt(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function it(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function ut(n){return Math.round(255*(n<=.00304?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function ot(n,t,e){return this instanceof ot?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof ot?new ot(n.r,n.g,n.b):st(""+n,ot,Vn):new ot(n,t,e)}function at(n){return new ot(n>>16,n>>8&255,255&n)}function lt(n){return at(n)+""}nt.brighter=function(n){return new Wn(Math.min(100,this.l+Jn*(arguments.length?n:1)),this.a,this.b)},nt.darker=function(n){return new Wn(Math.max(0,this.l-Jn*(arguments.length?n:1)),this.a,this.b)},nt.rgb=function(){return tt(this.l,this.a,this.b)},t.rgb=ot;var ct=ot.prototype=new In;function ft(n){return n<16?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function st(n,t,e){var r,i,u,o=0,a=0,l=0;if(r=/([a-z]+)\((.*)\)/.exec(n=n.toLowerCase()))switch(i=r[2].split(","),r[1]){case"hsl":return e(parseFloat(i[0]),parseFloat(i[1])/100,parseFloat(i[2])/100);case"rgb":return t(vt(i[0]),vt(i[1]),vt(i[2]))}return(u=dt.get(n))?t(u.r,u.g,u.b):(null==n||"#"!==n.charAt(0)||isNaN(u=parseInt(n.slice(1),16))||(4===n.length?(o=(3840&u)>>4,o|=o>>4,a=240&u,a|=a>>4,l=15&u,l|=l<<4):7===n.length&&(o=(16711680&u)>>16,a=(65280&u)>>8,l=255&u)),t(o,a,l))}function ht(n,t,e){var r,i,u=Math.min(n/=255,t/=255,e/=255),o=Math.max(n,t,e),a=o-u,l=(o+u)/2;return a?(i=l<.5?a/(o+u):a/(2-o-u),r=n==o?(t-e)/a+(t0&&l<1?0:r),new Yn(r,i,l)}function pt(n,t,e){var r=it((.4124564*(n=gt(n))+.3575761*(t=gt(t))+.1804375*(e=gt(e)))/Gn),i=it((.2126729*n+.7151522*t+.072175*e)/Kn);return Wn(116*i-16,500*(r-i),200*(i-it((.0193339*n+.119192*t+.9503041*e)/Qn)))}function gt(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function vt(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}ct.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,i=30;return t||e||r?(t&&t=200&&t<300||304===t){try{n=i.call(o,c)}catch(Za){return void a.error.call(o,Za)}a.load.call(o,n)}else a.error.call(o,c)}return!this.XDomainRequest||"withCredentials"in c||!/^(http(s)?:)?\/\//.test(n)||(c=new XDomainRequest),"onload"in c?c.onload=c.onerror=s:c.onreadystatechange=function(){c.readyState>3&&s()},c.onprogress=function(n){var e=t.event;t.event=n;try{a.progress.call(o,c)}finally{t.event=e}},o.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?l[n]:(null==t?delete l[n]:l[n]=t+"",o)},o.mimeType=function(n){return arguments.length?(e=null==n?null:n+"",o):e},o.responseType=function(n){return arguments.length?(f=n,o):f},o.response=function(n){return i=n,o},["get","post"].forEach(function(n){o[n]=function(){return o.send.apply(o,[n].concat(r(arguments)))}}),o.send=function(t,r,i){if(2===arguments.length&&"function"==typeof r&&(i=r,r=null),c.open(t,n,!0),null==e||"accept"in l||(l.accept=e+",*/*"),c.setRequestHeader)for(var u in l)c.setRequestHeader(u,l[u]);return null!=e&&c.overrideMimeType&&c.overrideMimeType(e),null!=f&&(c.responseType=f),null!=i&&o.on("error",i).on("load",function(n){i(null,n)}),a.beforesend.call(o,c),c.send(null==r?null:r),o},o.abort=function(){return c.abort(),o},t.rebind(o,a,"on"),null==u?o:o.get(function(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}(u))}dt.forEach(function(n,t){dt.set(n,at(t))}),t.functor=yt,t.xhr=mt(L),t.dsv=function(n,t){var e=new RegExp('["'+n+"\n]"),r=n.charCodeAt(0);function i(n,e,r){arguments.length<3&&(r=e,e=null);var i=Mt(n,t,null==e?u:o(e),r);return i.row=function(n){return arguments.length?i.response(null==(e=n)?u:o(n)):e},i}function u(n){return i.parse(n.responseText)}function o(n){return function(t){return i.parse(t.responseText,n)}}function a(t){return t.map(l).join(n)}function l(n){return e.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}return i.parse=function(n,t){var e;return i.parseRows(n,function(n,r){if(e)return e(n,r-1);var i=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");e=t?function(n,e){return t(i(n),e)}:i})},i.parseRows=function(n,t){var e,i,u={},o={},a=[],l=n.length,c=0,f=0;function s(){if(c>=l)return o;if(i)return i=!1,u;var t=c;if(34===n.charCodeAt(t)){for(var e=t;e++24?(isFinite(t)&&(clearTimeout(wt),wt=setTimeout(Nt,t)),_t=0):(_t=1,St(Nt))}function Et(){for(var n=Date.now(),t=xt;t;)n>=t.t&&t.c(n-t.t)&&(t.c=null),t=t.n;return n}function At(){for(var n,t=xt,e=1/0;t;)t.c?(t.t8?function(n){return n/e}:function(n){return n*e},symbol:n}});function Lt(n){var e=n.decimal,r=n.thousands,i=n.grouping,u=n.currency,o=i&&r?function(n,t){for(var e=n.length,u=[],o=0,a=i[0],l=0;e>0&&a>0&&(l+a+1>t&&(a=Math.max(1,t-l)),u.push(n.substring(e-=a,e+a)),!((l+=a+1)>t));)a=i[o=(o+1)%i.length];return u.reverse().join(r)}:L;return function(n){var r=qt.exec(n),i=r[1]||" ",a=r[2]||">",l=r[3]||"-",c=r[4]||"",f=r[5],s=+r[6],h=r[7],p=r[8],g=r[9],v=1,d="",y="",m=!1,M=!0;switch(p&&(p=+p.substring(1)),(f||"0"===i&&"="===a)&&(f=i="0",a="="),g){case"n":h=!0,g="g";break;case"%":v=100,y="%",g="f";break;case"p":v=100,y="%",g="r";break;case"b":case"o":case"x":case"X":"#"===c&&(d="0"+g.toLowerCase());case"c":M=!1;case"d":m=!0,p=0;break;case"s":v=-1,g="r"}"$"===c&&(d=u[0],y=u[1]),"r"!=g||p||(g="g"),null!=p&&("g"==g?p=Math.max(1,Math.min(21,p)):"e"!=g&&"f"!=g||(p=Math.max(0,Math.min(20,p)))),g=Tt.get(g)||Rt;var x=f&&h;return function(n){var r=y;if(m&&n%1)return"";var u=n<0||0===n&&1/n<0?(n=-n,"-"):"-"===l?"":l;if(v<0){var c=t.formatPrefix(n,p);n=c.scale(n),r=c.symbol+y}else n*=v;var b,_,w=(n=g(n,p)).lastIndexOf(".");if(w<0){var S=M?n.lastIndexOf("e"):-1;S<0?(b=n,_=""):(b=n.substring(0,S),_=n.substring(S))}else b=n.substring(0,w),_=e+n.substring(w+1);!f&&h&&(b=o(b,1/0));var k=d.length+b.length+_.length+(x?0:u.length),N=k"===a?N+u+n:"^"===a?N.substring(0,k>>=1)+u+n+N.substring(k):u+(x?n:N+n))+r}}}t.formatPrefix=function(n,e){var r=0;return(n=+n)&&(n<0&&(n*=-1),e&&(n=t.round(n,Ct(n,e))),r=1+Math.floor(1e-12+Math.log(n)/Math.LN10),r=Math.max(-24,Math.min(24,3*Math.floor((r-1)/3)))),zt[8+r/3]};var qt=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,Tt=t.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,e){return(n=t.round(n,Ct(n,e))).toFixed(Math.max(0,Math.min(20,Ct(n*(1+1e-15),e))))}});function Rt(n){return n+""}var Dt=t.time={},Pt=Date;function Ut(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}Ut.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){jt.setUTCDate.apply(this._,arguments)},setDay:function(){jt.setUTCDay.apply(this._,arguments)},setFullYear:function(){jt.setUTCFullYear.apply(this._,arguments)},setHours:function(){jt.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){jt.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){jt.setUTCMinutes.apply(this._,arguments)},setMonth:function(){jt.setUTCMonth.apply(this._,arguments)},setSeconds:function(){jt.setUTCSeconds.apply(this._,arguments)},setTime:function(){jt.setTime.apply(this._,arguments)}};var jt=Date.prototype;function Ft(n,t,e){function r(t){var e=n(t),r=u(e,1);return t-e1)for(;o=c)return-1;if(37===(i=t.charCodeAt(a++))){if(o=t.charAt(a++),!(u=_[o in It?t.charAt(a++):o])||(r=u(n,e,r))<0)return-1}else if(i!=e.charCodeAt(r++))return-1}return r}f.utc=function(n){var t=f(n);function e(n){try{var e=new(Pt=Ut);return e._=n,t(e)}finally{Pt=Date}}return e.parse=function(n){try{Pt=Ut;var e=t.parse(n);return e&&e._}finally{Pt=Date}},e.toString=t.toString,e},f.multi=f.utc.multi=ce;var h=t.map(),p=Xt(o),g=$t(o),v=Xt(a),d=$t(a),y=Xt(l),m=$t(l),M=Xt(c),x=$t(c);u.forEach(function(n,t){h.set(n.toLowerCase(),t)});var b={a:function(n){return a[n.getDay()]},A:function(n){return o[n.getDay()]},b:function(n){return c[n.getMonth()]},B:function(n){return l[n.getMonth()]},c:f(e),d:function(n,t){return Vt(n.getDate(),t,2)},e:function(n,t){return Vt(n.getDate(),t,2)},H:function(n,t){return Vt(n.getHours(),t,2)},I:function(n,t){return Vt(n.getHours()%12||12,t,2)},j:function(n,t){return Vt(1+Dt.dayOfYear(n),t,3)},L:function(n,t){return Vt(n.getMilliseconds(),t,3)},m:function(n,t){return Vt(n.getMonth()+1,t,2)},M:function(n,t){return Vt(n.getMinutes(),t,2)},p:function(n){return u[+(n.getHours()>=12)]},S:function(n,t){return Vt(n.getSeconds(),t,2)},U:function(n,t){return Vt(Dt.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return Vt(Dt.mondayOfYear(n),t,2)},x:f(r),X:f(i),y:function(n,t){return Vt(n.getFullYear()%100,t,2)},Y:function(n,t){return Vt(n.getFullYear()%1e4,t,4)},Z:ae,"%":function(){return"%"}},_={a:function(n,t,e){v.lastIndex=0;var r=v.exec(t.slice(e));return r?(n.w=d.get(r[0].toLowerCase()),e+r[0].length):-1},A:function(n,t,e){p.lastIndex=0;var r=p.exec(t.slice(e));return r?(n.w=g.get(r[0].toLowerCase()),e+r[0].length):-1},b:function(n,t,e){M.lastIndex=0;var r=M.exec(t.slice(e));return r?(n.m=x.get(r[0].toLowerCase()),e+r[0].length):-1},B:function(n,t,e){y.lastIndex=0;var r=y.exec(t.slice(e));return r?(n.m=m.get(r[0].toLowerCase()),e+r[0].length):-1},c:function(n,t,e){return s(n,b.c.toString(),t,e)},d:te,e:te,H:re,I:re,j:ee,L:oe,m:ne,M:ie,p:function(n,t,e){var r=h.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)},S:ue,U:Wt,w:Bt,W:Jt,x:function(n,t,e){return s(n,b.x.toString(),t,e)},X:function(n,t,e){return s(n,b.X.toString(),t,e)},y:Kt,Y:Gt,Z:Qt,"%":le};return f}Dt.year=Ft(function(n){return(n=Dt.day(n)).setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),Dt.years=Dt.year.range,Dt.years.utc=Dt.year.utc.range,Dt.day=Ft(function(n){var t=new Pt(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),Dt.days=Dt.day.range,Dt.days.utc=Dt.day.utc.range,Dt.dayOfYear=function(n){var t=Dt.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},["sunday","monday","tuesday","wednesday","thursday","friday","saturday"].forEach(function(n,t){t=7-t;var e=Dt[n]=Ft(function(n){return(n=Dt.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=Dt.year(n).getDay();return Math.floor((Dt.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});Dt[n+"s"]=e.range,Dt[n+"s"].utc=e.utc.range,Dt[n+"OfYear"]=function(n){var e=Dt.year(n).getDay();return Math.floor((Dt.dayOfYear(n)+(e+t)%7)/7)}}),Dt.week=Dt.sunday,Dt.weeks=Dt.sunday.range,Dt.weeks.utc=Dt.sunday.utc.range,Dt.weekOfYear=Dt.sundayOfYear;var It={"-":"",_:" ",0:"0"},Yt=/^\s*\d+/,Zt=/^%/;function Vt(n,t,e){var r=n<0?"-":"",i=(r?-n:n)+"",u=i.length;return r+(u68?1900:2e3),e+i[0].length):-1}function Qt(n,t,e){return/^[+-]\d{4}$/.test(t=t.slice(e,e+5))?(n.Z=-t,e+5):-1}function ne(n,t,e){Yt.lastIndex=0;var r=Yt.exec(t.slice(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function te(n,t,e){Yt.lastIndex=0;var r=Yt.exec(t.slice(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function ee(n,t,e){Yt.lastIndex=0;var r=Yt.exec(t.slice(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function re(n,t,e){Yt.lastIndex=0;var r=Yt.exec(t.slice(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function ie(n,t,e){Yt.lastIndex=0;var r=Yt.exec(t.slice(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function ue(n,t,e){Yt.lastIndex=0;var r=Yt.exec(t.slice(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function oe(n,t,e){Yt.lastIndex=0;var r=Yt.exec(t.slice(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function ae(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=m(t)/60|0,i=m(t)%60;return e+Vt(r,"0",2)+Vt(i,"0",2)}function le(n,t,e){Zt.lastIndex=0;var r=Zt.exec(t.slice(e,e+1));return r?e+r[0].length:-1}function ce(n){for(var t=n.length,e=-1;++e=0?1:-1,a=o*u,l=Math.cos(t),c=Math.sin(t),f=i*c,s=r*l+f*Math.cos(a),h=f*o*Math.sin(a);Le.add(Math.atan2(h,s)),e=n,r=l,i=c}qe.point=function(o,a){qe.point=u,e=(n=o)*zn,r=Math.cos(a=(t=a)*zn/2+Nn/4),i=Math.sin(a)},qe.lineEnd=function(){u(n,t)}}function Re(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function De(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function Pe(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function Ue(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function je(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function Fe(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function He(n){return[Math.atan2(n[1],n[0]),Dn(n[2])]}function Oe(n,t){return m(n[0]-t[0])Sn?i=90:c<-Sn&&(e=-90),s[0]=n,s[1]=r}};function p(t,u){f.push(s=[n=t,r=t]),ui&&(i=u)}function g(t,o){var a=Re([t*zn,o*zn]);if(l){var c=Pe(l,a),f=Pe([c[1],-c[0],0],c);Fe(f),f=He(f);var s=t-u,h=s>0?1:-1,g=f[0]*Ln*h,v=m(s)>180;if(v^(h*ui&&(i=d);else if(v^(h*u<(g=(g+360)%360-180)&&gi&&(i=o);v?tb(n,r)&&(r=t):b(t,r)>b(n,r)&&(n=t):r>=n?(tr&&(r=t)):t>u?b(n,t)>b(n,r)&&(r=t):b(t,r)>b(n,r)&&(n=t)}else p(t,o);l=a,u=t}function v(){h.point=g}function d(){s[0]=n,s[1]=r,h.point=p,l=null}function y(n,t){if(l){var e=n-u;c+=m(e)>180?e+(e>0?360:-360):e}else o=n,a=t;qe.point(n,t),g(n,t)}function M(){qe.lineStart()}function x(){y(o,a),qe.lineEnd(),m(c)>Sn&&(n=-(r=180)),s[0]=n,s[1]=r,l=null}function b(n,t){return(t-=n)<0?t+360:t}function _(n,t){return n[0]-t[0]}function w(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nb(v[0],v[1])&&(v[1]=p[1]),b(p[0],v[1])>b(v[0],v[1])&&(v[0]=p[0])):a.push(v=p);for(var l,c,p,g=-1/0,v=(o=0,a[c=a.length-1]);o<=c;v=p,++o)p=a[o],(l=b(v[1],p[0]))>g&&(g=l,n=p[0],r=v[1])}return f=s=null,n===1/0||e===1/0?[[NaN,NaN],[NaN,NaN]]:[[n,e],[r,i]]}}(),t.geo.centroid=function(n){xe=be=_e=we=Se=ke=Ne=Ee=Ae=Ce=ze=0,t.geo.stream(n,Ie);var e=Ae,r=Ce,i=ze,u=e*e+r*r+i*i;return u=0;--a)i.point((s=f[a])[0],s[1]);else r(p.x,p.p.x,-1,i);p=p.p}f=(p=p.o).z,g=!g}while(!p.v);i.lineEnd()}}}function Ge(n){if(t=n.length){for(var t,e,r=0,i=n[0];++r=0?1:-1,w=_*b,S=w>Nn,k=g*M;if(Le.add(Math.atan2(k*_*Math.sin(w),v*x+k*Math.cos(w))),u+=S?b+_*En:b,S^h>=e^y>=e){var N=Pe(Re(s),Re(n));Fe(N);var E=Pe(i,N);Fe(E);var A=(S^b>=0?-1:1)*Dn(E[2]);(r>A||r===A&&(N[0]||N[1]))&&(o+=S^b>=0?1:-1)}if(!d++)break;h=y,g=M,v=x,s=n}}return(u<-Sn||u0){for(M||(o.polygonStart(),M=!0),o.lineStart();++u1&&2&t&&e.push(e.pop().concat(e.shift())),a.push(e.filter(nr))}return f}}function nr(n){return n.length>1}function tr(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:D,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function er(n,t){return((n=n.x)[0]<0?n[1]-Cn-Sn:Cn-n[1])-((t=t.x)[0]<0?t[1]-Cn-Sn:Cn-t[1])}var rr=Qe(We,function(n){var t,e=NaN,r=NaN,i=NaN;return{lineStart:function(){n.lineStart(),t=1},point:function(u,o){var a=u>0?Nn:-Nn,l=m(u-e);m(l-Nn)0?Cn:-Cn),n.point(i,r),n.lineEnd(),n.lineStart(),n.point(a,r),n.point(u,r),t=0):i!==a&&l>=Nn&&(m(e-i)Sn?Math.atan((Math.sin(t)*(u=Math.cos(r))*Math.sin(e)-Math.sin(r)*(i=Math.cos(t))*Math.sin(n))/(i*u*o)):(t+r)/2}(e,r,u,o),n.point(i,r),n.lineEnd(),n.lineStart(),n.point(a,r),t=0),n.point(e=u,r=o),i=a},lineEnd:function(){n.lineEnd(),e=r=NaN},clean:function(){return 2-t}}},function(n,t,e,r){var i;if(null==n)i=e*Cn,r.point(-Nn,i),r.point(0,i),r.point(Nn,i),r.point(Nn,0),r.point(Nn,-i),r.point(0,-i),r.point(-Nn,-i),r.point(-Nn,0),r.point(-Nn,i);else if(m(n[0]-t[0])>Sn){var u=n[0]0)){if(u/=h,h<0){if(u0){if(u>s)return;u>f&&(f=u)}if(u=e-l,h||!(u<0)){if(u/=h,h<0){if(u>s)return;u>f&&(f=u)}else if(h>0){if(u0)){if(u/=p,p<0){if(u0){if(u>s)return;u>f&&(f=u)}if(u=r-c,p||!(u<0)){if(u/=p,p<0){if(u>s)return;u>f&&(f=u)}else if(p>0){if(u0&&(i.a={x:l+f*h,y:c+f*p}),s<1&&(i.b={x:l+s*h,y:c+s*p}),i}}}}}}var ur=1e9;function or(n,e,r,i){return function(l){var c,f,s,h,p,g,v,d,y,m,M,x=l,b=tr(),_=ir(n,e,r,i),w={point:N,lineStart:function(){w.point=E,f&&f.push(s=[]);m=!0,y=!1,v=d=NaN},lineEnd:function(){c&&(E(h,p),g&&y&&b.rejoin(),c.push(b.buffer()));w.point=N,y&&l.lineEnd()},polygonStart:function(){l=b,c=[],f=[],M=!0},polygonEnd:function(){l=x,c=t.merge(c);var e=function(n){for(var t=0,e=f.length,r=n[1],i=0;ir&&Tn(c,u,n)>0&&++t:u[1]<=r&&Tn(c,u,n)<0&&--t,c=u;return 0!==t}([n,i]),r=M&&e,u=c.length;(r||u)&&(l.polygonStart(),r&&(l.lineStart(),S(null,null,1,l),l.lineEnd()),u&&Je(c,o,e,S,l),l.polygonEnd()),c=f=s=null}};function S(t,o,l,c){var f=0,s=0;if(null==t||(f=u(t,l))!==(s=u(o,l))||a(t,o)<0^l>0)do{c.point(0===f||3===f?n:r,f>1?i:e)}while((f=(f+l+4)%4)!==s);else c.point(o[0],o[1])}function k(t,u){return n<=t&&t<=r&&e<=u&&u<=i}function N(n,t){k(n,t)&&l.point(n,t)}function E(n,t){var e=k(n=Math.max(-ur,Math.min(ur,n)),t=Math.max(-ur,Math.min(ur,t)));if(f&&s.push([n,t]),m)h=n,p=t,g=e,m=!1,e&&(l.lineStart(),l.point(n,t));else if(e&&y)l.point(n,t);else{var r={a:{x:v,y:d},b:{x:n,y:t}};_(r)?(y||(l.lineStart(),l.point(r.a.x,r.a.y)),l.point(r.b.x,r.b.y),e||l.lineEnd(),M=!1):e&&(l.lineStart(),l.point(n,t),M=!1)}v=n,d=t,y=e}return w};function u(t,i){return m(t[0]-n)0?0:3:m(t[0]-r)0?2:1:m(t[1]-e)0?1:0:i>0?3:2}function o(n,t){return a(n.x,t.x)}function a(n,t){var e=u(n,1),r=u(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}}function ar(n){var t=0,e=Nn/3,r=Lr(n),i=r(t,e);return i.parallels=function(n){return arguments.length?r(t=n[0]*Nn/180,e=n[1]*Nn/180):[t/Nn*180,e/Nn*180]},i}function lr(n,t){var e=Math.sin(n),r=(e+Math.sin(t))/2,i=1+e*(2*r-e),u=Math.sqrt(i)/r;function o(n,t){var e=Math.sqrt(i-2*r*Math.sin(t))/r;return[e*Math.sin(n*=r),u-e*Math.cos(n)]}return o.invert=function(n,t){var e=u-t;return[Math.atan2(n,e)/r,Dn((i-(n*n+e*e)*r*r)/(2*r))]},o}t.geo.clipExtent=function(){var n,t,e,r,i,u,o={stream:function(n){return i&&(i.valid=!1),(i=u(n)).valid=!0,i},extent:function(a){return arguments.length?(u=or(n=+a[0][0],t=+a[0][1],e=+a[1][0],r=+a[1][1]),i&&(i.valid=!1,i=null),o):[[n,t],[e,r]]}};return o.extent([[0,0],[960,500]])},(t.geo.conicEqualArea=function(){return ar(lr)}).raw=lr,t.geo.albers=function(){return t.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},t.geo.albersUsa=function(){var n,e,r,i,u=t.geo.albers(),o=t.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),a=t.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),l={point:function(t,e){n=[t,e]}};function c(t){var u=t[0],o=t[1];return n=null,e(u,o),n||(r(u,o),n)||i(u,o),n}return c.invert=function(n){var t=u.scale(),e=u.translate(),r=(n[0]-e[0])/t,i=(n[1]-e[1])/t;return(i>=.12&&i<.234&&r>=-.425&&r<-.214?o:i>=.166&&i<.234&&r>=-.214&&r<-.115?a:u).invert(n)},c.stream=function(n){var t=u.stream(n),e=o.stream(n),r=a.stream(n);return{point:function(n,i){t.point(n,i),e.point(n,i),r.point(n,i)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},c.precision=function(n){return arguments.length?(u.precision(n),o.precision(n),a.precision(n),c):u.precision()},c.scale=function(n){return arguments.length?(u.scale(n),o.scale(.35*n),a.scale(n),c.translate(u.translate())):u.scale()},c.translate=function(n){if(!arguments.length)return u.translate();var t=u.scale(),f=+n[0],s=+n[1];return e=u.translate(n).clipExtent([[f-.455*t,s-.238*t],[f+.455*t,s+.238*t]]).stream(l).point,r=o.translate([f-.307*t,s+.201*t]).clipExtent([[f-.425*t+Sn,s+.12*t+Sn],[f-.214*t-Sn,s+.234*t-Sn]]).stream(l).point,i=a.translate([f-.205*t,s+.212*t]).clipExtent([[f-.214*t+Sn,s+.166*t+Sn],[f-.115*t-Sn,s+.234*t-Sn]]).stream(l).point,c},c.scale(1070)};var cr,fr,sr,hr,pr,gr,vr={point:D,lineStart:D,lineEnd:D,polygonStart:function(){fr=0,vr.lineStart=dr},polygonEnd:function(){vr.lineStart=vr.lineEnd=vr.point=D,cr+=m(fr/2)}};function dr(){var n,t,e,r;function i(n,t){fr+=r*n-e*t,e=n,r=t}vr.point=function(u,o){vr.point=i,n=e=u,t=r=o},vr.lineEnd=function(){i(n,t)}}var yr={point:function(n,t){npr&&(pr=n);tgr&&(gr=t)},lineStart:D,lineEnd:D,polygonStart:D,polygonEnd:D};function mr(){var n=Mr(4.5),t=[],e={point:r,lineStart:function(){e.point=i},lineEnd:o,polygonStart:function(){e.lineEnd=a},polygonEnd:function(){e.lineEnd=o,e.point=r},pointRadius:function(t){return n=Mr(t),e},result:function(){if(t.length){var n=t.join("");return t=[],n}}};function r(e,r){t.push("M",e,",",r,n)}function i(n,r){t.push("M",n,",",r),e.point=u}function u(n,e){t.push("L",n,",",e)}function o(){e.point=r}function a(){t.push("Z")}return e}function Mr(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}var xr,br={point:_r,lineStart:wr,lineEnd:Sr,polygonStart:function(){br.lineStart=kr},polygonEnd:function(){br.point=_r,br.lineStart=wr,br.lineEnd=Sr}};function _r(n,t){_e+=n,we+=t,++Se}function wr(){var n,t;function e(e,r){var i=e-n,u=r-t,o=Math.sqrt(i*i+u*u);ke+=o*(n+e)/2,Ne+=o*(t+r)/2,Ee+=o,_r(n=e,t=r)}br.point=function(r,i){br.point=e,_r(n=r,t=i)}}function Sr(){br.point=_r}function kr(){var n,t,e,r;function i(n,t){var i=n-e,u=t-r,o=Math.sqrt(i*i+u*u);ke+=o*(e+n)/2,Ne+=o*(r+t)/2,Ee+=o,Ae+=(o=r*n-e*t)*(e+n),Ce+=o*(r+t),ze+=3*o,_r(e=n,r=t)}br.point=function(u,o){br.point=i,_r(n=e=u,t=r=o)},br.lineEnd=function(){i(n,t)}}function Nr(n){var t=4.5,e={point:r,lineStart:function(){e.point=i},lineEnd:o,polygonStart:function(){e.lineEnd=a},polygonEnd:function(){e.lineEnd=o,e.point=r},pointRadius:function(n){return t=n,e},result:D};function r(e,r){n.moveTo(e+t,r),n.arc(e,r,t,0,En)}function i(t,r){n.moveTo(t,r),e.point=u}function u(t,e){n.lineTo(t,e)}function o(){e.point=r}function a(){n.closePath()}return e}function Er(n){var t=.5,e=Math.cos(30*zn),r=16;function i(t){return(r?function(t){var e,i,o,a,l,c,f,s,h,p,g,v,d={point:y,lineStart:m,lineEnd:x,polygonStart:function(){t.polygonStart(),d.lineStart=b},polygonEnd:function(){t.polygonEnd(),d.lineStart=m}};function y(e,r){e=n(e,r),t.point(e[0],e[1])}function m(){s=NaN,d.point=M,t.lineStart()}function M(e,i){var o=Re([e,i]),a=n(e,i);u(s,h,f,p,g,v,s=a[0],h=a[1],f=e,p=o[0],g=o[1],v=o[2],r,t),t.point(s,h)}function x(){d.point=y,t.lineEnd()}function b(){m(),d.point=_,d.lineEnd=w}function _(n,t){M(e=n,t),i=s,o=h,a=p,l=g,c=v,d.point=M}function w(){u(s,h,f,p,g,v,i,o,e,a,l,c,r,t),d.lineEnd=x,x()}return d}:function(t){return Cr(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})})(t)}function u(r,i,o,a,l,c,f,s,h,p,g,v,d,y){var M=f-r,x=s-i,b=M*M+x*x;if(b>4*t&&d--){var _=a+p,w=l+g,S=c+v,k=Math.sqrt(_*_+w*w+S*S),N=Math.asin(S/=k),E=m(m(S)-1)t||m((M*L+x*q)/b-.5)>.3||a*p+l*g+c*v0&&16,i):Math.sqrt(t)},i}function Ar(n){this.stream=n}function Cr(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function zr(n){return Lr(function(){return n})()}function Lr(n){var e,r,i,u,o,a,l=Er(function(n,t){return[(n=e(n,t))[0]*c+u,o-n[1]*c]}),c=150,f=480,s=250,h=0,p=0,g=0,v=0,d=0,y=rr,M=L,x=null,b=null;function _(n){return[(n=i(n[0]*zn,n[1]*zn))[0]*c+u,o-n[1]*c]}function w(n){return(n=i.invert((n[0]-u)/c,(o-n[1])/c))&&[n[0]*Ln,n[1]*Ln]}function S(){i=Be(r=Dr(g,v,d),e);var n=e(h,p);return u=f-n[0]*c,o=s+n[1]*c,k()}function k(){return a&&(a.valid=!1,a=null),_}return _.stream=function(n){return a&&(a.valid=!1),(a=qr(y(r,l(M(n))))).valid=!0,a},_.clipAngle=function(n){return arguments.length?(y=null==n?(x=n,rr):function(n){var t=Math.cos(n),e=t>0,r=m(t)>Sn;return Qe(i,function(n){var t,a,l,c,f;return{lineStart:function(){c=l=!1,f=1},point:function(s,h){var p,g=[s,h],v=i(s,h),d=e?v?0:o(s,h):v?o(s+(s<0?Nn:-Nn),h):0;if(!t&&(c=l=v)&&n.lineStart(),v!==l&&(p=u(t,g),(Oe(t,p)||Oe(g,p))&&(g[0]+=Sn,g[1]+=Sn,v=i(g[0],g[1]))),v!==l)f=0,v?(n.lineStart(),p=u(g,t),n.point(p[0],p[1])):(p=u(t,g),n.point(p[0],p[1]),n.lineEnd()),t=p;else if(r&&t&&e^v){var y;d&a||!(y=u(g,t,!0))||(f=0,e?(n.lineStart(),n.point(y[0][0],y[0][1]),n.point(y[1][0],y[1][1]),n.lineEnd()):(n.point(y[1][0],y[1][1]),n.lineEnd(),n.lineStart(),n.point(y[0][0],y[0][1])))}!v||t&&Oe(t,g)||n.point(g[0],g[1]),t=g,l=v,a=d},lineEnd:function(){l&&n.lineEnd(),t=null},clean:function(){return f|(c&&l)<<1}}},Fr(n,6*zn),e?[0,-n]:[-Nn,n-Nn]);function i(n,e){return Math.cos(n)*Math.cos(e)>t}function u(n,e,r){var i=[1,0,0],u=Pe(Re(n),Re(e)),o=De(u,u),a=u[0],l=o-a*a;if(!l)return!r&&n;var c=t*o/l,f=-t*a/l,s=Pe(i,u),h=je(i,c);Ue(h,je(u,f));var p=s,g=De(h,p),v=De(p,p),d=g*g-v*(De(h,h)-1);if(!(d<0)){var y=Math.sqrt(d),M=je(p,(-g-y)/v);if(Ue(M,h),M=He(M),!r)return M;var x,b=n[0],_=e[0],w=n[1],S=e[1];_0^M[1]<(m(M[0]-b)Nn^(b<=M[0]&&M[0]<=_)){var E=je(p,(-g+y)/v);return Ue(E,h),[M,He(E)]}}}function o(t,r){var i=e?n:Nn-n,u=0;return t<-i?u|=1:t>i&&(u|=2),r<-i?u|=4:r>i&&(u|=8),u}}((x=+n)*zn),k()):x},_.clipExtent=function(n){return arguments.length?(b=n,M=n?or(n[0][0],n[0][1],n[1][0],n[1][1]):L,k()):b},_.scale=function(n){return arguments.length?(c=+n,S()):c},_.translate=function(n){return arguments.length?(f=+n[0],s=+n[1],S()):[f,s]},_.center=function(n){return arguments.length?(h=n[0]%360*zn,p=n[1]%360*zn,S()):[h*Ln,p*Ln]},_.rotate=function(n){return arguments.length?(g=n[0]%360*zn,v=n[1]%360*zn,d=n.length>2?n[2]%360*zn:0,S()):[g*Ln,v*Ln,d*Ln]},t.rebind(_,l,"precision"),function(){return e=n.apply(this,arguments),_.invert=e.invert&&w,S()}}function qr(n){return Cr(n,function(t,e){n.point(t*zn,e*zn)})}function Tr(n,t){return[n,t]}function Rr(n,t){return[n>Nn?n-En:n<-Nn?n+En:n,t]}function Dr(n,t,e){return n?t||e?Be(Ur(n),jr(t,e)):Ur(n):t||e?jr(t,e):Rr}function Pr(n){return function(t,e){return[(t+=n)>Nn?t-En:t<-Nn?t+En:t,e]}}function Ur(n){var t=Pr(n);return t.invert=Pr(-n),t}function jr(n,t){var e=Math.cos(n),r=Math.sin(n),i=Math.cos(t),u=Math.sin(t);function o(n,t){var o=Math.cos(t),a=Math.cos(n)*o,l=Math.sin(n)*o,c=Math.sin(t),f=c*e+a*r;return[Math.atan2(l*i-f*u,a*e-c*r),Dn(f*i+l*u)]}return o.invert=function(n,t){var o=Math.cos(t),a=Math.cos(n)*o,l=Math.sin(n)*o,c=Math.sin(t),f=c*i-l*u;return[Math.atan2(l*i+c*u,a*e+f*r),Dn(f*e-a*r)]},o}function Fr(n,t){var e=Math.cos(n),r=Math.sin(n);return function(i,u,o,a){var l=o*t;null!=i?(i=Hr(e,i),u=Hr(e,u),(o>0?iu)&&(i+=o*En)):(i=n+o*En,u=n-.5*l);for(var c,f=i;o>0?f>u:f2?n[2]*zn:0),t.invert=function(t){return(t=n.invert(t[0]*zn,t[1]*zn))[0]*=Ln,t[1]*=Ln,t},t},Rr.invert=Tr,t.geo.circle=function(){var n,t,e=[0,0],r=6;function i(){var n="function"==typeof e?e.apply(this,arguments):e,r=Dr(-n[0]*zn,-n[1]*zn,0).invert,i=[];return t(null,null,1,{point:function(n,t){i.push(n=r(n,t)),n[0]*=Ln,n[1]*=Ln}}),{type:"Polygon",coordinates:[i]}}return i.origin=function(n){return arguments.length?(e=n,i):e},i.angle=function(e){return arguments.length?(t=Fr((n=+e)*zn,r*zn),i):n},i.precision=function(e){return arguments.length?(t=Fr(n*zn,(r=+e)*zn),i):r},i.angle(90)},t.geo.distance=function(n,t){var e,r=(t[0]-n[0])*zn,i=n[1]*zn,u=t[1]*zn,o=Math.sin(r),a=Math.cos(r),l=Math.sin(i),c=Math.cos(i),f=Math.sin(u),s=Math.cos(u);return Math.atan2(Math.sqrt((e=s*o)*e+(e=c*f-l*s*a)*e),l*f+c*s*a)},t.geo.graticule=function(){var n,e,r,i,u,o,a,l,c,f,s,h,p=10,g=p,v=90,d=360,y=2.5;function M(){return{type:"MultiLineString",coordinates:x()}}function x(){return t.range(Math.ceil(i/v)*v,r,v).map(s).concat(t.range(Math.ceil(l/d)*d,a,d).map(h)).concat(t.range(Math.ceil(e/p)*p,n,p).filter(function(n){return m(n%v)>Sn}).map(c)).concat(t.range(Math.ceil(o/g)*g,u,g).filter(function(n){return m(n%d)>Sn}).map(f))}return M.lines=function(){return x().map(function(n){return{type:"LineString",coordinates:n}})},M.outline=function(){return{type:"Polygon",coordinates:[s(i).concat(h(a).slice(1),s(r).reverse().slice(1),h(l).reverse().slice(1))]}},M.extent=function(n){return arguments.length?M.majorExtent(n).minorExtent(n):M.minorExtent()},M.majorExtent=function(n){return arguments.length?(i=+n[0][0],r=+n[1][0],l=+n[0][1],a=+n[1][1],i>r&&(n=i,i=r,r=n),l>a&&(n=l,l=a,a=n),M.precision(y)):[[i,l],[r,a]]},M.minorExtent=function(t){return arguments.length?(e=+t[0][0],n=+t[1][0],o=+t[0][1],u=+t[1][1],e>n&&(t=e,e=n,n=t),o>u&&(t=o,o=u,u=t),M.precision(y)):[[e,o],[n,u]]},M.step=function(n){return arguments.length?M.majorStep(n).minorStep(n):M.minorStep()},M.majorStep=function(n){return arguments.length?(v=+n[0],d=+n[1],M):[v,d]},M.minorStep=function(n){return arguments.length?(p=+n[0],g=+n[1],M):[p,g]},M.precision=function(t){return arguments.length?(y=+t,c=Or(o,u,90),f=Ir(e,n,y),s=Or(l,a,90),h=Ir(i,r,y),M):y},M.majorExtent([[-180,-90+Sn],[180,90-Sn]]).minorExtent([[-180,-80-Sn],[180,80+Sn]])},t.geo.greatArc=function(){var n,e,r=Yr,i=Zr;function u(){return{type:"LineString",coordinates:[n||r.apply(this,arguments),e||i.apply(this,arguments)]}}return u.distance=function(){return t.geo.distance(n||r.apply(this,arguments),e||i.apply(this,arguments))},u.source=function(t){return arguments.length?(r=t,n="function"==typeof t?null:t,u):r},u.target=function(n){return arguments.length?(i=n,e="function"==typeof n?null:n,u):i},u.precision=function(){return arguments.length?u:0},u},t.geo.interpolate=function(n,t){return e=n[0]*zn,r=n[1]*zn,i=t[0]*zn,u=t[1]*zn,o=Math.cos(r),a=Math.sin(r),l=Math.cos(u),c=Math.sin(u),f=o*Math.cos(e),s=o*Math.sin(e),h=l*Math.cos(i),p=l*Math.sin(i),g=2*Math.asin(Math.sqrt(Un(u-r)+o*l*Un(i-e))),v=1/Math.sin(g),(d=g?function(n){var t=Math.sin(n*=g)*v,e=Math.sin(g-n)*v,r=e*f+t*h,i=e*s+t*p,u=e*a+t*c;return[Math.atan2(i,r)*Ln,Math.atan2(u,Math.sqrt(r*r+i*i))*Ln]}:function(){return[e*Ln,r*Ln]}).distance=g,d;var e,r,i,u,o,a,l,c,f,s,h,p,g,v,d},t.geo.length=function(n){return xr=0,t.geo.stream(n,Vr),xr};var Vr={sphere:D,point:D,lineStart:function(){var n,t,e;function r(r,i){var u=Math.sin(i*=zn),o=Math.cos(i),a=m((r*=zn)-n),l=Math.cos(a);xr+=Math.atan2(Math.sqrt((a=o*Math.sin(a))*a+(a=e*u-t*o*l)*a),t*u+e*o*l),n=r,t=u,e=o}Vr.point=function(i,u){n=i*zn,t=Math.sin(u*=zn),e=Math.cos(u),Vr.point=r},Vr.lineEnd=function(){Vr.point=Vr.lineEnd=D}},lineEnd:D,polygonStart:D,polygonEnd:D};function Xr(n,t){function e(t,e){var r=Math.cos(t),i=Math.cos(e),u=n(r*i);return[u*i*Math.sin(t),u*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),i=t(r),u=Math.sin(i),o=Math.cos(i);return[Math.atan2(n*u,r*o),Math.asin(r&&e*u/r)]},e}var $r=Xr(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(t.geo.azimuthalEqualArea=function(){return zr($r)}).raw=$r;var Br=Xr(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},L);function Wr(n,t){var e=Math.cos(n),r=function(n){return Math.tan(Nn/4+n/2)},i=n===t?Math.sin(n):Math.log(e/Math.cos(t))/Math.log(r(t)/r(n)),u=e*Math.pow(r(n),i)/i;if(!i)return Kr;function o(n,t){u>0?t<-Cn+Sn&&(t=-Cn+Sn):t>Cn-Sn&&(t=Cn-Sn);var e=u/Math.pow(r(t),i);return[e*Math.sin(i*n),u-e*Math.cos(i*n)]}return o.invert=function(n,t){var e=u-t,r=qn(i)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/i,2*Math.atan(Math.pow(u/r,1/i))-Cn]},o}function Jr(n,t){var e=Math.cos(n),r=n===t?Math.sin(n):(e-Math.cos(t))/(t-n),i=e/r+n;if(m(r)1&&Tn(n[e[r-2]],n[e[r-1]],n[i])<=0;)--r;e[r++]=i}return e.slice(0,r)}function oi(n,t){return n[0]-t[0]||n[1]-t[1]}(t.geo.stereographic=function(){return zr(ti)}).raw=ti,ei.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-Cn]},(t.geo.transverseMercator=function(){var n=Qr(ei),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):[(n=t())[1],-n[0]]},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):[(n=e())[0],n[1],n[2]-90]},e([0,0,90])}).raw=ei,t.geom={},t.geom.hull=function(n){var t=ri,e=ii;if(arguments.length)return r(n);function r(n){if(n.length<3)return[];var r,i=yt(t),u=yt(e),o=n.length,a=[],l=[];for(r=0;r=0;--r)p.push(n[a[c[r]][2]]);for(r=+s;rSn)a=a.L;else{if(!((i=u-Si(a,o))>Sn)){r>-Sn?(t=a.P,e=a):i>-Sn?(t=a,e=a.N):t=e=a;break}if(!a.R){t=a;break}a=a.R}var l=Mi(n);if(pi.insert(t,l),t||e){if(t===e)return Ci(t),e=Mi(t.site),pi.insert(l,e),l.edge=e.edge=qi(t.site,l.site),Ai(t),void Ai(e);if(e){Ci(t),Ci(e);var c=t.site,f=c.x,s=c.y,h=n.x-f,p=n.y-s,g=e.site,v=g.x-f,d=g.y-s,y=2*(h*d-p*v),m=h*h+p*p,M=v*v+d*d,x={x:(d*m-p*M)/y+f,y:(h*M-v*m)/y+s};Ti(e.edge,c,g,x),l.edge=qi(c,n,null,x),e.edge=qi(n,g,null,x),Ai(t),Ai(e)}else l.edge=qi(t.site,l.site)}}function wi(n,t){var e=n.site,r=e.x,i=e.y,u=i-t;if(!u)return r;var o=n.P;if(!o)return-1/0;var a=(e=o.site).x,l=e.y,c=l-t;if(!c)return a;var f=a-r,s=1/u-1/c,h=f/c;return s?(-h+Math.sqrt(h*h-2*s*(f*f/(-2*c)-l+c/2+i-u/2)))/s+r:(r+a)/2}function Si(n,t){var e=n.N;if(e)return wi(e,t);var r=n.site;return r.y===t?r.x:1/0}function ki(n){this.site=n,this.edges=[]}function Ni(n,t){return t.angle-n.angle}function Ei(){Pi(this),this.x=this.y=this.arc=this.site=this.cy=null}function Ai(n){var t=n.P,e=n.N;if(t&&e){var r=t.site,i=n.site,u=e.site;if(r!==u){var o=i.x,a=i.y,l=r.x-o,c=r.y-a,f=u.x-o,s=2*(l*(d=u.y-a)-c*f);if(!(s>=-kn)){var h=l*l+c*c,p=f*f+d*d,g=(d*h-c*p)/s,v=(l*p-f*h)/s,d=v+a,y=yi.pop()||new Ei;y.arc=n,y.site=i,y.x=g+o,y.y=d+Math.sqrt(g*g+v*v),y.cy=d,n.circle=y;for(var m=null,M=vi._;M;)if(y.y=a)return;if(h>g){if(u){if(u.y>=c)return}else u={x:d,y:l};e={x:d,y:c}}else{if(u){if(u.y1)if(h>g){if(u){if(u.y>=c)return}else u={x:(l-i)/r,y:l};e={x:(c-i)/r,y:c}}else{if(u){if(u.y=a)return}else u={x:o,y:r*o+i};e={x:a,y:r*a+i}}else{if(u){if(u.xSn||m(i-e)>Sn)&&(a.splice(o,0,new Ri((y=u.site,M=f,x=m(r-s)Sn?{x:s,y:m(t-s)Sn?{x:m(e-g)Sn?{x:h,y:m(t-h)Sn?{x:m(e-p)=e&&c.x<=i&&c.y>=r&&c.y<=o?[[e,o],[i,o],[i,r],[e,r]]:[]).point=n[a]}),t}function a(n){return n.map(function(n,t){return{x:Math.round(r(n,t)/Sn)*Sn,y:Math.round(i(n,t)/Sn)*Sn,i:t}})}return o.links=function(n){return Hi(a(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},o.triangles=function(n){var t=[];return Hi(a(n)).cells.forEach(function(e,r){for(var i,u,o,a,l=e.site,c=e.edges.sort(Ni),f=-1,s=c.length,h=c[s-1].edge,p=h.l===l?h.r:h.l;++fu&&(i=t.slice(u,i),a[o]?a[o]+=i:a[++o]=i),(e=e[0])===(r=r[0])?a[o]?a[o]+=r:a[++o]=r:(a[++o]=null,l.push({i:o,x:$i(e,r)})),u=Ji.lastIndex;return uv&&(v=l.x),l.y>d&&(d=l.y),c.push(l.x),f.push(l.y);else for(s=0;sv&&(v=x),b>d&&(d=b),c.push(x),f.push(b)}var _=v-p,w=d-g;function S(n,t,e,r,i,u,o,a){if(!isNaN(e)&&!isNaN(r))if(n.leaf){var l=n.x,c=n.y;if(null!=l)if(m(l-e)+m(c-r)<.01)k(n,t,e,r,i,u,o,a);else{var f=n.point;n.x=n.y=n.point=null,k(n,f,l,c,i,u,o,a),k(n,t,e,r,i,u,o,a)}else n.x=e,n.y=r,n.point=t}else k(n,t,e,r,i,u,o,a)}function k(n,t,e,r,i,u,o,a){var l=.5*(i+o),c=.5*(u+a),f=e>=l,s=r>=c,h=s<<1|f;n.leaf=!1,f?i=l:o=l,s?u=c:a=c,S(n=n.nodes[h]||(n.nodes[h]={leaf:!0,nodes:[],point:null,x:null,y:null}),t,e,r,i,u,o,a)}_>w?d=g+_:v=p+w;var N={leaf:!0,nodes:[],point:null,x:null,y:null,add:function(n){S(N,n,+y(n,++s),+M(n,s),p,g,v,d)}};if(N.visit=function(n){!function n(t,e,r,i,u,o){if(!t(e,r,i,u,o)){var a=.5*(r+u),l=.5*(i+o),c=e.nodes;c[0]&&n(t,c[0],r,i,a,l),c[1]&&n(t,c[1],a,i,u,l),c[2]&&n(t,c[2],r,l,a,o),c[3]&&n(t,c[3],a,l,u,o)}}(n,N,p,g,v,d)},N.find=function(n){return function(n,t,e,r,i,u,o){var a,l=1/0;return function n(c,f,s,h,p){if(!(f>u||s>o||h=b)<<1|t>=x,w=_+4;_=0&&!(r=t.interpolators[i](n,e)););return r}function Ki(n,t){var e,r=[],i=[],u=n.length,o=t.length,a=Math.min(n.length,t.length);for(e=0;e=1)return 1;var t=n*n,e=t*n;return 4*(n<.5?e:3*(n-t)+e-.75)}function au(n){return 1-Math.cos(n*Cn)}function lu(n){return Math.pow(2,10*(n-1))}function cu(n){return 1-Math.sqrt(1-n*n)}function fu(n){return n<1/2.75?7.5625*n*n:n<2/2.75?7.5625*(n-=1.5/2.75)*n+.75:n<2.5/2.75?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function su(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function hu(n){var t,e,r,i=[n.a,n.b],u=[n.c,n.d],o=gu(i),a=pu(i,u),l=gu(((t=u)[0]+=(r=-a)*(e=i)[0],t[1]+=r*e[1],t))||0;i[0]*u[1]=0?n.slice(0,r):n,u=r>=0?n.slice(r+1):"in";return i=nu.get(i)||Qi,u=tu.get(u)||L,t=u(i.apply(null,e.call(arguments,1))),function(n){return n<=0?0:n>=1?1:t(n)}},t.interpolateHcl=function(n,e){n=t.hcl(n),e=t.hcl(e);var r=n.h,i=n.c,u=n.l,o=e.h-r,a=e.c-i,l=e.l-u;isNaN(a)&&(a=0,i=isNaN(i)?e.c:i);isNaN(o)?(o=0,r=isNaN(r)?e.h:r):o>180?o-=360:o<-180&&(o+=360);return function(n){return Bn(r+o*n,i+a*n,u+l*n)+""}},t.interpolateHsl=function(n,e){n=t.hsl(n),e=t.hsl(e);var r=n.h,i=n.s,u=n.l,o=e.h-r,a=e.s-i,l=e.l-u;isNaN(a)&&(a=0,i=isNaN(i)?e.s:i);isNaN(o)?(o=0,r=isNaN(r)?e.h:r):o>180?o-=360:o<-180&&(o+=360);return function(n){return Vn(r+o*n,i+a*n,u+l*n)+""}},t.interpolateLab=function(n,e){n=t.lab(n),e=t.lab(e);var r=n.l,i=n.a,u=n.b,o=e.l-r,a=e.a-i,l=e.b-u;return function(n){return tt(r+o*n,i+a*n,u+l*n)+""}},t.interpolateRound=su,t.transform=function(n){var e=i.createElementNS(t.ns.prefix.svg,"g");return(t.transform=function(n){if(null!=n){e.setAttribute("transform",n);var t=e.transform.baseVal.consolidate()}return new hu(t?t.matrix:vu)})(n)},hu.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var vu={a:1,b:0,c:0,d:1,e:0,f:0};function du(n){return n.length?n.pop()+",":""}function yu(n,e){var r=[],i=[];return n=t.transform(n),e=t.transform(e),function(n,t,e,r){if(n[0]!==t[0]||n[1]!==t[1]){var i=e.push("translate(",null,",",null,")");r.push({i:i-4,x:$i(n[0],t[0])},{i:i-2,x:$i(n[1],t[1])})}else(t[0]||t[1])&&e.push("translate("+t+")")}(n.translate,e.translate,r,i),function(n,t,e,r){n!==t?(n-t>180?t+=360:t-n>180&&(n+=360),r.push({i:e.push(du(e)+"rotate(",null,")")-2,x:$i(n,t)})):t&&e.push(du(e)+"rotate("+t+")")}(n.rotate,e.rotate,r,i),function(n,t,e,r){n!==t?r.push({i:e.push(du(e)+"skewX(",null,")")-2,x:$i(n,t)}):t&&e.push(du(e)+"skewX("+t+")")}(n.skew,e.skew,r,i),function(n,t,e,r){if(n[0]!==t[0]||n[1]!==t[1]){var i=e.push(du(e)+"scale(",null,",",null,")");r.push({i:i-4,x:$i(n[0],t[0])},{i:i-2,x:$i(n[1],t[1])})}else 1===t[0]&&1===t[1]||e.push(du(e)+"scale("+t+")")}(n.scale,e.scale,r,i),n=e=null,function(n){for(var t,e=-1,u=i.length;++e0?r=t:(n.c=null,n.t=NaN,n=null,l.end({type:"end",alpha:r=0})):t>0&&(l.start({type:"start",alpha:r=t}),n=kt(a.tick)),a):r},a.start=function(){var n,t,e,r=y.length,l=m.length,f=c[0],g=c[1];for(n=0;n=0;)e.push(i[r])}function Lu(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(u=n.children)&&(i=u.length))for(var i,u,o=-1;++o=0;)o.push(f=c[l]),f.parent=u,f.depth=u.depth+1;e&&(u.value=0),u.children=c}else e&&(u.value=+e.call(r,u,u.depth)||0),delete u.children;return Lu(i,function(t){var r,i;n&&(r=t.children)&&r.sort(n),e&&(i=t.parent)&&(i.value+=t.value)}),a}return r.sort=function(t){return arguments.length?(n=t,r):n},r.children=function(n){return arguments.length?(t=n,r):t},r.value=function(n){return arguments.length?(e=n,r):e},r.revalue=function(n){return e&&(zu(n,function(n){n.children&&(n.value=0)}),Lu(n,function(n){var t;n.children||(n.value=+e.call(r,n,n.depth)||0),(t=n.parent)&&(t.value+=n.value)})),n},r},t.layout.partition=function(){var n=t.layout.hierarchy(),e=[1,1];function r(t,r){var i=n.call(this,t,r);return function n(t,e,r,i){var u=t.children;if(t.x=e,t.y=t.depth*i,t.dx=r,t.dy=i,u&&(o=u.length)){var o,a,l,c=-1;for(r=t.value?r/t.value:0;++ca&&(a=r),o.push(r)}for(e=0;ei&&(r=e,i=t);return r}function Vu(n){return n.reduce(Xu,0)}function Xu(n,t){return n+t[1]}function $u(n,t){return Bu(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function Bu(n,t){for(var e=-1,r=+n[0],i=(n[1]-r)/t,u=[];++e<=t;)u[e]=i*e+r;return u}function Wu(n){return[t.min(n),t.max(n)]}function Ju(n,t){return n.value-t.value}function Gu(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function Ku(n,t){n._pack_next=t,t._pack_prev=n}function Qu(n,t){var e=t.x-n.x,r=t.y-n.y,i=n.r+t.r;return.999*i*i>e*e+r*r}function no(n){if((t=n.children)&&(l=t.length)){var t,e,r,i,u,o,a,l,c=1/0,f=-1/0,s=1/0,h=-1/0;if(t.forEach(to),(e=t[0]).x=-e.r,e.y=0,M(e),l>1&&((r=t[1]).x=r.r,r.y=0,M(r),l>2))for(ro(e,r,i=t[2]),M(i),Gu(e,i),e._pack_prev=i,Gu(i,r),r=e._pack_next,u=3;u0)for(o=-1;++o=s[0]&&l<=s[1]&&((a=c[t.bisect(h,l,1,g)-1]).y+=v,a.push(u[o]));return c}return u.value=function(n){return arguments.length?(e=n,u):e},u.range=function(n){return arguments.length?(r=yt(n),u):r},u.bins=function(n){return arguments.length?(i="number"==typeof n?function(t){return Bu(t,n)}:yt(n),u):i},u.frequency=function(t){return arguments.length?(n=!!t,u):n},u},t.layout.pack=function(){var n,e=t.layout.hierarchy().sort(Ju),r=0,i=[1,1];function u(t,u){var o=e.call(this,t,u),a=o[0],l=i[0],c=i[1],f=null==n?Math.sqrt:"function"==typeof n?n:function(){return n};if(a.x=a.y=0,Lu(a,function(n){n.r=+f(n.value)}),Lu(a,no),r){var s=r*(n?1:Math.max(2*a.r/l,2*a.r/c))/2;Lu(a,function(n){n.r+=s}),Lu(a,no),Lu(a,function(n){n.r-=s})}return function n(t,e,r,i){var u=t.children;t.x=e+=i*t.x;t.y=r+=i*t.y;t.r*=i;if(u)for(var o=-1,a=u.length;++op.x&&(p=n),n.depth>g.depth&&(g=n)});var v=e(h,p)/2-h.x,d=r[0]/(p.x+e(p,h)/2+v),y=r[1]/(g.depth||1);zu(f,function(n){n.x=(n.x+v)*d,n.y=n.depth*y})}return c}function o(n){var t=n.children,r=n.parent.children,i=n.i?r[n.i-1]:null;if(t.length){!function(n){var t,e=0,r=0,i=n.children,u=i.length;for(;--u>=0;)(t=i[u]).z+=e,t.m+=e,e+=t.s+(r+=t.c)}(n);var u=(t[0].z+t[t.length-1].z)/2;i?(n.z=i.z+e(n._,i._),n.m=n.z-u):n.z=u}else i&&(n.z=i.z+e(n._,i._));n.parent.A=function(n,t,r){if(t){for(var i,u=n,o=n,a=t,l=u.parent.children[0],c=u.m,f=o.m,s=a.m,h=l.m;a=oo(a),u=uo(u),a&&u;)l=uo(l),(o=oo(o)).a=n,(i=a.z+s-u.z-c+e(a._,u._))>0&&(ao(lo(a,n,r),n,i),c+=i,f+=i),s+=a.m,c+=u.m,h+=l.m,f+=o.m;a&&!oo(o)&&(o.t=a,o.m+=s-f),u&&!uo(l)&&(l.t=u,l.m+=c-h,r=n)}return r}(n,i,n.parent.A||r[0])}function a(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function l(n){n.x*=r[0],n.y=n.depth*r[1]}return u.separation=function(n){return arguments.length?(e=n,u):e},u.size=function(n){return arguments.length?(i=null==(r=n)?l:null,u):i?null:r},u.nodeSize=function(n){return arguments.length?(i=null==(r=n)?null:l,u):i?r:null},Cu(u,n)},t.layout.cluster=function(){var n=t.layout.hierarchy().sort(null).value(null),e=io,r=[1,1],i=!1;function u(u,o){var a,l=n.call(this,u,o),c=l[0],f=0;Lu(c,function(n){var r=n.children;r&&r.length?(n.x=function(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}(r),n.y=function(n){return 1+t.max(n,function(n){return n.y})}(r)):(n.x=a?f+=e(n,a):0,n.y=0,a=n)});var s=function n(t){var e=t.children;return e&&e.length?n(e[0]):t}(c),h=function n(t){var e,r=t.children;return r&&(e=r.length)?n(r[e-1]):t}(c),p=s.x-e(s,h)/2,g=h.x+e(h,s)/2;return Lu(c,i?function(n){n.x=(n.x-c.x)*r[0],n.y=(c.y-n.y)*r[1]}:function(n){n.x=(n.x-p)/(g-p)*r[0],n.y=(1-(c.y?n.y/c.y:1))*r[1]}),l}return u.separation=function(n){return arguments.length?(e=n,u):e},u.size=function(n){return arguments.length?(i=null==(r=n),u):i?null:r},u.nodeSize=function(n){return arguments.length?(i=null!=(r=n),u):i?r:null},Cu(u,n)},t.layout.treemap=function(){var n,e=t.layout.hierarchy(),r=Math.round,i=[1,1],u=null,o=co,a=!1,l="squarify",c=.5*(1+Math.sqrt(5));function f(n,t){for(var e,r,i=-1,u=n.length;++i0;)a.push(e=c[i-1]),a.area+=e.area,"squarify"!==l||(r=p(a,v))<=h?(c.pop(),h=r):(a.area-=a.pop().area,g(a,v,u,!1),v=Math.min(u.dx,u.dy),a.length=a.area=0,h=1/0);a.length&&(g(a,v,u,!0),a.length=a.area=0),t.forEach(s)}}function h(n){var t=n.children;if(t&&t.length){var e,r=o(n),i=t.slice(),u=[];for(f(i,r.dx*r.dy/n.value),u.area=0;e=i.pop();)u.push(e),u.area+=e.area,null!=e.z&&(g(u,e.z?r.dx:r.dy,r,!i.length),u.length=u.area=0);t.forEach(h)}}function p(n,t){for(var e,r=n.area,i=0,u=1/0,o=-1,a=n.length;++oi&&(i=e));return t*=t,(r*=r)?Math.max(t*i*c/r,r/(t*u*c)):1/0}function g(n,t,e,i){var u,o=-1,a=n.length,l=e.x,c=e.y,f=t?r(n.area/t):0;if(t==e.dx){for((i||f>e.dy)&&(f=e.dy);++oe.dx)&&(f=e.dx);++o1);return n+t*e*Math.sqrt(-2*Math.log(i)/i)}},logNormal:function(){var n=t.random.normal.apply(t,arguments);return function(){return Math.exp(n())}},bates:function(n){var e=t.random.irwinHall(n);return function(){return e()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;e2?mo:po,a=i?Mu:mu;return u=n(t,e,a,r),o=n(e,t,a,Gi),l}function l(n){return u(n)}l.invert=function(n){return o(n)};l.domain=function(n){return arguments.length?(t=n.map(Number),a()):t};l.range=function(n){return arguments.length?(e=n,a()):e};l.rangeRound=function(n){return l.range(n).interpolate(su)};l.clamp=function(n){return arguments.length?(i=n,a()):i};l.interpolate=function(n){return arguments.length?(r=n,a()):r};l.ticks=function(n){return _o(t,n)};l.tickFormat=function(n,e){return wo(t,n,e)};l.nice=function(n){return xo(t,n),a()};l.copy=function(){return n(t,e,r,i)};return a()}([0,1],[0,1],Gi,!1)};var So={s:1,g:1,p:1,r:1,e:1};function ko(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}t.scale.log=function(){return function n(e,r,i,u){function o(n){return(i?Math.log(n<0?0:n):-Math.log(n>0?0:-n))/Math.log(r)}function a(n){return i?Math.pow(r,n):-Math.pow(r,-n)}function l(n){return e(o(n))}l.invert=function(n){return a(e.invert(n))};l.domain=function(n){return arguments.length?(i=n[0]>=0,e.domain((u=n.map(Number)).map(o)),l):u};l.base=function(n){return arguments.length?(r=+n,e.domain(u.map(o)),l):r};l.nice=function(){var n=go(u.map(o),i?Math:Eo);return e.domain(n),u=n.map(a),l};l.ticks=function(){var n=so(u),t=[],e=n[0],l=n[1],c=Math.floor(o(e)),f=Math.ceil(o(l)),s=r%1?2:r;if(isFinite(f-c)){if(i){for(;c0;h--)t.push(a(c)*h);for(c=0;t[c]l;f--);t=t.slice(c,f)}return t};l.tickFormat=function(n,e){if(!arguments.length)return No;arguments.length<2?e=No:"function"!=typeof e&&(e=t.format(e));var i=Math.max(1,r*n/l.ticks().length);return function(n){var t=n/a(Math.round(o(n)));return t*r0?i[n-1]:e[0],ns?0:1;if(c=An)return l(c,p)+(a?l(a,1-p):"")+"Z";var g,v,d,y,m,M,x,b,_,w,S,k,N=0,E=0,A=[];if((y=(+o.apply(this,arguments)||0)/2)&&(d=r===Ro?Math.sqrt(a*a+c*c):+r.apply(this,arguments),p||(E*=-1),c&&(E=Dn(d/c*Math.sin(y))),a&&(N=Dn(d/a*Math.sin(y)))),c){m=c*Math.cos(f+E),M=c*Math.sin(f+E),x=c*Math.cos(s-E),b=c*Math.sin(s-E);var C=Math.abs(s-f-2*E)<=Nn?0:1;if(E&&Ho(m,M,x,b)===p^C){var z=(f+s)/2;m=c*Math.cos(z),M=c*Math.sin(z),x=b=null}}else m=M=0;if(a){_=a*Math.cos(s-N),w=a*Math.sin(s-N),S=a*Math.cos(f+N),k=a*Math.sin(f+N);var L=Math.abs(f-s+2*N)<=Nn?0:1;if(N&&Ho(_,w,S,k)===1-p^L){var q=(f+s)/2;_=a*Math.cos(q),w=a*Math.sin(q),S=k=null}}else _=w=0;if(h>Sn&&(g=Math.min(Math.abs(c-a)/2,+e.apply(this,arguments)))>.001){v=a0?0:1}function Oo(n,t,e,r,i){var u=n[0]-t[0],o=n[1]-t[1],a=(i?r:-r)/Math.sqrt(u*u+o*o),l=a*o,c=-a*u,f=n[0]+l,s=n[1]+c,h=t[0]+l,p=t[1]+c,g=(f+h)/2,v=(s+p)/2,d=h-f,y=p-s,m=d*d+y*y,M=e-r,x=f*p-h*s,b=(y<0?-1:1)*Math.sqrt(Math.max(0,M*M*m-x*x)),_=(x*y-d*b)/m,w=(-x*d-y*b)/m,S=(x*y+d*b)/m,k=(-x*d+y*b)/m,N=_-g,E=w-v,A=S-g,C=k-v;return N*N+E*E>A*A+C*C&&(_=S,w=k),[[_-l,w-c],[_*e/M,w*e/M]]}function Io(n){var t=ri,e=ii,r=We,i=Zo,u=i.key,o=.7;function a(u){var a,l=[],c=[],f=-1,s=u.length,h=yt(t),p=yt(e);function g(){l.push("M",i(n(c),o))}for(;++f1&&i.push("H",r[0]);return i.join("")},"step-before":Xo,"step-after":$o,basis:Jo,"basis-open":function(n){if(n.length<4)return Zo(n);var t,e=[],r=-1,i=n.length,u=[0],o=[0];for(;++r<3;)t=n[r],u.push(t[0]),o.push(t[1]);e.push(Go(na,u)+","+Go(na,o)),--r;for(;++r9&&(i=3*t/Math.sqrt(i),o[a]=i*e,o[a+1]=i*r));a=-1;for(;++a<=l;)i=(n[Math.min(l,a+1)][0]-n[Math.max(0,a-1)][0])/(6*(1+o[a]*o[a])),u.push([i||0,o[a]*i||0]);return u}(n))}});function Zo(n){return n.length>1?n.join("L"):n+"Z"}function Vo(n){return n.join("L")+"Z"}function Xo(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1){a=t[1],u=n[l],l++,r+="C"+(i[0]+o[0])+","+(i[1]+o[1])+","+(u[0]-a[0])+","+(u[1]-a[1])+","+u[0]+","+u[1];for(var c=2;cNn)+",1 "+t}function l(n,t,e,r){return"Q 0,0 "+r}return u.radius=function(n){return arguments.length?(e=yt(n),u):e},u.source=function(t){return arguments.length?(n=yt(t),u):n},u.target=function(n){return arguments.length?(t=yt(n),u):t},u.startAngle=function(n){return arguments.length?(r=yt(n),u):r},u.endAngle=function(n){return arguments.length?(i=yt(n),u):i},u},t.svg.diagonal=function(){var n=Yr,t=Zr,e=oa;function r(r,i){var u=n.call(this,r,i),o=t.call(this,r,i),a=(u.y+o.y)/2,l=[u,{x:u.x,y:a},{x:o.x,y:a},o];return"M"+(l=l.map(e))[0]+"C"+l[1]+" "+l[2]+" "+l[3]}return r.source=function(t){return arguments.length?(n=yt(t),r):n},r.target=function(n){return arguments.length?(t=yt(n),r):t},r.projection=function(n){return arguments.length?(e=n,r):e},r},t.svg.diagonal.radial=function(){var n=t.svg.diagonal(),e=oa,r=n.projection;return n.projection=function(n){return arguments.length?r(function(n){return function(){var t=n.apply(this,arguments),e=t[0],r=t[1]-Cn;return[e*Math.cos(r),e*Math.sin(r)]}}(e=n)):e},n},t.svg.symbol=function(){var n=la,t=aa;function e(e,r){return(fa.get(n.call(this,e,r))||ca)(t.call(this,e,r))}return e.type=function(t){return arguments.length?(n=yt(t),e):n},e.size=function(n){return arguments.length?(t=yt(n),e):t},e};var fa=t.map({circle:ca,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*ha)),e=t*ha;return"M0,"+-t+"L"+e+",0 0,"+t+" "+-e+",0Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/sa),e=t*sa/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/sa),e=t*sa/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});t.svg.symbolTypes=fa.keys();var sa=Math.sqrt(3),ha=Math.tan(30*zn);$.transition=function(n){for(var t,e,r=da||++Ma,i=_a(n),u=[],o=ya||{time:Date.now(),ease:ou,delay:0,duration:250},a=-1,l=this.length;++a0;)c[--h].call(n,o);if(u>=1)return s.event&&s.event.end.call(n,n.__data__,t),--f.count?delete f[r]:delete n[e],1}s||(u=i.time,o=kt(function(n){var t=s.delay;if(o.t=t+u,t<=n)return h(n-t);o.c=h},0,u),s=f[r]={tween:new x,time:u,timer:o,delay:i.delay,duration:i.duration,ease:i.ease,index:t},i=null,++f.count)}ma.call=$.call,ma.empty=$.empty,ma.node=$.node,ma.size=$.size,t.transition=function(n,e){return n&&n.transition?da?n.transition(e):n:t.selection().transition(n)},t.transition.prototype=ma,ma.select=function(n){var t,e,r,i=this.id,u=this.namespace,o=[];n=B(n);for(var a=-1,l=this.length;++arect,.s>rect").attr("width",a[1]-a[0])}function v(n){n.select(".extent").attr("y",l[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",l[1]-l[0])}function d(){var s,d,y=this,m=t.select(t.event.target),M=r.of(y,arguments),x=t.select(y),b=m.datum(),_=!/^(n|s)$/.test(b)&&i,w=!/^(e|w)$/.test(b)&&u,S=m.classed("extent"),k=xn(y),N=t.mouse(y),E=t.select(o(y)).on("keydown.brush",function(){32==t.event.keyCode&&(S||(s=null,N[0]-=a[1],N[1]-=l[1],S=2),j())}).on("keyup.brush",function(){32==t.event.keyCode&&2==S&&(N[0]+=a[1],N[1]+=l[1],S=0,j())});if(t.event.changedTouches?E.on("touchmove.brush",z).on("touchend.brush",q):E.on("mousemove.brush",z).on("mouseup.brush",q),x.interrupt().selectAll("*").interrupt(),S)N[0]=a[0]-N[0],N[1]=l[0]-N[1];else if(b){var A=+/w$/.test(b),C=+/^n/.test(b);d=[a[1-A]-N[0],l[1-C]-N[1]],N[0]=a[A],N[1]=l[C]}else t.event.altKey&&(s=N.slice());function z(){var n=t.mouse(y),e=!1;d&&(n[0]+=d[0],n[1]+=d[1]),S||(t.event.altKey?(s||(s=[(a[0]+a[1])/2,(l[0]+l[1])/2]),N[0]=a[+(n[0]1?{floor:function(t){for(;a(t=n.floor(t));)t=Da(t-1);return t},ceil:function(t){for(;a(t=n.ceil(t));)t=Da(+t+1);return t}}:n))},i.ticks=function(n,t){var e=so(i.domain()),r=null==n?u(e,10):"number"==typeof n?u(e,n):!n.range&&[{range:n},t];return r&&(n=r[0],t=r[1]),n.range(e[0],Da(+e[1]+1),t<1?1:t)},i.tickFormat=function(){return r},i.copy=function(){return Ra(n.copy(),e,r)},Mo(i,n)}function Da(n){return new Date(n)}za.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?Ta:qa,Ta.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},Ta.toString=qa.toString,Dt.second=Ft(function(n){return new Pt(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),Dt.seconds=Dt.second.range,Dt.seconds.utc=Dt.second.utc.range,Dt.minute=Ft(function(n){return new Pt(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),Dt.minutes=Dt.minute.range,Dt.minutes.utc=Dt.minute.utc.range,Dt.hour=Ft(function(n){var t=n.getTimezoneOffset()/60;return new Pt(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),Dt.hours=Dt.hour.range,Dt.hours.utc=Dt.hour.utc.range,Dt.month=Ft(function(n){return(n=Dt.day(n)).setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),Dt.months=Dt.month.range,Dt.months.utc=Dt.month.utc.range;var Pa=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Ua=[[Dt.second,1],[Dt.second,5],[Dt.second,15],[Dt.second,30],[Dt.minute,1],[Dt.minute,5],[Dt.minute,15],[Dt.minute,30],[Dt.hour,1],[Dt.hour,3],[Dt.hour,6],[Dt.hour,12],[Dt.day,1],[Dt.day,2],[Dt.week,1],[Dt.month,1],[Dt.month,3],[Dt.year,1]],ja=za.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",We]]),Fa={range:function(n,e,r){return t.range(Math.ceil(n/r)*r,+e,r).map(Da)},floor:L,ceil:L};Ua.year=Dt.year,Dt.scale=function(){return Ra(t.scale.linear(),Ua,ja)};var Ha=Ua.map(function(n){return[n[0].utc,n[1]]}),Oa=La.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",We]]);function Ia(n){return JSON.parse(n.responseText)}function Ya(n){var t=i.createRange();return t.selectNode(i.body),t.createContextualFragment(n.responseText)}Ha.year=Dt.year.utc,Dt.scale.utc=function(){return Ra(t.scale.linear(),Ha,Oa)},t.text=mt(function(n){return n.responseText}),t.json=function(n,t){return Mt(n,"application/json",Ia,t)},t.html=function(n,t){return Mt(n,"text/html",Ya,t)},t.xml=mt(function(n){return n.responseXML}),"function"==typeof n&&n.amd?(this.d3=t,n(t)):"object"==typeof module&&module.exports?module.exports=t:this.d3=t}(); +},{}],"vXBk":[function(require,module,exports) { +!function(){var t={dev:!1};t.tooltip=t.tooltip||{},t.utils=t.utils||{},t.models=t.models||{},t.charts={},t.logs={},t.dom={},"undefined"!=typeof module&&"undefined"!=typeof exports&&"undefined"==typeof d3&&(d3=require("d3")),t.dispatch=d3.dispatch("render_start","render_end"),Function.prototype.bind||(Function.prototype.bind=function(t){if("function"!=typeof this)throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var e=Array.prototype.slice.call(arguments,1),n=this,r=function(){},i=function(){return n.apply(this instanceof r&&t?this:t,e.concat(Array.prototype.slice.call(arguments)))};return r.prototype=this.prototype,i.prototype=new r,i}),t.dev&&(t.dispatch.on("render_start",function(e){t.logs.startTime=+new Date}),t.dispatch.on("render_end",function(e){t.logs.endTime=+new Date,t.logs.totalTime=t.logs.endTime-t.logs.startTime,t.log("total",t.logs.totalTime)})),t.log=function(){if(t.dev&&window.console&&console.log&&console.log.apply)console.log.apply(console,arguments);else if(t.dev&&window.console&&"function"==typeof console.log&&Function.prototype.bind){Function.prototype.bind.call(console.log,console).apply(console,arguments)}return arguments[arguments.length-1]},t.deprecated=function(t,e){console&&console.warn&&console.warn("nvd3 warning: `"+t+"` has been deprecated. ",e||"")},t.render=function(e){e=e||1,t.render.active=!0,t.dispatch.render_start();var n=function(){for(var r,i,o=0;op||n>h||d3.event.relatedTarget&&void 0===d3.event.relatedTarget.ownerSVGElement||a){if(u&&d3.event.relatedTarget&&void 0===d3.event.relatedTarget.ownerSVGElement&&(void 0===d3.event.relatedTarget.className||d3.event.relatedTarget.className.match(l.nvPointerEventsClass)))return;return o.elementMouseout({mouseX:t,mouseY:n}),c.renderGuideLine(null),void l.hidden(!0)}l.hidden(!1);var s=void 0;if("function"==typeof i.rangeBands){var d=d3.bisect(i.range(),t)-1;if(!(i.range()[d]+i.rangeBand()>=t))return o.elementMouseout({mouseX:t,mouseY:n}),c.renderGuideLine(null),void l.hidden(!0);s=i.domain()[d3.bisect(i.range(),t)-1]}else s=i.invert(t);o.elementMousemove({mouseX:t,mouseY:n,pointXValue:s}),"dblclick"===d3.event.type&&o.elementDblclick({mouseX:t,mouseY:n,pointXValue:s}),"click"===d3.event.type&&o.elementClick({mouseX:t,mouseY:n,pointXValue:s}),"mousedown"===d3.event.type&&o.elementMouseDown({mouseX:t,mouseY:n,pointXValue:s}),"mouseup"===d3.event.type&&o.elementMouseUp({mouseX:t,mouseY:n,pointXValue:s})}g.enter().append("g").attr("class"," nv-wrap nv-interactiveLineLayer").append("g").attr("class","nv-interactiveGuideLine"),s&&(s.on("touchmove",v).on("mousemove",v,!0).on("mouseout",v,!0).on("mousedown",v,!0).on("mouseup",v,!0).on("dblclick",v).on("click",v),c.guideLine=null,c.renderGuideLine=function(e){a&&(c.guideLine&&c.guideLine.attr("x1")===e||t.dom.write(function(){var n=g.select(".nv-interactiveGuideLine").selectAll("line").data(null!=e?[t.utils.NaNtoZero(e)]:[],String);n.enter().append("line").attr("class","nv-guideline").attr("x1",function(t){return t}).attr("x2",function(t){return t}).attr("y1",h).attr("y2",0),n.exit().remove()}))})})}return l.duration(0).hideDelay(0).hidden(!1),c.dispatch=o,c.tooltip=l,c.margin=function(t){return arguments.length?(e.top=void 0!==t.top?t.top:e.top,e.left=void 0!==t.left?t.left:e.left,c):e},c.width=function(t){return arguments.length?(n=t,c):n},c.height=function(t){return arguments.length?(r=t,c):r},c.xScale=function(t){return arguments.length?(i=t,c):i},c.showGuideLine=function(t){return arguments.length?(a=t,c):a},c.svgContainer=function(t){return arguments.length?(s=t,c):s},c},t.interactiveBisect=function(t,e,n){"use strict";if(!(t instanceof Array))return null;var r;r="function"!=typeof n?function(t){return t.x}:n;var i=d3.bisector(function(t,e){return r(t)-e}).left,o=d3.max([0,i(t,e)-1]),a=r(t[o]);if(void 0===a&&(a=o),a===e)return o;var s=d3.min([o+1,t.length-1]),l=r(t[s]);return void 0===l&&(l=s),Math.abs(l-e)>=Math.abs(a-e)?o:s},t.nearestValueIndex=function(t,e,n){"use strict";var r=1/0,i=null;return t.forEach(function(t,o){var a=Math.abs(e-t);null!=t&&a<=r&&a"+t.footer+""),i},x=function(){var t={left:null!==d3.event?d3.event.clientX:0,top:null!==d3.event?d3.event.clientY:0};if("none"!=getComputedStyle(document.body).transform){var e=document.body.getBoundingClientRect();t.left-=e.left,t.top-=e.top}return t},b=function(e){if(e&&e.series){if(t.utils.isArray(e.series))return!0;if(t.utils.isObject(e.series))return e.series=[e.series],!0}return!1},k=function(){t.dom.read(function(){var t=x(),e=function(t){var e,n,o,a=u.node().offsetHeight,s=u.node().offsetWidth,l=document.documentElement.clientWidth,c=document.documentElement.clientHeight;switch(r){case"e":e=-s-i,n=-a/2,t.left+e<0&&(e=i),(o=t.top+n)<0&&(n-=o),(o=t.top+n+a)>c&&(n-=o-c);break;case"w":e=i,n=-a/2,t.left+e+s>l&&(e=-s-i),(o=t.top+n)<0&&(n-=o),(o=t.top+n+a)>c&&(n-=o-c);break;case"n":e=-s/2-5,n=i,t.top+n+a>c&&(n=-a-i),(o=t.left+e)<0&&(e-=o),(o=t.left+e+s)>l&&(e-=o-l);break;case"s":e=-s/2,n=-a-i,t.top+n<0&&(n=i),(o=t.left+e)<0&&(e-=o),(o=t.left+e+s)>l&&(e-=o-l);break;case"center":e=-s/2,n=-a/2;break;default:e=0,n=0}return{left:e,top:n}}(t),n=t.left+e.left,o=t.top+e.top;if(s)u.interrupt().transition().delay(l).duration(0).style("opacity",0);else{var a="translate("+c.left+"px, "+c.top+"px)",d="translate("+Math.round(n)+"px, "+Math.round(o)+"px)",p=d3.interpolateString(a,d),h=u.style("opacity")<.1;u.interrupt().transition().duration(h?0:f).styleTween("transform",function(t){return p},"important").styleTween("-webkit-transform",function(t){return p}).style("-ms-transform",d).style("opacity",1)}c.left=n,c.top=o})};function w(){if(d&&b(n))return t.dom.write(function(){u&&u.node()||((u=d3.select(document.body).select("#"+e).data([1])).enter().append("div").attr("class","nvtooltip "+(a||"xy-tooltip")).attr("id",e).style("top",0).style("left",0).style("opacity",0).style("position","fixed").selectAll("div, table, td, tr").classed(h,!0).classed(h,!0),u.exit().remove());var t=y(n,u.node());t&&(u.node().innerHTML=t),k()}),w}return w.nvPointerEventsClass=h,w.options=t.utils.optionsFunc.bind(w),w._options=Object.create({},{duration:{get:function(){return f},set:function(t){f=t}},gravity:{get:function(){return r},set:function(t){r=t}},distance:{get:function(){return i},set:function(t){i=t}},snapDistance:{get:function(){return o},set:function(t){o=t}},classes:{get:function(){return a},set:function(t){a=t}},enabled:{get:function(){return d},set:function(t){d=t}},hideDelay:{get:function(){return l},set:function(t){l=t}},contentGenerator:{get:function(){return y},set:function(t){y=t}},valueFormatter:{get:function(){return g},set:function(t){g=t}},headerFormatter:{get:function(){return v},set:function(t){v=t}},keyFormatter:{get:function(){return m},set:function(t){m=t}},headerEnabled:{get:function(){return p},set:function(t){p=t}},position:{get:function(){return x},set:function(t){x=t}},chartContainer:{get:function(){return document.body},set:function(e){t.deprecated("chartContainer","feature removed after 1.8.3")}},fixedTop:{get:function(){return null},set:function(e){t.deprecated("fixedTop","feature removed after 1.8.1")}},offset:{get:function(){return{left:0,top:0}},set:function(e){t.deprecated("offset","use chart.tooltip.distance() instead")}},hidden:{get:function(){return s},set:function(t){s!=t&&(s=!!t,w())}},data:{get:function(){return n},set:function(t){t.point&&(t.value=t.point.x,t.series=t.series||{},t.series.value=t.point.y,t.series.color=t.point.color||t.series.color),n=t}},node:{get:function(){return u.node()},set:function(t){}},id:{get:function(){return e},set:function(t){}}}),t.utils.initOptions(w),w},t.utils.windowSize=function(){var t={width:640,height:480};return window.innerWidth&&window.innerHeight?(t.width=window.innerWidth,t.height=window.innerHeight,t):"CSS1Compat"==document.compatMode&&document.documentElement&&document.documentElement.offsetWidth?(t.width=document.documentElement.offsetWidth,t.height=document.documentElement.offsetHeight,t):document.body&&document.body.offsetWidth?(t.width=document.body.offsetWidth,t.height=document.body.offsetHeight,t):t},t.utils.isArray=Array.isArray,t.utils.isObject=function(t){return null!==t&&"object"==typeof t},t.utils.isFunction=function(t){return"function"==typeof t},t.utils.isDate=function(t){return"[object Date]"===toString.call(t)},t.utils.isNumber=function(t){return!isNaN(t)&&"number"==typeof t},t.utils.windowResize=function(e){return window.addEventListener?window.addEventListener("resize",e):t.log("ERROR: Failed to bind to window.resize with: ",e),{callback:e,clear:function(){window.removeEventListener("resize",e)}}},t.utils.getColor=function(e){if(void 0===e)return t.utils.defaultColor();if(t.utils.isArray(e)){var n=d3.scale.ordinal().range(e);return function(t,e){var r=void 0===e?t:e;return t.color||n(r)}}return e},t.utils.defaultColor=function(){return t.utils.getColor(d3.scale.category20().range())},t.utils.customTheme=function(e,n,r){n=n||function(t){return t.key};var i=(r=r||d3.scale.category20().range()).length;return function(o,a){var s=n(o);return t.utils.isFunction(e[s])?e[s]():void 0!==e[s]?e[s]:(i||(i=r.length),r[i-=1])}},t.utils.pjax=function(e,n){var r=function(r){d3.html(r,function(r){var i=d3.select(n).node();i.parentNode.replaceChild(d3.select(r).select(n).node(),i),t.utils.pjax(e,n)})};d3.selectAll(e).on("click",function(){history.pushState(this.href,this.textContent,this.href),r(this.href),d3.event.preventDefault()}),d3.select(window).on("popstate",function(){d3.event.state&&r(d3.event.state)})},t.utils.calcApproxTextWidth=function(e){if(t.utils.isFunction(e.style)&&t.utils.isFunction(e.text)){var n=parseInt(e.style("font-size").replace("px",""),10),r=e.text().length;return t.utils.NaNtoZero(r*n*.5)}return 0},t.utils.NaNtoZero=function(e){return!t.utils.isNumber(e)||isNaN(e)||null===e||e===1/0||e===-1/0?0:e},d3.selection.prototype.watchTransition=function(t){var e=[this].concat([].slice.call(arguments,1));return t.transition.apply(t,e)},t.utils.renderWatch=function(e,n){if(!(this instanceof t.utils.renderWatch))return new t.utils.renderWatch(e,n);var r=void 0!==n?n:250,i=[],o=this;this.models=function(t){return(t=[].slice.call(arguments,0)).forEach(function(t){var e;t.__rendered=!1,(e=t).dispatch.on("renderEnd",function(t){e.__rendered=!0,o.renderEnd("model")}),i.indexOf(t)<0&&i.push(t)}),this},this.reset=function(t){void 0!==t&&(r=t),i=[]},this.transition=function(t,e,n){if(n=(e=arguments.length>1?[].slice.call(arguments,1):[]).length>1?e.pop():void 0!==r?r:250,t.__rendered=!1,i.indexOf(t)<0&&i.push(t),0===n)return t.__rendered=!0,t.delay=function(){return this},t.duration=function(){return this},t;0===t.length?t.__rendered=!0:t.every(function(t){return!t.length})?t.__rendered=!0:t.__rendered=!1;var a=0;return t.transition().duration(n).each(function(){++a}).each("end",function(n,r){0==--a&&(t.__rendered=!0,o.renderEnd.apply(this,e))})},this.renderEnd=function(){i.every(function(t){return t.__rendered})&&(i.forEach(function(t){t.__rendered=!1}),e.renderEnd.apply(this,arguments))}},t.utils.deepExtend=function(e){(arguments.length>1?[].slice.call(arguments,1):[]).forEach(function(n){for(var r in n){var i=t.utils.isArray(e[r]),o=t.utils.isObject(e[r]),a=t.utils.isObject(n[r]);o&&!i&&a?t.utils.deepExtend(e[r],n[r]):e[r]=n[r]}})},t.utils.state=function(){if(!(this instanceof t.utils.state))return new t.utils.state;var e={},n=function(){},r=function(){return{}},i=null;this.dispatch=d3.dispatch("change","set"),this.dispatch.on("set",function(t){n(t,!0)}),this.getter=function(t){return r=t,this},this.setter=function(t,e){return e||(e=function(){}),n=function(n,r){t(n),r&&e()},this},this.init=function(e){i=i||{},t.utils.deepExtend(i,e)};this.update=function(){i&&(n(i,!1),i=null),function(){var t=r();if(JSON.stringify(t)===JSON.stringify(e))return!1;for(var n in t)void 0===e[n]&&(e[n]={}),e[n]=t[n],!0;return!0}.call(this)&&this.dispatch.change(e)}},t.utils.optionsFunc=function(e){return e&&d3.map(e).forEach(function(e,n){t.utils.isFunction(this[e])&&this[e](n)}.bind(this)),this},t.utils.calcTicksX=function(e,n){for(var r=1,i=0;ir?o:r}return t.log("Requested number of ticks: ",e),t.log("Calculated max values to be: ",r),e=(e=e>r?e=r-1:e)<1?1:e,e=Math.floor(e),t.log("Calculating tick count as: ",e),e},t.utils.calcTicksY=function(e,n){return t.utils.calcTicksX(e,n)},t.utils.initOption=function(t,e){t._calls&&t._calls[e]?t[e]=t._calls[e]:(t[e]=function(n){return arguments.length?(t._overrides[e]=!0,t._options[e]=n,t):t._options[e]},t["_"+e]=function(n){return arguments.length?(t._overrides[e]||(t._options[e]=n),t):t._options[e]})},t.utils.initOptions=function(e){e._overrides=e._overrides||{};var n=Object.getOwnPropertyNames(e._options||{}),r=Object.getOwnPropertyNames(e._calls||{});for(var i in n=n.concat(r))t.utils.initOption(e,n[i])},t.utils.inheritOptionsD3=function(t,e,n){t._d3options=n.concat(t._d3options||[]),t._d3options=(t._d3options||[]).filter(function(t,e,n){return n.indexOf(t)===e}),n.unshift(e),n.unshift(t),d3.rebind.apply(this,n)},t.utils.arrayUnique=function(t){return t.sort().filter(function(e,n){return!n||e!=t[n-1]})},t.utils.symbolMap=d3.map(),t.utils.symbol=function(){var e,n=64;function r(r,i){var o=e.call(this,r,i),a=n.call(this,r,i);return-1!==d3.svg.symbolTypes.indexOf(o)?d3.svg.symbol().type(o).size(a)():t.utils.symbolMap.get(o)(a)}return r.type=function(t){return arguments.length?(e=d3.functor(t),r):e},r.size=function(t){return arguments.length?(n=d3.functor(t),r):n},r},t.utils.inheritOptions=function(e,n){var r=Object.getOwnPropertyNames(n._options||{}),i=Object.getOwnPropertyNames(n._calls||{}),o=n._inherited||[],a=n._d3options||[],s=r.concat(i).concat(o).concat(a);s.unshift(n),s.unshift(e),d3.rebind.apply(this,s),e._inherited=t.utils.arrayUnique(r.concat(i).concat(o).concat(r).concat(e._inherited||[])),e._d3options=t.utils.arrayUnique(a.concat(e._d3options||[]))},t.utils.initSVG=function(t){t.classed({"nvd3-svg":!0})},t.utils.sanitizeHeight=function(t,e){return t||parseInt(e.style("height"),10)||400},t.utils.sanitizeWidth=function(t,e){return t||parseInt(e.style("width"),10)||960},t.utils.availableHeight=function(e,n,r){return Math.max(0,t.utils.sanitizeHeight(e,n)-r.top-r.bottom)},t.utils.availableWidth=function(e,n,r){return Math.max(0,t.utils.sanitizeWidth(e,n)-r.left-r.right)},t.utils.noData=function(e,n){var r=e.options(),i=r.margin(),o=r.noData(),a=null==o?["No Data Available."]:[o],s=t.utils.availableHeight(null,n,i),l=t.utils.availableWidth(null,n,i),u=i.left+l/2,c=i.top+s/2;n.selectAll("g").remove();var d=n.selectAll(".nv-noData").data(a);d.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),d.attr("x",u).attr("y",c).text(function(t){return t})},t.utils.wrapTicks=function(t,e){t.each(function(){for(var t,n=d3.select(this),r=n.text().split(/\s+/).reverse(),i=[],o=0,a=n.attr("y"),s=parseFloat(n.attr("dy")),l=n.text(null).append("tspan").attr("x",0).attr("y",a).attr("dy",s+"em");t=r.pop();)i.push(t),l.text(i.join(" ")),l.node().getComputedTextLength()>e&&(i.pop(),l.text(i.join(" ")),i=[t],l=n.append("tspan").attr("x",0).attr("y",a).attr("dy",1.1*++o+s+"em").text(t))})},t.utils.arrayEquals=function(e,n){if(e===n)return!0;if(!e||!n)return!1;if(e.length!=n.length)return!1;for(var r=0,i=e.length;r2&&(A=i.range()[i.range().length-1]+(i.range()[1]-i.range()[0])),C.attr("text-anchor","middle").attr("y",0).attr("x",A/2),u&&((M=y.selectAll("g.nv-axisMaxMin").data(i.domain())).enter().append("g").attr("class",function(t,e){return["nv-axisMaxMin","nv-axisMaxMin-x",0==e?"nv-axisMin-x":"nv-axisMax-x"].join(" ")}).append("text"),M.exit().remove(),M.attr("transform",function(e,n){return"translate("+t.utils.NaNtoZero(i(e))+",0)"}).select("text").attr("dy","-0.5em").attr("y",-r.tickPadding()).attr("text-anchor","middle").text(function(t,n){var r=(e||k)(t);return(""+r).match("NaN")?"":r}),M.watchTransition(x,"min-max top").attr("transform",function(e,n){return"translate("+t.utils.NaNtoZero(i.range()[n])+",0)"}));break;case"bottom":w=g+36;var S=30,W=0,E=b.selectAll("g").select("text"),F="";if(c%360){E.attr("transform",""),E.each(function(t,e){var n=this.getBoundingClientRect(),r=n.width;W=n.height,r>S&&(S=r)}),F="rotate("+c+" 0,"+(W/2+r.tickPadding())+")";var N=Math.abs(Math.sin(c*Math.PI/180));w=(N?N*S:S)+30,E.attr("transform",F).style("text-anchor",c%360>0?"start":"end")}else f?E.attr("transform",function(t,e){return"translate(0,"+(e%2==0?"0":"12")+")"}):E.attr("transform","translate(0,0)");C.enter().append("text").attr("class","nv-axislabel"),A=0,1===i.range().length?A=p?2*i.range()[0]+i.rangeBand():0:2===i.range().length?A=p?i.range()[0]+i.range()[1]+i.rangeBand():i.range()[1]:i.range().length>2&&(A=i.range()[i.range().length-1]+(i.range()[1]-i.range()[0])),C.attr("text-anchor","middle").attr("y",w).attr("x",A/2),u&&((M=y.selectAll("g.nv-axisMaxMin").data([i.domain()[0],i.domain()[i.domain().length-1]])).enter().append("g").attr("class",function(t,e){return["nv-axisMaxMin","nv-axisMaxMin-x",0==e?"nv-axisMin-x":"nv-axisMax-x"].join(" ")}).append("text"),M.exit().remove(),M.attr("transform",function(e,n){return"translate("+t.utils.NaNtoZero(i(e)+(p?i.rangeBand()/2:0))+",0)"}).select("text").attr("dy",".71em").attr("y",r.tickPadding()).attr("transform",F).style("text-anchor",c?c%360>0?"start":"end":"middle").text(function(t,n){var r=(e||k)(t);return(""+r).match("NaN")?"":r}),M.watchTransition(x,"min-max bottom").attr("transform",function(e,n){return"translate("+t.utils.NaNtoZero(i(e)+(p?i.rangeBand()/2:0))+",0)"}));break;case"right":C.enter().append("text").attr("class","nv-axislabel"),C.style("text-anchor",d?"middle":"begin").attr("transform",d?"rotate(90)":"").attr("y",d?12-Math.max(o.right,a)-(g||0):-10).attr("x",d?d3.max(i.range())/2:r.tickPadding()),u&&((M=y.selectAll("g.nv-axisMaxMin").data(i.domain())).enter().append("g").attr("class",function(t,e){return["nv-axisMaxMin","nv-axisMaxMin-y",0==e?"nv-axisMin-y":"nv-axisMax-y"].join(" ")}).append("text").style("opacity",0),M.exit().remove(),M.attr("transform",function(e,n){return"translate(0,"+t.utils.NaNtoZero(i(e))+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",r.tickPadding()).style("text-anchor","start").text(function(t,n){var r=(e||k)(t);return(""+r).match("NaN")?"":r}),M.watchTransition(x,"min-max right").attr("transform",function(e,n){return"translate(0,"+t.utils.NaNtoZero(i.range()[n])+")"}).select("text").style("opacity",1));break;case"left":C.enter().append("text").attr("class","nv-axislabel"),C.style("text-anchor",d?"middle":"end").attr("transform",d?"rotate(-90)":"").attr("y",d?25-Math.max(o.left,a)-(g||0):-10).attr("x",d?-d3.max(i.range())/2:-r.tickPadding()),u&&((M=y.selectAll("g.nv-axisMaxMin").data(i.domain())).enter().append("g").attr("class",function(t,e){return["nv-axisMaxMin","nv-axisMaxMin-y",0==e?"nv-axisMin-y":"nv-axisMax-y"].join(" ")}).append("text").style("opacity",0),M.exit().remove(),M.attr("transform",function(e,r){return"translate(0,"+t.utils.NaNtoZero(n(e))+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",-r.tickPadding()).attr("text-anchor","end").text(function(t,n){var r=(e||k)(t);return(""+r).match("NaN")?"":r}),M.watchTransition(x,"min-max right").attr("transform",function(e,n){return"translate(0,"+t.utils.NaNtoZero(i.range()[n])+")"}).select("text").style("opacity",1))}if(C.text(function(t){return t}),!u||"left"!==r.orient()&&"right"!==r.orient()||(b.selectAll("g").each(function(t,e){d3.select(this).select("text").attr("opacity",1),(i(t)i.range()[0]-10)&&((t>1e-10||t<-1e-10)&&d3.select(this).attr("opacity",0),d3.select(this).select("text").attr("opacity",0))}),i.domain()[0]==i.domain()[1]&&0==i.domain()[0]&&y.selectAll("g.nv-axisMaxMin").style("opacity",function(t,e){return e?0:1})),u&&("top"===r.orient()||"bottom"===r.orient())){var D=[];y.selectAll("g.nv-axisMaxMin").each(function(t,e){try{e?D.push(i(t)-this.getBoundingClientRect().width-4):D.push(i(t)+this.getBoundingClientRect().width+4)}catch(n){e?D.push(i(t)-4):D.push(i(t)+4)}}),b.selectAll("g").each(function(t,e){(i(t)D[1])&&(t>1e-10||t<-1e-10?d3.select(this).remove():d3.select(this).select("text").remove())})}b.selectAll(".tick").filter(function(t){return!parseFloat(Math.round(1e5*t)/1e6)&&void 0!==t}).classed("zero",!0),n=i.copy()}),x.renderEnd("axis immediate"),b}return b.axis=r,b.dispatch=y,b.options=t.utils.optionsFunc.bind(b),b._options=Object.create({},{axisLabelDistance:{get:function(){return g},set:function(t){g=t}},staggerLabels:{get:function(){return f},set:function(t){f=t}},rotateLabels:{get:function(){return c},set:function(t){c=t}},rotateYLabel:{get:function(){return d},set:function(t){d=t}},showMaxMin:{get:function(){return u},set:function(t){u=t}},axisLabel:{get:function(){return l},set:function(t){l=t}},height:{get:function(){return s},set:function(t){s=t}},ticks:{get:function(){return h},set:function(t){h=t}},width:{get:function(){return a},set:function(t){a=t}},fontSize:{get:function(){return v},set:function(t){v=t}},tickFormatMaxMin:{get:function(){return e},set:function(t){e=t}},margin:{get:function(){return o},set:function(t){o.top=void 0!==t.top?t.top:o.top,o.right=void 0!==t.right?t.right:o.right,o.bottom=void 0!==t.bottom?t.bottom:o.bottom,o.left=void 0!==t.left?t.left:o.left}},duration:{get:function(){return m},set:function(t){m=t,x.reset(m)}},scale:{get:function(){return i},set:function(e){i=e,r.scale(i),p="function"==typeof i.rangeBands,t.utils.inheritOptionsD3(b,i,["domain","range","rangeBand","rangeBands"])}}}),t.utils.initOptions(b),t.utils.inheritOptionsD3(b,r,["orient","tickValues","tickSubdivide","tickSize","tickPadding","tickFormat"]),t.utils.inheritOptionsD3(b,i,["domain","range","rangeBand","rangeBands"]),b},t.models.boxPlot=function(){"use strict";var e,n,r,i,o,a,s={top:0,right:0,bottom:0,left:0},l=960,u=500,c=Math.floor(1e4*Math.random()),d=d3.scale.ordinal(),f=d3.scale.linear(),p=function(t){return t.label},h=function(t){return t.values.Q1},g=function(t){return t.values.Q2},v=function(t){return t.values.Q3},m=function(t){return t.values.whisker_low},y=function(t){return t.values.whisker_high},x=function(t){return t.color},b=function(t){return t.values.outliers},k=function(t,e,n){return t},w=function(t,e,n){return t},M=function(t,e,n){},A=t.utils.defaultColor(),C=null,S=d3.dispatch("elementMouseover","elementMouseout","elementMousemove","renderEnd"),W=250,E=null,F=t.utils.renderWatch(S,W);function N(c){return F.reset(),c.each(function(c){var N=l-s.left-s.right,D=u-s.top-s.bottom;C=d3.select(this),t.utils.initSVG(C),d.domain(e||c.map(function(t,e){return p(t,e)})).rangeBands(n||[0,N],.1);var O=[];if(!r){var _=[];c.forEach(function(t,e){var n=h(t),r=v(t),i=m(t),o=y(t),a=b(t);a&&a.forEach(function(t,e){_.push(k(t,e,void 0))}),i&&_.push(i),n&&_.push(n),r&&_.push(r),o&&_.push(o)}),O=[d3.min(_),d3.max(_)]}f.domain(r||O),f.range(i||[D,0]),o=o||d,a=a||f.copy().range([f(0),f(0)]);var L=C.selectAll("g.nv-wrap").data([c]);L.enter().append("g").attr("class","nvd3 nv-wrap");L.attr("transform","translate("+s.left+","+s.top+")");var B=L.selectAll(".nv-boxplot").data(function(t){return t}),P=B.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6);B.attr("class","nv-boxplot").attr("transform",function(t,e,n){return"translate("+(d(p(t,e))+.05*d.rangeBand())+", 0)"}).classed("hover",function(t){return t.hover}),B.watchTransition(F,"nv-boxplot: boxplots").style("stroke-opacity",1).style("fill-opacity",.75).delay(function(t,e){return e*W/c.length}).attr("transform",function(t,e){return"translate("+(d(p(t,e))+.05*d.rangeBand())+", 0)"}),B.exit().remove(),P.each(function(t,e){var n=d3.select(this);[m,y].forEach(function(r){if(void 0!==r(t)&&null!==r(t)){var i=r===m?"low":"high";n.append("line").style("stroke",x(t)||A(t,e)).attr("class","nv-boxplot-whisker nv-boxplot-"+i),n.append("line").style("stroke",x(t)||A(t,e)).attr("class","nv-boxplot-tick nv-boxplot-"+i)}})});var T=function(){return null===E?.9*d.rangeBand():Math.min(75,.9*d.rangeBand())},I=function(){return.45*d.rangeBand()-T()/2},z=function(){return.45*d.rangeBand()+T()/2};[m,y].forEach(function(t){var e=t===m?"low":"high",n=t===m?h:v;B.select("line.nv-boxplot-whisker.nv-boxplot-"+e).watchTransition(F,"nv-boxplot: boxplots").attr("x1",.45*d.rangeBand()).attr("y1",function(e,n){return f(t(e))}).attr("x2",.45*d.rangeBand()).attr("y2",function(t,e){return f(n(t))}),B.select("line.nv-boxplot-tick.nv-boxplot-"+e).watchTransition(F,"nv-boxplot: boxplots").attr("x1",I).attr("y1",function(e,n){return f(t(e))}).attr("x2",z).attr("y2",function(e,n){return f(t(e))})}),[m,y].forEach(function(t){var e=t===m?"low":"high";P.selectAll(".nv-boxplot-"+e).on("mouseover",function(e,n,r){d3.select(this).classed("hover",!0),S.elementMouseover({series:{key:t(e),color:x(e)||A(e,r)},e:d3.event})}).on("mouseout",function(e,n,r){d3.select(this).classed("hover",!1),S.elementMouseout({series:{key:t(e),color:x(e)||A(e,r)},e:d3.event})}).on("mousemove",function(t,e){S.elementMousemove({e:d3.event})})}),P.append("rect").attr("class","nv-boxplot-box").on("mouseover",function(t,e){d3.select(this).classed("hover",!0),S.elementMouseover({key:p(t),value:p(t),series:[{key:"Q3",value:v(t),color:x(t)||A(t,e)},{key:"Q2",value:g(t),color:x(t)||A(t,e)},{key:"Q1",value:h(t),color:x(t)||A(t,e)}],data:t,index:e,e:d3.event})}).on("mouseout",function(t,e){d3.select(this).classed("hover",!1),S.elementMouseout({key:p(t),value:p(t),series:[{key:"Q3",value:v(t),color:x(t)||A(t,e)},{key:"Q2",value:g(t),color:x(t)||A(t,e)},{key:"Q1",value:h(t),color:x(t)||A(t,e)}],data:t,index:e,e:d3.event})}).on("mousemove",function(t,e){S.elementMousemove({e:d3.event})}),B.select("rect.nv-boxplot-box").watchTransition(F,"nv-boxplot: boxes").attr("y",function(t,e){return f(v(t))}).attr("width",T).attr("x",I).attr("height",function(t,e){return Math.abs(f(v(t))-f(h(t)))||1}).style("fill",function(t,e){return x(t)||A(t,e)}).style("stroke",function(t,e){return x(t)||A(t,e)}),P.append("line").attr("class","nv-boxplot-median"),B.select("line.nv-boxplot-median").watchTransition(F,"nv-boxplot: boxplots line").attr("x1",I).attr("y1",function(t,e){return f(g(t))}).attr("x2",z).attr("y2",function(t,e){return f(g(t))});var V=B.selectAll(".nv-boxplot-outlier").data(function(t){return b(t)||[]});V.enter().append("circle").style("fill",function(t,e,n){return M(t,e,n)||A(t,n)}).style("stroke",function(t,e,n){return M(t,e,n)||A(t,n)}).style("z-index",9e3).on("mouseover",function(t,e,n){d3.select(this).classed("hover",!0),S.elementMouseover({series:{key:w(t,e,n),color:M(t,e,n)||A(t,n)},e:d3.event})}).on("mouseout",function(t,e,n){d3.select(this).classed("hover",!1),S.elementMouseout({series:{key:w(t,e,n),color:M(t,e,n)||A(t,n)},e:d3.event})}).on("mousemove",function(t,e){S.elementMousemove({e:d3.event})}),V.attr("class","nv-boxplot-outlier"),V.watchTransition(F,"nv-boxplot: nv-boxplot-outlier").attr("cx",.45*d.rangeBand()).attr("cy",function(t,e,n){return f(k(t,e,n))}).attr("r","3"),V.exit().remove(),o=d.copy(),a=f.copy()}),F.renderEnd("nv-boxplot immediate"),N}return N.dispatch=S,N.options=t.utils.optionsFunc.bind(N),N._options=Object.create({},{width:{get:function(){return l},set:function(t){l=t}},height:{get:function(){return u},set:function(t){u=t}},maxBoxWidth:{get:function(){return E},set:function(t){E=t}},x:{get:function(){return p},set:function(t){p=t}},q1:{get:function(){return h},set:function(t){h=t}},q2:{get:function(){return g},set:function(t){g=t}},q3:{get:function(){return v},set:function(t){v=t}},wl:{get:function(){return m},set:function(t){m=t}},wh:{get:function(){return y},set:function(t){y=t}},itemColor:{get:function(){return x},set:function(t){x=t}},outliers:{get:function(){return b},set:function(t){b=t}},outlierValue:{get:function(){return k},set:function(t){k=t}},outlierLabel:{get:function(){return w},set:function(t){w=t}},outlierColor:{get:function(){return M},set:function(t){M=t}},xScale:{get:function(){return d},set:function(t){d=t}},yScale:{get:function(){return f},set:function(t){f=t}},xDomain:{get:function(){return e},set:function(t){e=t}},yDomain:{get:function(){return r},set:function(t){r=t}},xRange:{get:function(){return n},set:function(t){n=t}},yRange:{get:function(){return i},set:function(t){i=t}},id:{get:function(){return c},set:function(t){c=t}},y:{get:function(){return console.warn("BoxPlot 'y' chart option is deprecated. Please use model overrides instead."),{}},set:function(t){console.warn("BoxPlot 'y' chart option is deprecated. Please use model overrides instead.")}},margin:{get:function(){return s},set:function(t){s.top=void 0!==t.top?t.top:s.top,s.right=void 0!==t.right?t.right:s.right,s.bottom=void 0!==t.bottom?t.bottom:s.bottom,s.left=void 0!==t.left?t.left:s.left}},color:{get:function(){return A},set:function(e){A=t.utils.getColor(e)}},duration:{get:function(){return W},set:function(t){W=t,F.reset(W)}}}),t.utils.initOptions(N),N},t.models.boxPlotChart=function(){"use strict";var e,n,r=t.models.boxPlot(),i=t.models.axis(),o=t.models.axis(),a={top:15,right:10,bottom:50,left:60},s=null,l=null,u=t.utils.getColor(),c=!0,d=!0,f=!1,p=!1,h=t.models.tooltip(),g="No Data Available.",v=d3.dispatch("beforeUpdate","renderEnd"),m=250;i.orient("bottom").showMaxMin(!1).tickFormat(function(t){return t}),o.orient(f?"right":"left").tickFormat(d3.format(",.1f")),h.duration(0);var y=t.utils.renderWatch(v,m);function x(u){return y.reset(),y.models(r),c&&y.models(i),d&&y.models(o),u.each(function(u){var h=d3.select(this);t.utils.initSVG(h);var y=(s||parseInt(h.style("width"))||960)-a.left-a.right,b=(l||parseInt(h.style("height"))||400)-a.top-a.bottom;if(x.update=function(){v.beforeUpdate(),h.transition().duration(m).call(x)},x.container=this,!u||!u.length){var k=h.selectAll(".nv-noData").data([g]);return k.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),k.attr("x",a.left+y/2).attr("y",a.top+b/2).text(function(t){return t}),x}h.selectAll(".nv-noData").remove(),e=r.xScale(),n=r.yScale().clamp(!0);var w=h.selectAll("g.nv-wrap.nv-boxPlotWithAxes").data([u]),M=w.enter().append("g").attr("class","nvd3 nv-wrap nv-boxPlotWithAxes").append("g"),A=M.append("defs"),C=w.select("g");if(M.append("g").attr("class","nv-x nv-axis"),M.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),M.append("g").attr("class","nv-barsWrap"),C.attr("transform","translate("+a.left+","+a.top+")"),f&&C.select(".nv-y.nv-axis").attr("transform","translate("+y+",0)"),r.width(y).height(b),C.select(".nv-barsWrap").datum(u.filter(function(t){return!t.disabled})).transition().call(r),A.append("clipPath").attr("id","nv-x-label-clip-"+r.id()).append("rect"),C.select("#nv-x-label-clip-"+r.id()+" rect").attr("width",e.rangeBand()*(p?2:1)).attr("height",16).attr("x",-e.rangeBand()/(p?1:2)),c){i.scale(e).ticks(t.utils.calcTicksX(y/100,u)).tickSize(-b,0),C.select(".nv-x.nv-axis").attr("transform","translate(0,"+n.range()[0]+")"),C.select(".nv-x.nv-axis").call(i);var S=C.select(".nv-x.nv-axis").selectAll("g");p&&S.selectAll("text").attr("transform",function(t,e,n){return"translate(0,"+(n%2==0?"5":"17")+")"})}d&&(o.scale(n).ticks(Math.floor(b/36)).tickSize(-y,0),C.select(".nv-y.nv-axis").call(o)),C.select(".nv-zeroLine line").attr("x1",0).attr("x2",y).attr("y1",n(0)).attr("y2",n(0))}),y.renderEnd("nv-boxplot chart immediate"),x}return r.dispatch.on("elementMouseover.tooltip",function(t){h.data(t).hidden(!1)}),r.dispatch.on("elementMouseout.tooltip",function(t){h.data(t).hidden(!0)}),r.dispatch.on("elementMousemove.tooltip",function(t){h()}),x.dispatch=v,x.boxplot=r,x.xAxis=i,x.yAxis=o,x.tooltip=h,x.options=t.utils.optionsFunc.bind(x),x._options=Object.create({},{width:{get:function(){return s},set:function(t){s=t}},height:{get:function(){return l},set:function(t){l=t}},staggerLabels:{get:function(){return p},set:function(t){p=t}},showXAxis:{get:function(){return c},set:function(t){c=t}},showYAxis:{get:function(){return d},set:function(t){d=t}},tooltipContent:{get:function(){return h},set:function(t){h=t}},noData:{get:function(){return g},set:function(t){g=t}},margin:{get:function(){return a},set:function(t){a.top=void 0!==t.top?t.top:a.top,a.right=void 0!==t.right?t.right:a.right,a.bottom=void 0!==t.bottom?t.bottom:a.bottom,a.left=void 0!==t.left?t.left:a.left}},duration:{get:function(){return m},set:function(t){m=t,y.reset(m),r.duration(m),i.duration(m),o.duration(m)}},color:{get:function(){return u},set:function(e){u=t.utils.getColor(e),r.color(u)}},rightAlignYAxis:{get:function(){return f},set:function(t){f=t,o.orient(t?"right":"left")}}}),t.utils.inheritOptions(x,r),t.utils.initOptions(x),x},t.models.bullet=function(){"use strict";var e={top:0,right:0,bottom:0,left:0},n="left",r=!1,i=function(t){return t.ranges},o=function(t){return t.markers?t.markers:[]},a=function(t){return t.markerLines?t.markerLines:[0]},s=function(t){return t.measures},l=function(t){return t.rangeLabels?t.rangeLabels:[]},u=function(t){return t.markerLabels?t.markerLabels:[]},c=function(t){return t.markerLineLabels?t.markerLineLabels:[]},d=function(t){return t.measureLabels?t.measureLabels:[]},f=[0],p=380,h=30,g=null,v=null,m=t.utils.getColor(["#1f77b4"]),y=d3.dispatch("elementMouseover","elementMouseout","elementMousemove"),x=["Maximum","Mean","Minimum"],b=["Max","Avg","Min"],k=1e3;function w(t,e){var n=t.slice();t.sort(function(t,r){var i=n.indexOf(t),o=n.indexOf(r);return d3.descending(e[i],e[o])})}function M(n){return n.each(function(n,v){var M=p-e.left-e.right,A=h-e.top-e.bottom;g=d3.select(this),t.utils.initSVG(g);var C=i.call(this,n,v).slice(),S=o.call(this,n,v).slice(),W=a.call(this,n,v).slice(),E=s.call(this,n,v).slice(),F=l.call(this,n,v).slice(),N=u.call(this,n,v).slice(),D=c.call(this,n,v).slice(),O=d.call(this,n,v).slice();w(F,C),w(N,S),w(D,W),w(O,E),C.sort(d3.descending),S.sort(d3.descending),W.sort(d3.descending),E.sort(d3.descending);var _=d3.scale.linear().domain(d3.extent(d3.merge([f,C]))).range(r?[M,0]:[0,M]);this.__chart__||d3.scale.linear().domain([0,1/0]).range(_.range());this.__chart__=_;d3.min(C),d3.max(C),C[1];for(var L=g.selectAll("g.nv-wrap.nv-bullet").data([n]),B=L.enter().append("g").attr("class","nvd3 nv-wrap nv-bullet").append("g"),P=L.select("g"),T=(v=0,C.length);vg(t,e)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+n+"-"+e});_.append("line").attr("class","nv-candlestick-lines").attr("transform",function(t,e){return"translate("+c(f(t,e))+",0)"}).attr("x1",0).attr("y1",function(t,e){return d(v(t,e))}).attr("x2",0).attr("y2",function(t,e){return d(m(t,e))}),_.append("rect").attr("class","nv-candlestick-rects nv-bars").attr("transform",function(t,e){return"translate("+(c(f(t,e))-S/2)+","+(d(p(t,e))-(h(t,e)>g(t,e)?d(g(t,e))-d(h(t,e)):0))+")"}).attr("x",0).attr("y",0).attr("width",S).attr("height",function(t,e){var n=h(t,e),r=g(t,e);return n>r?d(r)-d(n):d(n)-d(r)});O.select(".nv-candlestick-lines").transition().attr("transform",function(t,e){return"translate("+c(f(t,e))+",0)"}).attr("x1",0).attr("y1",function(t,e){return d(v(t,e))}).attr("x2",0).attr("y2",function(t,e){return d(m(t,e))}),O.select(".nv-candlestick-rects").transition().attr("transform",function(t,e){return"translate("+(c(f(t,e))-S/2)+","+(d(p(t,e))-(h(t,e)>g(t,e)?d(g(t,e))-d(h(t,e)):0))+")"}).attr("x",0).attr("y",0).attr("width",S).attr("height",function(t,e){var n=h(t,e),r=g(t,e);return n>r?d(r)-d(n):d(n)-d(r)})}),C}return C.highlightPoint=function(t,n){C.clearHighlights(),e.select(".nv-candlestickBar .nv-tick-0-"+t).classed("hover",n)},C.clearHighlights=function(){e.select(".nv-candlestickBar .nv-tick.hover").classed("hover",!1)},C.dispatch=A,C.options=t.utils.optionsFunc.bind(C),C._options=Object.create({},{width:{get:function(){return s},set:function(t){s=t}},height:{get:function(){return l},set:function(t){l=t}},xScale:{get:function(){return c},set:function(t){c=t}},yScale:{get:function(){return d},set:function(t){d=t}},xDomain:{get:function(){return n},set:function(t){n=t}},yDomain:{get:function(){return r},set:function(t){r=t}},xRange:{get:function(){return i},set:function(t){i=t}},yRange:{get:function(){return o},set:function(t){o=t}},forceX:{get:function(){return y},set:function(t){y=t}},forceY:{get:function(){return x},set:function(t){x=t}},padData:{get:function(){return b},set:function(t){b=t}},clipEdge:{get:function(){return k},set:function(t){k=t}},id:{get:function(){return u},set:function(t){u=t}},interactive:{get:function(){return M},set:function(t){M=t}},x:{get:function(){return f},set:function(t){f=t}},y:{get:function(){return p},set:function(t){p=t}},open:{get:function(){return h()},set:function(t){h=t}},close:{get:function(){return g()},set:function(t){g=t}},high:{get:function(){return v},set:function(t){v=t}},low:{get:function(){return m},set:function(t){m=t}},margin:{get:function(){return a},set:function(t){a.top=null!=t.top?t.top:a.top,a.right=null!=t.right?t.right:a.right,a.bottom=null!=t.bottom?t.bottom:a.bottom,a.left=null!=t.left?t.left:a.left}},color:{get:function(){return w},set:function(e){w=t.utils.getColor(e)}}}),t.utils.initOptions(C),C},t.models.cumulativeLineChart=function(){"use strict";var e,n,r=t.models.line(),i=t.models.axis(),o=t.models.axis(),a=t.models.legend(),s=t.models.legend(),l=t.interactiveGuideline(),u=t.models.tooltip(),c={top:30,right:30,bottom:50,left:60},d=null,f=t.utils.defaultColor(),p=null,h=null,g=!0,v=!0,m=!0,y=!1,x=!0,b=!1,k=!0,w=r.id(),M=t.utils.state(),A=null,C=null,S=function(t){return t.average},W=d3.dispatch("stateChange","changeState","renderEnd"),E=250,F=!1;M.index=0,M.rescaleY=k,i.orient("bottom").tickPadding(7),o.orient(y?"right":"left"),u.valueFormatter(function(t,e){return o.tickFormat()(t,e)}).headerFormatter(function(t,e){return i.tickFormat()(t,e)}),s.updateState(!1);var N,D=d3.scale.linear(),O={i:0,x:0},_=t.utils.renderWatch(W,E),L=function(t){return function(){return{active:t.map(function(t){return!t.disabled}),index:O.i,rescaleY:k}}},B=function(t){return function(e){void 0!==e.index&&(O.i=e.index),void 0!==e.rescaleY&&(k=e.rescaleY),void 0!==e.active&&t.forEach(function(t,n){t.disabled=!e.active[n]})}};function P(u){return _.reset(),_.models(r),v&&_.models(i),m&&_.models(o),u.each(function(u){var C=d3.select(this);t.utils.initSVG(C),C.classed("nv-chart-"+w,!0);var _,z=t.utils.availableWidth(p,C,c),V=t.utils.availableHeight(h,C,c);if(P.update=function(){0===E?C.call(P):C.transition().duration(E).call(P)},P.container=this,M.setter(B(u),P.update).getter(L(u)).update(),M.disabled=u.map(function(t){return!!t.disabled}),!A)for(_ in A={},M)M[_]instanceof Array?A[_]=M[_].slice(0):A[_]=M[_];var H=d3.behavior.drag().on("dragstart",function(t,e){d3.select(P.container).style("cursor","ew-resize")}).on("drag",function(t,e){O.x=d3.event.x,O.i=Math.round(D.invert(O.x)),$()}).on("dragend",function(t,e){d3.select(P.container).style("cursor","auto"),M.index=O.i,W.stateChange(M)});if(!(u&&u.length&&u.filter(function(t){return t.values.length}).length))return t.utils.noData(P,C),P;C.selectAll(".nv-noData").remove(),e=r.xScale(),n=r.yScale(),D.domain([0,u[0].values.length-1]).range([0,z]).clamp(!0);u=function(t,e){T||(T=r.y());return e.map(function(e,n){if(!e.values)return e;var r=e.values[t];if(null==r)return e;var i=T(r,t);return Math.abs(i)<1e-5&&!F?(e.tempDisabled=!0,e):(e.tempDisabled=!1,e.values=e.values.map(function(t,e){return t.display={y:(T(t,e)-i)/i},t}),e)})}(O.i,u);void 0===N&&(N=I(u)),k?r.yDomain(null):(r.yDomain(N),r.clipEdge(!0));var Y=b?"none":"all",G=C.selectAll("g.nv-wrap.nv-cumulativeLine").data([u]),X=G.enter().append("g").attr("class","nvd3 nv-wrap nv-cumulativeLine").append("g"),j=G.select("g");if(X.append("g").attr("class","nv-interactive"),X.append("g").attr("class","nv-x nv-axis").style("pointer-events","none"),X.append("g").attr("class","nv-y nv-axis"),X.append("g").attr("class","nv-background"),X.append("g").attr("class","nv-linesWrap").style("pointer-events",Y),X.append("g").attr("class","nv-avgLinesWrap").style("pointer-events","none"),X.append("g").attr("class","nv-legendWrap"),X.append("g").attr("class","nv-controlsWrap"),g?(a.width(z),j.select(".nv-legendWrap").datum(u).call(a),d||a.height()===c.top||(c.top=a.height(),V=t.utils.availableHeight(h,C,c)),j.select(".nv-legendWrap").attr("transform","translate(0,"+-c.top+")")):j.select(".nv-legendWrap").selectAll("*").remove(),x){var R=[{key:"Re-scale y-axis",disabled:!k}];s.width(140).color(["#444","#444","#444"]).rightAlign(!1).margin({top:5,right:0,bottom:5,left:20}),j.select(".nv-controlsWrap").datum(R).attr("transform","translate(0,"+-c.top+")").call(s)}else j.select(".nv-controlsWrap").selectAll("*").remove();G.attr("transform","translate("+c.left+","+c.top+")"),y&&j.select(".nv-y.nv-axis").attr("transform","translate("+z+",0)");var Z=u.filter(function(t){return t.tempDisabled});G.select(".tempDisabled").remove(),Z.length&&G.append("text").attr("class","tempDisabled").attr("x",z/2).attr("y","-.71em").style("text-anchor","end").text(Z.map(function(t){return t.key}).join(", ")+" values cannot be calculated for this time period."),b&&(l.width(z).height(V).margin({left:c.left,top:c.top}).svgContainer(C).xScale(e),G.select(".nv-interactive").call(l)),X.select(".nv-background").append("rect"),j.select(".nv-background rect").attr("width",z).attr("height",V),r.y(function(t){return t.display.y}).width(z).height(V).color(u.map(function(t,e){return t.color||f(t,e)}).filter(function(t,e){return!u[e].disabled&&!u[e].tempDisabled}));var q=j.select(".nv-linesWrap").datum(u.filter(function(t){return!t.disabled&&!t.tempDisabled}));q.call(r),u.forEach(function(t,e){t.seriesIndex=e});var K=u.filter(function(t){return!t.disabled&&!!S(t)}),U=j.select(".nv-avgLinesWrap").selectAll("line").data(K,function(t){return t.key}),Q=function(t){var e=n(S(t));return e<0?0:e>V?V:e};U.enter().append("line").style("stroke-width",2).style("stroke-dasharray","10,10").style("stroke",function(t,e){return r.color()(t,t.seriesIndex)}).attr("x1",0).attr("x2",z).attr("y1",Q).attr("y2",Q),U.style("stroke-opacity",function(t){var e=n(S(t));return e<0||e>V?0:1}).attr("x1",0).attr("x2",z).attr("y1",Q).attr("y2",Q),U.exit().remove();var J=q.selectAll(".nv-indexLine").data([O]);function $(){J.data([O]);var t=P.duration();P.duration(0),P.update(),P.duration(t)}J.enter().append("rect").attr("class","nv-indexLine").attr("width",3).attr("x",-2).attr("fill","red").attr("fill-opacity",.5).style("pointer-events","all").call(H),J.attr("transform",function(t){return"translate("+D(t.i)+",0)"}).attr("height",V),v&&(i.scale(e)._ticks(t.utils.calcTicksX(z/70,u)).tickSize(-V,0),j.select(".nv-x.nv-axis").attr("transform","translate(0,"+n.range()[0]+")"),j.select(".nv-x.nv-axis").call(i)),m&&(o.scale(n)._ticks(t.utils.calcTicksY(V/36,u)).tickSize(-z,0),j.select(".nv-y.nv-axis").call(o)),j.select(".nv-background rect").on("click",function(){O.x=d3.mouse(this)[0],O.i=Math.round(D.invert(O.x)),M.index=O.i,W.stateChange(M),$()}),r.dispatch.on("elementClick",function(t){O.i=t.pointIndex,O.x=D(O.i),M.index=O.i,W.stateChange(M),$()}),s.dispatch.on("legendClick",function(t,e){t.disabled=!t.disabled,k=!t.disabled,M.rescaleY=k,k||(N=I(u)),W.stateChange(M),P.update()}),a.dispatch.on("stateChange",function(t){for(var e in t)M[e]=t[e];W.stateChange(M),P.update()}),l.dispatch.on("elementMousemove",function(e){r.clearHighlights();var n,a,s,c=[];if(u.filter(function(t,e){return t.seriesIndex=e,!(t.disabled||t.tempDisabled)}).forEach(function(i,o){a=t.interactiveBisect(i.values,e.pointXValue,P.x()),r.highlightPoint(o,a,!0);var l=i.values[a];void 0!==l&&(void 0===n&&(n=l),void 0===s&&(s=P.xScale()(P.x()(l,a))),c.push({key:i.key,value:P.y()(l,a),color:f(i,i.seriesIndex)}))}),c.length>2){var d=P.yScale().invert(e.mouseY),p=.03*Math.abs(P.yScale().domain()[0]-P.yScale().domain()[1]),h=t.nearestValueIndex(c.map(function(t){return t.value}),d,p);null!==h&&(c[h].highlight=!0)}var g=i.tickFormat()(P.x()(n,a),a);l.tooltip.valueFormatter(function(t,e){return o.tickFormat()(t)}).data({value:g,series:c})(),l.renderGuideLine(s)}),l.dispatch.on("elementMouseout",function(t){r.clearHighlights()}),W.on("changeState",function(t){void 0!==t.disabled&&(u.forEach(function(e,n){e.disabled=t.disabled[n]}),M.disabled=t.disabled),void 0!==t.index&&(O.i=t.index,O.x=D(O.i),M.index=t.index,J.data([O])),void 0!==t.rescaleY&&(k=t.rescaleY),P.update()})}),_.renderEnd("cumulativeLineChart immediate"),P}r.dispatch.on("elementMouseover.tooltip",function(t){var e={x:P.x()(t.point),y:P.y()(t.point),color:t.point.color};t.point=e,u.data(t).hidden(!1)}),r.dispatch.on("elementMouseout.tooltip",function(t){u.hidden(!0)});var T=null;function I(t){var e=t.filter(function(t){return!(t.disabled||t.tempDisabled)}).map(function(t,e){return d3.extent(t.values,function(t){return t.display.y})});return[d3.min(e,function(t){return t[0]}),d3.max(e,function(t){return t[1]})]}return P.dispatch=W,P.lines=r,P.legend=a,P.controls=s,P.xAxis=i,P.yAxis=o,P.interactiveLayer=l,P.state=M,P.tooltip=u,P.options=t.utils.optionsFunc.bind(P),P._options=Object.create({},{width:{get:function(){return p},set:function(t){p=t}},height:{get:function(){return h},set:function(t){h=t}},showControls:{get:function(){return x},set:function(t){x=t}},showLegend:{get:function(){return g},set:function(t){g=t}},average:{get:function(){return S},set:function(t){S=t}},defaultState:{get:function(){return A},set:function(t){A=t}},noData:{get:function(){return C},set:function(t){C=t}},showXAxis:{get:function(){return v},set:function(t){v=t}},showYAxis:{get:function(){return m},set:function(t){m=t}},noErrorCheck:{get:function(){return F},set:function(t){F=t}},rescaleY:{get:function(){return k},set:function(t){k=t,P.state.rescaleY=t}},margin:{get:function(){return c},set:function(t){void 0!==t.top&&(c.top=t.top,d=t.top),c.right=void 0!==t.right?t.right:c.right,c.bottom=void 0!==t.bottom?t.bottom:c.bottom,c.left=void 0!==t.left?t.left:c.left}},color:{get:function(){return f},set:function(e){f=t.utils.getColor(e),a.color(f)}},useInteractiveGuideline:{get:function(){return b},set:function(t){b=t,!0===t&&(P.interactive(!1),P.useVoronoi(!1))}},rightAlignYAxis:{get:function(){return y},set:function(t){y=t,o.orient(t?"right":"left")}},duration:{get:function(){return E},set:function(t){E=t,r.duration(E),i.duration(E),o.duration(E),_.reset(E)}}}),t.utils.inheritOptions(P,r),t.utils.initOptions(P),P},t.models.discreteBar=function(){"use strict";var e,n,r,i,o,a,s,l={top:0,right:0,bottom:0,left:0},u=960,c=500,d=Math.floor(1e4*Math.random()),f=d3.scale.ordinal(),p=d3.scale.linear(),h=function(t){return t.x},g=function(t){return t.y},v=[0],m=t.utils.defaultColor(),y=!1,x=d3.format(",.2f"),b=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),k="discreteBar",w=250,M=t.utils.renderWatch(b,w);function A(d){return M.reset(),d.each(function(d){var w=u-l.left-l.right,A=c-l.top-l.bottom;e=d3.select(this),t.utils.initSVG(e),d.forEach(function(t,e){t.values.forEach(function(t){t.series=e})});var C=n&&r?[]:d.map(function(t){return t.values.map(function(t,e){return{x:h(t,e),y:g(t,e),y0:t.y0}})});f.domain(n||d3.merge(C).map(function(t){return t.x})).rangeBands(i||[0,w],.1),p.domain(r||d3.extent(d3.merge(C).map(function(t){return t.y}).concat(v))),y?p.range(o||[A-(p.domain()[0]<0?12:0),p.domain()[1]>0?12:0]):p.range(o||[A,0]),a=a||f,s=s||p.copy().range([p(0),p(0)]);var S=e.selectAll("g.nv-wrap.nv-discretebar").data([d]),W=S.enter().append("g").attr("class","nvd3 nv-wrap nv-discretebar").append("g");S.select("g");W.append("g").attr("class","nv-groups"),S.attr("transform","translate("+l.left+","+l.top+")");var E=S.select(".nv-groups").selectAll(".nv-group").data(function(t){return t},function(t){return t.key});E.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),E.exit().watchTransition(M,"discreteBar: exit groups").style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),E.attr("class",function(t,e){return"nv-group nv-series-"+e}).classed("hover",function(t){return t.hover}),E.watchTransition(M,"discreteBar: groups").style("stroke-opacity",1).style("fill-opacity",.75);var F=E.selectAll("g.nv-bar").data(function(t){return t.values});F.exit().remove();var N=F.enter().append("g").attr("transform",function(t,e,n){return"translate("+(f(h(t,e))+.05*f.rangeBand())+", "+p(0)+")"}).on("mouseover",function(t,e){d3.select(this).classed("hover",!0),b.elementMouseover({data:t,index:e,color:d3.select(this).style("fill")})}).on("mouseout",function(t,e){d3.select(this).classed("hover",!1),b.elementMouseout({data:t,index:e,color:d3.select(this).style("fill")})}).on("mousemove",function(t,e){b.elementMousemove({data:t,index:e,color:d3.select(this).style("fill")})}).on("click",function(t,e){b.elementClick({data:t,index:e,color:d3.select(this).style("fill"),event:d3.event,element:this}),d3.event.stopPropagation()}).on("dblclick",function(t,e){b.elementDblClick({data:t,index:e,color:d3.select(this).style("fill")}),d3.event.stopPropagation()});N.append("rect").attr("height",0).attr("width",.9*f.rangeBand()/d.length),y?(N.append("text").attr("text-anchor","middle"),F.select("text").text(function(t,e){return x(g(t,e))}).watchTransition(M,"discreteBar: bars text").attr("x",.9*f.rangeBand()/2).attr("y",function(t,e){return g(t,e)<0?p(g(t,e))-p(0)+12:-4})):F.selectAll("text").remove(),F.attr("class",function(t,e){return g(t,e)<0?"nv-bar negative":"nv-bar positive"}).style("fill",function(t,e){return t.color||m(t,e)}).style("stroke",function(t,e){return t.color||m(t,e)}).select("rect").attr("class",k).watchTransition(M,"discreteBar: bars rect").attr("width",.9*f.rangeBand()/d.length),F.watchTransition(M,"discreteBar: bars").attr("transform",function(t,e){return"translate("+(f(h(t,e))+.05*f.rangeBand())+", "+(g(t,e)<0?p(0):p(0)-p(g(t,e))<1?p(0)-1:p(g(t,e)))+")"}).select("rect").attr("height",function(t,e){return Math.max(Math.abs(p(g(t,e))-p(0)),1)}),a=f.copy(),s=p.copy()}),M.renderEnd("discreteBar immediate"),A}return A.dispatch=b,A.options=t.utils.optionsFunc.bind(A),A._options=Object.create({},{width:{get:function(){return u},set:function(t){u=t}},height:{get:function(){return c},set:function(t){c=t}},forceY:{get:function(){return v},set:function(t){v=t}},showValues:{get:function(){return y},set:function(t){y=t}},x:{get:function(){return h},set:function(t){h=t}},y:{get:function(){return g},set:function(t){g=t}},xScale:{get:function(){return f},set:function(t){f=t}},yScale:{get:function(){return p},set:function(t){p=t}},xDomain:{get:function(){return n},set:function(t){n=t}},yDomain:{get:function(){return r},set:function(t){r=t}},xRange:{get:function(){return i},set:function(t){i=t}},yRange:{get:function(){return o},set:function(t){o=t}},valueFormat:{get:function(){return x},set:function(t){x=t}},id:{get:function(){return d},set:function(t){d=t}},rectClass:{get:function(){return k},set:function(t){k=t}},margin:{get:function(){return l},set:function(t){l.top=void 0!==t.top?t.top:l.top,l.right=void 0!==t.right?t.right:l.right,l.bottom=void 0!==t.bottom?t.bottom:l.bottom,l.left=void 0!==t.left?t.left:l.left}},color:{get:function(){return m},set:function(e){m=t.utils.getColor(e)}},duration:{get:function(){return w},set:function(t){w=t,M.reset(w)}}}),t.utils.initOptions(A),A},t.models.discreteBarChart=function(){"use strict";var e,n,r=t.models.discreteBar(),i=t.models.axis(),o=t.models.axis(),a=t.models.legend(),s=t.models.tooltip(),l={top:15,right:10,bottom:50,left:60},u=null,c=null,d=null,f=t.utils.getColor(),p=!1,h=!0,g=!0,v=!1,m=!1,y=!1,x=0,b=null,k=d3.dispatch("beforeUpdate","renderEnd"),w=250;i.orient("bottom").showMaxMin(!1).tickFormat(function(t){return t}),o.orient(v?"right":"left").tickFormat(d3.format(",.1f")),s.duration(0).headerEnabled(!1).valueFormatter(function(t,e){return o.tickFormat()(t,e)}).keyFormatter(function(t,e){return i.tickFormat()(t,e)});var M=t.utils.renderWatch(k,w);function A(s){return M.reset(),M.models(r),h&&M.models(i),g&&M.models(o),s.each(function(s){var f=d3.select(this);t.utils.initSVG(f);var b=t.utils.availableWidth(c,f,l),M=t.utils.availableHeight(d,f,l);if(A.update=function(){k.beforeUpdate(),f.transition().duration(w).call(A)},A.container=this,!(s&&s.length&&s.filter(function(t){return t.values.length}).length))return t.utils.noData(A,f),A;f.selectAll(".nv-noData").remove(),e=r.xScale(),n=r.yScale().clamp(!0);var C=f.selectAll("g.nv-wrap.nv-discreteBarWithAxes").data([s]),S=C.enter().append("g").attr("class","nvd3 nv-wrap nv-discreteBarWithAxes").append("g"),W=S.append("defs"),E=C.select("g");if(S.append("g").attr("class","nv-x nv-axis"),S.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),S.append("g").attr("class","nv-barsWrap"),S.append("g").attr("class","nv-legendWrap"),E.attr("transform","translate("+l.left+","+l.top+")"),p?(a.width(b),E.select(".nv-legendWrap").datum(s).call(a),u||a.height()===l.top||(l.top=a.height(),M=t.utils.availableHeight(d,f,l)),C.select(".nv-legendWrap").attr("transform","translate(0,"+-l.top+")")):E.select(".nv-legendWrap").selectAll("*").remove(),v&&E.select(".nv-y.nv-axis").attr("transform","translate("+b+",0)"),r.width(b).height(M),E.select(".nv-barsWrap").datum(s.filter(function(t){return!t.disabled})).transition().call(r),W.append("clipPath").attr("id","nv-x-label-clip-"+r.id()).append("rect"),E.select("#nv-x-label-clip-"+r.id()+" rect").attr("width",e.rangeBand()*(m?2:1)).attr("height",16).attr("x",-e.rangeBand()/(m?1:2)),h){i.scale(e)._ticks(t.utils.calcTicksX(b/100,s)).tickSize(-M,0),E.select(".nv-x.nv-axis").attr("transform","translate(0,"+(n.range()[0]+(r.showValues()&&n.domain()[0]<0?16:0))+")"),E.select(".nv-x.nv-axis").call(i);var F=E.select(".nv-x.nv-axis").selectAll("g");m&&F.selectAll("text").attr("transform",function(t,e,n){return"translate(0,"+(n%2==0?"5":"17")+")"}),x&&F.selectAll(".tick text").attr("transform","rotate("+x+" 0,0)").style("text-anchor",x>0?"start":"end"),y&&E.selectAll(".tick text").call(t.utils.wrapTicks,A.xAxis.rangeBand())}g&&(o.scale(n)._ticks(t.utils.calcTicksY(M/36,s)).tickSize(-b,0),E.select(".nv-y.nv-axis").call(o)),E.select(".nv-zeroLine line").attr("x1",0).attr("x2",v?-b:b).attr("y1",n(0)).attr("y2",n(0))}),M.renderEnd("discreteBar chart immediate"),A}return r.dispatch.on("elementMouseover.tooltip",function(t){t.series={key:A.x()(t.data),value:A.y()(t.data),color:t.color},s.data(t).hidden(!1)}),r.dispatch.on("elementMouseout.tooltip",function(t){s.hidden(!0)}),r.dispatch.on("elementMousemove.tooltip",function(t){s()}),A.dispatch=k,A.discretebar=r,A.legend=a,A.xAxis=i,A.yAxis=o,A.tooltip=s,A.options=t.utils.optionsFunc.bind(A),A._options=Object.create({},{width:{get:function(){return c},set:function(t){c=t}},height:{get:function(){return d},set:function(t){d=t}},showLegend:{get:function(){return p},set:function(t){p=t}},staggerLabels:{get:function(){return m},set:function(t){m=t}},rotateLabels:{get:function(){return x},set:function(t){x=t}},wrapLabels:{get:function(){return y},set:function(t){y=!!t}},showXAxis:{get:function(){return h},set:function(t){h=t}},showYAxis:{get:function(){return g},set:function(t){g=t}},noData:{get:function(){return b},set:function(t){b=t}},margin:{get:function(){return l},set:function(t){void 0!==t.top&&(l.top=t.top,u=t.top),l.right=void 0!==t.right?t.right:l.right,l.bottom=void 0!==t.bottom?t.bottom:l.bottom,l.left=void 0!==t.left?t.left:l.left}},duration:{get:function(){return w},set:function(t){w=t,M.reset(w),r.duration(w),i.duration(w),o.duration(w)}},color:{get:function(){return f},set:function(e){f=t.utils.getColor(e),r.color(f),a.color(f)}},rightAlignYAxis:{get:function(){return v},set:function(t){v=t,o.orient(t?"right":"left")}}}),t.utils.inheritOptions(A,r),t.utils.initOptions(A),A},t.models.distribution=function(){"use strict";var e,n={top:0,right:0,bottom:0,left:0},r=400,i=8,o="x",a=function(t){return t[o]},s=t.utils.defaultColor(),l=d3.scale.linear(),u=250,c=d3.dispatch("renderEnd"),d=t.utils.renderWatch(c,u);function f(r){return d.reset(),r.each(function(r){"x"===o?(n.left,n.right):(n.top,n.bottom);var u="x"==o?"y":"x",c=d3.select(this);t.utils.initSVG(c),e=e||l;var f=c.selectAll("g.nv-distribution").data([r]),p=(f.enter().append("g").attr("class","nvd3 nv-distribution").append("g"),f.select("g"));f.attr("transform","translate("+n.left+","+n.top+")");var h=p.selectAll("g.nv-dist").data(function(t){return t},function(t){return t.key});h.enter().append("g"),h.attr("class",function(t,e){return"nv-dist nv-series-"+e}).style("stroke",function(t,e){return s(t,e)});var g=h.selectAll("line.nv-dist"+o).data(function(t){return t.values});g.enter().append("line").attr(o+"1",function(t,n){return e(a(t,n))}).attr(o+"2",function(t,n){return e(a(t,n))}),d.transition(h.exit().selectAll("line.nv-dist"+o),"dist exit").attr(o+"1",function(t,e){return l(a(t,e))}).attr(o+"2",function(t,e){return l(a(t,e))}).style("stroke-opacity",0).remove(),g.attr("class",function(t,e){return"nv-dist"+o+" nv-dist"+o+"-"+e}).attr(u+"1",0).attr(u+"2",i),d.transition(g,"dist").attr(o+"1",function(t,e){return l(a(t,e))}).attr(o+"2",function(t,e){return l(a(t,e))}),e=l.copy()}),d.renderEnd("distribution immediate"),f}return f.options=t.utils.optionsFunc.bind(f),f.dispatch=c,f.margin=function(t){return arguments.length?(n.top=void 0!==t.top?t.top:n.top,n.right=void 0!==t.right?t.right:n.right,n.bottom=void 0!==t.bottom?t.bottom:n.bottom,n.left=void 0!==t.left?t.left:n.left,f):n},f.width=function(t){return arguments.length?(r=t,f):r},f.axis=function(t){return arguments.length?(o=t,f):o},f.size=function(t){return arguments.length?(i=t,f):i},f.getData=function(t){return arguments.length?(a=d3.functor(t),f):a},f.scale=function(t){return arguments.length?(l=t,f):l},f.color=function(e){return arguments.length?(s=t.utils.getColor(e),f):s},f.duration=function(t){return arguments.length?(u=t,d.reset(u),f):u},f},t.models.focus=function(e){"use strict";e=e||t.models.line();var n,r,i=t.models.axis(),o=t.models.axis(),a=d3.svg.brush(),s={top:10,right:0,bottom:30,left:0},l=t.utils.defaultColor(),u=null,c=70,d=!0,f=!1,p=!1,h=null,g=250,v=d3.dispatch("brush","onBrush","renderEnd"),m=!0;e.interactive(!1),e.pointActive(function(t){return!1});var y=t.utils.renderWatch(v,g);function x(b){return y.reset(),y.models(e),d&&y.models(i),f&&y.models(o),b.each(function(y){var b=d3.select(this);t.utils.initSVG(b);var k=t.utils.availableWidth(u,b,s),w=c-s.top-s.bottom;x.update=function(){0===g?b.call(x):b.transition().duration(g).call(x)},x.container=this,n=e.xScale(),r=e.yScale();var M=b.selectAll("g.nv-focus").data([y]),A=M.enter().append("g").attr("class","nvd3 nv-focus").append("g"),C=M.select("g");M.attr("transform","translate("+s.left+","+s.top+")"),A.append("g").attr("class","nv-background").append("rect"),A.append("g").attr("class","nv-x nv-axis"),A.append("g").attr("class","nv-y nv-axis"),A.append("g").attr("class","nv-contentWrap"),A.append("g").attr("class","nv-brushBackground"),A.append("g").attr("class","nv-x nv-brush"),p&&C.select(".nv-y.nv-axis").attr("transform","translate("+k+",0)"),C.select(".nv-background rect").attr("width",k).attr("height",w),e.width(k).height(w).color(y.map(function(t,e){return t.color||l(t,e)}).filter(function(t,e){return!y[e].disabled}));var S=C.select(".nv-contentWrap").datum(y.filter(function(t){return!t.disabled}));d3.transition(S).call(e),a.x(n).on("brush",function(){N(m)}),a.on("brushend",function(){m||v.onBrush(a.empty()?n.domain():a.extent())}),h&&a.extent(h);var W=C.select(".nv-brushBackground").selectAll("g").data([h||a.extent()]),E=W.enter().append("g");E.append("rect").attr("class","left").attr("x",0).attr("y",0).attr("height",w),E.append("rect").attr("class","right").attr("x",0).attr("y",0).attr("height",w);var F=C.select(".nv-x.nv-brush").call(a);function N(t){h=a.empty()?null:a.extent();var e=a.empty()?n.domain():a.extent();v.brush({extent:e,brush:a}),a.empty()||a.extent(h),W.data([a.empty()?n.domain():h]).each(function(t,e){var r=n(t[0])-n.range()[0],i=k-n(t[1]);d3.select(this).select(".left").attr("width",r<0?0:r),d3.select(this).select(".right").attr("x",n(t[1])).attr("width",i<0?0:i)}),t&&v.onBrush(e)}F.selectAll("rect").attr("height",w),F.selectAll(".resize").append("path").attr("d",function(t){var e=+("e"==t),n=e?1:-1,r=w/3;return"M"+.5*n+","+r+"A6,6 0 0 "+e+" "+6.5*n+","+(r+6)+"V"+(2*r-6)+"A6,6 0 0 "+e+" "+.5*n+","+2*r+"ZM"+2.5*n+","+(r+8)+"V"+(2*r-8)+"M"+4.5*n+","+(r+8)+"V"+(2*r-8)}),N(!0),C.select(".nv-background rect").attr("width",k).attr("height",w),d&&(i.scale(n)._ticks(t.utils.calcTicksX(k/100,y)).tickSize(-w,0),C.select(".nv-x.nv-axis").attr("transform","translate(0,"+r.range()[0]+")"),d3.transition(C.select(".nv-x.nv-axis")).call(i)),f&&(o.scale(r)._ticks(t.utils.calcTicksY(w/36,y)).tickSize(-k,0),d3.transition(C.select(".nv-y.nv-axis")).call(o)),C.select(".nv-x.nv-axis").attr("transform","translate(0,"+r.range()[0]+")")}),y.renderEnd("focus immediate"),x}return x.dispatch=v,x.content=e,x.brush=a,x.xAxis=i,x.yAxis=o,x.options=t.utils.optionsFunc.bind(x),x._options=Object.create({},{width:{get:function(){return u},set:function(t){u=t}},height:{get:function(){return c},set:function(t){c=t}},showXAxis:{get:function(){return d},set:function(t){d=t}},showYAxis:{get:function(){return f},set:function(t){f=t}},brushExtent:{get:function(){return h},set:function(t){h=t}},syncBrushing:{get:function(){return m},set:function(t){m=t}},margin:{get:function(){return s},set:function(t){s.top=void 0!==t.top?t.top:s.top,s.right=void 0!==t.right?t.right:s.right,s.bottom=void 0!==t.bottom?t.bottom:s.bottom,s.left=void 0!==t.left?t.left:s.left}},duration:{get:function(){return g},set:function(t){g=t,y.reset(g),e.duration(g),i.duration(g),o.duration(g)}},color:{get:function(){return l},set:function(n){l=t.utils.getColor(n),e.color(l)}},interpolate:{get:function(){return e.interpolate()},set:function(t){e.interpolate(t)}},xTickFormat:{get:function(){return i.tickFormat()},set:function(t){i.tickFormat(t)}},yTickFormat:{get:function(){return o.tickFormat()},set:function(t){o.tickFormat(t)}},x:{get:function(){return e.x()},set:function(t){e.x(t)}},y:{get:function(){return e.y()},set:function(t){e.y(t)}},rightAlignYAxis:{get:function(){return p},set:function(t){p=t,o.orient(p?"right":"left")}}}),t.utils.inheritOptions(x,e),t.utils.initOptions(x),x},t.models.forceDirectedGraph=function(){"use strict";var e={top:2,right:0,bottom:2,left:0},n=400,r=32,i=null,o=d3.dispatch("renderEnd"),a=t.utils.getColor(["#000"]),s=t.models.tooltip(),l=null,u=.1,c=.9,d=30,f=-120,p=.1,h=.8,g=.1,v=5,m=function(t){},y=function(t){},x=d3.functor(0),b=d3.functor(0),k=t.utils.renderWatch(o);function w(o){return k.reset(),o.each(function(o){i=d3.select(this),t.utils.initSVG(i);var l=t.utils.availableWidth(n,i,e),x=t.utils.availableHeight(r,i,e);if(i.attr("width",l).attr("height",x),!(o&&o.links&&o.nodes))return t.utils.noData(w,i),w;i.selectAll(".nv-noData").remove(),i.selectAll("*").remove();var b=new Set;o.nodes.forEach(function(t){Object.keys(t).forEach(function(t){b.add(t)})});var k=d3.layout.force().nodes(o.nodes).links(o.links).size([l,x]).linkStrength(u).friction(c).linkDistance(d).charge(f).gravity(p).theta(h).alpha(g).start(),M=i.selectAll(".link").data(o.links).enter().append("line").attr("class","nv-force-link").style("stroke-width",function(t){return Math.sqrt(t.value)}),A=i.selectAll(".node").data(o.nodes).enter().append("g").attr("class","nv-force-node").call(k.drag);A.append("circle").attr("r",v).style("fill",function(t){return a(t)}).on("mouseover",function(t){i.select(".nv-series-"+t.seriesIndex+" .nv-distx-"+t.pointIndex).attr("y1",t.py),i.select(".nv-series-"+t.seriesIndex+" .nv-disty-"+t.pointIndex).attr("x2",t.px);var e=a(t);t.series=[],b.forEach(function(n){t.series.push({color:e,key:n,value:t[n]})}),s.data(t).hidden(!1)}).on("mouseout",function(t){s.hidden(!0)}),s.headerFormatter(function(t){return"Node"}),y(M),m(A),k.on("tick",function(){M.attr("x1",function(t){return t.source.x}).attr("y1",function(t){return t.source.y}).attr("x2",function(t){return t.target.x}).attr("y2",function(t){return t.target.y}),A.attr("transform",function(t){return"translate("+t.x+", "+t.y+")"})})}),w}return w.options=t.utils.optionsFunc.bind(w),w._options=Object.create({},{width:{get:function(){return n},set:function(t){n=t}},height:{get:function(){return r},set:function(t){r=t}},linkStrength:{get:function(){return u},set:function(t){u=t}},friction:{get:function(){return c},set:function(t){c=t}},linkDist:{get:function(){return d},set:function(t){d=t}},charge:{get:function(){return f},set:function(t){f=t}},gravity:{get:function(){return p},set:function(t){p=t}},theta:{get:function(){return h},set:function(t){h=t}},alpha:{get:function(){return g},set:function(t){g=t}},radius:{get:function(){return v},set:function(t){v=t}},x:{get:function(){return x},set:function(t){x=d3.functor(t)}},y:{get:function(){return b},set:function(t){b=d3.functor(t)}},margin:{get:function(){return e},set:function(t){e.top=void 0!==t.top?t.top:e.top,e.right=void 0!==t.right?t.right:e.right,e.bottom=void 0!==t.bottom?t.bottom:e.bottom,e.left=void 0!==t.left?t.left:e.left}},color:{get:function(){return a},set:function(e){a=t.utils.getColor(e)}},noData:{get:function(){return l},set:function(t){l=t}},nodeExtras:{get:function(){return m},set:function(t){m=t}},linkExtras:{get:function(){return y},set:function(t){y=t}}}),w.dispatch=o,w.tooltip=s,t.utils.initOptions(w),w},t.models.furiousLegend=function(){"use strict";var e={top:5,right:0,bottom:5,left:0},n=400,r=20,i=function(t){return t.key},o=function(t){return t},a=t.utils.getColor(),s=20,l=!0,u=28,c=!0,d=!0,f=!1,p=!1,h=d3.dispatch("legendClick","legendDblclick","legendMouseover","legendMouseout","stateChange"),g="classic";function v(m){function y(t,e){return"furious"!=g?"#000":p?t.disengaged?a(t,e):"#fff":p?void 0:t.disabled?a(t,e):"#fff"}function x(t,e){return p&&"furious"==g?t.disengaged?"#fff":a(t,e):t.disabled?"#fff":a(t,e)}return m.each(function(v){var m=n-e.left-e.right,b=d3.select(this);t.utils.initSVG(b);var k=b.selectAll("g.nv-legend").data([v]),w=(k.enter().append("g").attr("class","nvd3 nv-legend").append("g"),k.select("g"));k.attr("transform","translate("+e.left+","+e.top+")");var M,A=w.selectAll(".nv-series").data(function(t){return"furious"!=g?t:t.filter(function(t){return!!p||!t.disengaged})}),C=A.enter().append("g").attr("class","nv-series");if("classic"==g)C.append("circle").style("stroke-width",2).attr("class","nv-legend-symbol").attr("r",5),M=A.select("circle");else if("furious"==g){C.append("rect").style("stroke-width",2).attr("class","nv-legend-symbol").attr("rx",3).attr("ry",3),M=A.select("rect"),C.append("g").attr("class","nv-check-box").property("innerHTML",'').attr("transform","translate(-10,-8)scale(0.5)"),A.select(".nv-check-box").each(function(t,e){d3.select(this).selectAll("path").attr("stroke",y(t,e))})}C.append("text").attr("text-anchor","start").attr("class","nv-legend-text").attr("dy",".32em").attr("dx","8");var S,W=A.select("text.nv-legend-text");switch(A.on("mouseover",function(t,e){h.legendMouseover(t,e)}).on("mouseout",function(t,e){h.legendMouseout(t,e)}).on("click",function(t,e){h.legendClick(t,e);var n=A.data();if(d){if("classic"==g)f?(n.forEach(function(t){t.disabled=!0}),t.disabled=!1):(t.disabled=!t.disabled,n.every(function(t){return t.disabled})&&n.forEach(function(t){t.disabled=!1}));else if("furious"==g)if(p)t.disengaged=!t.disengaged,t.userDisabled=null==t.userDisabled?!!t.disabled:t.userDisabled,t.disabled=t.disengaged||t.userDisabled;else if(!p){t.disabled=!t.disabled,t.userDisabled=t.disabled,n.filter(function(t){return!t.disengaged}).every(function(t){return t.userDisabled})&&n.forEach(function(t){t.disabled=t.userDisabled=!1})}h.stateChange({disabled:n.map(function(t){return!!t.disabled}),disengaged:n.map(function(t){return!!t.disengaged})})}}).on("dblclick",function(t,e){if(("furious"!=g||!p)&&(h.legendDblclick(t,e),d)){var n=A.data();n.forEach(function(t){t.disabled=!0,"furious"==g&&(t.userDisabled=t.disabled)}),t.disabled=!1,"furious"==g&&(t.userDisabled=t.disabled),h.stateChange({disabled:n.map(function(t){return!!t.disabled})})}}),A.classed("nv-disabled",function(t){return t.userDisabled}),A.exit().remove(),W.attr("fill",y).text(function(t){return o(i(t))}),g){case"furious":S=23;break;case"classic":S=20}if(l){var E=[];A.each(function(e,n){var r,a;if(o(i(e))&&o(i(e)).length>s){var l=o(i(e)).substring(0,s);r=d3.select(this).select("text").text(l+"..."),d3.select(this).append("svg:title").text(o(i(e)))}else r=d3.select(this).select("text");try{if((a=r.node().getComputedTextLength())<=0)throw Error()}catch(c){a=t.utils.calcApproxTextWidth(r)}E.push(a+u)});for(var F=0,N=0,D=[];Nm&&F>1;){D=[],F--;for(var O=0;O(D[O%F]||0)&&(D[O%F]=E[O]);N=D.reduce(function(t,e,n,r){return t+e})}for(var _=[],L=0,B=0;Lz&&(z=I),"translate("+P+","+T+")"}),w.attr("transform","translate("+(n-e.right-z)+","+e.top+")"),r=e.top+e.bottom+T+15}"furious"==g&&M.attr("width",function(t,e){return W[0][e].getComputedTextLength()+27}).attr("height",18).attr("y",-9).attr("x",-15),M.style("fill",x).style("stroke",function(t,e){return t.color||a(t,e)})}),v}return v.dispatch=h,v.options=t.utils.optionsFunc.bind(v),v._options=Object.create({},{width:{get:function(){return n},set:function(t){n=t}},height:{get:function(){return r},set:function(t){r=t}},key:{get:function(){return i},set:function(t){i=t}},keyFormatter:{get:function(){return o},set:function(t){o=t}},align:{get:function(){return l},set:function(t){l=t}},rightAlign:{get:function(){return c},set:function(t){c=t}},maxKeyLength:{get:function(){return s},set:function(t){s=t}},padding:{get:function(){return u},set:function(t){u=t}},updateState:{get:function(){return d},set:function(t){d=t}},radioButtonMode:{get:function(){return f},set:function(t){f=t}},expanded:{get:function(){return p},set:function(t){p=t}},vers:{get:function(){return g},set:function(t){g=t}},margin:{get:function(){return e},set:function(t){e.top=void 0!==t.top?t.top:e.top,e.right=void 0!==t.right?t.right:e.right,e.bottom=void 0!==t.bottom?t.bottom:e.bottom,e.left=void 0!==t.left?t.left:e.left}},color:{get:function(){return a},set:function(e){a=t.utils.getColor(e)}}}),t.utils.initOptions(v),v},t.models.historicalBar=function(){"use strict";var e,n,r,i,o={top:0,right:0,bottom:0,left:0},a=null,s=null,l=Math.floor(1e4*Math.random()),u=null,c=d3.scale.linear(),d=d3.scale.linear(),f=function(t){return t.x},p=function(t){return t.y},h=[],g=[0],v=!1,m=!0,y=t.utils.defaultColor(),x=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),b=!0,k=t.utils.renderWatch(x,0);function w(M){return M.each(function(w){k.reset(),u=d3.select(this);var M=t.utils.availableWidth(a,u,o),A=t.utils.availableHeight(s,u,o);t.utils.initSVG(u),c.domain(e||d3.extent(w[0].values.map(f).concat(h))),v?c.range(r||[.5*M/w[0].values.length,M*(w[0].values.length-.5)/w[0].values.length]):c.range(r||[0,M]),d.domain(n||d3.extent(w[0].values.map(p).concat(g))).range(i||[A,0]),c.domain()[0]===c.domain()[1]&&(c.domain()[0]?c.domain([c.domain()[0]-.01*c.domain()[0],c.domain()[1]+.01*c.domain()[1]]):c.domain([-1,1])),d.domain()[0]===d.domain()[1]&&(d.domain()[0]?d.domain([d.domain()[0]+.01*d.domain()[0],d.domain()[1]-.01*d.domain()[1]]):d.domain([-1,1]));var C=u.selectAll("g.nv-wrap.nv-historicalBar-"+l).data([w[0].values]),S=C.enter().append("g").attr("class","nvd3 nv-wrap nv-historicalBar-"+l),W=S.append("defs"),E=S.append("g"),F=C.select("g");E.append("g").attr("class","nv-bars"),C.attr("transform","translate("+o.left+","+o.top+")"),u.on("click",function(t,e){x.chartClick({data:t,index:e,pos:d3.event,id:l})}),W.append("clipPath").attr("id","nv-chart-clip-path-"+l).append("rect"),C.select("#nv-chart-clip-path-"+l+" rect").attr("width",M).attr("height",A),F.attr("clip-path",m?"url(#nv-chart-clip-path-"+l+")":"");var N=C.select(".nv-bars").selectAll(".nv-bar").data(function(t){return t},function(t,e){return f(t,e)});N.exit().remove(),N.enter().append("rect").attr("x",0).attr("y",function(e,n){return t.utils.NaNtoZero(d(Math.max(0,p(e,n))))}).attr("height",function(e,n){return t.utils.NaNtoZero(Math.abs(d(p(e,n))-d(0)))}).attr("transform",function(t,e){return"translate("+(c(f(t,e))-M/w[0].values.length*.45)+",0)"}).on("mouseover",function(t,e){b&&(d3.select(this).classed("hover",!0),x.elementMouseover({data:t,index:e,color:d3.select(this).style("fill")}))}).on("mouseout",function(t,e){b&&(d3.select(this).classed("hover",!1),x.elementMouseout({data:t,index:e,color:d3.select(this).style("fill")}))}).on("mousemove",function(t,e){b&&x.elementMousemove({data:t,index:e,color:d3.select(this).style("fill")})}).on("click",function(t,e){if(b){x.elementClick({data:t,index:e,color:d3.select(this).style("fill"),event:d3.event,element:this}),d3.event.stopPropagation()}}).on("dblclick",function(t,e){b&&(x.elementDblClick({data:t,index:e,color:d3.select(this).style("fill")}),d3.event.stopPropagation())}),N.attr("fill",function(t,e){return y(t,e)}).attr("class",function(t,e,n){return(p(t,e)<0?"nv-bar negative":"nv-bar positive")+" nv-bar-"+n+"-"+e}).watchTransition(k,"bars").attr("transform",function(t,e){return"translate("+(c(f(t,e))-M/w[0].values.length*.45)+",0)"}).attr("width",M/w[0].values.length*.9),N.watchTransition(k,"bars").attr("y",function(e,n){var r=p(e,n)<0?d(0):d(0)-d(p(e,n))<1?d(0)-1:d(p(e,n));return t.utils.NaNtoZero(r)}).attr("height",function(e,n){return t.utils.NaNtoZero(Math.max(Math.abs(d(p(e,n))-d(0)),1))})}),k.renderEnd("historicalBar immediate"),w}return w.highlightPoint=function(t,e){u.select(".nv-bars .nv-bar-0-"+t).classed("hover",e)},w.clearHighlights=function(){u.select(".nv-bars .nv-bar.hover").classed("hover",!1)},w.dispatch=x,w.options=t.utils.optionsFunc.bind(w),w._options=Object.create({},{width:{get:function(){return a},set:function(t){a=t}},height:{get:function(){return s},set:function(t){s=t}},forceX:{get:function(){return h},set:function(t){h=t}},forceY:{get:function(){return g},set:function(t){g=t}},padData:{get:function(){return v},set:function(t){v=t}},x:{get:function(){return f},set:function(t){f=t}},y:{get:function(){return p},set:function(t){p=t}},xScale:{get:function(){return c},set:function(t){c=t}},yScale:{get:function(){return d},set:function(t){d=t}},xDomain:{get:function(){return e},set:function(t){e=t}},yDomain:{get:function(){return n},set:function(t){n=t}},xRange:{get:function(){return r},set:function(t){r=t}},yRange:{get:function(){return i},set:function(t){i=t}},clipEdge:{get:function(){return m},set:function(t){m=t}},id:{get:function(){return l},set:function(t){l=t}},interactive:{get:function(){return b},set:function(t){b=t}},margin:{get:function(){return o},set:function(t){o.top=void 0!==t.top?t.top:o.top,o.right=void 0!==t.right?t.right:o.right,o.bottom=void 0!==t.bottom?t.bottom:o.bottom,o.left=void 0!==t.left?t.left:o.left}},color:{get:function(){return y},set:function(e){y=t.utils.getColor(e)}}}),t.utils.initOptions(w),w},t.models.historicalBarChart=function(e){"use strict";var n,r,i=e||t.models.historicalBar(),o=t.models.axis(),a=t.models.axis(),s=t.models.legend(),l=t.interactiveGuideline(),u=t.models.tooltip(),c={top:30,right:90,bottom:50,left:90},d=null,f=t.utils.defaultColor(),p=null,h=null,g=!1,v=!0,m=!0,y=!1,x=!1,b={},k=null,w=null,M=d3.dispatch("tooltipHide","stateChange","changeState","renderEnd"),A=250;o.orient("bottom").tickPadding(7),a.orient(y?"right":"left"),u.duration(0).headerEnabled(!1).valueFormatter(function(t,e){return a.tickFormat()(t,e)}).headerFormatter(function(t,e){return o.tickFormat()(t,e)});var C=t.utils.renderWatch(M,0);function S(e){return e.each(function(u){C.reset(),C.models(i),v&&C.models(o),m&&C.models(a);var w=d3.select(this);t.utils.initSVG(w);var W,E=t.utils.availableWidth(p,w,c),F=t.utils.availableHeight(h,w,c);if(S.update=function(){w.transition().duration(A).call(S)},S.container=this,b.disabled=u.map(function(t){return!!t.disabled}),!k)for(W in k={},b)b[W]instanceof Array?k[W]=b[W].slice(0):k[W]=b[W];if(!(u&&u.length&&u.filter(function(t){return t.values.length}).length))return t.utils.noData(S,w),S;w.selectAll(".nv-noData").remove(),n=i.xScale(),r=i.yScale();var N=w.selectAll("g.nv-wrap.nv-historicalBarChart").data([u]),D=N.enter().append("g").attr("class","nvd3 nv-wrap nv-historicalBarChart").append("g"),O=N.select("g");D.append("g").attr("class","nv-x nv-axis"),D.append("g").attr("class","nv-y nv-axis"),D.append("g").attr("class","nv-barsWrap"),D.append("g").attr("class","nv-legendWrap"),D.append("g").attr("class","nv-interactive"),g?(s.width(E),O.select(".nv-legendWrap").datum(u).call(s),d||s.height()===c.top||(c.top=s.height(),F=t.utils.availableHeight(h,w,c)),N.select(".nv-legendWrap").attr("transform","translate(0,"+-c.top+")")):O.select(".nv-legendWrap").selectAll("*").remove(),N.attr("transform","translate("+c.left+","+c.top+")"),y&&O.select(".nv-y.nv-axis").attr("transform","translate("+E+",0)"),x&&(l.width(E).height(F).margin({left:c.left,top:c.top}).svgContainer(w).xScale(n),N.select(".nv-interactive").call(l)),i.width(E).height(F).color(u.map(function(t,e){return t.color||f(t,e)}).filter(function(t,e){return!u[e].disabled})),O.select(".nv-barsWrap").datum(u.filter(function(t){return!t.disabled})).transition().call(i),v&&(o.scale(n)._ticks(t.utils.calcTicksX(E/100,u)).tickSize(-F,0),O.select(".nv-x.nv-axis").attr("transform","translate(0,"+r.range()[0]+")"),O.select(".nv-x.nv-axis").transition().call(o)),m&&(a.scale(r)._ticks(t.utils.calcTicksY(F/36,u)).tickSize(-E,0),O.select(".nv-y.nv-axis").transition().call(a)),l.dispatch.on("elementMousemove",function(e){i.clearHighlights();var n,r,s,c=[];u.filter(function(t,e){return t.seriesIndex=e,!t.disabled}).forEach(function(o,a){r=t.interactiveBisect(o.values,e.pointXValue,S.x()),i.highlightPoint(r,!0);var l=o.values[r];void 0!==l&&(void 0===n&&(n=l),void 0===s&&(s=S.xScale()(S.x()(l,r))),c.push({key:o.key,value:S.y()(l,r),color:f(o,o.seriesIndex),data:o.values[r]}))});var d=o.tickFormat()(S.x()(n,r));l.tooltip.valueFormatter(function(t,e){return a.tickFormat()(t)}).data({value:d,index:r,series:c})(),l.renderGuideLine(s)}),l.dispatch.on("elementMouseout",function(t){M.tooltipHide(),i.clearHighlights()}),s.dispatch.on("legendClick",function(t,n){t.disabled=!t.disabled,u.filter(function(t){return!t.disabled}).length||u.map(function(t){return t.disabled=!1,N.selectAll(".nv-series").classed("disabled",!1),t}),b.disabled=u.map(function(t){return!!t.disabled}),M.stateChange(b),e.transition().call(S)}),s.dispatch.on("legendDblclick",function(t){u.forEach(function(t){t.disabled=!0}),t.disabled=!1,b.disabled=u.map(function(t){return!!t.disabled}),M.stateChange(b),S.update()}),M.on("changeState",function(t){void 0!==t.disabled&&(u.forEach(function(e,n){e.disabled=t.disabled[n]}),b.disabled=t.disabled),S.update()})}),C.renderEnd("historicalBarChart immediate"),S}return i.dispatch.on("elementMouseover.tooltip",function(t){t.series={key:S.x()(t.data),value:S.y()(t.data),color:t.color},u.data(t).hidden(!1)}),i.dispatch.on("elementMouseout.tooltip",function(t){u.hidden(!0)}),i.dispatch.on("elementMousemove.tooltip",function(t){u()}),S.dispatch=M,S.bars=i,S.legend=s,S.xAxis=o,S.yAxis=a,S.interactiveLayer=l,S.tooltip=u,S.options=t.utils.optionsFunc.bind(S),S._options=Object.create({},{width:{get:function(){return p},set:function(t){p=t}},height:{get:function(){return h},set:function(t){h=t}},showLegend:{get:function(){return g},set:function(t){g=t}},showXAxis:{get:function(){return v},set:function(t){v=t}},showYAxis:{get:function(){return m},set:function(t){m=t}},defaultState:{get:function(){return k},set:function(t){k=t}},noData:{get:function(){return w},set:function(t){w=t}},margin:{get:function(){return c},set:function(t){void 0!==t.top&&(c.top=t.top,d=t.top),c.right=void 0!==t.right?t.right:c.right,c.bottom=void 0!==t.bottom?t.bottom:c.bottom,c.left=void 0!==t.left?t.left:c.left}},color:{get:function(){return f},set:function(e){f=t.utils.getColor(e),s.color(f),i.color(f)}},duration:{get:function(){return A},set:function(t){A=t,C.reset(A),a.duration(A),o.duration(A)}},rightAlignYAxis:{get:function(){return y},set:function(t){y=t,a.orient(t?"right":"left")}},useInteractiveGuideline:{get:function(){return x},set:function(t){x=t,!0===t&&S.interactive(!1)}}}),t.utils.inheritOptions(S,i),t.utils.initOptions(S),S},t.models.ohlcBarChart=function(){var e=t.models.historicalBarChart(t.models.ohlcBar());return e.useInteractiveGuideline(!0),e.interactiveLayer.tooltip.contentGenerator(function(t){var n=t.series[0].data;return'

'+t.value+"

open:"+e.yAxis.tickFormat()(n.open)+"
close:"+e.yAxis.tickFormat()(n.close)+"
high"+e.yAxis.tickFormat()(n.high)+"
low:"+e.yAxis.tickFormat()(n.low)+"
"}),e},t.models.candlestickBarChart=function(){var e=t.models.historicalBarChart(t.models.candlestickBar());return e.useInteractiveGuideline(!0),e.interactiveLayer.tooltip.contentGenerator(function(t){var n=t.series[0].data;return'

'+t.value+"

open:"+e.yAxis.tickFormat()(n.open)+"
close:"+e.yAxis.tickFormat()(n.close)+"
high"+e.yAxis.tickFormat()(n.high)+"
low:"+e.yAxis.tickFormat()(n.low)+"
"}),e},t.models.legend=function(){"use strict";var e={top:5,right:0,bottom:5,left:0},n=400,r=20,i=function(t){return t.key},o=function(t){return t},a=t.utils.getColor(),s=20,l=!0,u=32,c=!0,d=!0,f=!0,p=!1,h=!1,g=d3.dispatch("legendClick","legendDblclick","legendMouseover","legendMouseout","stateChange"),v="classic";function m(y){function x(t,e){return"furious"!=v?"#000":h?t.disengaged?"#000":"#fff":h?void 0:(t.color||(t.color=a(t,e)),t.disabled?t.color:"#fff")}function b(t,e){return h&&"furious"==v&&t.disengaged?"#eee":t.color||a(t,e)}function k(t,e){return h&&"furious"==v?1:t.disabled?0:1}return y.each(function(a){var m=n-e.left-e.right,y=d3.select(this);t.utils.initSVG(y);var w=y.selectAll("g.nv-legend").data([a]),M=w.enter().append("g").attr("class","nvd3 nv-legend").append("g"),A=w.select("g");c?w.attr("transform","translate("+-e.right+","+e.top+")"):w.attr("transform","translate("+e.left+","+e.top+")");var C,S,W=A.selectAll(".nv-series").data(function(t){return"furious"!=v?t:t.filter(function(t){return!!h||!t.disengaged})}),E=W.enter().append("g").attr("class","nv-series");switch(v){case"furious":S=23;break;case"classic":S=20}if("classic"==v)E.append("circle").style("stroke-width",2).attr("class","nv-legend-symbol").attr("r",5),C=W.select(".nv-legend-symbol");else if("furious"==v){E.append("rect").style("stroke-width",2).attr("class","nv-legend-symbol").attr("rx",3).attr("ry",3),C=W.select(".nv-legend-symbol"),E.append("g").attr("class","nv-check-box").property("innerHTML",'').attr("transform","translate(-10,-8)scale(0.5)"),W.select(".nv-check-box").each(function(t,e){d3.select(this).selectAll("path").attr("stroke",x(t,e))})}E.append("text").attr("text-anchor","start").attr("class","nv-legend-text").attr("dy",".32em").attr("dx","8");var F=W.select("text.nv-legend-text");W.on("mouseover",function(t,e){g.legendMouseover(t,e)}).on("mouseout",function(t,e){g.legendMouseout(t,e)}).on("click",function(t,e){g.legendClick(t,e);var n=W.data();if(d){if("classic"==v)p?(n.forEach(function(t){t.disabled=!0}),t.disabled=!1):(t.disabled=!t.disabled,n.every(function(t){return t.disabled})&&n.forEach(function(t){t.disabled=!1}));else if("furious"==v)if(h)t.disengaged=!t.disengaged,t.userDisabled=null==t.userDisabled?!!t.disabled:t.userDisabled,t.disabled=t.disengaged||t.userDisabled;else if(!h){t.disabled=!t.disabled,t.userDisabled=t.disabled,n.filter(function(t){return!t.disengaged}).every(function(t){return t.userDisabled})&&n.forEach(function(t){t.disabled=t.userDisabled=!1})}g.stateChange({disabled:n.map(function(t){return!!t.disabled}),disengaged:n.map(function(t){return!!t.disengaged})})}}).on("dblclick",function(t,e){if(f){if("furious"==v&&h)return;if(g.legendDblclick(t,e),d){var n=W.data();n.forEach(function(t){t.disabled=!0,"furious"==v&&(t.userDisabled=t.disabled)}),t.disabled=!1,"furious"==v&&(t.userDisabled=t.disabled),g.stateChange({disabled:n.map(function(t){return!!t.disabled})})}}}),W.classed("nv-disabled",function(t){return t.userDisabled}),W.exit().remove(),F.attr("fill",x).text(function(t){return o(i(t))});var N=0;if(l){var D=[];W.each(function(e,n){var r,a;if(o(i(e))&&o(i(e)).length>s){var l=o(i(e)).substring(0,s);r=d3.select(this).select("text").text(l+"..."),d3.select(this).append("svg:title").text(o(i(e)))}else r=d3.select(this).select("text");try{if((a=r.node().getComputedTextLength())<=0)throw Error()}catch(c){a=t.utils.calcApproxTextWidth(r)}D.push(a+u)});var O=0,_=[];for(N=0;Nm&&O>1;){_=[],O--;for(var L=0;L(_[L%O]||0)&&(_[L%O]=D[L]);N=_.reduce(function(t,e,n,r){return t+e})}for(var B=[],P=0,T=0;PH&&(H=V),N0?k:0),S.attr("clip-path",h?"url(#nv-edge-clip-"+r.id()+")":""),W.attr("clip-path",h?"url(#nv-edge-clip-"+r.id()+")":"");var E=w.select(".nv-groups").selectAll(".nv-group").data(function(t){return t},function(t){return t.key});E.enter().append("g").style("stroke-opacity",1e-6).style("stroke-width",function(t){return t.strokeWidth||l}).style("fill-opacity",1e-6),E.exit().remove(),E.attr("class",function(t,e){return(t.classed||"")+" nv-group nv-series-"+e}).classed("hover",function(t){return t.hover}).style("fill",function(t,e){return u(t,e)}).style("stroke",function(t,e){return u(t,e)}),E.watchTransition(b,"line: groups").style("stroke-opacity",1).style("fill-opacity",function(t){return t.fillOpacity||.5});var F=E.selectAll("path.nv-area").data(function(t){return p(t)?[t]:[]});F.enter().append("path").attr("class","nv-area").attr("d",function(e){return d3.svg.area().interpolate(g).defined(f).x(function(e,n){return t.utils.NaNtoZero(y(c(e,n)))}).y0(function(e,n){return t.utils.NaNtoZero(x(d(e,n)))}).y1(function(t,e){return x(n.domain()[0]<=0?n.domain()[1]>=0?0:n.domain()[1]:n.domain()[0])}).apply(this,[e.values])}),E.exit().selectAll("path.nv-area").remove(),F.watchTransition(b,"line: areaPaths").attr("d",function(r){return d3.svg.area().interpolate(g).defined(f).x(function(n,r){return t.utils.NaNtoZero(e(c(n,r)))}).y0(function(e,r){return t.utils.NaNtoZero(n(d(e,r)))}).y1(function(t,e){return n(n.domain()[0]<=0?n.domain()[1]>=0?0:n.domain()[1]:n.domain()[0])}).apply(this,[r.values])});var N=E.selectAll("path.nv-line").data(function(t){return[t.values]});N.enter().append("path").attr("class","nv-line").attr("d",d3.svg.line().interpolate(g).defined(f).x(function(e,n){return t.utils.NaNtoZero(y(c(e,n)))}).y(function(e,n){return t.utils.NaNtoZero(x(d(e,n)))})),N.watchTransition(b,"line: linePaths").attr("d",d3.svg.line().interpolate(g).defined(f).x(function(n,r){return t.utils.NaNtoZero(e(c(n,r)))}).y(function(e,r){return t.utils.NaNtoZero(n(d(e,r)))})),y=e.copy(),x=n.copy()}),b.renderEnd("line immediate"),k}return k.dispatch=m,k.scatter=r,r.dispatch.on("elementClick",function(){m.elementClick.apply(this,arguments)}),r.dispatch.on("elementMouseover",function(){m.elementMouseover.apply(this,arguments)}),r.dispatch.on("elementMouseout",function(){m.elementMouseout.apply(this,arguments)}),k.options=t.utils.optionsFunc.bind(k),k._options=Object.create({},{width:{get:function(){return o},set:function(t){o=t}},height:{get:function(){return a},set:function(t){a=t}},defined:{get:function(){return f},set:function(t){f=t}},interpolate:{get:function(){return g},set:function(t){g=t}},clipEdge:{get:function(){return h},set:function(t){h=t}},margin:{get:function(){return i},set:function(t){i.top=void 0!==t.top?t.top:i.top,i.right=void 0!==t.right?t.right:i.right,i.bottom=void 0!==t.bottom?t.bottom:i.bottom,i.left=void 0!==t.left?t.left:i.left}},duration:{get:function(){return v},set:function(t){v=t,b.reset(v),r.duration(v)}},isArea:{get:function(){return p},set:function(t){p=d3.functor(t)}},x:{get:function(){return c},set:function(t){c=t,r.x(t)}},y:{get:function(){return d},set:function(t){d=t,r.y(t)}},color:{get:function(){return u},set:function(e){u=t.utils.getColor(e),r.color(u)}}}),t.utils.inheritOptions(k,r),t.utils.initOptions(k),k},t.models.lineChart=function(){"use strict";var e,n,r=t.models.line(),i=t.models.axis(),o=t.models.axis(),a=t.models.legend(),s=t.interactiveGuideline(),l=t.models.tooltip(),u=t.models.focus(t.models.line()),c={top:30,right:20,bottom:50,left:60},d=null,f=t.utils.defaultColor(),p=null,h=null,g=!0,v="top",m=!0,y=!0,x=!1,b=!1,k=!1,w=t.utils.state(),M=null,A=null,C=d3.dispatch("stateChange","changeState","renderEnd"),S=250;i.orient("bottom").tickPadding(7),o.orient(x?"right":"left"),r.clipEdge(!0).duration(0),l.valueFormatter(function(t,e){return o.tickFormat()(t,e)}).headerFormatter(function(t,e){return i.tickFormat()(t,e)}),s.tooltip.valueFormatter(function(t,e){return o.tickFormat()(t,e)}).headerFormatter(function(t,e){return i.tickFormat()(t,e)});var W=t.utils.renderWatch(C,S),E=function(t){return function(){return{active:t.map(function(t){return!t.disabled})}}},F=function(t){return function(e){void 0!==e.active&&t.forEach(function(t,n){t.disabled=!e.active[n]})}};function N(l){return W.reset(),W.models(r),m&&W.models(i),y&&W.models(o),l.each(function(l){var A=d3.select(this);t.utils.initSVG(A);var W,D=t.utils.availableWidth(p,A,c),O=t.utils.availableHeight(h,A,c)-(k?u.height():0);if(N.update=function(){0===S?A.call(N):A.transition().duration(S).call(N)},N.container=this,w.setter(F(l),N.update).getter(E(l)).update(),w.disabled=l.map(function(t){return!!t.disabled}),!M)for(W in M={},w)w[W]instanceof Array?M[W]=w[W].slice(0):M[W]=w[W];if(!(l&&l.length&&l.filter(function(t){return t.values.length}).length))return t.utils.noData(N,A),N;A.selectAll(".nv-noData").remove(),u.dispatch.on("onBrush",function(t){H(t)}),e=r.xScale(),n=r.yScale();var _=A.selectAll("g.nv-wrap.nv-lineChart").data([l]),L=_.enter().append("g").attr("class","nvd3 nv-wrap nv-lineChart").append("g"),B=_.select("g");L.append("g").attr("class","nv-legendWrap");var P=L.append("g").attr("class","nv-focus");P.append("g").attr("class","nv-background").append("rect"),P.append("g").attr("class","nv-x nv-axis"),P.append("g").attr("class","nv-y nv-axis"),P.append("g").attr("class","nv-linesWrap"),P.append("g").attr("class","nv-interactive");L.append("g").attr("class","nv-focusWrap");g?(a.width(D),B.select(".nv-legendWrap").datum(l).call(a),"bottom"===v?(c.bottom=i.height()+a.height(),O=t.utils.availableHeight(h,A,c),B.select(".nv-legendWrap").attr("transform","translate(0,"+(O+i.height())+")")):"top"===v&&(d||a.height()===c.top||(c.top=a.height(),O=t.utils.availableHeight(h,A,c)-(k?u.height():0)),_.select(".nv-legendWrap").attr("transform","translate(0,"+-c.top+")"))):B.select(".nv-legendWrap").selectAll("*").remove(),_.attr("transform","translate("+c.left+","+c.top+")"),x&&B.select(".nv-y.nv-axis").attr("transform","translate("+D+",0)"),b&&(s.width(D).height(O).margin({left:c.left,top:c.top}).svgContainer(A).xScale(e),_.select(".nv-interactive").call(s)),B.select(".nv-focus .nv-background rect").attr("width",D).attr("height",O),r.width(D).height(O).color(l.map(function(t,e){return t.color||f(t,e)}).filter(function(t,e){return!l[e].disabled}));var T=B.select(".nv-linesWrap").datum(l.filter(function(t){return!t.disabled}));function I(){m&&B.select(".nv-focus .nv-x.nv-axis").transition().duration(S).call(i)}function z(){y&&B.select(".nv-focus .nv-y.nv-axis").transition().duration(S).call(o)}if(m&&i.scale(e)._ticks(t.utils.calcTicksX(D/100,l)).tickSize(-O,0),y&&o.scale(n)._ticks(t.utils.calcTicksY(O/36,l)).tickSize(-D,0),B.select(".nv-focus .nv-x.nv-axis").attr("transform","translate(0,"+O+")"),k||null!==u.brush.extent()){u.width(D),B.select(".nv-focusWrap").style("display",k?"initial":"none").attr("transform","translate(0,"+(O+c.bottom+u.margin().top)+")").call(u);var V=u.brush.empty()?u.xDomain():u.brush.extent();null!==V&&H(V)}else T.call(r),I(),z();function H(t){B.select(".nv-focus .nv-linesWrap").datum(l.filter(function(t){return!t.disabled}).map(function(e,n){return{key:e.key,area:e.area,classed:e.classed,values:e.values.filter(function(e,n){return r.x()(e,n)>=t[0]&&r.x()(e,n)<=t[1]}),disableTooltip:e.disableTooltip}})).transition().duration(S).call(r),I(),z()}a.dispatch.on("stateChange",function(t){for(var e in t)w[e]=t[e];C.stateChange(w),N.update()}),s.dispatch.on("elementMousemove",function(n){r.clearHighlights();var i,a,c,d=[];if(l.filter(function(t,e){return t.seriesIndex=e,!t.disabled&&!t.disableTooltip}).forEach(function(o,s){var l=null!==u.brush.extent()?u.brush.empty()?u.xScale().domain():u.brush.extent():e.domain(),p=o.values.filter(function(t,e){return l[0]<=l[1]?r.x()(t,e)>=l[0]&&r.x()(t,e)<=l[1]:r.x()(t,e)>=l[1]&&r.x()(t,e)<=l[0]}),h=p[a=t.interactiveBisect(p,n.pointXValue,r.x())],g=N.y()(h,a);null!==g&&r.highlightPoint(s,a,!0),void 0!==h&&(void 0===i&&(i=h),void 0===c&&(c=N.xScale()(N.x()(h,a))),d.push({key:o.key,value:g,color:f(o,o.seriesIndex),data:h}))}),d.length>2){var p=N.yScale().invert(n.mouseY),h=.03*Math.abs(N.yScale().domain()[0]-N.yScale().domain()[1]),g=t.nearestValueIndex(d.map(function(t){return t.value}),p,h);null!==g&&(d[g].highlight=!0)}s.tooltip.valueFormatter(s.tooltip.valueFormatter()||function(t,e){return null==t?"N/A":o.tickFormat()(t)}).data({value:N.x()(i,a),index:a,series:d})(),s.renderGuideLine(c)}),s.dispatch.on("elementClick",function(e){var n,i=[];l.filter(function(t,e){return t.seriesIndex=e,!t.disabled}).forEach(function(r){var o=t.interactiveBisect(r.values,e.pointXValue,N.x()),a=r.values[o];if(void 0!==a){void 0===n&&(n=N.xScale()(N.x()(a,o)));var s=N.yScale()(N.y()(a,o));i.push({point:a,pointIndex:o,pos:[n,s],seriesIndex:r.seriesIndex,series:r})}}),r.dispatch.elementClick(i)}),s.dispatch.on("elementMouseout",function(t){r.clearHighlights()}),C.on("changeState",function(t){void 0!==t.disabled&&l.length===t.disabled.length&&(l.forEach(function(e,n){e.disabled=t.disabled[n]}),w.disabled=t.disabled),N.update()})}),W.renderEnd("lineChart immediate"),N}return r.dispatch.on("elementMouseover.tooltip",function(t){t.series.disableTooltip||l.data(t).hidden(!1)}),r.dispatch.on("elementMouseout.tooltip",function(t){l.hidden(!0)}),N.dispatch=C,N.lines=r,N.legend=a,N.focus=u,N.xAxis=i,N.x2Axis=u.xAxis,N.yAxis=o,N.y2Axis=u.yAxis,N.interactiveLayer=s,N.tooltip=l,N.state=w,N.dispatch=C,N.options=t.utils.optionsFunc.bind(N),N._options=Object.create({},{width:{get:function(){return p},set:function(t){p=t}},height:{get:function(){return h},set:function(t){h=t}},showLegend:{get:function(){return g},set:function(t){g=t}},legendPosition:{get:function(){return v},set:function(t){v=t}},showXAxis:{get:function(){return m},set:function(t){m=t}},showYAxis:{get:function(){return y},set:function(t){y=t}},defaultState:{get:function(){return M},set:function(t){M=t}},noData:{get:function(){return A},set:function(t){A=t}},focusEnable:{get:function(){return k},set:function(t){k=t}},focusHeight:{get:function(){return u.height()},set:function(t){u.height(t)}},focusShowAxisX:{get:function(){return u.showXAxis()},set:function(t){u.showXAxis(t)}},focusShowAxisY:{get:function(){return u.showYAxis()},set:function(t){u.showYAxis(t)}},brushExtent:{get:function(){return u.brushExtent()},set:function(t){u.brushExtent(t)}},focusMargin:{get:function(){return u.margin},set:function(t){void 0!==t.top&&(c.top=t.top,d=t.top),u.margin.right=void 0!==t.right?t.right:u.margin.right,u.margin.bottom=void 0!==t.bottom?t.bottom:u.margin.bottom,u.margin.left=void 0!==t.left?t.left:u.margin.left}},margin:{get:function(){return c},set:function(t){c.top=void 0!==t.top?t.top:c.top,c.right=void 0!==t.right?t.right:c.right,c.bottom=void 0!==t.bottom?t.bottom:c.bottom,c.left=void 0!==t.left?t.left:c.left}},duration:{get:function(){return S},set:function(t){S=t,W.reset(S),r.duration(S),u.duration(S),i.duration(S),o.duration(S)}},color:{get:function(){return f},set:function(e){f=t.utils.getColor(e),a.color(f),r.color(f),u.color(f)}},interpolate:{get:function(){return r.interpolate()},set:function(t){r.interpolate(t),u.interpolate(t)}},xTickFormat:{get:function(){return i.tickFormat()},set:function(t){i.tickFormat(t),u.xTickFormat(t)}},yTickFormat:{get:function(){return o.tickFormat()},set:function(t){o.tickFormat(t),u.yTickFormat(t)}},x:{get:function(){return r.x()},set:function(t){r.x(t),u.x(t)}},y:{get:function(){return r.y()},set:function(t){r.y(t),u.y(t)}},rightAlignYAxis:{get:function(){return x},set:function(t){x=t,o.orient(x?"right":"left")}},useInteractiveGuideline:{get:function(){return b},set:function(t){(b=t)&&(r.interactive(!1),r.useVoronoi(!1))}}}),t.utils.inheritOptions(N,r),t.utils.initOptions(N),N},t.models.lineWithFocusChart=function(){return t.models.lineChart().margin({bottom:30}).focusEnable(!0)},t.models.linePlusBarChart=function(){"use strict";var e,n,r,i,o,a,s,l=t.models.line(),u=t.models.line(),c=t.models.historicalBar(),d=t.models.historicalBar(),f=t.models.axis(),p=t.models.axis(),h=t.models.axis(),g=t.models.axis(),v=t.models.axis(),m=t.models.axis(),y=t.models.legend(),x=d3.svg.brush(),b=t.models.tooltip(),k={top:30,right:30,bottom:30,left:60},w=null,M={top:0,right:30,bottom:20,left:60},A=null,C=null,S=function(t){return t.x},W=function(t){return t.y},E=t.utils.defaultColor(),F=!0,N=!0,D=!1,O=!0,_=50,L=null,B=null,P=d3.dispatch("brush","stateChange","changeState"),T=0,I=t.utils.state(),z=null,V=" (left axis)",H=" (right axis)",Y=!1;l.clipEdge(!0),u.interactive(!1),u.pointActive(function(t){return!1}),f.orient("bottom").tickPadding(5),h.orient("left"),g.orient("right"),p.orient("bottom").tickPadding(5),v.orient("left"),m.orient("right"),b.headerEnabled(!0).headerFormatter(function(t,e){return f.tickFormat()(t,e)});var G=function(t){return function(){return{active:t.map(function(t){return!t.disabled})}}},X=function(t){return function(e){void 0!==e.active&&t.forEach(function(t,n){t.disabled=!e.active[n]})}},j=function(t){return t.every(function(t){return t.disabled})};function R(b){return b.each(function(b){var B=d3.select(this);t.utils.initSVG(B);var Z,q=t.utils.availableWidth(A,B,k),K=t.utils.availableHeight(C,B,k)-(N?_:0),U=_-M.top-M.bottom;if(R.update=function(){B.transition().duration(T).call(R)},R.container=this,I.setter(X(b),R.update).getter(G(b)).update(),I.disabled=b.map(function(t){return!!t.disabled}),!z)for(Z in z={},I)I[Z]instanceof Array?z[Z]=I[Z].slice(0):z[Z]=I[Z];if(!(b&&b.length&&b.filter(function(t){return t.values.length}).length))return t.utils.noData(R,B),R;B.selectAll(".nv-noData").remove();var Q=b.filter(function(t){return!t.disabled&&t.bar}),J=b.filter(function(t){return!t.bar});n=Q.length&&!Y?c.xScale():l.xScale(),r=p.scale(),i=Y?l.yScale():c.yScale(),o=Y?c.yScale():l.yScale(),a=Y?u.yScale():d.yScale(),s=Y?d.yScale():u.yScale();var $=b.filter(function(t){return!t.disabled&&(Y?!t.bar:t.bar)}).map(function(t){return t.values.map(function(t,e){return{x:S(t,e),y:W(t,e)}})}),tt=b.filter(function(t){return!t.disabled&&(Y?t.bar:!t.bar)}).map(function(t){return t.values.map(function(t,e){return{x:S(t,e),y:W(t,e)}})});n.range([0,q]),r.domain(d3.extent(d3.merge($.concat(tt)),function(t){return t.x})).range([0,q]);var et=B.selectAll("g.nv-wrap.nv-linePlusBar").data([b]),nt=et.enter().append("g").attr("class","nvd3 nv-wrap nv-linePlusBar").append("g"),rt=et.select("g");nt.append("g").attr("class","nv-legendWrap");var it=nt.append("g").attr("class","nv-focus");it.append("g").attr("class","nv-x nv-axis"),it.append("g").attr("class","nv-y1 nv-axis"),it.append("g").attr("class","nv-y2 nv-axis"),it.append("g").attr("class","nv-barsWrap"),it.append("g").attr("class","nv-linesWrap");var ot=nt.append("g").attr("class","nv-context");if(ot.append("g").attr("class","nv-x nv-axis"),ot.append("g").attr("class","nv-y1 nv-axis"),ot.append("g").attr("class","nv-y2 nv-axis"),ot.append("g").attr("class","nv-barsWrap"),ot.append("g").attr("class","nv-linesWrap"),ot.append("g").attr("class","nv-brushBackground"),ot.append("g").attr("class","nv-x nv-brush"),F){var at=y.align()?q/2:q,st=y.align()?at:0;y.width(at),rt.select(".nv-legendWrap").datum(b.map(function(t){return t.originalKey=void 0===t.originalKey?t.key:t.originalKey,t.key=Y?t.originalKey+(t.bar?H:V):t.originalKey+(t.bar?V:H),t})).call(y),w||y.height()===k.top||(k.top=y.height(),K=t.utils.availableHeight(C,B,k)-_),rt.select(".nv-legendWrap").attr("transform","translate("+st+","+-k.top+")")}else rt.select(".nv-legendWrap").selectAll("*").remove();et.attr("transform","translate("+k.left+","+k.top+")"),rt.select(".nv-context").style("display",N?"initial":"none"),d.width(q).height(U).color(b.map(function(t,e){return t.color||E(t,e)}).filter(function(t,e){return!b[e].disabled&&b[e].bar})),u.width(q).height(U).color(b.map(function(t,e){return t.color||E(t,e)}).filter(function(t,e){return!b[e].disabled&&!b[e].bar}));var lt=rt.select(".nv-context .nv-barsWrap").datum(Q.length?Q:[{values:[]}]),ut=rt.select(".nv-context .nv-linesWrap").datum(j(J)?[{values:[]}]:J.filter(function(t){return!t.disabled}));rt.select(".nv-context").attr("transform","translate(0,"+(K+k.bottom+M.top)+")"),lt.transition().call(d),ut.transition().call(u),O&&(p._ticks(t.utils.calcTicksX(q/100,b)).tickSize(-U,0),rt.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+a.range()[0]+")"),rt.select(".nv-context .nv-x.nv-axis").transition().call(p)),D&&(v.scale(a)._ticks(U/36).tickSize(-q,0),m.scale(s)._ticks(U/36).tickSize(Q.length?0:-q,0),rt.select(".nv-context .nv-y3.nv-axis").style("opacity",Q.length?1:0).attr("transform","translate(0,"+r.range()[0]+")"),rt.select(".nv-context .nv-y2.nv-axis").style("opacity",J.length?1:0).attr("transform","translate("+r.range()[1]+",0)"),rt.select(".nv-context .nv-y1.nv-axis").transition().call(v),rt.select(".nv-context .nv-y2.nv-axis").transition().call(m)),x.x(r).on("brush",pt),L&&x.extent(L);var ct=rt.select(".nv-brushBackground").selectAll("g").data([L||x.extent()]),dt=ct.enter().append("g");dt.append("rect").attr("class","left").attr("x",0).attr("y",0).attr("height",U),dt.append("rect").attr("class","right").attr("x",0).attr("y",0).attr("height",U);var ft=rt.select(".nv-x.nv-brush").call(x);function pt(){L=x.empty()?null:x.extent(),e=x.empty()?r.domain():x.extent(),P.brush({extent:e,brush:x}),x.empty()||x.extent(L),ct.data([x.empty()?r.domain():L]).each(function(t,e){var n=r(t[0])-r.range()[0],i=r.range()[1]-r(t[1]);d3.select(this).select(".left").attr("width",n<0?0:n),d3.select(this).select(".right").attr("x",r(t[1])).attr("width",i<0?0:i)}),c.width(q).height(K).color(b.map(function(t,e){return t.color||E(t,e)}).filter(function(t,e){return!b[e].disabled&&b[e].bar})),l.width(q).height(K).color(b.map(function(t,e){return t.color||E(t,e)}).filter(function(t,e){return!b[e].disabled&&!b[e].bar}));var a=rt.select(".nv-focus .nv-barsWrap").datum(Q.length?Q.map(function(t,n){return{key:t.key,values:t.values.filter(function(t,n){return c.x()(t,n)>=e[0]&&c.x()(t,n)<=e[1]})}}):[{values:[]}]),s=rt.select(".nv-focus .nv-linesWrap").datum(j(J)?[{values:[]}]:J.filter(function(t){return!t.disabled}).map(function(t,n){return{area:t.area,fillOpacity:t.fillOpacity,strokeWidth:t.strokeWidth,key:t.key,values:t.values.filter(function(t,n){return l.x()(t,n)>=e[0]&&l.x()(t,n)<=e[1]})}}));n=Q.length&&!Y?c.xScale():l.xScale(),f.scale(n)._ticks(t.utils.calcTicksX(q/100,b)).tickSize(-K,0),f.domain([Math.ceil(e[0]),Math.floor(e[1])]),rt.select(".nv-x.nv-axis").transition().duration(T).call(f),a.transition().duration(T).call(c),s.transition().duration(T).call(l),rt.select(".nv-focus .nv-x.nv-axis").attr("transform","translate(0,"+i.range()[0]+")"),h.scale(i)._ticks(t.utils.calcTicksY(K/36,b)).tickSize(-q,0),g.scale(o)._ticks(t.utils.calcTicksY(K/36,b)),Y?g.tickSize(J.length?0:-q,0):g.tickSize(Q.length?0:-q,0);var u=Q.length?1:0,d=J.length&&!j(J)?1:0,p=Y?d:u,v=Y?u:d;rt.select(".nv-focus .nv-y1.nv-axis").style("opacity",p),rt.select(".nv-focus .nv-y2.nv-axis").style("opacity",v).attr("transform","translate("+n.range()[1]+",0)"),rt.select(".nv-focus .nv-y1.nv-axis").transition().duration(T).call(h),rt.select(".nv-focus .nv-y2.nv-axis").transition().duration(T).call(g)}ft.selectAll("rect").attr("height",U),ft.selectAll(".resize").append("path").attr("d",function(t){var e=+("e"==t),n=e?1:-1,r=U/3;return"M"+.5*n+","+r+"A6,6 0 0 "+e+" "+6.5*n+","+(r+6)+"V"+(2*r-6)+"A6,6 0 0 "+e+" "+.5*n+","+2*r+"ZM"+2.5*n+","+(r+8)+"V"+(2*r-8)+"M"+4.5*n+","+(r+8)+"V"+(2*r-8)}),y.dispatch.on("stateChange",function(t){for(var e in t)I[e]=t[e];P.stateChange(I),R.update()}),P.on("changeState",function(t){void 0!==t.disabled&&(b.forEach(function(e,n){e.disabled=t.disabled[n]}),I.disabled=t.disabled),R.update()}),pt()}),R}return l.dispatch.on("elementMouseover.tooltip",function(t){b.duration(100).valueFormatter(function(t,e){return(Y?{main:h,focus:v}:{main:g,focus:m}).main.tickFormat()(t,e)}).data(t).hidden(!1)}),l.dispatch.on("elementMouseout.tooltip",function(t){b.hidden(!0)}),c.dispatch.on("elementMouseover.tooltip",function(t){t.value=R.x()(t.data),t.series={value:R.y()(t.data),color:t.color},b.duration(0).valueFormatter(function(t,e){return(Y?{main:g,focus:m}:{main:h,focus:v}).main.tickFormat()(t,e)}).data(t).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(t){b.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(t){b()}),R.dispatch=P,R.legend=y,R.lines=l,R.lines2=u,R.bars=c,R.bars2=d,R.xAxis=f,R.x2Axis=p,R.y1Axis=h,R.y2Axis=g,R.y3Axis=v,R.y4Axis=m,R.tooltip=b,R.options=t.utils.optionsFunc.bind(R),R._options=Object.create({},{width:{get:function(){return A},set:function(t){A=t}},height:{get:function(){return C},set:function(t){C=t}},showLegend:{get:function(){return F},set:function(t){F=t}},brushExtent:{get:function(){return L},set:function(t){L=t}},noData:{get:function(){return B},set:function(t){B=t}},focusEnable:{get:function(){return N},set:function(t){N=t}},focusHeight:{get:function(){return _},set:function(t){_=t}},focusShowAxisX:{get:function(){return O},set:function(t){O=t}},focusShowAxisY:{get:function(){return D},set:function(t){D=t}},legendLeftAxisHint:{get:function(){return V},set:function(t){V=t}},legendRightAxisHint:{get:function(){return H},set:function(t){H=t}},margin:{get:function(){return k},set:function(t){void 0!==t.top&&(k.top=t.top,w=t.top),k.right=void 0!==t.right?t.right:k.right,k.bottom=void 0!==t.bottom?t.bottom:k.bottom,k.left=void 0!==t.left?t.left:k.left}},focusMargin:{get:function(){return M},set:function(t){M.top=void 0!==t.top?t.top:M.top,M.right=void 0!==t.right?t.right:M.right,M.bottom=void 0!==t.bottom?t.bottom:M.bottom,M.left=void 0!==t.left?t.left:M.left}},duration:{get:function(){return T},set:function(t){T=t}},color:{get:function(){return E},set:function(e){E=t.utils.getColor(e),y.color(E)}},x:{get:function(){return S},set:function(t){S=t,l.x(t),u.x(t),c.x(t),d.x(t)}},y:{get:function(){return W},set:function(t){W=t,l.y(t),u.y(t),c.y(t),d.y(t)}},switchYAxisOrder:{get:function(){return Y},set:function(t){if(Y!==t){var e=h;h=g,g=e;var n=v;v=m,m=n}Y=t,h.orient("left"),g.orient("right"),v.orient("left"),m.orient("right")}}}),t.utils.inheritOptions(R,l),t.utils.initOptions(R),R},t.models.multiBar=function(){"use strict";var e,n,r,i,o,a,s,l={top:0,right:0,bottom:0,left:0},u=960,c=500,d=d3.scale.ordinal(),f=d3.scale.linear(),p=Math.floor(1e4*Math.random()),h=null,g=function(t){return t.x},v=function(t){return t.y},m=[0],y=!0,x=!1,b="zero",k=t.utils.defaultColor(),w=!1,M=null,A=500,C=.1,S=.75,W=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),E=t.utils.renderWatch(W,A),F=0;function N(D){return E.reset(),D.each(function(N){var D=u-l.left-l.right,O=c-l.top-l.bottom;h=d3.select(this),t.utils.initSVG(h);var _=0;if(w&&N.length&&(w=[{values:N[0].values.map(function(t){return{x:t.x,y:0,series:t.series,size:.01}})}]),x){var L=d3.layout.stack().offset(b).values(function(t){return t.values}).y(v)(!N.length&&w?w:N);L.forEach(function(t,e){t.nonStackable?(N[e].nonStackableSeries=_++,L[e]=N[e]):e>0&&L[e-1].nonStackable&&L[e].values.map(function(t,n){t.y0-=L[e-1].values[n].y,t.y1=t.y0+t.y})}),N=L}N.forEach(function(t,e){t.values.forEach(function(n){n.series=e,n.key=t.key})}),x&&N.length>0&&N[0].values.map(function(t,e){var n=0,r=0;N.map(function(t,i){if(!N[i].nonStackable){var o=t.values[e];o.size=Math.abs(o.y),o.y<0?(o.y1=r,r-=o.size):(o.y1=o.size+n,n+=o.size)}})});var B=n&&r?[]:N.map(function(t,e){return t.values.map(function(t,n){return{x:g(t,n),y:v(t,n),y0:t.y0,y1:t.y1,idx:e}})});d.domain(n||d3.merge(B).map(function(t){return t.x})).rangeBands(i||[0,D],C),f.domain(r||d3.extent(d3.merge(B).map(function(t){var e=t.y;return x&&!N[t.idx].nonStackable&&(e=t.y>0?t.y1:t.y1+t.y),e}).concat(m))).range(o||[O,0]),d.domain()[0]===d.domain()[1]&&(d.domain()[0]?d.domain([d.domain()[0]-.01*d.domain()[0],d.domain()[1]+.01*d.domain()[1]]):d.domain([-1,1])),f.domain()[0]===f.domain()[1]&&(f.domain()[0]?f.domain([f.domain()[0]+.01*f.domain()[0],f.domain()[1]-.01*f.domain()[1]]):f.domain([-1,1])),a=a||d,s=s||f;var P=h.selectAll("g.nv-wrap.nv-multibar").data([N]),T=P.enter().append("g").attr("class","nvd3 nv-wrap nv-multibar"),I=T.append("defs"),z=T.append("g"),V=P.select("g");z.append("g").attr("class","nv-groups"),P.attr("transform","translate("+l.left+","+l.top+")"),I.append("clipPath").attr("id","nv-edge-clip-"+p).append("rect"),P.select("#nv-edge-clip-"+p+" rect").attr("width",D).attr("height",O),V.attr("clip-path",y?"url(#nv-edge-clip-"+p+")":"");var H=P.select(".nv-groups").selectAll(".nv-group").data(function(t){return t},function(t,e){return e});H.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6);var Y=E.transition(H.exit().selectAll("rect.nv-bar"),"multibarExit",Math.min(100,A)).attr("y",function(t,e,n){var r=s(0)||0;return x&&N[t.series]&&!N[t.series].nonStackable&&(r=s(t.y0)),r}).attr("height",0).remove();Y.delay&&Y.delay(function(t,e){return e*(A/(F+1))-e}),H.attr("class",function(t,e){return"nv-group nv-series-"+e}).classed("hover",function(t){return t.hover}).style("fill",function(t,e){return k(t,e)}).style("stroke",function(t,e){return k(t,e)}),H.style("stroke-opacity",1).style("fill-opacity",S);var G=H.selectAll("rect.nv-bar").data(function(t){return w&&!N.length?w.values:t.values});G.exit().remove();G.enter().append("rect").attr("class",function(t,e){return v(t,e)<0?"nv-bar negative":"nv-bar positive"}).attr("x",function(t,e,n){return x&&!N[n].nonStackable?0:n*d.rangeBand()/N.length}).attr("y",function(t,e,n){return s(x&&!N[n].nonStackable?t.y0:0)||0}).attr("height",0).attr("width",function(t,e,n){return d.rangeBand()/(x&&!N[n].nonStackable?1:N.length)}).attr("transform",function(t,e){return"translate("+d(g(t,e))+",0)"});G.style("fill",function(t,e,n){return k(t,n,e)}).style("stroke",function(t,e,n){return k(t,n,e)}).on("mouseover",function(t,e,n){d3.select(this).classed("hover",!0),W.elementMouseover({data:t,index:e,series:N[n],color:d3.select(this).style("fill")})}).on("mouseout",function(t,e,n){d3.select(this).classed("hover",!1),W.elementMouseout({data:t,index:e,series:N[n],color:d3.select(this).style("fill")})}).on("mousemove",function(t,e,n){W.elementMousemove({data:t,index:e,series:N[n],color:d3.select(this).style("fill")})}).on("click",function(t,e,n){W.elementClick({data:t,index:e,series:N[n],color:d3.select(this).style("fill"),event:d3.event,element:this}),d3.event.stopPropagation()}).on("dblclick",function(t,e,n){W.elementDblClick({data:t,index:e,series:N[n],color:d3.select(this).style("fill")}),d3.event.stopPropagation()}),G.attr("class",function(t,e){return v(t,e)<0?"nv-bar negative":"nv-bar positive"}).attr("transform",function(t,e){return"translate("+d(g(t,e))+",0)"}),M&&(e||(e=N.map(function(){return!0})),G.style("fill",function(t,n,r){return d3.rgb(M(t,n)).darker(e.map(function(t,e){return e}).filter(function(t,n){return!e[n]})[r]).toString()}).style("stroke",function(t,n,r){return d3.rgb(M(t,n)).darker(e.map(function(t,e){return e}).filter(function(t,n){return!e[n]})[r]).toString()}));var X=G.watchTransition(E,"multibar",Math.min(250,A)).delay(function(t,e){return e*A/N[0].values.length});x?X.attr("y",function(t,e,n){return N[n].nonStackable?v(t,e)<0?f(0):f(0)-f(v(t,e))<-1?f(0)-1:f(v(t,e))||0:f(t.y1)}).attr("height",function(t,e,n){return N[n].nonStackable?Math.max(Math.abs(f(v(t,e))-f(0)),0)||0:Math.max(Math.abs(f(t.y+t.y0)-f(t.y0)),0)}).attr("x",function(t,e,n){var r=0;return N[n].nonStackable&&(r=t.series*d.rangeBand()/N.length,N.length!==_&&(r=N[n].nonStackableSeries*d.rangeBand()/(2*_))),r}).attr("width",function(t,e,n){if(N[n].nonStackable){var r=d.rangeBand()/_;return N.length!==_&&(r=d.rangeBand()/(2*_)),r}return d.rangeBand()}):X.attr("x",function(t,e){return t.series*d.rangeBand()/N.length}).attr("width",d.rangeBand()/N.length).attr("y",function(t,e){return v(t,e)<0?f(0):f(0)-f(v(t,e))<1?f(0)-1:f(v(t,e))||0}).attr("height",function(t,e){return Math.max(Math.abs(f(v(t,e))-f(0)),1)||0}),a=d.copy(),s=f.copy(),N[0]&&N[0].values&&(F=N[0].values.length)}),E.renderEnd("multibar immediate"),N}return N.dispatch=W,N.options=t.utils.optionsFunc.bind(N),N._options=Object.create({},{width:{get:function(){return u},set:function(t){u=t}},height:{get:function(){return c},set:function(t){c=t}},x:{get:function(){return g},set:function(t){g=t}},y:{get:function(){return v},set:function(t){v=t}},xScale:{get:function(){return d},set:function(t){d=t}},yScale:{get:function(){return f},set:function(t){f=t}},xDomain:{get:function(){return n},set:function(t){n=t}},yDomain:{get:function(){return r},set:function(t){r=t}},xRange:{get:function(){return i},set:function(t){i=t}},yRange:{get:function(){return o},set:function(t){o=t}},forceY:{get:function(){return m},set:function(t){m=t}},stacked:{get:function(){return x},set:function(t){x=t}},stackOffset:{get:function(){return b},set:function(t){b=t}},clipEdge:{get:function(){return y},set:function(t){y=t}},disabled:{get:function(){return e},set:function(t){e=t}},id:{get:function(){return p},set:function(t){p=t}},hideable:{get:function(){return w},set:function(t){w=t}},groupSpacing:{get:function(){return C},set:function(t){C=t}},fillOpacity:{get:function(){return S},set:function(t){S=t}},margin:{get:function(){return l},set:function(t){l.top=void 0!==t.top?t.top:l.top,l.right=void 0!==t.right?t.right:l.right,l.bottom=void 0!==t.bottom?t.bottom:l.bottom,l.left=void 0!==t.left?t.left:l.left}},duration:{get:function(){return A},set:function(t){A=t,E.reset(A)}},color:{get:function(){return k},set:function(e){k=t.utils.getColor(e)}},barColor:{get:function(){return M},set:function(e){M=e?t.utils.getColor(e):null}}}),t.utils.initOptions(N),N},t.models.multiBarChart=function(){"use strict";var e,n,r=t.models.multiBar(),i=t.models.axis(),o=t.models.axis(),a=t.interactiveGuideline(),s=t.models.legend(),l=t.models.legend(),u=t.models.tooltip(),c={top:30,right:20,bottom:50,left:60},d=null,f=null,p=null,h=t.utils.defaultColor(),g=!0,v={},m=!0,y=null,x=!0,b=!0,k=!1,w=!0,M=!1,A=!1,C=0,S=t.utils.state(),W=null,E=null,F=d3.dispatch("stateChange","changeState","renderEnd"),N=function(){return g?180:0},D=250,O=!1;S.stacked=!1,r.stacked(!1),i.orient("bottom").tickPadding(7).showMaxMin(!1).tickFormat(function(t){return t}),o.orient(k?"right":"left").tickFormat(d3.format(",.1f")),u.duration(0).valueFormatter(function(t,e){return o.tickFormat()(t,e)}).headerFormatter(function(t,e){return i.tickFormat()(t,e)}),a.tooltip.valueFormatter(function(t,e){return null==t?"N/A":o.tickFormat()(t,e)}).headerFormatter(function(t,e){return i.tickFormat()(t,e)}),a.tooltip.valueFormatter(function(t,e){return null==t?"N/A":o.tickFormat()(t,e)}).headerFormatter(function(t,e){return i.tickFormat()(t,e)}),a.tooltip.duration(0).valueFormatter(function(t,e){return o.tickFormat()(t,e)}).headerFormatter(function(t,e){return i.tickFormat()(t,e)}),l.updateState(!1);var _=t.utils.renderWatch(F),L=!1,B=function(t){return function(){return{active:t.map(function(t){return!t.disabled}),stacked:L}}},P=function(t){return function(e){void 0!==e.stacked&&(L=e.stacked),void 0!==e.active&&t.forEach(function(t,n){t.disabled=!e.active[n]})}};function T(E){return _.reset(),_.models(r),x&&_.models(i),b&&_.models(o),E.each(function(E){var _=d3.select(this);t.utils.initSVG(_);var I,z=t.utils.availableWidth(f,_,c),V=t.utils.availableHeight(p,_,c);if(T.update=function(){0===D?_.call(T):_.transition().duration(D).call(T)},T.container=this,S.setter(P(E),T.update).getter(B(E)).update(),S.disabled=E.map(function(t){return!!t.disabled}),!W)for(I in W={},S)S[I]instanceof Array?W[I]=S[I].slice(0):W[I]=S[I];if(!(E&&E.length&&E.filter(function(t){return t.values.length}).length))return t.utils.noData(T,_),T;_.selectAll(".nv-noData").remove(),e=r.xScale(),n=r.yScale();var H=_.selectAll("g.nv-wrap.nv-multiBarWithLegend").data([E]),Y=H.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarWithLegend").append("g"),G=H.select("g");if(Y.append("g").attr("class","nv-x nv-axis"),Y.append("g").attr("class","nv-y nv-axis"),Y.append("g").attr("class","nv-barsWrap"),Y.append("g").attr("class","nv-legendWrap"),Y.append("g").attr("class","nv-controlsWrap"),Y.append("g").attr("class","nv-interactive"),m?"bottom"===y?(s.width(z-c.right),G.select(".nv-legendWrap").datum(E).call(s),c.bottom=i.height()+s.height(),V=t.utils.availableHeight(p,_,c),G.select(".nv-legendWrap").attr("transform","translate(0,"+(V+i.height())+")")):(s.width(z-N()),G.select(".nv-legendWrap").datum(E).call(s),d||s.height()===c.top||(c.top=s.height(),V=t.utils.availableHeight(p,_,c)),G.select(".nv-legendWrap").attr("transform","translate("+N()+","+-c.top+")")):G.select(".nv-legendWrap").selectAll("*").remove(),g){var X=[{key:v.grouped||"Grouped",disabled:r.stacked()},{key:v.stacked||"Stacked",disabled:!r.stacked()}];l.width(N()).color(["#444","#444","#444"]),G.select(".nv-controlsWrap").datum(X).attr("transform","translate(0,"+-c.top+")").call(l)}else G.select(".nv-controlsWrap").selectAll("*").remove();if(H.attr("transform","translate("+c.left+","+c.top+")"),k&&G.select(".nv-y.nv-axis").attr("transform","translate("+z+",0)"),r.disabled(E.map(function(t){return t.disabled})).width(z).height(V).color(E.map(function(t,e){return t.color||h(t,e)}).filter(function(t,e){return!E[e].disabled})),G.select(".nv-barsWrap").datum(E.filter(function(t){return!t.disabled})).call(r),x){i.scale(e)._ticks(t.utils.calcTicksX(z/100,E)).tickSize(-V,0),G.select(".nv-x.nv-axis").attr("transform","translate(0,"+n.range()[0]+")"),G.select(".nv-x.nv-axis").call(i);var j=G.select(".nv-x.nv-axis > g").selectAll("g");if(j.selectAll("line, text").style("opacity",1),M){var R=function(t,e){return"translate("+t+","+e+")"};j.selectAll("text").attr("transform",function(t,e,n){return R(0,n%2==0?5:17)});var Z=d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;G.selectAll(".nv-x.nv-axis .nv-axisMaxMin text").attr("transform",function(t,e){return R(0,0===e||Z%2!=0?17:5)})}A&&G.selectAll(".tick text").call(t.utils.wrapTicks,T.xAxis.rangeBand()),w&&j.filter(function(t,e){return e%Math.ceil(E[0].values.length/(z/100))!=0}).selectAll("text, line").style("opacity",0),C&&j.selectAll(".tick text").attr("transform","rotate("+C+" 0,0)").style("text-anchor",C>0?"start":"end"),G.select(".nv-x.nv-axis").selectAll("g.nv-axisMaxMin text").style("opacity",1)}b&&(o.scale(n)._ticks(t.utils.calcTicksY(V/36,E)).tickSize(-z,0),G.select(".nv-y.nv-axis").call(o)),O&&(a.width(z).height(V).margin({left:c.left,top:c.top}).svgContainer(_).xScale(e),H.select(".nv-interactive").call(a)),s.dispatch.on("stateChange",function(t){for(var e in t)S[e]=t[e];F.stateChange(S),T.update()}),l.dispatch.on("legendClick",function(t,e){if(t.disabled){switch(X=X.map(function(t){return t.disabled=!0,t}),t.disabled=!1,t.key){case"Grouped":case v.grouped:r.stacked(!1);break;case"Stacked":case v.stacked:r.stacked(!0)}S.stacked=r.stacked(),F.stateChange(S),T.update()}}),F.on("changeState",function(t){void 0!==t.disabled&&(E.forEach(function(e,n){e.disabled=t.disabled[n]}),S.disabled=t.disabled),void 0!==t.stacked&&(r.stacked(t.stacked),S.stacked=t.stacked,L=t.stacked),T.update()}),O?(a.dispatch.on("elementMousemove",function(t){if(null!=t.pointXValue){var n,r,i,o,s=[];E.filter(function(t,e){return t.seriesIndex=e,!t.disabled}).forEach(function(a,l){r=e.domain().indexOf(t.pointXValue);var u=a.values[r];void 0!==u&&(o=u.x,void 0===n&&(n=u),void 0===i&&(i=t.mouseX),s.push({key:a.key,value:T.y()(u,r),color:h(a,a.seriesIndex),data:a.values[r]}))}),a.tooltip.data({value:o,index:r,series:s})(),a.renderGuideLine(i)}}),a.dispatch.on("elementMouseout",function(t){a.tooltip.hidden(!0)})):(r.dispatch.on("elementMouseover.tooltip",function(t){t.value=T.x()(t.data),t.series={key:t.data.key,value:T.y()(t.data),color:t.color},u.data(t).hidden(!1)}),r.dispatch.on("elementMouseout.tooltip",function(t){u.hidden(!0)}),r.dispatch.on("elementMousemove.tooltip",function(t){u()}))}),_.renderEnd("multibarchart immediate"),T}return T.dispatch=F,T.multibar=r,T.legend=s,T.controls=l,T.xAxis=i,T.yAxis=o,T.state=S,T.tooltip=u,T.interactiveLayer=a,T.options=t.utils.optionsFunc.bind(T),T._options=Object.create({},{width:{get:function(){return f},set:function(t){f=t}},height:{get:function(){return p},set:function(t){p=t}},showLegend:{get:function(){return m},set:function(t){m=t}},legendPosition:{get:function(){return y},set:function(t){y=t}},showControls:{get:function(){return g},set:function(t){g=t}},controlLabels:{get:function(){return v},set:function(t){v=t}},showXAxis:{get:function(){return x},set:function(t){x=t}},showYAxis:{get:function(){return b},set:function(t){b=t}},defaultState:{get:function(){return W},set:function(t){W=t}},noData:{get:function(){return E},set:function(t){E=t}},reduceXTicks:{get:function(){return w},set:function(t){w=t}},rotateLabels:{get:function(){return C},set:function(t){C=t}},staggerLabels:{get:function(){return M},set:function(t){M=t}},wrapLabels:{get:function(){return A},set:function(t){A=!!t}},margin:{get:function(){return c},set:function(t){void 0!==t.top&&(c.top=t.top,d=t.top),c.right=void 0!==t.right?t.right:c.right,c.bottom=void 0!==t.bottom?t.bottom:c.bottom,c.left=void 0!==t.left?t.left:c.left}},duration:{get:function(){return D},set:function(t){D=t,r.duration(D),i.duration(D),o.duration(D),_.reset(D)}},color:{get:function(){return h},set:function(e){h=t.utils.getColor(e),s.color(h)}},rightAlignYAxis:{get:function(){return k},set:function(t){k=t,o.orient(k?"right":"left")}},useInteractiveGuideline:{get:function(){return O},set:function(t){O=t}},barColor:{get:function(){return r.barColor},set:function(t){r.barColor(t),s.color(function(t,e){return d3.rgb("#ccc").darker(1.5*e).toString()})}}}),t.utils.inheritOptions(T,r),t.utils.initOptions(T),T},t.models.multiBarHorizontal=function(){"use strict";var e,n,r,i,o,a,s,l={top:0,right:0,bottom:0,left:0},u=960,c=500,d=Math.floor(1e4*Math.random()),f=null,p=d3.scale.ordinal(),h=d3.scale.linear(),g=function(t){return t.x},v=function(t){return t.y},m=function(t){return t.yErr},y=[0],x=t.utils.defaultColor(),b=null,k=!1,w=!1,M=!1,A=60,C=.1,S=.75,W=d3.format(",.2f"),E=250,F=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),N=t.utils.renderWatch(F,E);function D(d){return N.reset(),d.each(function(d){var E=u-l.left-l.right,D=c-l.top-l.bottom;f=d3.select(this),t.utils.initSVG(f),k&&(d=d3.layout.stack().offset("zero").values(function(t){return t.values}).y(v)(d)),d.forEach(function(t,e){t.values.forEach(function(n){n.series=e,n.key=t.key})}),k&&d[0].values.map(function(t,e){var n=0,r=0;d.map(function(t){var i=t.values[e];i.size=Math.abs(i.y),i.y<0?(i.y1=r-i.size,r-=i.size):(i.y1=n,n+=i.size)})});var O=n&&r?[]:d.map(function(t){return t.values.map(function(t,e){return{x:g(t,e),y:v(t,e),y0:t.y0,y1:t.y1}})});p.domain(n||d3.merge(O).map(function(t){return t.x})).rangeBands(i||[0,D],C),h.domain(r||d3.extent(d3.merge(O).map(function(t){return k?t.y>0?t.y1+t.y:t.y1:t.y}).concat(y))),w&&!k?h.range(o||[h.domain()[0]<0?A:0,E-(h.domain()[1]>0?A:0)]):h.range(o||[0,E]),a=a||p,s=s||d3.scale.linear().domain(h.domain()).range([h(0),h(0)]);var _=d3.select(this).selectAll("g.nv-wrap.nv-multibarHorizontal").data([d]),L=_.enter().append("g").attr("class","nvd3 nv-wrap nv-multibarHorizontal"),B=(L.append("defs"),L.append("g"));_.select("g");B.append("g").attr("class","nv-groups"),_.attr("transform","translate("+l.left+","+l.top+")");var P=_.select(".nv-groups").selectAll(".nv-group").data(function(t){return t},function(t,e){return e});P.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),P.exit().watchTransition(N,"multibarhorizontal: exit groups").style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),P.attr("class",function(t,e){return"nv-group nv-series-"+e}).classed("hover",function(t){return t.hover}).style("fill",function(t,e){return x(t,e)}).style("stroke",function(t,e){return x(t,e)}),P.watchTransition(N,"multibarhorizontal: groups").style("stroke-opacity",1).style("fill-opacity",S);var T=P.selectAll("g.nv-bar").data(function(t){return t.values});T.exit().remove();var I=T.enter().append("g").attr("transform",function(t,e,n){return"translate("+s(k?t.y0:0)+","+(k?0:n*p.rangeBand()/d.length+p(g(t,e)))+")"});I.append("rect").attr("width",0).attr("height",p.rangeBand()/(k?1:d.length)),T.on("mouseover",function(t,e){d3.select(this).classed("hover",!0),F.elementMouseover({data:t,index:e,color:d3.select(this).style("fill")})}).on("mouseout",function(t,e){d3.select(this).classed("hover",!1),F.elementMouseout({data:t,index:e,color:d3.select(this).style("fill")})}).on("mouseout",function(t,e){F.elementMouseout({data:t,index:e,color:d3.select(this).style("fill")})}).on("mousemove",function(t,e){F.elementMousemove({data:t,index:e,color:d3.select(this).style("fill")})}).on("click",function(t,e){F.elementClick({data:t,index:e,color:d3.select(this).style("fill"),event:d3.event,element:this}),d3.event.stopPropagation()}).on("dblclick",function(t,e){F.elementDblClick({data:t,index:e,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}),m(d[0],0)&&(I.append("polyline"),T.select("polyline").attr("fill","none").attr("points",function(t,e){var n=m(t,e),r=.8*p.rangeBand()/(2*(k?1:d.length));return[[(n=(n=n.length?n:[-Math.abs(n),Math.abs(n)]).map(function(n){return h(n+(v(t,e)<0?0:v(t,e)))-h(0)}))[0],-r],[n[0],r],[n[0],0],[n[1],0],[n[1],-r],[n[1],r]].map(function(t){return t.join(",")}).join(" ")}).attr("transform",function(t,e){return"translate(0, "+p.rangeBand()/(2*(k?1:d.length))+")"})),I.append("text"),w&&!k?(T.select("text").attr("text-anchor",function(t,e){return v(t,e)<0?"end":"start"}).attr("y",p.rangeBand()/(2*d.length)).attr("dy",".32em").text(function(t,e){var n=W(v(t,e)),r=m(t,e);return void 0===r?n:r.length?n+"+"+W(Math.abs(r[1]))+"-"+W(Math.abs(r[0])):n+"±"+W(Math.abs(r))}),T.watchTransition(N,"multibarhorizontal: bars").select("text").attr("x",function(t,e){return v(t,e)<0?-4:h(v(t,e))-h(0)+4})):T.selectAll("text").text(""),M&&!k?(I.append("text").classed("nv-bar-label",!0),T.select("text.nv-bar-label").attr("text-anchor",function(t,e){return v(t,e)<0?"start":"end"}).attr("y",p.rangeBand()/(2*d.length)).attr("dy",".32em").text(function(t,e){return g(t,e)}),T.watchTransition(N,"multibarhorizontal: bars").select("text.nv-bar-label").attr("x",function(t,e){return v(t,e)<0?h(0)-h(v(t,e))+4:-4})):T.selectAll("text.nv-bar-label").text(""),T.attr("class",function(t,e){return v(t,e)<0?"nv-bar negative":"nv-bar positive"}),b&&(e||(e=d.map(function(){return!0})),T.style("fill",function(t,n,r){return d3.rgb(b(t,n)).darker(e.map(function(t,e){return e}).filter(function(t,n){return!e[n]})[r]).toString()}).style("stroke",function(t,n,r){return d3.rgb(b(t,n)).darker(e.map(function(t,e){return e}).filter(function(t,n){return!e[n]})[r]).toString()})),k?T.watchTransition(N,"multibarhorizontal: bars").attr("transform",function(t,e){return"translate("+h(t.y1)+","+p(g(t,e))+")"}).select("rect").attr("width",function(t,e){return Math.abs(h(v(t,e)+t.y0)-h(t.y0))||0}).attr("height",p.rangeBand()):T.watchTransition(N,"multibarhorizontal: bars").attr("transform",function(t,e){return"translate("+(v(t,e)<0?h(v(t,e)):h(0))+","+(t.series*p.rangeBand()/d.length+p(g(t,e)))+")"}).select("rect").attr("height",p.rangeBand()/d.length).attr("width",function(t,e){return Math.max(Math.abs(h(v(t,e))-h(0)),1)||0}),a=p.copy(),s=h.copy()}),N.renderEnd("multibarHorizontal immediate"),D}return D.dispatch=F,D.options=t.utils.optionsFunc.bind(D),D._options=Object.create({},{width:{get:function(){return u},set:function(t){u=t}},height:{get:function(){return c},set:function(t){c=t}},x:{get:function(){return g},set:function(t){g=t}},y:{get:function(){return v},set:function(t){v=t}},yErr:{get:function(){return m},set:function(t){m=t}},xScale:{get:function(){return p},set:function(t){p=t}},yScale:{get:function(){return h},set:function(t){h=t}},xDomain:{get:function(){return n},set:function(t){n=t}},yDomain:{get:function(){return r},set:function(t){r=t}},xRange:{get:function(){return i},set:function(t){i=t}},yRange:{get:function(){return o},set:function(t){o=t}},forceY:{get:function(){return y},set:function(t){y=t}},stacked:{get:function(){return k},set:function(t){k=t}},showValues:{get:function(){return w},set:function(t){w=t}},disabled:{get:function(){return e},set:function(t){e=t}},id:{get:function(){return d},set:function(t){d=t}},valueFormat:{get:function(){return W},set:function(t){W=t}},valuePadding:{get:function(){return A},set:function(t){A=t}},groupSpacing:{get:function(){return C},set:function(t){C=t}},fillOpacity:{get:function(){return S},set:function(t){S=t}},margin:{get:function(){return l},set:function(t){l.top=void 0!==t.top?t.top:l.top,l.right=void 0!==t.right?t.right:l.right,l.bottom=void 0!==t.bottom?t.bottom:l.bottom,l.left=void 0!==t.left?t.left:l.left}},duration:{get:function(){return E},set:function(t){E=t,N.reset(E)}},color:{get:function(){return x},set:function(e){x=t.utils.getColor(e)}},barColor:{get:function(){return b},set:function(e){b=e?t.utils.getColor(e):null}}}),t.utils.initOptions(D),D},t.models.multiBarHorizontalChart=function(){"use strict";var e,n,r=t.models.multiBarHorizontal(),i=t.models.axis(),o=t.models.axis(),a=t.models.legend().height(30),s=t.models.legend().height(30),l=t.models.tooltip(),u={top:30,right:20,bottom:50,left:60},c=null,d=null,f=null,p=t.utils.defaultColor(),h=!0,g="top",v={},m=!0,y="top",x=!0,b=!0,k=!1,w=t.utils.state(),M=null,A=null,C=d3.dispatch("stateChange","changeState","renderEnd"),S=function(){return h?180:0},W=250;w.stacked=!1,r.stacked(k),i.orient("left").tickPadding(5).showMaxMin(!1).tickFormat(function(t){return t}),o.orient("bottom").tickFormat(d3.format(",.1f")),l.duration(0).valueFormatter(function(t,e){return o.tickFormat()(t,e)}).headerFormatter(function(t,e){return i.tickFormat()(t,e)}),s.updateState(!1);var E=function(t){return function(){return{active:t.map(function(t){return!t.disabled}),stacked:k}}},F=function(t){return function(e){void 0!==e.stacked&&(k=e.stacked),void 0!==e.active&&t.forEach(function(t,n){t.disabled=!e.active[n]})}},N=t.utils.renderWatch(C,W);function D(l){return N.reset(),N.models(r),x&&N.models(i),b&&N.models(o),l.each(function(l){var A=d3.select(this);t.utils.initSVG(A);var N,O=t.utils.availableWidth(d,A,u),_=t.utils.availableHeight(f,A,u);if(D.update=function(){A.transition().duration(W).call(D)},D.container=this,k=r.stacked(),w.setter(F(l),D.update).getter(E(l)).update(),w.disabled=l.map(function(t){return!!t.disabled}),!M)for(N in M={},w)w[N]instanceof Array?M[N]=w[N].slice(0):M[N]=w[N];if(!(l&&l.length&&l.filter(function(t){return t.values.length}).length))return t.utils.noData(D,A),D;A.selectAll(".nv-noData").remove(),e=r.xScale(),n=r.yScale().clamp(!0);var L=A.selectAll("g.nv-wrap.nv-multiBarHorizontalChart").data([l]),B=L.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarHorizontalChart").append("g"),P=L.select("g");if(B.append("g").attr("class","nv-x nv-axis"),B.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),B.append("g").attr("class","nv-barsWrap"),B.append("g").attr("class","nv-legendWrap"),B.append("g").attr("class","nv-controlsWrap"),m?(a.width(O-S()),P.select(".nv-legendWrap").datum(l).call(a),"bottom"===y?(u.bottom=i.height()+a.height(),_=t.utils.availableHeight(f,A,u),P.select(".nv-legendWrap").attr("transform","translate("+S()+","+(_+i.height())+")")):"top"===y&&(c||a.height()===u.top||(u.top=a.height(),_=t.utils.availableHeight(f,A,u)),P.select(".nv-legendWrap").attr("transform","translate("+S()+","+-u.top+")"))):P.select(".nv-legendWrap").selectAll("*").remove(),h){var T=[{key:v.grouped||"Grouped",disabled:r.stacked()},{key:v.stacked||"Stacked",disabled:!r.stacked()}];s.width(S()).color(["#444","#444","#444"]),"bottom"===g?(u.bottom=i.height()+a.height(),_=t.utils.availableHeight(f,A,u),P.select(".nv-controlsWrap").datum(T).attr("transform","translate(0,"+(_+i.height())+")").call(s)):"top"===g&&P.select(".nv-controlsWrap").datum(T).attr("transform","translate(0,"+-u.top+")").call(s)}else P.select(".nv-controlsWrap").selectAll("*").remove();(L.attr("transform","translate("+u.left+","+u.top+")"),r.disabled(l.map(function(t){return t.disabled})).width(O).height(_).color(l.map(function(t,e){return t.color||p(t,e)}).filter(function(t,e){return!l[e].disabled})),P.select(".nv-barsWrap").datum(l.filter(function(t){return!t.disabled})).transition().call(r),x)&&(i.scale(e)._ticks(t.utils.calcTicksY(_/24,l)).tickSize(-O,0),P.select(".nv-x.nv-axis").call(i),P.select(".nv-x.nv-axis").selectAll("g").selectAll("line, text"));b&&(o.scale(n)._ticks(t.utils.calcTicksX(O/100,l)).tickSize(-_,0),P.select(".nv-y.nv-axis").attr("transform","translate(0,"+_+")"),P.select(".nv-y.nv-axis").call(o)),P.select(".nv-zeroLine line").attr("x1",n(0)).attr("x2",n(0)).attr("y1",0).attr("y2",-_),a.dispatch.on("stateChange",function(t){for(var e in t)w[e]=t[e];C.stateChange(w),D.update()}),s.dispatch.on("legendClick",function(t,e){if(t.disabled){switch(T=T.map(function(t){return t.disabled=!0,t}),t.disabled=!1,t.key){case"Grouped":case v.grouped:r.stacked(!1);break;case"Stacked":case v.stacked:r.stacked(!0)}w.stacked=r.stacked(),C.stateChange(w),k=r.stacked(),D.update()}}),C.on("changeState",function(t){void 0!==t.disabled&&(l.forEach(function(e,n){e.disabled=t.disabled[n]}),w.disabled=t.disabled),void 0!==t.stacked&&(r.stacked(t.stacked),w.stacked=t.stacked,k=t.stacked),D.update()})}),N.renderEnd("multibar horizontal chart immediate"),D}return r.dispatch.on("elementMouseover.tooltip",function(t){t.value=D.x()(t.data),t.series={key:t.data.key,value:D.y()(t.data),color:t.color},l.data(t).hidden(!1)}),r.dispatch.on("elementMouseout.tooltip",function(t){l.hidden(!0)}),r.dispatch.on("elementMousemove.tooltip",function(t){l()}),D.dispatch=C,D.multibar=r,D.legend=a,D.controls=s,D.xAxis=i,D.yAxis=o,D.state=w,D.tooltip=l,D.options=t.utils.optionsFunc.bind(D),D._options=Object.create({},{width:{get:function(){return d},set:function(t){d=t}},height:{get:function(){return f},set:function(t){f=t}},showLegend:{get:function(){return m},set:function(t){m=t}},legendPosition:{get:function(){return y},set:function(t){y=t}},controlsPosition:{get:function(){return g},set:function(t){g=t}},showControls:{get:function(){return h},set:function(t){h=t}},controlLabels:{get:function(){return v},set:function(t){v=t}},showXAxis:{get:function(){return x},set:function(t){x=t}},showYAxis:{get:function(){return b},set:function(t){b=t}},defaultState:{get:function(){return M},set:function(t){M=t}},noData:{get:function(){return A},set:function(t){A=t}},margin:{get:function(){return u},set:function(t){void 0!==t.top&&(u.top=t.top,c=t.top),u.right=void 0!==t.right?t.right:u.right,u.bottom=void 0!==t.bottom?t.bottom:u.bottom,u.left=void 0!==t.left?t.left:u.left}},duration:{get:function(){return W},set:function(t){W=t,N.reset(W),r.duration(W),i.duration(W),o.duration(W)}},color:{get:function(){return p},set:function(e){p=t.utils.getColor(e),a.color(p)}},barColor:{get:function(){return r.barColor},set:function(t){r.barColor(t),a.color(function(t,e){return d3.rgb("#ccc").darker(1.5*e).toString()})}}}),t.utils.inheritOptions(D,r),t.utils.initOptions(D),D},t.models.multiChart=function(){"use strict";var e,n,r={top:30,right:20,bottom:50,left:60},i=null,o=t.utils.defaultColor(),a=null,s=null,l=!0,u=null,c=function(t){return t.x},d=function(t){return t.y},f="linear",p=!0,h=t.interactiveGuideline(),g=!1,v=" (right axis)",m=250,y=d3.scale.linear(),x=d3.scale.linear(),b=d3.scale.linear(),k=t.models.line().yScale(x).duration(m),w=t.models.line().yScale(b).duration(m),M=t.models.scatter().yScale(x).duration(m),A=t.models.scatter().yScale(b).duration(m),C=t.models.multiBar().stacked(!1).yScale(x).duration(m),S=t.models.multiBar().stacked(!1).yScale(b).duration(m),W=t.models.stackedArea().yScale(x).duration(m),E=t.models.stackedArea().yScale(b).duration(m),F=t.models.axis().scale(y).orient("bottom").tickPadding(5).duration(m),N=t.models.axis().scale(x).orient("left").duration(m),D=t.models.axis().scale(b).orient("right").duration(m),O=t.models.legend().height(30),_=t.models.tooltip(),L=d3.dispatch(),B=[k,w,M,A,C,S,W,E];function P(u){return u.each(function(u){var p=d3.select(this);t.utils.initSVG(p),P.update=function(){p.transition().call(P)},P.container=this;var m=t.utils.availableWidth(a,p,r),L=t.utils.availableHeight(s,p,r),T=u.filter(function(t){return"line"==t.type&&1==t.yAxis}),I=u.filter(function(t){return"line"==t.type&&2==t.yAxis}),z=u.filter(function(t){return"scatter"==t.type&&1==t.yAxis}),V=u.filter(function(t){return"scatter"==t.type&&2==t.yAxis}),H=u.filter(function(t){return"bar"==t.type&&1==t.yAxis}),Y=u.filter(function(t){return"bar"==t.type&&2==t.yAxis}),G=u.filter(function(t){return"area"==t.type&&1==t.yAxis}),X=u.filter(function(t){return"area"==t.type&&2==t.yAxis});if(!(u&&u.length&&u.filter(function(t){return t.values.length}).length))return t.utils.noData(P,p),P;p.selectAll(".nv-noData").remove();var j=u.filter(function(t){return!t.disabled&&1==t.yAxis}).map(function(t){return t.values.map(function(t,e){return{x:c(t),y:d(t)}})}),R=u.filter(function(t){return!t.disabled&&2==t.yAxis}).map(function(t){return t.values.map(function(t,e){return{x:c(t),y:d(t)}})});y.domain(d3.extent(d3.merge(j.concat(R)),function(t){return t.x})).range([0,m]);var Z=p.selectAll("g.wrap.multiChart").data([u]),q=Z.enter().append("g").attr("class","wrap nvd3 multiChart").append("g");q.append("g").attr("class","nv-x nv-axis"),q.append("g").attr("class","nv-y1 nv-axis"),q.append("g").attr("class","nv-y2 nv-axis"),q.append("g").attr("class","stack1Wrap"),q.append("g").attr("class","stack2Wrap"),q.append("g").attr("class","bars1Wrap"),q.append("g").attr("class","bars2Wrap"),q.append("g").attr("class","scatters1Wrap"),q.append("g").attr("class","scatters2Wrap"),q.append("g").attr("class","lines1Wrap"),q.append("g").attr("class","lines2Wrap"),q.append("g").attr("class","legendWrap"),q.append("g").attr("class","nv-interactive");var K=Z.select("g"),U=u.map(function(t,e){return u[e].color||o(t,e)});if(l){var Q=O.align()?m/2:m,J=O.align()?Q:0;O.width(Q),O.color(U),K.select(".legendWrap").datum(u.map(function(t){return t.originalKey=void 0===t.originalKey?t.key:t.originalKey,t.key=t.originalKey+(1==t.yAxis?"":v),t})).call(O),i||O.height()===r.top||(r.top=O.height(),L=t.utils.availableHeight(s,p,r)),K.select(".legendWrap").attr("transform","translate("+J+","+-r.top+")")}else K.select(".legendWrap").selectAll("*").remove();k.width(m).height(L).interpolate(f).color(U.filter(function(t,e){return!u[e].disabled&&1==u[e].yAxis&&"line"==u[e].type})),w.width(m).height(L).interpolate(f).color(U.filter(function(t,e){return!u[e].disabled&&2==u[e].yAxis&&"line"==u[e].type})),M.width(m).height(L).color(U.filter(function(t,e){return!u[e].disabled&&1==u[e].yAxis&&"scatter"==u[e].type})),A.width(m).height(L).color(U.filter(function(t,e){return!u[e].disabled&&2==u[e].yAxis&&"scatter"==u[e].type})),C.width(m).height(L).color(U.filter(function(t,e){return!u[e].disabled&&1==u[e].yAxis&&"bar"==u[e].type})),S.width(m).height(L).color(U.filter(function(t,e){return!u[e].disabled&&2==u[e].yAxis&&"bar"==u[e].type})),W.width(m).height(L).interpolate(f).color(U.filter(function(t,e){return!u[e].disabled&&1==u[e].yAxis&&"area"==u[e].type})),E.width(m).height(L).interpolate(f).color(U.filter(function(t,e){return!u[e].disabled&&2==u[e].yAxis&&"area"==u[e].type})),K.attr("transform","translate("+r.left+","+r.top+")");var $=K.select(".lines1Wrap").datum(T.filter(function(t){return!t.disabled})),tt=K.select(".scatters1Wrap").datum(z.filter(function(t){return!t.disabled})),et=K.select(".bars1Wrap").datum(H.filter(function(t){return!t.disabled})),nt=K.select(".stack1Wrap").datum(G.filter(function(t){return!t.disabled})),rt=K.select(".lines2Wrap").datum(I.filter(function(t){return!t.disabled})),it=K.select(".scatters2Wrap").datum(V.filter(function(t){return!t.disabled})),ot=K.select(".bars2Wrap").datum(Y.filter(function(t){return!t.disabled})),at=K.select(".stack2Wrap").datum(X.filter(function(t){return!t.disabled})),st=[];C.stacked()&&H.length&&((st=H.filter(function(t){return!t.disabled}).map(function(t){return t.values})).length>0&&(st=st.reduce(function(t,e){return t.map(function(t,n){return{x:t.x,y:t.y+e[n].y}})})));H.length&&st.push({x:0,y:0});var lt=[];S.stacked()&&Y.length&&((lt=Y.filter(function(t){return!t.disabled}).map(function(t){return t.values})).length>0&&(lt=lt.reduce(function(t,e){return t.map(function(t,n){return{x:t.x,y:t.y+e[n].y}})})));function ut(t){var e=2===t.series.yAxis?D:N;t.value=t.point.x,t.series={value:t.point.y,color:t.point.color,key:t.series.key},_.duration(0).headerFormatter(function(t,e){return F.tickFormat()(t,e)}).valueFormatter(function(t,n){return e.tickFormat()(t,n)}).data(t).hidden(!1)}function ct(t){var e=2===t.series.yAxis?D:N;t.value=t.point.x,t.series={value:t.point.y,color:t.point.color,key:t.series.key},_.duration(100).headerFormatter(function(t,e){return F.tickFormat()(t,e)}).valueFormatter(function(t,n){return e.tickFormat()(t,n)}).data(t).hidden(!1)}function dt(t){var e=2===t.series.yAxis?D:N;t.point.x=W.x()(t.point),t.point.y=W.y()(t.point),_.duration(0).headerFormatter(function(t,e){return F.tickFormat()(t,e)}).valueFormatter(function(t,n){return e.tickFormat()(t,n)}).data(t).hidden(!1)}function ft(t){var e=2===t.series.yAxis?D:N;t.value=C.x()(t.data),t.series={value:C.y()(t.data),color:t.color,key:t.data.key},_.duration(0).headerFormatter(function(t,e){return F.tickFormat()(t,e)}).valueFormatter(function(t,n){return e.tickFormat()(t,n)}).data(t).hidden(!1)}function pt(){for(var t=0,e=B.length;t=u[0]&&P.x()(t,e)<=u[1]}),d=c[r=t.interactiveBisect(c,e.pointXValue,P.x())],f=P.y()(d,r);null!==f&&function(t,n,r){for(var i=0,o=B.length;ig(t,e)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+n+"-"+e}).attr("d",function(t,e){return"m0,0l0,"+(d(h(t,e))-d(v(t,e)))+"l"+-W/2+",0l"+W/2+",0l0,"+(d(m(t,e))-d(h(t,e)))+"l0,"+(d(g(t,e))-d(m(t,e)))+"l"+W/2+",0l"+-W/2+",0z"}).attr("transform",function(t,e){return"translate("+c(f(t,e))+","+d(v(t,e))+")"}).attr("fill",function(t,e){return w[0]}).attr("stroke",function(t,e){return w[0]}).attr("x",0).attr("y",function(t,e){return d(Math.max(0,p(t,e)))}).attr("height",function(t,e){return Math.abs(d(p(t,e))-d(0))}),_.attr("class",function(t,e,n){return(h(t,e)>g(t,e)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+n+"-"+e}),d3.transition(_).attr("transform",function(t,e){return"translate("+c(f(t,e))+","+d(v(t,e))+")"}).attr("d",function(t,e){var n=C/M[0].values.length*.9;return"m0,0l0,"+(d(h(t,e))-d(v(t,e)))+"l"+-n/2+",0l"+n/2+",0l0,"+(d(m(t,e))-d(h(t,e)))+"l0,"+(d(g(t,e))-d(m(t,e)))+"l"+n/2+",0l"+-n/2+",0z"})}),C}return C.highlightPoint=function(t,e){C.clearHighlights(),u.select(".nv-ohlcBar .nv-tick-0-"+t).classed("hover",e)},C.clearHighlights=function(){u.select(".nv-ohlcBar .nv-tick.hover").classed("hover",!1)},C.dispatch=A,C.options=t.utils.optionsFunc.bind(C),C._options=Object.create({},{width:{get:function(){return a},set:function(t){a=t}},height:{get:function(){return s},set:function(t){s=t}},xScale:{get:function(){return c},set:function(t){c=t}},yScale:{get:function(){return d},set:function(t){d=t}},xDomain:{get:function(){return e},set:function(t){e=t}},yDomain:{get:function(){return n},set:function(t){n=t}},xRange:{get:function(){return r},set:function(t){r=t}},yRange:{get:function(){return i},set:function(t){i=t}},forceX:{get:function(){return y},set:function(t){y=t}},forceY:{get:function(){return x},set:function(t){x=t}},padData:{get:function(){return b},set:function(t){b=t}},clipEdge:{get:function(){return k},set:function(t){k=t}},id:{get:function(){return l},set:function(t){l=t}},interactive:{get:function(){return M},set:function(t){M=t}},x:{get:function(){return f},set:function(t){f=t}},y:{get:function(){return p},set:function(t){p=t}},open:{get:function(){return h()},set:function(t){h=t}},close:{get:function(){return g()},set:function(t){g=t}},high:{get:function(){return v},set:function(t){v=t}},low:{get:function(){return m},set:function(t){m=t}},margin:{get:function(){return o},set:function(t){o.top=null!=t.top?t.top:o.top,o.right=null!=t.right?t.right:o.right,o.bottom=null!=t.bottom?t.bottom:o.bottom,o.left=null!=t.left?t.left:o.left}},color:{get:function(){return w},set:function(e){w=t.utils.getColor(e)}}}),t.utils.initOptions(C),C},t.models.parallelCoordinates=function(){"use strict";var e,n,r,i={top:30,right:0,bottom:10,left:0},o=null,a=null,s=null,l=null,u=d3.scale.ordinal(),c={},d="undefined values",f=[],p=[],h=[],g=!0,v=t.utils.defaultColor(),m=[],y=[],x=[],b=[],k=1,w=d3.svg.line(),M=d3.svg.axis(),A=d3.dispatch("brushstart","brush","brushEnd","dimensionsOrder","stateChange","elementClick","elementMouseover","elementMouseout","elementMousemove","renderEnd","activeChanged"),C=t.utils.renderWatch(A);function S(W){return C.reset(),W.each(function(C){var S=d3.select(this);if(s=t.utils.availableWidth(o,S,i),l=t.utils.availableHeight(a,S,i),t.utils.initSVG(S),void 0===C[0].values){var W=[];C.forEach(function(t){var e={};Object.keys(t).forEach(function(n){"name"!==n&&(e[n]=t[n])}),W.push({key:t.name,values:e})}),C=W}var E=C.map(function(t){return t.values});0===y.length&&(y=C),h=f.sort(function(t,e){return t.currentPosition-e.currentPosition}).map(function(t){return t.key}),p=f.filter(function(t){return!t.disabled}),u.rangePoints([0,s],1).domain(p.map(function(t){return t.key}));var F={},N=!1,D=[];h.forEach(function(t){var e=d3.extent(E,function(e){return+e[t]}),n=e[0],r=e[1],i=!1;(isNaN(n)||isNaN(r))&&(i=!0,n=0,r=0),n===r&&(n-=1,r+=1);var o=m.filter(function(e){return e.dimension==t});0!==o.length&&(i?(n=c[t].domain()[0],r=c[t].domain()[1]):!o[0].hasOnlyNaN&&g?(n=n>o[0].extent[0]?o[0].extent[0]:n,r=r0||N?(B.style("display","inline"),P.style("display","inline")):(B.style("display","none"),P.style("display","none"))),[u(e.key),c[e.key](t.values[e.key])]}))}function j(t){m.forEach(function(e){var n=c[e.dimension].brush.y().domain();e.hasOnlyNaN&&(e.extent[1]=(c[e.dimension].domain()[1]-n[0])*(e.extent[1]-e.extent[0])/(F[e.dimension]-e.extent[0])+n[0]),e.hasNaN&&(e.extent[0]=n[0]),t&&c[e.dimension].brush.extent(e.extent)}),r.select(".nv-brushBackground").each(function(t){d3.select(this).call(c[t.key].brush)}).selectAll("rect").attr("x",-8).attr("width",16),K()}function R(){!1===g&&(g=!0,j(!0))}function Z(){H=h.filter(function(t){return!c[t].brush.empty()}),Y=H.map(function(t){return c[t].brush.extent()}),m=[],H.forEach(function(t,e){m[e]={dimension:t,extent:Y[e],hasNaN:!1,hasOnlyNaN:!1}}),y=[],e.style("display",function(t){var e=H.every(function(e,n){return!(!isNaN(t.values[e])&&!isNaN(parseFloat(t.values[e]))||Y[n][0]!=c[e].brush.y().domain()[0])||Y[n][0]<=t.values[e]&&t.values[e]<=Y[n][1]&&!isNaN(parseFloat(t.values[e]))});return e&&y.push(t),e?null:"none"}),K(),A.brush({filters:m,active:y})}function q(){var t=H.length>0;m.forEach(function(t){t.extent[0]===c[t.dimension].brush.y().domain()[0]&&b.indexOf(t.dimension)>=0&&(t.hasNaN=!0),t.extent[1]c[t.key].domain()[0]&&(D[t.key]=[n[0].extent[1]]),n[0].extent[0]>=c[t.key].domain()[0]&&D[t.key].push(n[0].extent[0])),d3.select(this).call(M.scale(c[t.key]).tickFormat(t.format).tickValues(D[t.key]))})}function U(t){var e=x[t];return null==e?u(t):e}y=[],e.style("display",function(t){var e=H.every(function(e,n){return!(!isNaN(t.values[e])&&!isNaN(parseFloat(t.values[e]))||Y[n][0]!=c[e].brush.y().domain()[0])||Y[n][0]<=t.values[e]&&t.values[e]<=Y[n][1]&&!isNaN(parseFloat(t.values[e]))});return e&&y.push(t),e?null:"none"}),(m.length>0||!t.utils.arrayEquals(y,G))&&A.activeChanged(y)}),S}return S.dispatch=A,S.options=t.utils.optionsFunc.bind(S),S._options=Object.create({},{width:{get:function(){return o},set:function(t){o=t}},height:{get:function(){return a},set:function(t){a=t}},dimensionData:{get:function(){return f},set:function(t){f=t}},displayBrush:{get:function(){return g},set:function(t){g=t}},filters:{get:function(){return m},set:function(t){m=t}},active:{get:function(){return y},set:function(t){y=t}},lineTension:{get:function(){return k},set:function(t){k=t}},undefinedValuesLabel:{get:function(){return d},set:function(t){d=t}},dimensions:{get:function(){return f.map(function(t){return t.key})},set:function(e){t.deprecated("dimensions","use dimensionData instead"),0===f.length?e.forEach(function(t){f.push({key:t})}):e.forEach(function(t,e){f[e].key=t})}},dimensionNames:{get:function(){return f.map(function(t){return t.key})},set:function(e){t.deprecated("dimensionNames","use dimensionData instead"),h=[],0===f.length?e.forEach(function(t){f.push({key:t})}):e.forEach(function(t,e){f[e].key=t})}},dimensionFormats:{get:function(){return f.map(function(t){return t.format})},set:function(e){t.deprecated("dimensionFormats","use dimensionData instead"),0===f.length?e.forEach(function(t){f.push({format:t})}):e.forEach(function(t,e){f[e].format=t})}},margin:{get:function(){return i},set:function(t){i.top=void 0!==t.top?t.top:i.top,i.right=void 0!==t.right?t.right:i.right,i.bottom=void 0!==t.bottom?t.bottom:i.bottom,i.left=void 0!==t.left?t.left:i.left}},color:{get:function(){return v},set:function(e){v=t.utils.getColor(e)}}}),t.utils.initOptions(S),S},t.models.parallelCoordinatesChart=function(){"use strict";var e=t.models.parallelCoordinates(),n=t.models.legend(),r=t.models.tooltip(),i=(t.models.tooltip(),{top:0,right:0,bottom:0,left:0}),o=null,a=null,s=null,l=!0,u=t.utils.defaultColor(),c=t.utils.state(),d=[],f=!0,p=null,h=null,g="undefined",v=d3.dispatch("dimensionsOrder","brushEnd","stateChange","changeState","renderEnd"),m=t.utils.renderWatch(v),y=function(t){return function(){return{active:t.map(function(t){return!t.disabled})}}},x=function(t){return function(e){void 0!==e.active&&t.forEach(function(t,n){t.disabled=!e.active[n]})}};function b(r){return m.reset(),m.models(e),r.each(function(r){var u=d3.select(this);t.utils.initSVG(u);var h,g=t.utils.availableWidth(a,u,i),m=t.utils.availableHeight(s,u,i);if(b.update=function(){u.call(b)},b.container=this,c.setter(x(d),b.update).getter(y(d)).update(),c.disabled=d.map(function(t){return!!t.disabled}),(d=d.map(function(t){return t.disabled=!!t.disabled,t})).forEach(function(t,e){t.originalPosition=isNaN(t.originalPosition)?e:t.originalPosition,t.currentPosition=isNaN(t.currentPosition)?e:t.currentPosition}),!p)for(h in p={},c)c[h]instanceof Array?p[h]=c[h].slice(0):p[h]=c[h];if(!r||!r.length)return t.utils.noData(b,u),b;u.selectAll(".nv-noData").remove();var k=u.selectAll("g.nv-wrap.nv-parallelCoordinatesChart").data([r]),w=k.enter().append("g").attr("class","nvd3 nv-wrap nv-parallelCoordinatesChart").append("g"),M=k.select("g");w.append("g").attr("class","nv-parallelCoordinatesWrap"),w.append("g").attr("class","nv-legendWrap"),M.select("rect").attr("width",g).attr("height",m>0?m:0),l?(n.width(g).color(function(t){return"rgb(188,190,192)"}),M.select(".nv-legendWrap").datum(d.sort(function(t,e){return t.originalPosition-e.originalPosition})).call(n),o||n.height()===i.top||(i.top=n.height(),m=t.utils.availableHeight(s,u,i)),k.select(".nv-legendWrap").attr("transform","translate( 0 ,"+-i.top+")")):M.select(".nv-legendWrap").selectAll("*").remove(),k.attr("transform","translate("+i.left+","+i.top+")"),e.width(g).height(m).dimensionData(d).displayBrush(f),M.select(".nv-parallelCoordinatesWrap ").datum(r).transition().call(e),e.dispatch.on("brushEnd",function(t,e){e?(f=!0,v.brushEnd(t)):f=!1}),n.dispatch.on("stateChange",function(t){for(var e in t)c[e]=t[e];v.stateChange(c),b.update()}),e.dispatch.on("dimensionsOrder",function(t){d.sort(function(t,e){return t.currentPosition-e.currentPosition});var e=!1;d.forEach(function(t,n){t.currentPosition=n,t.currentPosition!==t.originalPosition&&(e=!0)}),v.dimensionsOrder(d,e)}),v.on("changeState",function(t){void 0!==t.disabled&&(d.forEach(function(e,n){e.disabled=t.disabled[n]}),c.disabled=t.disabled),b.update()})}),m.renderEnd("parraleleCoordinateChart immediate"),b}return r.contentGenerator(function(t){var e='";return 0!==t.series.length&&(e+='',t.series.forEach(function(t){e=e+'"}),e+=""),e+="
'+t.key+"
'+t.key+''+t.value+"
"}),e.dispatch.on("elementMouseover.tooltip",function(t){var e={key:t.label,color:t.color,series:[]};t.values&&(Object.keys(t.values).forEach(function(n){var r,i=t.dimensions.filter(function(t){return t.key===n})[0];i&&(r=isNaN(t.values[n])||isNaN(parseFloat(t.values[n]))?g:i.format(t.values[n]),e.series.push({idx:i.currentPosition,key:n,value:r,color:i.color}))}),e.series.sort(function(t,e){return t.idx-e.idx})),r.data(e).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(t){r.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){r()}),b.dispatch=v,b.parallelCoordinates=e,b.legend=n,b.tooltip=r,b.options=t.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return a},set:function(t){a=t}},height:{get:function(){return s},set:function(t){s=t}},showLegend:{get:function(){return l},set:function(t){l=t}},defaultState:{get:function(){return p},set:function(t){p=t}},dimensionData:{get:function(){return d},set:function(t){d=t}},displayBrush:{get:function(){return f},set:function(t){f=t}},noData:{get:function(){return h},set:function(t){h=t}},nanValue:{get:function(){return g},set:function(t){g=t}},margin:{get:function(){return i},set:function(t){void 0!==t.top&&(i.top=t.top,o=t.top),i.right=void 0!==t.right?t.right:i.right,i.bottom=void 0!==t.bottom?t.bottom:i.bottom,i.left=void 0!==t.left?t.left:i.left}},color:{get:function(){return u},set:function(r){u=t.utils.getColor(r),n.color(u),e.color(u)}}}),t.utils.inheritOptions(b,e),t.utils.initOptions(b),b},t.models.pie=function(){"use strict";var e={top:0,right:0,bottom:0,left:0},n=500,r=500,i=function(t){return t.x},o=function(t){return t.y},a=Math.floor(1e4*Math.random()),s=null,l=t.utils.defaultColor(),u=d3.format(",.2f"),c=!0,d=!1,f="key",p=.02,h=!1,g=!1,v=!1,m=!0,y=0,x=!1,b=!1,k=!1,w=!1,M=0,A=.5,C=250,S=[],W=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),E=[],F=[],N=t.utils.renderWatch(W);function D(O){return N.reset(),O.each(function(D){var O=n-e.left-e.right,_=r-e.top-e.bottom,L=Math.min(O,_)/2,B=[],P=[];if(s=d3.select(this),0===S.length)for(var T=L-L/10,I=A*L,z=0;z=p){var o=J(r);Q[o]&&(r[1]-=14),Q[J(r)]=!0}return"translate("+r+")"}),q.select(".nv-label text").style("text-anchor",function(t,e){return x?(t.startAngle+t.endAngle)/20;--t)u(s*=.99),p(),d(),l(s),p(),d();function l(t){function n(t){return(t.source.y+t.sy+t.dy/2)*t.value}e.forEach(function(e,r){e.forEach(function(e){if(e.targetLinks.length){var r=d3.sum(e.targetLinks,n)/d3.sum(e.targetLinks,f);e.y+=(r-c(e))*t}})})}function u(t){function n(t){return(t.target.y+t.ty+t.dy/2)*t.value}e.slice().reverse().forEach(function(e){e.forEach(function(e){if(e.sourceLinks.length){var r=d3.sum(e.sourceLinks,n)/d3.sum(e.sourceLinks,f);e.y+=(r-c(e))*t}})})}function p(){e.forEach(function(t){var e,n,o,a=0,s=t.length;for(t.sort(h),o=0;o0&&(e.y+=n),a=e.y+e.dy+r;if((n=a-r-i[1])>0)for(a=e.y-=n,o=s-2;o>=0;--o)e=t[o],(n=e.y+e.dy+r-a)>0&&(e.y-=n),a=e.y})}function h(t,e){return t.y-e.y}}(t)},u=function(){var t=.5;function e(e){var n=e.source.x+e.source.dx,r=e.target.x,i=d3.interpolateNumber(n,r),o=i(t),a=i(1-t),s=e.source.y+e.sy+e.dy/2,l=e.target.y+e.ty+e.dy/2;return"M"+n+","+s+"C"+o+","+s+" "+a+","+l+" "+r+","+l}return e.curvature=function(n){return arguments.length?(t=+n,e):t},e},c=function(t){return t.y+t.dy/2};function d(){function t(t,e){return t.source.y-e.source.y}function e(t,e){return t.target.y-e.target.y}o.forEach(function(n){n.sourceLinks.sort(e),n.targetLinks.sort(t)}),o.forEach(function(t){var e=0,n=0;t.sourceLinks.forEach(function(t){t.sy=e,e+=t.dy}),t.targetLinks.forEach(function(t){t.ty=n,n+=t.dy})})}function f(t){return t.value}return e.options=t.utils.optionsFunc.bind(e),e._options=Object.create({},{nodeWidth:{get:function(){return n},set:function(t){n=+t}},nodePadding:{get:function(){return r},set:function(t){r=t}},nodes:{get:function(){return o},set:function(t){o=t}},links:{get:function(){return a},set:function(t){a=t}},size:{get:function(){return i},set:function(t){i=t}},sinksRight:{get:function(){return s},set:function(t){s=t}},layout:{get:function(){l(32)},set:function(t){l(t)}},relayout:{get:function(){d()},set:function(t){}},center:{get:function(){return c()},set:function(t){"function"==typeof t&&(c=t)}},link:{get:function(){return u()},set:function(t){return"function"==typeof t&&(u=t),u()}}}),t.utils.initOptions(e),e},t.models.sankeyChart=function(){"use strict";var e={top:5,right:0,bottom:5,left:0},n=t.models.sankey(),r=600,i=400,o=36,a=40,s="units",l=void 0,u=d3.format(",.0f"),c=function(t){return u(t)+" "+s},d=d3.scale.category20(),f=function(t){return t.source.name+" → "+t.target.name+"\n"+c(t.value)},p=function(t){return t.color=d(t.name.replace(/ .*/,""))},h=function(t){return d3.rgb(t.color).darker(2)},g=function(t){return t.name+"\n"+c(t.value)},v=function(t,e){t.append("text").attr("x",0).attr("y",0).attr("class","nvd3-sankey-chart-error").attr("text-anchor","middle").text(e)};function m(t){return t.each(function(e){var s={nodes:[{node:1,name:"Test 1"},{node:2,name:"Test 2"},{node:3,name:"Test 3"},{node:4,name:"Test 4"},{node:5,name:"Test 5"},{node:6,name:"Test 6"}],links:[{source:0,target:1,value:2295},{source:0,target:5,value:1199},{source:1,target:2,value:1119},{source:1,target:5,value:1176},{source:2,target:3,value:487},{source:2,target:5,value:632},{source:3,target:4,value:301},{source:3,target:5,value:186}]},u=!1,c=!1;if(("object"==typeof e.nodes&&e.nodes.length)>=0&&("object"==typeof e.links&&e.links.length)>=0&&(u=!0),e.nodes&&e.nodes.length>0&&e.links&&e.links.length>0&&(c=!0),!u)return console.error("NVD3 Sankey chart error:","invalid data format for",e),console.info("Valid data format is: ",s,JSON.stringify(s)),v(t,"Error loading chart, data is invalid"),!1;if(!c)return v(t,"No data available"),!1;var d=t.append("svg").attr("width",r).attr("height",i).append("g").attr("class","nvd3 nv-wrap nv-sankeyChart");n.nodeWidth(o).nodePadding(a).size([r,i]);var m=n.link();n.nodes(e.nodes).links(e.links).layout(32).center(l);var y=d.append("g").selectAll(".link").data(e.links).enter().append("path").attr("class","link").attr("d",m).style("stroke-width",function(t){return Math.max(1,t.dy)}).sort(function(t,e){return e.dy-t.dy});y.append("title").text(f);var x=d.append("g").selectAll(".node").data(e.nodes).enter().append("g").attr("class","node").attr("transform",function(t){return"translate("+t.x+","+t.y+")"}).call(d3.behavior.drag().origin(function(t){return t}).on("dragstart",function(){this.parentNode.appendChild(this)}).on("drag",function(t){d3.select(this).attr("transform","translate("+t.x+","+(t.y=Math.max(0,Math.min(i-t.dy,d3.event.y)))+")"),n.relayout(),y.attr("d",m)}));x.append("rect").attr("height",function(t){return t.dy}).attr("width",n.nodeWidth()).style("fill",p).style("stroke",h).append("title").text(g),x.append("text").attr("x",-6).attr("y",function(t){return t.dy/2}).attr("dy",".35em").attr("text-anchor","end").attr("transform",null).text(function(t){return t.name}).filter(function(t){return t.x0?$+20:0),ct.attr("clip-path",N?"url(#nv-edge-clip-"+h+")":""),R=!0;var ft=at.select(".nv-groups").selectAll(".nv-group").data(function(t){return t},function(t){return t.key});ft.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),ft.exit().remove(),ft.attr("class",function(t,e){return(t.classed||"")+" nv-group nv-series-"+e}).classed("nv-noninteractive",!S).classed("hover",function(t){return t.hover}),ft.watchTransition(Z,"scatter: groups").style("fill",function(t,e){return f(t,e)}).style("stroke",function(t,e){return t.pointBorderColor||p||f(t,e)}).style("stroke-opacity",1).style("fill-opacity",.5);var pt=ft.selectAll("path.nv-point").data(function(t){return t.values.map(function(t,e){return[t,e]}).filter(function(t,e){return W(t[0],e)})});if(pt.enter().append("path").attr("class",function(t){return"nv-point nv-point-"+t[1]}).style("fill",function(t){return t.color}).style("stroke",function(t){return t.color}).attr("transform",function(r){return"translate("+t.utils.NaNtoZero(e(x(r[0],r[1])))+","+t.utils.NaNtoZero(n(b(r[0],r[1])))+")"}).attr("d",t.utils.symbol().type(function(t){return w(t[0])}).size(function(t){return y(k(t[0],t[1]))})),pt.exit().each(U).remove(),ft.exit().selectAll("path.nv-point").watchTransition(Z,"scatter exit").attr("transform",function(e){return"translate("+t.utils.NaNtoZero(v(x(e[0],e[1])))+","+t.utils.NaNtoZero(m(b(e[0],e[1])))+")"}).remove(),pt.filter(function(t){return Q(t,"x",x,"y",b)||rt||it||ot}).watchTransition(Z,"scatter points").attr("transform",function(e){return"translate("+t.utils.NaNtoZero(v(x(e[0],e[1])))+","+t.utils.NaNtoZero(m(b(e[0],e[1])))+")"}),pt.filter(function(t){return Q(t,"shape",w,"size",k)||rt||it||ot}).watchTransition(Z,"scatter points").attr("d",t.utils.symbol().type(function(t){return w(t[0])}).size(function(t){return y(k(t[0],t[1]))})),j){var ht=ft.selectAll(".nv-label").data(function(t){return t.values.map(function(t,e){return[t,e]}).filter(function(t,e){return W(t[0],e)})});ht.enter().append("text").style("fill",function(t,e){return t.color}).style("stroke-opacity",0).style("fill-opacity",1).attr("transform",function(r){return"translate("+(t.utils.NaNtoZero(e(x(r[0],r[1])))+Math.sqrt(y(k(r[0],r[1]))/Math.PI)+2)+","+t.utils.NaNtoZero(n(b(r[0],r[1])))+")"}).text(function(t,e){return t[0].label}),ht.exit().remove(),ft.exit().selectAll("path.nv-label").watchTransition(Z,"scatter exit").attr("transform",function(e){return"translate("+(t.utils.NaNtoZero(v(x(e[0],e[1])))+Math.sqrt(y(k(e[0],e[1]))/Math.PI)+2)+","+t.utils.NaNtoZero(m(b(e[0],e[1])))+")"}).remove(),ht.each(function(t){d3.select(this).classed("nv-label",!0).classed("nv-label-"+t[1],!1).classed("hover",!1)}),ht.watchTransition(Z,"scatter labels").attr("transform",function(e){return"translate("+(t.utils.NaNtoZero(v(x(e[0],e[1])))+Math.sqrt(y(k(e[0],e[1]))/Math.PI)+2)+","+t.utils.NaNtoZero(m(b(e[0],e[1])))+")"})}X?(clearTimeout(l),l=setTimeout(dt,X)):dt(),e=v.copy(),n=m.copy(),r=y.copy(),a=c,s=d}),Z.renderEnd("scatter immediate"),J}return J.dispatch=H,J.options=t.utils.optionsFunc.bind(J),J._calls=new function(){this.clearHighlights=function(){return t.dom.write(function(){g.selectAll(".nv-point.hover").classed("hover",!1)}),null},this.highlightPoint=function(e,n,r){t.dom.write(function(){g.select(".nv-groups").selectAll(".nv-series-"+e).selectAll(".nv-point-"+n).classed("hover",r)})}},H.on("elementMouseover.point",function(t){S&&J._calls.highlightPoint(t.seriesIndex,t.pointIndex,!0)}),H.on("elementMouseout.point",function(t){S&&J._calls.highlightPoint(t.seriesIndex,t.pointIndex,!1)}),J._options=Object.create({},{width:{get:function(){return c},set:function(t){c=t}},height:{get:function(){return d},set:function(t){d=t}},xScale:{get:function(){return v},set:function(t){v=t}},yScale:{get:function(){return m},set:function(t){m=t}},pointScale:{get:function(){return y},set:function(t){y=t}},xDomain:{get:function(){return L},set:function(t){L=t}},yDomain:{get:function(){return B},set:function(t){B=t}},pointDomain:{get:function(){return I},set:function(t){I=t}},xRange:{get:function(){return P},set:function(t){P=t}},yRange:{get:function(){return T},set:function(t){T=t}},pointRange:{get:function(){return z},set:function(t){z=t}},forceX:{get:function(){return M},set:function(t){M=t}},forceY:{get:function(){return A},set:function(t){A=t}},forcePoint:{get:function(){return C},set:function(t){C=t}},interactive:{get:function(){return S},set:function(t){S=t}},pointActive:{get:function(){return W},set:function(t){W=t}},padDataOuter:{get:function(){return F},set:function(t){F=t}},padData:{get:function(){return E},set:function(t){E=t}},clipEdge:{get:function(){return N},set:function(t){N=t}},clipVoronoi:{get:function(){return D},set:function(t){D=t}},clipRadius:{get:function(){return _},set:function(t){_=t}},showVoronoi:{get:function(){return O},set:function(t){O=t}},id:{get:function(){return h},set:function(t){h=t}},interactiveUpdateDelay:{get:function(){return X},set:function(t){X=t}},showLabels:{get:function(){return j},set:function(t){j=t}},pointBorderColor:{get:function(){return p},set:function(t){p=t}},x:{get:function(){return x},set:function(t){x=d3.functor(t)}},y:{get:function(){return b},set:function(t){b=d3.functor(t)}},pointSize:{get:function(){return k},set:function(t){k=d3.functor(t)}},pointShape:{get:function(){return w},set:function(t){w=d3.functor(t)}},margin:{get:function(){return u},set:function(t){u.top=void 0!==t.top?t.top:u.top,u.right=void 0!==t.right?t.right:u.right,u.bottom=void 0!==t.bottom?t.bottom:u.bottom,u.left=void 0!==t.left?t.left:u.left}},duration:{get:function(){return G},set:function(t){G=t,Z.reset(G)}},color:{get:function(){return f},set:function(e){f=t.utils.getColor(e)}},useVoronoi:{get:function(){return Y},set:function(t){!1===(Y=t)&&(D=!1)}}}),t.utils.initOptions(J),J},t.models.scatterChart=function(){"use strict";var e=t.models.scatter(),n=t.models.axis(),r=t.models.axis(),i=t.models.legend(),o=t.models.distribution(),a=t.models.distribution(),s=t.models.tooltip(),l={top:30,right:20,bottom:50,left:75},u=null,c=null,d=null,f=null,p=t.utils.defaultColor(),h=e.xScale(),g=e.yScale(),v=!1,m=!1,y=!0,x=!0,b=!0,k=!1,w=t.utils.state(),M=null,A=d3.dispatch("stateChange","changeState","renderEnd"),C=null,S=250,W=!1;e.xScale(h).yScale(g),n.orient("bottom").tickPadding(10),r.orient(k?"right":"left").tickPadding(10),o.axis("x"),a.axis("y"),s.headerFormatter(function(t,e){return n.tickFormat()(t,e)}).valueFormatter(function(t,e){return r.tickFormat()(t,e)});var E=t.utils.renderWatch(A,S),F=function(t){return function(){return{active:t.map(function(t){return!t.disabled})}}},N=function(t){return function(e){void 0!==e.active&&t.forEach(function(t,n){t.disabled=!e.active[n]})}};function D(C){return E.reset(),E.models(e),x&&E.models(n),b&&E.models(r),v&&E.models(o),m&&E.models(a),C.each(function(C){f=d3.select(this),t.utils.initSVG(f);var O,_=t.utils.availableWidth(c,f,l),L=t.utils.availableHeight(d,f,l);if(D.update=function(){0===S?f.call(D):f.transition().duration(S).call(D)},D.container=this,w.setter(N(C),D.update).getter(F(C)).update(),w.disabled=C.map(function(t){return!!t.disabled}),!M)for(O in M={},w)w[O]instanceof Array?M[O]=w[O].slice(0):M[O]=w[O];if(!(C&&C.length&&C.filter(function(t){return t.values.length}).length))return t.utils.noData(D,f),E.renderEnd("scatter immediate"),D;f.selectAll(".nv-noData").remove(),h=e.xScale(),g=e.yScale();var B=f.selectAll("g.nv-wrap.nv-scatterChart").data([C]),P=B.enter().append("g").attr("class","nvd3 nv-wrap nv-scatterChart nv-chart-"+e.id()).append("g"),T=B.select("g");if(P.append("rect").attr("class","nvd3 nv-background").style("pointer-events","none"),P.append("g").attr("class","nv-x nv-axis"),P.append("g").attr("class","nv-y nv-axis"),P.append("g").attr("class","nv-scatterWrap"),P.append("g").attr("class","nv-regressionLinesWrap"),P.append("g").attr("class","nv-distWrap"),P.append("g").attr("class","nv-legendWrap"),k&&T.select(".nv-y.nv-axis").attr("transform","translate("+_+",0)"),y){var I=_;i.width(I),B.select(".nv-legendWrap").datum(C).call(i),u||i.height()===l.top||(l.top=i.height(),L=t.utils.availableHeight(d,f,l)),B.select(".nv-legendWrap").attr("transform","translate(0,"+-l.top+")")}else T.select(".nv-legendWrap").selectAll("*").remove();B.attr("transform","translate("+l.left+","+l.top+")"),e.width(_).height(L).color(C.map(function(t,e){return t.color=t.color||p(t,e),t.color}).filter(function(t,e){return!C[e].disabled})).showLabels(W),B.select(".nv-scatterWrap").datum(C.filter(function(t){return!t.disabled})).call(e),B.select(".nv-regressionLinesWrap").attr("clip-path","url(#nv-edge-clip-"+e.id()+")");var z=B.select(".nv-regressionLinesWrap").selectAll(".nv-regLines").data(function(t){return t});z.enter().append("g").attr("class","nv-regLines");var V=z.selectAll(".nv-regLine").data(function(t){return[t]});V.enter().append("line").attr("class","nv-regLine").style("stroke-opacity",0),V.filter(function(t){return t.intercept&&t.slope}).watchTransition(E,"scatterPlusLineChart: regline").attr("x1",h.range()[0]).attr("x2",h.range()[1]).attr("y1",function(t,e){return g(h.domain()[0]*t.slope+t.intercept)}).attr("y2",function(t,e){return g(h.domain()[1]*t.slope+t.intercept)}).style("stroke",function(t,e,n){return p(t,n)}).style("stroke-opacity",function(t,e){return t.disabled||void 0===t.slope||void 0===t.intercept?0:1}),x&&(n.scale(h)._ticks(t.utils.calcTicksX(_/100,C)).tickSize(-L,0),T.select(".nv-x.nv-axis").attr("transform","translate(0,"+g.range()[0]+")").call(n)),b&&(r.scale(g)._ticks(t.utils.calcTicksY(L/36,C)).tickSize(-_,0),T.select(".nv-y.nv-axis").call(r)),v&&(o.getData(e.x()).scale(h).width(_).color(C.map(function(t,e){return t.color||p(t,e)}).filter(function(t,e){return!C[e].disabled})),P.select(".nv-distWrap").append("g").attr("class","nv-distributionX"),T.select(".nv-distributionX").attr("transform","translate(0,"+g.range()[0]+")").datum(C.filter(function(t){return!t.disabled})).call(o)),m&&(a.getData(e.y()).scale(g).width(L).color(C.map(function(t,e){return t.color||p(t,e)}).filter(function(t,e){return!C[e].disabled})),P.select(".nv-distWrap").append("g").attr("class","nv-distributionY"),T.select(".nv-distributionY").attr("transform","translate("+(k?_:-a.size())+",0)").datum(C.filter(function(t){return!t.disabled})).call(a)),i.dispatch.on("stateChange",function(t){for(var e in t)w[e]=t[e];A.stateChange(w),D.update()}),A.on("changeState",function(t){void 0!==t.disabled&&(C.forEach(function(e,n){e.disabled=t.disabled[n]}),w.disabled=t.disabled),D.update()}),e.dispatch.on("elementMouseout.tooltip",function(t){s.hidden(!0),f.select(".nv-chart-"+e.id()+" .nv-series-"+t.seriesIndex+" .nv-distx-"+t.pointIndex).attr("y1",0),f.select(".nv-chart-"+e.id()+" .nv-series-"+t.seriesIndex+" .nv-disty-"+t.pointIndex).attr("x2",a.size())}),e.dispatch.on("elementMouseover.tooltip",function(t){f.select(".nv-series-"+t.seriesIndex+" .nv-distx-"+t.pointIndex).attr("y1",t.relativePos[1]-L),f.select(".nv-series-"+t.seriesIndex+" .nv-disty-"+t.pointIndex).attr("x2",t.relativePos[0]+o.size()),s.data(t).hidden(!1)}),h.copy(),g.copy()}),E.renderEnd("scatter with line immediate"),D}return D.dispatch=A,D.scatter=e,D.legend=i,D.xAxis=n,D.yAxis=r,D.distX=o,D.distY=a,D.tooltip=s,D.options=t.utils.optionsFunc.bind(D),D._options=Object.create({},{width:{get:function(){return c},set:function(t){c=t}},height:{get:function(){return d},set:function(t){d=t}},container:{get:function(){return f},set:function(t){f=t}},showDistX:{get:function(){return v},set:function(t){v=t}},showDistY:{get:function(){return m},set:function(t){m=t}},showLegend:{get:function(){return y},set:function(t){y=t}},showXAxis:{get:function(){return x},set:function(t){x=t}},showYAxis:{get:function(){return b},set:function(t){b=t}},defaultState:{get:function(){return M},set:function(t){M=t}},noData:{get:function(){return C},set:function(t){C=t}},duration:{get:function(){return S},set:function(t){S=t}},showLabels:{get:function(){return W},set:function(t){W=t}},margin:{get:function(){return l},set:function(t){void 0!==t.top&&(l.top=t.top,u=t.top),l.right=void 0!==t.right?t.right:l.right,l.bottom=void 0!==t.bottom?t.bottom:l.bottom,l.left=void 0!==t.left?t.left:l.left}},rightAlignYAxis:{get:function(){return k},set:function(t){k=t,r.orient(t?"right":"left")}},color:{get:function(){return p},set:function(e){p=t.utils.getColor(e),i.color(p),o.color(p),a.color(p)}}}),t.utils.inheritOptions(D,e),t.utils.initOptions(D),D},t.models.sparkline=function(){"use strict";var e,n,r,i,o={top:2,right:0,bottom:2,left:0},a=400,s=32,l=null,u=!0,c=d3.scale.linear(),d=d3.scale.linear(),f=function(t){return t.x},p=function(t){return t.y},h=t.utils.getColor(["#000"]),g=!0,v=!0,m=d3.dispatch("renderEnd"),y=t.utils.renderWatch(m);function x(u){return y.reset(),u.each(function(u){var m=a-o.left-o.right,y=s-o.top-o.bottom;l=d3.select(this),t.utils.initSVG(l),c.domain(e||d3.extent(u,f)).range(r||[0,m]),d.domain(n||d3.extent(u,p)).range(i||[y,0]);var x=l.selectAll("g.nv-wrap.nv-sparkline").data([u]);x.enter().append("g").attr("class","nvd3 nv-wrap nv-sparkline").append("g"),x.select("g");x.attr("transform","translate("+o.left+","+o.top+")");var b=x.selectAll("path").data(function(t){return[t]});b.enter().append("path"),b.exit().remove(),b.style("stroke",function(t,e){return t.color||h(t,e)}).attr("d",d3.svg.line().x(function(t,e){return c(f(t,e))}).y(function(t,e){return d(p(t,e))}));var k=x.selectAll("circle.nv-point").data(function(t){var e=t.map(function(t,e){return p(t,e)});function n(e){if(-1!=e){var n=t[e];return n.pointIndex=e,n}return null}var r=n(e.lastIndexOf(d.domain()[1])),i=n(e.indexOf(d.domain()[0])),o=n(e.length-1);return[g?i:null,g?r:null,v?o:null].filter(function(t){return null!=t})});k.enter().append("circle"),k.exit().remove(),k.attr("cx",function(t,e){return c(f(t,t.pointIndex))}).attr("cy",function(t,e){return d(p(t,t.pointIndex))}).attr("r",2).attr("class",function(t,e){return f(t,t.pointIndex)==c.domain()[1]?"nv-point nv-currentValue":p(t,t.pointIndex)==d.domain()[0]?"nv-point nv-minValue":"nv-point nv-maxValue"})}),y.renderEnd("sparkline immediate"),x}return x.options=t.utils.optionsFunc.bind(x),x._options=Object.create({},{width:{get:function(){return a},set:function(t){a=t}},height:{get:function(){return s},set:function(t){s=t}},xDomain:{get:function(){return e},set:function(t){e=t}},yDomain:{get:function(){return n},set:function(t){n=t}},xRange:{get:function(){return r},set:function(t){r=t}},yRange:{get:function(){return i},set:function(t){i=t}},xScale:{get:function(){return c},set:function(t){c=t}},yScale:{get:function(){return d},set:function(t){d=t}},animate:{get:function(){return u},set:function(t){u=t}},showMinMaxPoints:{get:function(){return g},set:function(t){g=t}},showCurrentPoint:{get:function(){return v},set:function(t){v=t}},x:{get:function(){return f},set:function(t){f=d3.functor(t)}},y:{get:function(){return p},set:function(t){p=d3.functor(t)}},margin:{get:function(){return o},set:function(t){o.top=void 0!==t.top?t.top:o.top,o.right=void 0!==t.right?t.right:o.right,o.bottom=void 0!==t.bottom?t.bottom:o.bottom,o.left=void 0!==t.left?t.left:o.left}},color:{get:function(){return h},set:function(e){h=t.utils.getColor(e)}}}),x.dispatch=m,t.utils.initOptions(x),x},t.models.sparklinePlus=function(){"use strict";var e,n,r=t.models.sparkline(),i={top:15,right:100,bottom:10,left:50},o=null,a=null,s=[],l=!1,u=d3.format(",r"),c=d3.format(",.2f"),d=!0,f=!0,p=!1,h=null,g=d3.dispatch("renderEnd"),v=t.utils.renderWatch(g);function m(h){return v.reset(),v.models(r),h.each(function(h){var g=d3.select(this);t.utils.initSVG(g);var v=t.utils.availableWidth(o,g,i),y=t.utils.availableHeight(a,g,i);if(m.update=function(){g.call(m)},m.container=this,!h||!h.length)return t.utils.noData(m,g),m;g.selectAll(".nv-noData").remove();var x=r.y()(h[h.length-1],h.length-1);e=r.xScale(),n=r.yScale();var b=g.selectAll("g.nv-wrap.nv-sparklineplus").data([h]),k=b.enter().append("g").attr("class","nvd3 nv-wrap nv-sparklineplus").append("g"),w=b.select("g");k.append("g").attr("class","nv-sparklineWrap"),k.append("g").attr("class","nv-valueWrap"),k.append("g").attr("class","nv-hoverArea"),b.attr("transform","translate("+i.left+","+i.top+")");var M=w.select(".nv-sparklineWrap");if(r.width(v).height(y),M.call(r),d){var A=w.select(".nv-valueWrap").selectAll(".nv-currentValue").data([x]);A.enter().append("text").attr("class","nv-currentValue").attr("dx",p?-8:8).attr("dy",".9em").style("text-anchor",p?"end":"start"),A.attr("x",v+(p?i.right:0)).attr("y",f?function(t){return n(t)}:0).style("fill",r.color()(h[h.length-1],h.length-1)).text(c(x))}function C(){if(!l){var t=w.selectAll(".nv-hoverValue").data(s),n=t.enter().append("g").attr("class","nv-hoverValue").style("stroke-opacity",0).style("fill-opacity",0);t.exit().transition().duration(250).style("stroke-opacity",0).style("fill-opacity",0).remove(),t.attr("transform",function(t){return"translate("+e(r.x()(h[t],t))+",0)"}).transition().duration(250).style("stroke-opacity",1).style("fill-opacity",1),s.length&&(n.append("line").attr("x1",0).attr("y1",-i.top).attr("x2",0).attr("y2",y),n.append("text").attr("class","nv-xValue").attr("x",-6).attr("y",-i.top).attr("text-anchor","end").attr("dy",".9em"),w.select(".nv-hoverValue .nv-xValue").text(u(r.x()(h[s[0]],s[0]))),n.append("text").attr("class","nv-yValue").attr("x",6).attr("y",-i.top).attr("text-anchor","start").attr("dy",".9em"),w.select(".nv-hoverValue .nv-yValue").text(c(r.y()(h[s[0]],s[0]))))}}k.select(".nv-hoverArea").append("rect").on("mousemove",function(){if(l)return;var t=d3.mouse(this)[0]-i.left;s=[function(t,e){for(var n=Math.abs(r.x()(t[0],0)-e),i=0,o=0;o=t[0]&&r.x()(e,n)<=t[1]}),disableTooltip:e.disableTooltip}})).transition().duration(_).call(r),tt(),et()}r.dispatch.on("areaClick.toggle",function(t){1===u.filter(function(t){return!t.disabled}).length?u.forEach(function(t){t.disabled=!1}):u.forEach(function(e,n){e.disabled=n!=t.seriesIndex}),S.disabled=u.map(function(t){return!!t.disabled}),F.stateChange(S),V.update()}),a.dispatch.on("stateChange",function(t){for(var e in t)S[e]=t[e];F.stateChange(S),V.update()}),s.dispatch.on("legendClick",function(t,e){t.disabled&&(U=U.map(function(t){return t.disabled=!0,t}),t.disabled=!1,r.style(t.style),S.style=r.style(),F.stateChange(S),V.update())}),l.dispatch.on("elementMousemove",function(e){r.clearHighlights();var n,i,o,a=[],s=0,c=!0,d=!1;if(u.filter(function(t,e){return t.seriesIndex=e,!t.disabled}).forEach(function(l,f){i=t.interactiveBisect(l.values,e.pointXValue,V.x());var p=l.values[i],h=V.y()(p,i);if(null!=h&&h>0&&(r.highlightPoint(f,i,!0),d=!0),f!==u.length-1||d||r.highlightPoint(f,i,!0),void 0!==p){void 0===n&&(n=p),void 0===o&&(o=V.xScale()(V.x()(p,i)));var v="expand"==r.style()?p.display.y:V.y()(p,i);a.push({key:l.key,value:v,color:g(l,l.seriesIndex),point:p}),A&&"expand"!=r.style()&&null!=v&&(s+=v,c=!1)}}),a.reverse(),a.length>2){var f=V.yScale().invert(e.mouseY),p=null;a.forEach(function(t,e){f=Math.abs(f);var n=Math.abs(t.point.display.y0),r=Math.abs(t.point.display.y);f>=n&&f<=r+n&&(p=e)}),null!=p&&(a[p].highlight=!0)}A&&"expand"!=r.style()&&a.length>=2&&!c&&a.push({key:C,value:s,total:!0});var h=V.x()(n,i),v=l.tooltip.valueFormatter();"expand"===r.style()||"stack_percent"===r.style()?(B||(B=v),v=d3.format(".1%")):B&&(v=B,B=null),l.tooltip.valueFormatter(v).data({value:h,series:a})(),l.renderGuideLine(o)}),l.dispatch.on("elementMouseout",function(t){r.clearHighlights()}),c.dispatch.on("onBrush",function(t){rt(t)}),F.on("changeState",function(t){void 0!==t.disabled&&u.length===t.disabled.length&&(u.forEach(function(e,n){e.disabled=t.disabled[n]}),S.disabled=t.disabled),void 0!==t.style&&(r.style(t.style),t.style),V.update()})}),P.renderEnd("stacked Area chart immediate"),V}return r.dispatch.on("elementMouseover.tooltip",function(t){t.point.x=r.x()(t.point),t.point.y=r.y()(t.point),u.data(t).hidden(!1)}),r.dispatch.on("elementMouseout.tooltip",function(t){u.hidden(!0)}),V.dispatch=F,V.stacked=r,V.legend=a,V.controls=s,V.xAxis=i,V.x2Axis=c.xAxis,V.yAxis=o,V.y2Axis=c.yAxis,V.interactiveLayer=l,V.tooltip=u,V.focus=c,V.dispatch=F,V.options=t.utils.optionsFunc.bind(V),V._options=Object.create({},{width:{get:function(){return p},set:function(t){p=t}},height:{get:function(){return h},set:function(t){h=t}},showLegend:{get:function(){return m},set:function(t){m=t}},legendPosition:{get:function(){return y},set:function(t){y=t}},showXAxis:{get:function(){return x},set:function(t){x=t}},showYAxis:{get:function(){return b},set:function(t){b=t}},defaultState:{get:function(){return W},set:function(t){W=t}},noData:{get:function(){return E},set:function(t){E=t}},showControls:{get:function(){return v},set:function(t){v=t}},controlLabels:{get:function(){return O},set:function(t){O=t}},controlOptions:{get:function(){return D},set:function(t){D=t}},showTotalInTooltip:{get:function(){return A},set:function(t){A=t}},totalLabel:{get:function(){return C},set:function(t){C=t}},focusEnable:{get:function(){return w},set:function(t){w=t}},focusHeight:{get:function(){return c.height()},set:function(t){c.height(t)}},brushExtent:{get:function(){return c.brushExtent()},set:function(t){c.brushExtent(t)}},margin:{get:function(){return d},set:function(t){void 0!==t.top&&(d.top=t.top,f=t.top),d.right=void 0!==t.right?t.right:d.right,d.bottom=void 0!==t.bottom?t.bottom:d.bottom,d.left=void 0!==t.left?t.left:d.left}},focusMargin:{get:function(){return c.margin},set:function(t){c.margin.top=void 0!==t.top?t.top:c.margin.top,c.margin.right=void 0!==t.right?t.right:c.margin.right,c.margin.bottom=void 0!==t.bottom?t.bottom:c.margin.bottom,c.margin.left=void 0!==t.left?t.left:c.margin.left}},duration:{get:function(){return _},set:function(t){_=t,P.reset(_),r.duration(_),i.duration(_),o.duration(_)}},color:{get:function(){return g},set:function(e){g=t.utils.getColor(e),a.color(g),r.color(g),c.color(g)}},x:{get:function(){return r.x()},set:function(t){r.x(t),c.x(t)}},y:{get:function(){return r.y()},set:function(t){r.y(t),c.y(t)}},rightAlignYAxis:{get:function(){return k},set:function(t){k=t,o.orient(k?"right":"left")}},useInteractiveGuideline:{get:function(){return M},set:function(t){M=!!t,V.interactive(!t),V.useVoronoi(!t),r.scatter.interactive(!t)}}}),t.utils.inheritOptions(V,r),t.utils.initOptions(V),V},t.models.stackedAreaWithFocusChart=function(){return t.models.stackedAreaChart().margin({bottom:30}).focusEnable(!0)},t.models.sunburst=function(){"use strict";var e,n,r,i,o={top:0,right:0,bottom:0,left:0},a=600,s=600,l="count",u={count:function(t){return 1},value:function(t){return t.value||t.size},size:function(t){return t.value||t.size}},c=Math.floor(1e4*Math.random()),d=null,f=t.utils.defaultColor(),p=!1,h=function(t){return"count"===l?t.name+" #"+t.value:t.name+" "+(t.value||t.size)},g=.02,v=function(t,e){return t.name>e.name},m=function(t,e){return void 0!==t.parent?t.name+"-"+t.parent.name+"-"+e:t.name},y=!0,x=500,b=d3.dispatch("chartClick","elementClick","elementDblClick","elementMousemove","elementMouseover","elementMouseout","renderEnd"),k=d3.scale.linear().range([0,2*Math.PI]),w=d3.scale.sqrt(),M=d3.layout.partition().sort(v),A={},C=d3.svg.arc().startAngle(function(t){return Math.max(0,Math.min(2*Math.PI,k(t.x)))}).endAngle(function(t){return Math.max(0,Math.min(2*Math.PI,k(t.x+t.dx)))}).innerRadius(function(t){return Math.max(0,w(t.y))}).outerRadius(function(t){return Math.max(0,w(t.y+t.dy))});function S(t){return W(t)>90?180:0}function W(t){return(Math.max(0,Math.min(2*Math.PI,k(t.x)))+Math.max(0,Math.min(2*Math.PI,k(t.x+t.dx))))/2*(180/Math.PI)-90}function E(t){var e=Math.max(0,Math.min(2*Math.PI,k(t.x)));return(Math.max(0,Math.min(2*Math.PI,k(t.x+t.dx)))-e)/(2*Math.PI)}function F(t){var e=Math.max(0,Math.min(2*Math.PI,k(t.x)));return Math.max(0,Math.min(2*Math.PI,k(t.x+t.dx)))-e>g}function N(t,n){var r=d3.interpolate(k.domain(),[e.x,e.x+e.dx]),o=d3.interpolate(w.domain(),[e.y,1]),a=d3.interpolate(w.range(),[e.y?20:0,i]);return 0===n?function(){return C(t)}:function(e){return k.domain(r(e)),w.domain(o(e)).range(a(e)),C(t)}}function D(t){var e=d3.interpolate({x:t.x0,dx:t.dx0,y:t.y0,dy:t.dy0},t);return function(n){var r=e(n);return t.x0=r.x,t.dx0=r.dx,t.y0=r.y,t.dy0=r.dy,C(r)}}function O(t){t.forEach(function(t){var e=m(t),n=A[e];n?(t.dx0=n.dx,t.x0=n.x,t.dy0=n.dy,t.y0=n.y):(t.dx0=t.dx,t.x0=t.x,t.dy0=t.dy,t.y0=t.y),function(t){var e=m(t);A[e]||(A[e]={});var n=A[e];n.dx=t.dx,n.x=t.x,n.dy=t.dy,n.y=t.y}(t)})}function _(t){var n=d.selectAll("text"),r=d.selectAll("path");n.transition().attr("opacity",0),e=t,r.transition().duration(x).attrTween("d",N).each("end",function(e){e.x>=t.x&&e.x=t.depth&&d3.select(this.parentNode).select("text").transition().duration(x).text(function(t){return h(t)}).attr("opacity",function(t){return F(t)?1:0}).attr("transform",function(){var n=this.getBBox().width;if(0===e.depth)return"translate("+n/2*-1+",0)";if(e.depth===t.depth)return"translate("+(w(e.y)+5)+",0)";var r=W(e),i=S(e);return 0===i?"rotate("+r+")translate("+(w(e.y)+5)+",0)":"rotate("+r+")translate("+(w(e.y)+n+5)+",0)rotate("+i+")"}))})}var L=t.utils.renderWatch(b);function B(e){return L.reset(),e.each(function(e){d=d3.select(this),n=t.utils.availableWidth(a,d,o),r=t.utils.availableHeight(s,d,o),i=Math.min(n,r)/2,w.range([0,i]);var g=d.select("g.nvd3.nv-wrap.nv-sunburst");g[0][0]?g.attr("transform","translate("+(n/2+o.left+o.right)+","+(r/2+o.top+o.bottom)+")"):g=d.append("g").attr("class","nvd3 nv-wrap nv-sunburst nv-chart-"+c).attr("transform","translate("+(n/2+o.left+o.right)+","+(r/2+o.top+o.bottom)+")"),d.on("click",function(t,e){b.chartClick({data:t,index:e,pos:d3.event,id:c})}),M.value(u[l]||u.count);var v=M.nodes(e[0]).reverse();O(v);var k=g.selectAll(".arc-container").data(v,m);k.enter().append("g").attr("class","arc-container").append("path").attr("d",C).style("fill",function(t){return t.color?t.color:f(y?(t.children?t:t.parent).name:t.name)}).style("stroke","#FFF").on("click",function(t,e){_(t),b.elementClick({data:t,index:e})}).on("mouseover",function(t,e){d3.select(this).classed("hover",!0).style("opacity",.8),b.elementMouseover({data:t,color:d3.select(this).style("fill"),percent:E(t)})}).on("mouseout",function(t,e){d3.select(this).classed("hover",!1).style("opacity",1),b.elementMouseout({data:t})}).on("mousemove",function(t,e){b.elementMousemove({data:t})}),k.each(function(t){d3.select(this).select("path").transition().duration(x).attrTween("d",D)}),p&&(k.selectAll("text").remove(),k.append("text").text(function(t){return h(t)}).transition().duration(x).attr("opacity",function(t){return F(t)?1:0}).attr("transform",function(t){var e=this.getBBox().width;if(0===t.depth)return"rotate(0)translate("+e/2*-1+",0)";var n=W(t),r=S(t);return 0===r?"rotate("+n+")translate("+(w(t.y)+5)+",0)":"rotate("+n+")translate("+(w(t.y)+e+5)+",0)rotate("+r+")"})),_(v[v.length-1]),k.exit().transition().duration(x).attr("opacity",0).each("end",function(t){var e=m(t);A[e]=void 0}).remove()}),L.renderEnd("sunburst immediate"),B}return B.dispatch=b,B.options=t.utils.optionsFunc.bind(B),B._options=Object.create({},{width:{get:function(){return a},set:function(t){a=t}},height:{get:function(){return s},set:function(t){s=t}},mode:{get:function(){return l},set:function(t){l=t}},id:{get:function(){return c},set:function(t){c=t}},duration:{get:function(){return x},set:function(t){x=t}},groupColorByParent:{get:function(){return y},set:function(t){y=!!t}},showLabels:{get:function(){return p},set:function(t){p=!!t}},labelFormat:{get:function(){return h},set:function(t){h=t}},labelThreshold:{get:function(){return g},set:function(t){g=t}},sort:{get:function(){return v},set:function(t){v=t}},key:{get:function(){return m},set:function(t){m=t}},margin:{get:function(){return o},set:function(t){o.top=null!=t.top?t.top:o.top,o.right=null!=t.right?t.right:o.right,o.bottom=null!=t.bottom?t.bottom:o.bottom,o.left=null!=t.left?t.left:o.left}},color:{get:function(){return f},set:function(e){f=t.utils.getColor(e)}}}),t.utils.initOptions(B),B},t.models.sunburstChart=function(){"use strict";var e=t.models.sunburst(),n=t.models.tooltip(),r={top:30,right:20,bottom:20,left:20},i=null,o=null,a=t.utils.defaultColor(),s=!1,l=(Math.round(1e5*Math.random()),null),u=null,c=250,d=d3.dispatch("stateChange","changeState","renderEnd"),f=t.utils.renderWatch(d);function p(n){return f.reset(),f.models(e),n.each(function(n){var a=d3.select(this);t.utils.initSVG(a);var s=t.utils.availableWidth(i,a,r),l=t.utils.availableHeight(o,a,r);if(p.update=function(){0===c?a.call(p):a.transition().duration(c).call(p)},p.container=a,!n||!n.length)return t.utils.noData(p,a),p;a.selectAll(".nv-noData").remove(),e.width(s).height(l).margin(r),a.call(e)}),f.renderEnd("sunburstChart immediate"),p}return n.duration(0).headerEnabled(!1).valueFormatter(function(t){return t}),e.dispatch.on("elementMouseover.tooltip",function(t){t.series={key:t.data.name,value:t.data.value||t.data.size,color:t.color,percent:t.percent},s||(delete t.percent,delete t.series.percent),n.data(t).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(t){n.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(t){n()}),p.dispatch=d,p.sunburst=e,p.tooltip=n,p.options=t.utils.optionsFunc.bind(p),p._options=Object.create({},{noData:{get:function(){return u},set:function(t){u=t}},defaultState:{get:function(){return l},set:function(t){l=t}},showTooltipPercent:{get:function(){return s},set:function(t){s=t}},color:{get:function(){return a},set:function(t){a=t,e.color(a)}},duration:{get:function(){return c},set:function(t){c=t,f.reset(c),e.duration(c)}},margin:{get:function(){return r},set:function(t){r.top=void 0!==t.top?t.top:r.top,r.right=void 0!==t.right?t.right:r.right,r.bottom=void 0!==t.bottom?t.bottom:r.bottom,r.left=void 0!==t.left?t.left:r.left,e.margin(r)}}}),t.utils.inheritOptions(p,e),t.utils.initOptions(p),p},t.version="1.8.6"}(); +},{"d3":"tokj"}],"h15N":[function(require,module,exports) { +var global = arguments[3]; +var define; +var n,r=arguments[3];!function(){var t="object"==typeof self&&self.self===self&&self||"object"==typeof r&&r.global===r&&r||this||{},e=t._,u=Array.prototype,i=Object.prototype,o="undefined"!=typeof Symbol?Symbol.prototype:null,a=u.push,c=u.slice,l=i.toString,f=i.hasOwnProperty,s=Array.isArray,p=Object.keys,v=Object.create,h=function(){},y=function(n){return n instanceof y?n:this instanceof y?void(this._wrapped=n):new y(n)};"undefined"==typeof exports||exports.nodeType?t._=y:("undefined"!=typeof module&&!module.nodeType&&module.exports&&(exports=module.exports=y),exports._=y),y.VERSION="1.9.1";var d,g=function(n,r,t){if(void 0===r)return n;switch(null==t?3:t){case 1:return function(t){return n.call(r,t)};case 3:return function(t,e,u){return n.call(r,t,e,u)};case 4:return function(t,e,u,i){return n.call(r,t,e,u,i)}}return function(){return n.apply(r,arguments)}},m=function(n,r,t){return y.iteratee!==d?y.iteratee(n,r):null==n?y.identity:y.isFunction(n)?g(n,r,t):y.isObject(n)&&!y.isArray(n)?y.matcher(n):y.property(n)};y.iteratee=d=function(n,r){return m(n,r,1/0)};var b=function(n,r){return r=null==r?n.length-1:+r,function(){for(var t=Math.max(arguments.length-r,0),e=Array(t),u=0;u=0&&r<=w};y.each=y.forEach=function(n,r,t){var e,u;if(r=g(r,t),k(n))for(e=0,u=n.length;e=3;return function(r,t,e,u){var i=!k(r)&&y.keys(r),o=(i||r).length,a=n>0?0:o-1;for(u||(e=r[i?i[a]:a],a+=n);a>=0&&a=0},y.invoke=b(function(n,r,t){var e,u;return y.isFunction(r)?u=r:y.isArray(r)&&(e=r.slice(0,-1),r=r[r.length-1]),y.map(n,function(n){var i=u;if(!i){if(e&&e.length&&(n=A(n,e)),null==n)return;i=n[r]}return null==i?i:i.apply(n,t)})}),y.pluck=function(n,r){return y.map(n,y.property(r))},y.where=function(n,r){return y.filter(n,y.matcher(r))},y.findWhere=function(n,r){return y.find(n,y.matcher(r))},y.max=function(n,r,t){var e,u,i=-1/0,o=-1/0;if(null==r||"number"==typeof r&&"object"!=typeof n[0]&&null!=n)for(var a=0,c=(n=k(n)?n:y.values(n)).length;ai&&(i=e);else r=m(r,t),y.each(n,function(n,t,e){((u=r(n,t,e))>o||u===-1/0&&i===-1/0)&&(i=n,o=u)});return i},y.min=function(n,r,t){var e,u,i=1/0,o=1/0;if(null==r||"number"==typeof r&&"object"!=typeof n[0]&&null!=n)for(var a=0,c=(n=k(n)?n:y.values(n)).length;ae||void 0===t)return 1;if(t0?0:u-1;i>=0&&i0?o=i>=0?i:Math.max(i+a,o):a=i>=0?Math.min(i+1,a):i+a+1;else if(t&&i&&a)return e[i=t(e,u)]===u?i:-1;if(u!=u)return(i=r(c.call(e,o,a),y.isNaN))>=0?i+o:-1;for(i=n>0?o:a-1;i>=0&&ir?(e&&(clearTimeout(e),e=null),a=l,o=n.apply(u,i),e||(u=i=null)):e||!1===t.trailing||(e=setTimeout(c,f)),o};return l.cancel=function(){clearTimeout(e),a=0,e=u=i=null},l},y.debounce=function(n,r,t){var e,u,i=function(r,t){e=null,t&&(u=n.apply(r,t))},o=b(function(o){if(e&&clearTimeout(e),t){var a=!e;e=setTimeout(i,r),a&&(u=n.apply(this,o))}else e=y.delay(i,r,this,o);return u});return o.cancel=function(){clearTimeout(e),e=null},o},y.wrap=function(n,r){return y.partial(r,n)},y.negate=function(n){return function(){return!n.apply(this,arguments)}},y.compose=function(){var n=arguments,r=n.length-1;return function(){for(var t=r,e=n[r].apply(this,arguments);t--;)e=n[t].call(this,e);return e}},y.after=function(n,r){return function(){if(--n<1)return r.apply(this,arguments)}},y.before=function(n,r){var t;return function(){return--n>0&&(t=r.apply(this,arguments)),n<=1&&(r=null),t}},y.once=y.partial(y.before,2),y.restArguments=b;var B=!{toString:null}.propertyIsEnumerable("toString"),R=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"],q=function(n,r){var t=R.length,e=n.constructor,u=y.isFunction(e)&&e.prototype||i,o="constructor";for(_(n,o)&&!y.contains(r,o)&&r.push(o);t--;)(o=R[t])in n&&n[o]!==u[o]&&!y.contains(r,o)&&r.push(o)};y.keys=function(n){if(!y.isObject(n))return[];if(p)return p(n);var r=[];for(var t in n)_(n,t)&&r.push(t);return B&&q(n,r),r},y.allKeys=function(n){if(!y.isObject(n))return[];var r=[];for(var t in n)r.push(t);return B&&q(n,r),r},y.values=function(n){for(var r=y.keys(n),t=r.length,e=Array(t),u=0;u1&&(e=g(e,r[1])),r=y.allKeys(n)):(e=L,r=E(r,!1,!1),n=Object(n));for(var u=0,i=r.length;u1&&(t=r[1])):(r=y.map(E(r,!1,!1),String),e=function(n,t){return!y.contains(r,t)}),y.pick(n,e,t)}),y.defaults=K(y.allKeys,!0),y.create=function(n,r){var t=j(n);return r&&y.extendOwn(t,r),t},y.clone=function(n){return y.isObject(n)?y.isArray(n)?n.slice():y.extend({},n):n},y.tap=function(n,r){return r(n),n},y.isMatch=function(n,r){var t=y.keys(r),e=t.length;if(null==n)return!e;for(var u=Object(n),i=0;i":">",'"':""","'":"'","`":"`"},C=y.invert(W),J=function(n){var r=function(r){return n[r]},t="(?:"+y.keys(n).join("|")+")",e=RegExp(t),u=RegExp(t,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,r):n}};y.escape=J(W),y.unescape=J(C),y.result=function(n,r,t){y.isArray(r)||(r=[r]);var e=r.length;if(!e)return y.isFunction(t)?t.call(n):t;for(var u=0;u/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var V=/(.)^/,$={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},G=/\\|'|\r|\n|\u2028|\u2029/g,H=function(n){return"\\"+$[n]};y.template=function(n,r,t){!r&&t&&(r=t),r=y.defaults({},r,y.templateSettings);var e,u=RegExp([(r.escape||V).source,(r.interpolate||V).source,(r.evaluate||V).source].join("|")+"|$","g"),i=0,o="__p+='";n.replace(u,function(r,t,e,u,a){return o+=n.slice(i,a).replace(G,H),i=a+r.length,t?o+="'+\n((__t=("+t+"))==null?'':_.escape(__t))+\n'":e?o+="'+\n((__t=("+e+"))==null?'':__t)+\n'":u&&(o+="';\n"+u+"\n__p+='"),r}),o+="';\n",r.variable||(o="with(obj||{}){\n"+o+"}\n"),o="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+o+"return __p;\n";try{e=new Function(r.variable||"obj","_",o)}catch(l){throw l.source=o,l}var a=function(n){return e.call(this,n,y)},c=r.variable||"obj";return a.source="function("+c+"){\n"+o+"}",a},y.chain=function(n){var r=y(n);return r._chain=!0,r};var Q=function(n,r){return n._chain?y(r).chain():r};y.mixin=function(n){return y.each(y.functions(n),function(r){var t=y[r]=n[r];y.prototype[r]=function(){var n=[this._wrapped];return a.apply(n,arguments),Q(this,t.apply(y,n))}}),y},y.mixin(y),y.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var r=u[n];y.prototype[n]=function(){var t=this._wrapped;return r.apply(t,arguments),"shift"!==n&&"splice"!==n||0!==t.length||delete t[0],Q(this,t)}}),y.each(["concat","join","slice"],function(n){var r=u[n];y.prototype[n]=function(){return Q(this,r.apply(this._wrapped,arguments))}}),y.prototype.value=function(){return this._wrapped},y.prototype.valueOf=y.prototype.toJSON=y.prototype.value,y.prototype.toString=function(){return String(this._wrapped)},"function"==typeof n&&n.amd&&n("underscore",[],function(){return y})}(); +},{}],"pBGv":[function(require,module,exports) { -var t,e,n=module.exports={};function r(){throw new Error("setTimeout has not been defined")}function o(){throw new Error("clearTimeout has not been defined")}function i(e){if(t===setTimeout)return setTimeout(e,0);if((t===r||!t)&&setTimeout)return t=setTimeout,setTimeout(e,0);try{return t(e,0)}catch(n){try{return t.call(null,e,0)}catch(n){return t.call(this,e,0)}}}function u(t){if(e===clearTimeout)return clearTimeout(t);if((e===o||!e)&&clearTimeout)return e=clearTimeout,clearTimeout(t);try{return e(t)}catch(n){try{return e.call(null,t)}catch(n){return e.call(this,t)}}}!function(){try{t="function"==typeof setTimeout?setTimeout:r}catch(e){t=r}try{e="function"==typeof clearTimeout?clearTimeout:o}catch(t){e=o}}();var c,s=[],l=!1,a=-1;function f(){l&&c&&(l=!1,c.length?s=c.concat(s):a=-1,s.length&&h())}function h(){if(!l){var t=i(f);l=!0;for(var e=s.length;e;){for(c=s,s=[];++a1)for(var n=1;n1)for(var n=1;n0&&t-1 in e)}m.fn=m.prototype={jquery:v,constructor:m,length:0,toArray:function(){return o.call(this)},get:function(e){return null==e?o.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=m.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return m.each(this,e)},map:function(e){return this.pushStack(m.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(o.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n+~]|"+P+")"+P+"*"),z=new RegExp("="+P+"*([^\\]'\"]*?)"+P+"*\\]","g"),X=new RegExp(I),U=new RegExp("^"+R+"$"),V={ID:new RegExp("^#("+R+")"),CLASS:new RegExp("^\\.("+R+")"),TAG:new RegExp("^("+R+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+I),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:new RegExp("^(?:"+O+")$","i"),needsContext:new RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},G=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Q=/^[^{]+\{\s*\[native \w/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,K=/[+~]/,Z=new RegExp("\\\\([\\da-f]{1,6}"+P+"?|("+P+")|.)","ig"),ee=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},te=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ne=function(e,t){return t?"\0"===e?"�":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},re=function(){p()},ie=ye(function(e){return!0===e.disabled&&("form"in e||"label"in e)},{dir:"parentNode",next:"legend"});try{L.apply(j=H.call(w.childNodes),w.childNodes),j[w.childNodes.length].nodeType}catch(e){L={apply:j.length?function(e,t){q.apply(e,H.call(t))}:function(e,t){for(var n=e.length,r=0;e[n++]=t[r++];);e.length=n-1}}}function oe(e,t,r,i){var o,s,l,c,f,h,m,y=t&&t.ownerDocument,T=t?t.nodeType:9;if(r=r||[],"string"!=typeof e||!e||1!==T&&9!==T&&11!==T)return r;if(!i&&((t?t.ownerDocument||t:w)!==d&&p(t),t=t||d,g)){if(11!==T&&(f=J.exec(e)))if(o=f[1]){if(9===T){if(!(l=t.getElementById(o)))return r;if(l.id===o)return r.push(l),r}else if(y&&(l=y.getElementById(o))&&x(t,l)&&l.id===o)return r.push(l),r}else{if(f[2])return L.apply(r,t.getElementsByTagName(e)),r;if((o=f[3])&&n.getElementsByClassName&&t.getElementsByClassName)return L.apply(r,t.getElementsByClassName(o)),r}if(n.qsa&&!S[e+" "]&&(!v||!v.test(e))){if(1!==T)y=t,m=e;else if("object"!==t.nodeName.toLowerCase()){for((c=t.getAttribute("id"))?c=c.replace(te,ne):t.setAttribute("id",c=b),s=(h=a(e)).length;s--;)h[s]="#"+c+" "+me(h[s]);m=h.join(","),y=K.test(e)&&ge(t.parentNode)||t}if(m)try{return L.apply(r,y.querySelectorAll(m)),r}catch(e){}finally{c===b&&t.removeAttribute("id")}}}return u(e.replace($,"$1"),t,r,i)}function ae(){var e=[];return function t(n,i){return e.push(n+" ")>r.cacheLength&&delete t[e.shift()],t[n+" "]=i}}function se(e){return e[b]=!0,e}function ue(e){var t=d.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function le(e,t){for(var n=e.split("|"),i=n.length;i--;)r.attrHandle[n[i]]=t}function ce(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)for(;n=n.nextSibling;)if(n===t)return-1;return e?1:-1}function fe(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}function pe(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function de(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&ie(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function he(e){return se(function(t){return t=+t,se(function(n,r){for(var i,o=e([],n.length,t),a=o.length;a--;)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function ge(e){return e&&void 0!==e.getElementsByTagName&&e}n=oe.support={},o=oe.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},p=oe.setDocument=function(e){var t,i,a=e?e.ownerDocument||e:w;return a!==d&&9===a.nodeType&&a.documentElement?(h=(d=a).documentElement,g=!o(d),w!==d&&(i=d.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",re,!1):i.attachEvent&&i.attachEvent("onunload",re)),n.attributes=ue(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ue(function(e){return e.appendChild(d.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=Q.test(d.getElementsByClassName),n.getById=ue(function(e){return h.appendChild(e).id=b,!d.getElementsByName||!d.getElementsByName(b).length}),n.getById?(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){return e.getAttribute("id")===t}},r.find.ID=function(e,t){if(void 0!==t.getElementById&&g){var n=t.getElementById(e);return n?[n]:[]}}):(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){var n=void 0!==e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}},r.find.ID=function(e,t){if(void 0!==t.getElementById&&g){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];for(i=t.getElementsByName(e),r=0;o=i[r++];)if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),r.find.TAG=n.getElementsByTagName?function(e,t){return void 0!==t.getElementsByTagName?t.getElementsByTagName(e):n.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){for(;n=o[i++];)1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=n.getElementsByClassName&&function(e,t){if(void 0!==t.getElementsByClassName&&g)return t.getElementsByClassName(e)},m=[],v=[],(n.qsa=Q.test(d.querySelectorAll))&&(ue(function(e){h.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+P+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+P+"*(?:value|"+O+")"),e.querySelectorAll("[id~="+b+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+b+"+*").length||v.push(".#.+[+~]")}),ue(function(e){e.innerHTML="";var t=d.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+P+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),h.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(n.matchesSelector=Q.test(y=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&ue(function(e){n.disconnectedMatch=y.call(e,"*"),y.call(e,"[s!='']:x"),m.push("!=",I)}),v=v.length&&new RegExp(v.join("|")),m=m.length&&new RegExp(m.join("|")),t=Q.test(h.compareDocumentPosition),x=t||Q.test(h.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},N=t?function(e,t){if(e===t)return f=!0,0;var r=!e.compareDocumentPosition-!t.compareDocumentPosition;return r||(1&(r=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!n.sortDetached&&t.compareDocumentPosition(e)===r?e===d||e.ownerDocument===w&&x(w,e)?-1:t===d||t.ownerDocument===w&&x(w,t)?1:c?F(c,e)-F(c,t):0:4&r?-1:1)}:function(e,t){if(e===t)return f=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===d?-1:t===d?1:i?-1:o?1:c?F(c,e)-F(c,t):0;if(i===o)return ce(e,t);for(n=e;n=n.parentNode;)a.unshift(n);for(n=t;n=n.parentNode;)s.unshift(n);for(;a[r]===s[r];)r++;return r?ce(a[r],s[r]):a[r]===w?-1:s[r]===w?1:0},d):d},oe.matches=function(e,t){return oe(e,null,null,t)},oe.matchesSelector=function(e,t){if((e.ownerDocument||e)!==d&&p(e),t=t.replace(z,"='$1']"),n.matchesSelector&&g&&!S[t+" "]&&(!m||!m.test(t))&&(!v||!v.test(t)))try{var r=y.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(e){}return oe(t,d,null,[e]).length>0},oe.contains=function(e,t){return(e.ownerDocument||e)!==d&&p(e),x(e,t)},oe.attr=function(e,t){(e.ownerDocument||e)!==d&&p(e);var i=r.attrHandle[t.toLowerCase()],o=i&&D.call(r.attrHandle,t.toLowerCase())?i(e,t,!g):void 0;return void 0!==o?o:n.attributes||!g?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null},oe.escape=function(e){return(e+"").replace(te,ne)},oe.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},oe.uniqueSort=function(e){var t,r=[],i=0,o=0;if(f=!n.detectDuplicates,c=!n.sortStable&&e.slice(0),e.sort(N),f){for(;t=e[o++];)t===e[o]&&(i=r.push(o));for(;i--;)e.splice(r[i],1)}return c=null,e},i=oe.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else for(;t=e[r++];)n+=i(t);return n},(r=oe.selectors={cacheLength:50,createPseudo:se,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(Z,ee),e[3]=(e[3]||e[4]||e[5]||"").replace(Z,ee),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||oe.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&oe.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return V.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=a(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(Z,ee).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=E[e+" "];return t||(t=new RegExp("(^|"+P+")"+e+"("+P+"|$)"))&&E(e,function(e){return t.test("string"==typeof e.className&&e.className||void 0!==e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=oe.attr(r,e);return null==i?"!="===t:!t||(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i.replace(W," ")+" ").indexOf(n)>-1:"|="===t&&(i===n||i.slice(0,n.length+1)===n+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,d,h,g=o!==a?"nextSibling":"previousSibling",v=t.parentNode,m=s&&t.nodeName.toLowerCase(),y=!u&&!s,x=!1;if(v){if(o){for(;g;){for(p=t;p=p[g];)if(s?p.nodeName.toLowerCase()===m:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?v.firstChild:v.lastChild],a&&y){for(x=(d=(l=(c=(f=(p=v)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1])&&l[2],p=d&&v.childNodes[d];p=++d&&p&&p[g]||(x=d=0)||h.pop();)if(1===p.nodeType&&++x&&p===t){c[e]=[T,d,x];break}}else if(y&&(x=d=(l=(c=(f=(p=t)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1]),!1===x)for(;(p=++d&&p&&p[g]||(x=d=0)||h.pop())&&((s?p.nodeName.toLowerCase()!==m:1!==p.nodeType)||!++x||(y&&((c=(f=p[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]=[T,x]),p!==t)););return(x-=i)===r||x%r==0&&x/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||oe.error("unsupported pseudo: "+e);return i[b]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?se(function(e,n){for(var r,o=i(e,t),a=o.length;a--;)e[r=F(e,o[a])]=!(n[r]=o[a])}):function(e){return i(e,0,n)}):i}},pseudos:{not:se(function(e){var t=[],n=[],r=s(e.replace($,"$1"));return r[b]?se(function(e,t,n,i){for(var o,a=r(e,null,i,[]),s=e.length;s--;)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),t[0]=null,!n.pop()}}),has:se(function(e){return function(t){return oe(e,t).length>0}}),contains:se(function(e){return e=e.replace(Z,ee),function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:se(function(e){return U.test(e||"")||oe.error("unsupported lang: "+e),e=e.replace(Z,ee).toLowerCase(),function(t){var n;do{if(n=g?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===h},focus:function(e){return e===d.activeElement&&(!d.hasFocus||d.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:de(!1),disabled:de(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return Y.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:he(function(){return[0]}),last:he(function(e,t){return[t-1]}),eq:he(function(e,t,n){return[n<0?n+t:n]}),even:he(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:he(function(e,t,n){for(var r=n<0?n+t:n;++r1?function(t,n,r){for(var i=e.length;i--;)if(!e[i](t,n,r))return!1;return!0}:e[0]}function be(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;s-1&&(o[l]=!(a[l]=f))}}else m=be(m===a?m.splice(h,m.length):m),i?i(null,a,m,u):L.apply(a,m)})}function Te(e){for(var t,n,i,o=e.length,a=r.relative[e[0].type],s=a||r.relative[" "],u=a?1:0,c=ye(function(e){return e===t},s,!0),f=ye(function(e){return F(t,e)>-1},s,!0),p=[function(e,n,r){var i=!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):f(e,n,r));return t=null,i}];u1&&xe(p),u>1&&me(e.slice(0,u-1).concat({value:" "===e[u-2].type?"*":""})).replace($,"$1"),n,u0,u=i.length>0,c=function(e,t,n,a,c){var f,h,v,m=0,y="0",x=e&&[],b=[],w=l,C=e||u&&r.find.TAG("*",c),E=T+=null==w?1:Math.random()||.1,k=C.length;for(c&&(l=t===d||t||c);y!==k&&null!=(f=C[y]);y++){if(u&&f){for(h=0,t||f.ownerDocument===d||(p(f),n=!g);v=i[h++];)if(v(f,t||d,n)){a.push(f);break}c&&(T=E)}s&&((f=!v&&f)&&m--,e&&x.push(f))}if(m+=y,s&&y!==m){for(h=0;v=o[h++];)v(x,b,t,n);if(e){if(m>0)for(;y--;)x[y]||b[y]||(b[y]=A.call(a));b=be(b)}L.apply(a,b),c&&!e&&b.length>0&&m+o.length>1&&oe.uniqueSort(a)}return c&&(T=E,l=w),x},s?se(c):c))).selector=e}return v},u=oe.select=function(e,t,n,i){var o,u,l,c,f,p="function"==typeof e&&e,d=!i&&a(e=p.selector||e);if(n=n||[],1===d.length){if((u=d[0]=d[0].slice(0)).length>2&&"ID"===(l=u[0]).type&&9===t.nodeType&&g&&r.relative[u[1].type]){if(!(t=(r.find.ID(l.matches[0].replace(Z,ee),t)||[])[0]))return n;p&&(t=t.parentNode),e=e.slice(u.shift().value.length)}for(o=V.needsContext.test(e)?0:u.length;o--&&(l=u[o],!r.relative[c=l.type]);)if((f=r.find[c])&&(i=f(l.matches[0].replace(Z,ee),K.test(u[0].type)&&ge(t.parentNode)||t))){if(u.splice(o,1),!(e=i.length&&me(u)))return L.apply(n,i),n;break}}return(p||s(e,d))(i,t,!g,n,!t||K.test(e)&&ge(t.parentNode)||t),n},n.sortStable=b.split("").sort(N).join("")===b,n.detectDuplicates=!!f,p(),n.sortDetached=ue(function(e){return 1&e.compareDocumentPosition(d.createElement("fieldset"))}),ue(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||le("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&ue(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||le("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ue(function(e){return null==e.getAttribute("disabled")})||le(O,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),oe}(e);m.find=C,m.expr=C.selectors,m.expr[":"]=m.expr.pseudos,m.uniqueSort=m.unique=C.uniqueSort,m.text=C.getText,m.isXMLDoc=C.isXML,m.contains=C.contains,m.escapeSelector=C.escape;var E=function(e,t,n){for(var r=[],i=void 0!==n;(e=e[t])&&9!==e.nodeType;)if(1===e.nodeType){if(i&&m(e).is(n))break;r.push(e)}return r},k=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},S=m.expr.match.needsContext;function N(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var D=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,j=/^.[^:#\[\.,]*$/;function A(e,t,n){return m.isFunction(t)?m.grep(e,function(e,r){return!!t.call(e,r,e)!==n}):t.nodeType?m.grep(e,function(e){return e===t!==n}):"string"!=typeof t?m.grep(e,function(e){return u.call(t,e)>-1!==n}):j.test(t)?m.filter(t,e,n):(t=m.filter(t,e),m.grep(e,function(e){return u.call(t,e)>-1!==n&&1===e.nodeType}))}m.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?m.find.matchesSelector(r,e)?[r]:[]:m.find.matches(e,m.grep(t,function(e){return 1===e.nodeType}))},m.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(m(e).filter(function(){for(t=0;t1?m.uniqueSort(n):n},filter:function(e){return this.pushStack(A(this,e||[],!1))},not:function(e){return this.pushStack(A(this,e||[],!0))},is:function(e){return!!A(this,"string"==typeof e&&S.test(e)?m(e):e||[],!1).length}});var q,L=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(m.fn.init=function(e,t,n){var i,o;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(i="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:L.exec(e))||!i[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(i[1]){if(t=t instanceof m?t[0]:t,m.merge(this,m.parseHTML(i[1],t&&t.nodeType?t.ownerDocument||t:r,!0)),D.test(i[1])&&m.isPlainObject(t))for(i in t)m.isFunction(this[i])?this[i](t[i]):this.attr(i,t[i]);return this}return(o=r.getElementById(i[2]))&&(this[0]=o,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m.isFunction(e)?void 0!==n.ready?n.ready(e):e(m):m.makeArray(e,this)}).prototype=m.fn,q=m(r);var H=/^(?:parents|prev(?:Until|All))/,F={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){for(;(e=e[t])&&1!==e.nodeType;);return e}m.fn.extend({has:function(e){var t=m(e,this),n=t.length;return this.filter(function(){for(var e=0;e-1:1===n.nodeType&&m.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?m.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?u.call(m(e),this[0]):u.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(m.uniqueSort(m.merge(this.get(),m(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),m.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return E(e,"parentNode")},parentsUntil:function(e,t,n){return E(e,"parentNode",n)},next:function(e){return O(e,"nextSibling")},prev:function(e){return O(e,"previousSibling")},nextAll:function(e){return E(e,"nextSibling")},prevAll:function(e){return E(e,"previousSibling")},nextUntil:function(e,t,n){return E(e,"nextSibling",n)},prevUntil:function(e,t,n){return E(e,"previousSibling",n)},siblings:function(e){return k((e.parentNode||{}).firstChild,e)},children:function(e){return k(e.firstChild)},contents:function(e){return N(e,"iframe")?e.contentDocument:(N(e,"template")&&(e=e.content||e),m.merge([],e.childNodes))}},function(e,t){m.fn[e]=function(n,r){var i=m.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=m.filter(r,i)),this.length>1&&(F[e]||m.uniqueSort(i),H.test(e)&&i.reverse()),this.pushStack(i)}});var P=/[^\x20\t\r\n\f]+/g;function R(e){return e}function M(e){throw e}function I(e,t,n,r){var i;try{e&&m.isFunction(i=e.promise)?i.call(e).done(t).fail(n):e&&m.isFunction(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}m.Callbacks=function(e){var t,n;e="string"==typeof e?(t=e,n={},m.each(t.match(P)||[],function(e,t){n[t]=!0}),n):m.extend({},e);var r,i,o,a,s=[],u=[],l=-1,c=function(){for(a=a||e.once,o=r=!0;u.length;l=-1)for(i=u.shift();++l-1;)s.splice(n,1),n<=l&&l--}),this},has:function(e){return e?m.inArray(e,s)>-1:s.length>0},empty:function(){return s&&(s=[]),this},disable:function(){return a=u=[],s=i="",this},disabled:function(){return!s},lock:function(){return a=u=[],i||r||(s=i=""),this},locked:function(){return!!a},fireWith:function(e,t){return a||(t=[e,(t=t||[]).slice?t.slice():t],u.push(t),r||c()),this},fire:function(){return f.fireWith(this,arguments),this},fired:function(){return!!o}};return f},m.extend({Deferred:function(t){var n=[["notify","progress",m.Callbacks("memory"),m.Callbacks("memory"),2],["resolve","done",m.Callbacks("once memory"),m.Callbacks("once memory"),0,"resolved"],["reject","fail",m.Callbacks("once memory"),m.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},catch:function(e){return i.then(null,e)},pipe:function(){var e=arguments;return m.Deferred(function(t){m.each(n,function(n,r){var i=m.isFunction(e[r[4]])&&e[r[4]];o[r[1]](function(){var e=i&&i.apply(this,arguments);e&&m.isFunction(e.promise)?e.promise().progress(t.notify).done(t.resolve).fail(t.reject):t[r[0]+"With"](this,i?[e]:arguments)})}),e=null}).promise()},then:function(t,r,i){var o=0;function a(t,n,r,i){return function(){var s=this,u=arguments,l=function(){var e,l;if(!(t=o&&(r!==M&&(s=void 0,u=[e]),n.rejectWith(s,u))}};t?c():(m.Deferred.getStackHook&&(c.stackTrace=m.Deferred.getStackHook()),e.setTimeout(c))}}return m.Deferred(function(e){n[0][3].add(a(0,e,m.isFunction(i)?i:R,e.notifyWith)),n[1][3].add(a(0,e,m.isFunction(t)?t:R)),n[2][3].add(a(0,e,m.isFunction(r)?r:M))}).promise()},promise:function(e){return null!=e?m.extend(e,i):i}},o={};return m.each(n,function(e,t){var a=t[2],s=t[5];i[t[1]]=a.add,s&&a.add(function(){r=s},n[3-e][2].disable,n[0][2].lock),a.add(t[3].fire),o[t[0]]=function(){return o[t[0]+"With"](this===o?void 0:this,arguments),this},o[t[0]+"With"]=a.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(e){var t=arguments.length,n=t,r=Array(n),i=o.call(arguments),a=m.Deferred(),s=function(e){return function(n){r[e]=this,i[e]=arguments.length>1?o.call(arguments):n,--t||a.resolveWith(r,i)}};if(t<=1&&(I(e,a.done(s(n)).resolve,a.reject,!t),"pending"===a.state()||m.isFunction(i[n]&&i[n].then)))return a.then();for(;n--;)I(i[n],s(n),a.reject);return a.promise()}});var W=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;m.Deferred.exceptionHook=function(t,n){e.console&&e.console.warn&&t&&W.test(t.name)&&e.console.warn("jQuery.Deferred exception: "+t.message,t.stack,n)},m.readyException=function(t){e.setTimeout(function(){throw t})};var $=m.Deferred();function B(){r.removeEventListener("DOMContentLoaded",B),e.removeEventListener("load",B),m.ready()}m.fn.ready=function(e){return $.then(e).catch(function(e){m.readyException(e)}),this},m.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--m.readyWait:m.isReady)||(m.isReady=!0,!0!==e&&--m.readyWait>0||$.resolveWith(r,[m]))}}),m.ready.then=$.then,"complete"===r.readyState||"loading"!==r.readyState&&!r.documentElement.doScroll?e.setTimeout(m.ready):(r.addEventListener("DOMContentLoaded",B),e.addEventListener("load",B));var _=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===m.type(n)){i=!0;for(s in n)_(e,t,s,n[s],!0,o,a)}else if(void 0!==r&&(i=!0,m.isFunction(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(m(e),n)})),t))for(;s1,null,!0)},removeData:function(e){return this.each(function(){V.remove(this,e)})}}),m.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=U.get(e,t),n&&(!r||Array.isArray(n)?r=U.access(e,t,m.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=m.queue(e,t),r=n.length,i=n.shift(),o=m._queueHooks(e,t);"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,function(){m.dequeue(e,t)},o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return U.get(e,n)||U.access(e,n,{empty:m.Callbacks("once memory").add(function(){U.remove(e,[t+"queue",n])})})}}),m.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length\x20\t\r\n\f]+)/i,se=/^$|\/(?:java|ecma)script/i,ue={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function le(e,t){var n;return n=void 0!==e.getElementsByTagName?e.getElementsByTagName(t||"*"):void 0!==e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&N(e,t)?m.merge([e],n):n}function ce(e,t){for(var n=0,r=e.length;n-1)i&&i.push(o);else if(l=m.contains(o.ownerDocument,o),a=le(f.appendChild(o),"script"),l&&ce(a),n)for(c=0;o=a[c++];)se.test(o.type||"")&&n.push(o);return f}fe=r.createDocumentFragment().appendChild(r.createElement("div")),(pe=r.createElement("input")).setAttribute("type","radio"),pe.setAttribute("checked","checked"),pe.setAttribute("name","t"),fe.appendChild(pe),h.checkClone=fe.cloneNode(!0).cloneNode(!0).lastChild.checked,fe.innerHTML="",h.noCloneChecked=!!fe.cloneNode(!0).lastChild.defaultValue;var ge=r.documentElement,ve=/^key/,me=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ye=/^([^.]*)(?:\.(.+)|)/;function xe(){return!0}function be(){return!1}function we(){try{return r.activeElement}catch(e){}}function Te(e,t,n,r,i,o){var a,s;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(s in t)Te(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=be;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return m().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=m.guid++)),e.each(function(){m.event.add(this,t,i,r,n)})}m.event={global:{},add:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=U.get(e);if(v)for(n.handler&&(n=(o=n).handler,i=o.selector),i&&m.find.matchesSelector(ge,i),n.guid||(n.guid=m.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(t){return void 0!==m&&m.event.triggered!==t.type?m.event.dispatch.apply(e,arguments):void 0}),l=(t=(t||"").match(P)||[""]).length;l--;)d=g=(s=ye.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=m.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=m.event.special[d]||{},c=m.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&m.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(e,r,h,a)||e.addEventListener&&e.addEventListener(d,a)),f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),m.event.global[d]=!0)},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=U.hasData(e)&&U.get(e);if(v&&(u=v.events)){for(l=(t=(t||"").match(P)||[""]).length;l--;)if(d=g=(s=ye.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){for(f=m.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;o--;)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||m.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)m.event.remove(e,d+t[l],n,r,!0);m.isEmptyObject(u)&&U.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=m.event.fix(e),u=new Array(arguments.length),l=(U.get(this,"events")||{})[s.type]||[],c=m.event.special[s.type]||{};for(u[0]=s,t=1;t=1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n-1:m.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u\x20\t\r\n\f]*)[^>]*)\/>/gi,Ee=/\s*$/g;function De(e,t){return N(e,"table")&&N(11!==t.nodeType?t:t.firstChild,"tr")&&m(">tbody",e)[0]||e}function je(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Ae(e){var t=Se.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function qe(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(U.hasData(e)&&(o=U.access(e),a=U.set(t,o),l=o.events)){delete a.handle,a.events={};for(i in l)for(n=0,r=l[i].length;n1&&"string"==typeof v&&!h.checkClone&&ke.test(v))return e.each(function(i){var o=e.eq(i);y&&(t[0]=v.call(this,i,o.html())),Le(o,t,n,r)});if(p&&(o=(i=he(t,e[0].ownerDocument,!1,e,r)).firstChild,1===i.childNodes.length&&(i=o),o||r)){for(u=(s=m.map(le(i,"script"),je)).length;f")},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=m.contains(e.ownerDocument,e);if(!(h.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||m.isXMLDoc(e)))for(a=le(c),r=0,i=(o=le(e)).length;r0&&ce(a,!f&&le(e,"script")),c},cleanData:function(e){for(var t,n,r,i=m.event.special,o=0;void 0!==(n=e[o]);o++)if(z(n)){if(t=n[U.expando]){if(t.events)for(r in t.events)i[r]?m.event.remove(n,r):m.removeEvent(n,r,t.handle);n[U.expando]=void 0}n[V.expando]&&(n[V.expando]=void 0)}}}),m.fn.extend({detach:function(e){return He(this,e,!0)},remove:function(e){return He(this,e)},text:function(e){return _(this,function(e){return void 0===e?m.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Le(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||De(this,e).appendChild(e)})},prepend:function(){return Le(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=De(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Le(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Le(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(m.cleanData(le(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return m.clone(this,e,t)})},html:function(e){return _(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Ee.test(e)&&!ue[(ae.exec(e)||["",""])[1].toLowerCase()]){e=m.htmlPrefilter(e);try{for(;n1)}}),m.Tween=Ye,Ye.prototype={constructor:Ye,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||m.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(m.cssNumber[n]?"":"px")},cur:function(){var e=Ye.propHooks[this.prop];return e&&e.get?e.get(this):Ye.propHooks._default.get(this)},run:function(e){var t,n=Ye.propHooks[this.prop];return this.options.duration?this.pos=t=m.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):Ye.propHooks._default.set(this),this}},Ye.prototype.init.prototype=Ye.prototype,Ye.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=m.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){m.fx.step[e.prop]?m.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[m.cssProps[e.prop]]&&!m.cssHooks[e.prop]?e.elem[e.prop]=e.now:m.style(e.elem,e.prop,e.now+e.unit)}}},Ye.propHooks.scrollTop=Ye.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},m.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},m.fx=Ye.prototype.init,m.fx.step={};var Qe,Je,Ke,Ze,et=/^(?:toggle|show|hide)$/,tt=/queueHooks$/;function nt(){Je&&(!1===r.hidden&&e.requestAnimationFrame?e.requestAnimationFrame(nt):e.setTimeout(nt,m.fx.interval),m.fx.tick())}function rt(){return e.setTimeout(function(){Qe=void 0}),Qe=m.now()}function it(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=Z[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function ot(e,t,n){for(var r,i=(at.tweeners[t]||[]).concat(at.tweeners["*"]),o=0,a=i.length;o1)},removeAttr:function(e){return this.each(function(){m.removeAttr(this,e)})}}),m.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return void 0===e.getAttribute?m.prop(e,t,n):(1===o&&m.isXMLDoc(e)||(i=m.attrHooks[t.toLowerCase()]||(m.expr.match.bool.test(t)?st:void 0)),void 0!==n?null===n?void m.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=m.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!h.radioValue&&"radio"===t&&N(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(P);if(i&&1===e.nodeType)for(;n=i[r++];)e.removeAttribute(n)}}),st={set:function(e,t,n){return!1===t?m.removeAttr(e,n):e.setAttribute(n,n),n}},m.each(m.expr.match.bool.source.match(/\w+/g),function(e,t){var n=ut[t]||m.find.attr;ut[t]=function(e,t,r){var i,o,a=t.toLowerCase();return r||(o=ut[a],ut[a]=i,i=null!=n(e,t,r)?a:null,ut[a]=o),i}});var lt=/^(?:input|select|textarea|button)$/i,ct=/^(?:a|area)$/i;function ft(e){return(e.match(P)||[]).join(" ")}function pt(e){return e.getAttribute&&e.getAttribute("class")||""}m.fn.extend({prop:function(e,t){return _(this,m.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[m.propFix[e]||e]})}}),m.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&m.isXMLDoc(e)||(t=m.propFix[t]||t,i=m.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=m.find.attr(e,"tabindex");return t?parseInt(t,10):lt.test(e.nodeName)||ct.test(e.nodeName)&&e.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),h.optSelected||(m.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),m.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){m.propFix[this.toLowerCase()]=this}),m.fn.extend({addClass:function(e){var t,n,r,i,o,a,s,u=0;if(m.isFunction(e))return this.each(function(t){m(this).addClass(e.call(this,t,pt(this)))});if("string"==typeof e&&e)for(t=e.match(P)||[];n=this[u++];)if(i=pt(n),r=1===n.nodeType&&" "+ft(i)+" "){for(a=0;o=t[a++];)r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=ft(r))&&n.setAttribute("class",s)}return this},removeClass:function(e){var t,n,r,i,o,a,s,u=0;if(m.isFunction(e))return this.each(function(t){m(this).removeClass(e.call(this,t,pt(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof e&&e)for(t=e.match(P)||[];n=this[u++];)if(i=pt(n),r=1===n.nodeType&&" "+ft(i)+" "){for(a=0;o=t[a++];)for(;r.indexOf(" "+o+" ")>-1;)r=r.replace(" "+o+" "," ");i!==(s=ft(r))&&n.setAttribute("class",s)}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):m.isFunction(e)?this.each(function(n){m(this).toggleClass(e.call(this,n,pt(this),t),t)}):this.each(function(){var t,r,i,o;if("string"===n)for(r=0,i=m(this),o=e.match(P)||[];t=o[r++];)i.hasClass(t)?i.removeClass(t):i.addClass(t);else void 0!==e&&"boolean"!==n||((t=pt(this))&&U.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":U.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;for(t=" "+e+" ";n=this[r++];)if(1===n.nodeType&&(" "+ft(pt(n))+" ").indexOf(t)>-1)return!0;return!1}});var dt=/\r/g;m.fn.extend({val:function(e){var t,n,r,i=this[0];return arguments.length?(r=m.isFunction(e),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,m(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=m.map(i,function(e){return null==e?"":e+""})),(t=m.valHooks[this.type]||m.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))})):i?(t=m.valHooks[i.type]||m.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(dt,""):null==n?"":n:void 0}}),m.extend({valHooks:{option:{get:function(e){var t=m.find.attr(e,"value");return null!=t?t:ft(m.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),m.each(["radio","checkbox"],function(){m.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=m.inArray(m(e).val(),t)>-1}},h.checkOn||(m.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var ht=/^(?:focusinfocus|focusoutblur)$/;m.extend(m.event,{trigger:function(t,n,i,o){var a,s,u,l,c,p,d,h=[i||r],g=f.call(t,"type")?t.type:t,v=f.call(t,"namespace")?t.namespace.split("."):[];if(s=u=i=i||r,3!==i.nodeType&&8!==i.nodeType&&!ht.test(g+m.event.triggered)&&(g.indexOf(".")>-1&&(g=(v=g.split(".")).shift(),v.sort()),c=g.indexOf(":")<0&&"on"+g,(t=t[m.expando]?t:new m.Event(g,"object"==typeof t&&t)).isTrigger=o?2:3,t.namespace=v.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+v.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=i),n=null==n?[t]:m.makeArray(n,[t]),d=m.event.special[g]||{},o||!d.trigger||!1!==d.trigger.apply(i,n))){if(!o&&!d.noBubble&&!m.isWindow(i)){for(l=d.delegateType||g,ht.test(l+g)||(s=s.parentNode);s;s=s.parentNode)h.push(s),u=s;u===(i.ownerDocument||r)&&h.push(u.defaultView||u.parentWindow||e)}for(a=0;(s=h[a++])&&!t.isPropagationStopped();)t.type=a>1?l:d.bindType||g,(p=(U.get(s,"events")||{})[t.type]&&U.get(s,"handle"))&&p.apply(s,n),(p=c&&s[c])&&p.apply&&z(s)&&(t.result=p.apply(s,n),!1===t.result&&t.preventDefault());return t.type=g,o||t.isDefaultPrevented()||d._default&&!1!==d._default.apply(h.pop(),n)||!z(i)||c&&m.isFunction(i[g])&&!m.isWindow(i)&&((u=i[c])&&(i[c]=null),m.event.triggered=g,i[g](),m.event.triggered=void 0,u&&(i[c]=u)),t.result}},simulate:function(e,t,n){var r=m.extend(new m.Event,n,{type:e,isSimulated:!0});m.event.trigger(r,null,t)}}),m.fn.extend({trigger:function(e,t){return this.each(function(){m.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return m.event.trigger(e,t,n,!0)}}),m.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,t){m.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),m.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),h.focusin="onfocusin"in e,h.focusin||m.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){m.event.simulate(t,e.target,m.event.fix(e))};m.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=U.access(r,t);i||r.addEventListener(e,n,!0),U.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=U.access(r,t)-1;i?U.access(r,t,i):(r.removeEventListener(e,n,!0),U.remove(r,t))}}});var gt=e.location,vt=m.now(),mt=/\?/;m.parseXML=function(t){var n;if(!t||"string"!=typeof t)return null;try{n=(new e.DOMParser).parseFromString(t,"text/xml")}catch(e){n=void 0}return n&&!n.getElementsByTagName("parsererror").length||m.error("Invalid XML: "+t),n};var yt=/\[\]$/,xt=/\r?\n/g,bt=/^(?:submit|button|image|reset|file)$/i,wt=/^(?:input|select|textarea|keygen)/i;function Tt(e,t,n,r){var i;if(Array.isArray(t))m.each(t,function(t,i){n||yt.test(e)?r(e,i):Tt(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==m.type(t))r(e,t);else for(i in t)Tt(e+"["+i+"]",t[i],n,r)}m.param=function(e,t){var n,r=[],i=function(e,t){var n=m.isFunction(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(e)||e.jquery&&!m.isPlainObject(e))m.each(e,function(){i(this.name,this.value)});else for(n in e)Tt(n,e[n],t,i);return r.join("&")},m.fn.extend({serialize:function(){return m.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=m.prop(this,"elements");return e?m.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!m(this).is(":disabled")&&wt.test(this.nodeName)&&!bt.test(e)&&(this.checked||!oe.test(e))}).map(function(e,t){var n=m(this).val();return null==n?null:Array.isArray(n)?m.map(n,function(e){return{name:t.name,value:e.replace(xt,"\r\n")}}):{name:t.name,value:n.replace(xt,"\r\n")}}).get()}});var Ct=/%20/g,Et=/#.*$/,kt=/([?&])_=[^&]*/,St=/^(.*?):[ \t]*([^\r\n]*)$/gm,Nt=/^(?:GET|HEAD)$/,Dt=/^\/\//,jt={},At={},qt="*/".concat("*"),Lt=r.createElement("a");function Ht(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(P)||[];if(m.isFunction(n))for(;r=o[i++];)"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function Ft(e,t,n,r){var i={},o=e===At;function a(s){var u;return i[s]=!0,m.each(e[s]||[],function(e,s){var l=s(t,n,r);return"string"!=typeof l||o||i[l]?o?!(u=l):void 0:(t.dataTypes.unshift(l),a(l),!1)}),u}return a(t.dataTypes[0])||!i["*"]&&a("*")}function Ot(e,t){var n,r,i=m.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&m.extend(!0,e,r),e}Lt.href=gt.href,m.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:gt.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(gt.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":qt,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":m.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?Ot(Ot(e,m.ajaxSettings),t):Ot(m.ajaxSettings,e)},ajaxPrefilter:Ht(jt),ajaxTransport:Ht(At),ajax:function(t,n){"object"==typeof t&&(n=t,t=void 0),n=n||{};var i,o,a,s,u,l,c,f,p,d,h=m.ajaxSetup({},n),g=h.context||h,v=h.context&&(g.nodeType||g.jquery)?m(g):m.event,y=m.Deferred(),x=m.Callbacks("once memory"),b=h.statusCode||{},w={},T={},C="canceled",E={readyState:0,getResponseHeader:function(e){var t;if(c){if(!s)for(s={};t=St.exec(a);)s[t[1].toLowerCase()]=t[2];t=s[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return c?a:null},setRequestHeader:function(e,t){return null==c&&(e=T[e.toLowerCase()]=T[e.toLowerCase()]||e,w[e]=t),this},overrideMimeType:function(e){return null==c&&(h.mimeType=e),this},statusCode:function(e){var t;if(e)if(c)E.always(e[E.status]);else for(t in e)b[t]=[b[t],e[t]];return this},abort:function(e){var t=e||C;return i&&i.abort(t),k(0,t),this}};if(y.promise(E),h.url=((t||h.url||gt.href)+"").replace(Dt,gt.protocol+"//"),h.type=n.method||n.type||h.method||h.type,h.dataTypes=(h.dataType||"*").toLowerCase().match(P)||[""],null==h.crossDomain){l=r.createElement("a");try{l.href=h.url,l.href=l.href,h.crossDomain=Lt.protocol+"//"+Lt.host!=l.protocol+"//"+l.host}catch(e){h.crossDomain=!0}}if(h.data&&h.processData&&"string"!=typeof h.data&&(h.data=m.param(h.data,h.traditional)),Ft(jt,h,n,E),c)return E;(f=m.event&&h.global)&&0==m.active++&&m.event.trigger("ajaxStart"),h.type=h.type.toUpperCase(),h.hasContent=!Nt.test(h.type),o=h.url.replace(Et,""),h.hasContent?h.data&&h.processData&&0===(h.contentType||"").indexOf("application/x-www-form-urlencoded")&&(h.data=h.data.replace(Ct,"+")):(d=h.url.slice(o.length),h.data&&(o+=(mt.test(o)?"&":"?")+h.data,delete h.data),!1===h.cache&&(o=o.replace(kt,"$1"),d=(mt.test(o)?"&":"?")+"_="+vt+++d),h.url=o+d),h.ifModified&&(m.lastModified[o]&&E.setRequestHeader("If-Modified-Since",m.lastModified[o]),m.etag[o]&&E.setRequestHeader("If-None-Match",m.etag[o])),(h.data&&h.hasContent&&!1!==h.contentType||n.contentType)&&E.setRequestHeader("Content-Type",h.contentType),E.setRequestHeader("Accept",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+("*"!==h.dataTypes[0]?", "+qt+"; q=0.01":""):h.accepts["*"]);for(p in h.headers)E.setRequestHeader(p,h.headers[p]);if(h.beforeSend&&(!1===h.beforeSend.call(g,E,h)||c))return E.abort();if(C="abort",x.add(h.complete),E.done(h.success),E.fail(h.error),i=Ft(At,h,n,E)){if(E.readyState=1,f&&v.trigger("ajaxSend",[E,h]),c)return E;h.async&&h.timeout>0&&(u=e.setTimeout(function(){E.abort("timeout")},h.timeout));try{c=!1,i.send(w,k)}catch(e){if(c)throw e;k(-1,e)}}else k(-1,"No Transport");function k(t,n,r,s){var l,p,d,w,T,C=n;c||(c=!0,u&&e.clearTimeout(u),i=void 0,a=s||"",E.readyState=t>0?4:0,l=t>=200&&t<300||304===t,r&&(w=function(e,t,n){for(var r,i,o,a,s=e.contents,u=e.dataTypes;"*"===u[0];)u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}(h,E,r)),w=function(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];for(o=c.shift();o;)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if((s=i.split(" "))[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e.throws)t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}(h,w,E,l),l?(h.ifModified&&((T=E.getResponseHeader("Last-Modified"))&&(m.lastModified[o]=T),(T=E.getResponseHeader("etag"))&&(m.etag[o]=T)),204===t||"HEAD"===h.type?C="nocontent":304===t?C="notmodified":(C=w.state,p=w.data,l=!(d=w.error))):(d=C,!t&&C||(C="error",t<0&&(t=0))),E.status=t,E.statusText=(n||C)+"",l?y.resolveWith(g,[p,C,E]):y.rejectWith(g,[E,C,d]),E.statusCode(b),b=void 0,f&&v.trigger(l?"ajaxSuccess":"ajaxError",[E,h,l?p:d]),x.fireWith(g,[E,C]),f&&(v.trigger("ajaxComplete",[E,h]),--m.active||m.event.trigger("ajaxStop")))}return E},getJSON:function(e,t,n){return m.get(e,t,n,"json")},getScript:function(e,t){return m.get(e,void 0,t,"script")}}),m.each(["get","post"],function(e,t){m[t]=function(e,n,r,i){return m.isFunction(n)&&(i=i||r,r=n,n=void 0),m.ajax(m.extend({url:e,type:t,dataType:i,data:n,success:r},m.isPlainObject(e)&&e))}}),m._evalUrl=function(e){return m.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,throws:!0})},m.fn.extend({wrapAll:function(e){var t;return this[0]&&(m.isFunction(e)&&(e=e.call(this[0])),t=m(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){for(var e=this;e.firstElementChild;)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return m.isFunction(e)?this.each(function(t){m(this).wrapInner(e.call(this,t))}):this.each(function(){var t=m(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=m.isFunction(e);return this.each(function(n){m(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){m(this).replaceWith(this.childNodes)}),this}}),m.expr.pseudos.hidden=function(e){return!m.expr.pseudos.visible(e)},m.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},m.ajaxSettings.xhr=function(){try{return new e.XMLHttpRequest}catch(e){}};var Pt={0:200,1223:204},Rt=m.ajaxSettings.xhr();h.cors=!!Rt&&"withCredentials"in Rt,h.ajax=Rt=!!Rt,m.ajaxTransport(function(t){var n,r;if(h.cors||Rt&&!t.crossDomain)return{send:function(i,o){var a,s=t.xhr();if(s.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(a in t.xhrFields)s[a]=t.xhrFields[a];t.mimeType&&s.overrideMimeType&&s.overrideMimeType(t.mimeType),t.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");for(a in i)s.setRequestHeader(a,i[a]);n=function(e){return function(){n&&(n=r=s.onload=s.onerror=s.onabort=s.onreadystatechange=null,"abort"===e?s.abort():"error"===e?"number"!=typeof s.status?o(0,"error"):o(s.status,s.statusText):o(Pt[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=n(),r=s.onerror=n("error"),void 0!==s.onabort?s.onabort=r:s.onreadystatechange=function(){4===s.readyState&&e.setTimeout(function(){n&&r()})},n=n("abort");try{s.send(t.hasContent&&t.data||null)}catch(e){if(n)throw e}},abort:function(){n&&n()}}}),m.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),m.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return m.globalEval(e),e}}}),m.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),m.ajaxTransport("script",function(e){var t,n;if(e.crossDomain)return{send:function(i,o){t=m("",returnEnd:!0,subLanguage:["actionscript","javascript","handlebars","xml"]}},r,{className:"meta",begin:/<\?\w+/,end:/\?>/,relevance:10},{className:"tag",begin:"",contains:[{className:"name",begin:/[^\/><\s]+/,relevance:0},a]}]}}},{name:"xquery",create:function(){var e="for let if while then else return where group by xquery encoding versionmodule namespace boundary-space preserve strip default collation base-uri orderingcopy-namespaces order declare import schema namespace function option in allowing emptyat tumbling window sliding window start when only end when previous next stable ascendingdescending empty greatest least some every satisfies switch case typeswitch try catch andor to union intersect instance of treat as castable cast map array delete insert intoreplace value rename copy modify update",t="false true xs:string xs:integer element item xs:date xs:datetime xs:float xs:double xs:decimal QName xs:anyURI xs:long xs:int xs:short xs:byte attribute",r={begin:/\$[a-zA-Z0-9\-]+/,relevance:5},a={className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},i={className:"string",variants:[{begin:/"/,end:/"/,contains:[{begin:/""/,relevance:0}]},{begin:/'/,end:/'/,contains:[{begin:/''/,relevance:0}]}]},n={className:"meta",begin:"%\\w+"},s={className:"comment",begin:"\\(:",end:":\\)",relevance:10,contains:[{className:"doctag",begin:"@\\w+"}]},l={begin:"{",end:"}"},o=[r,i,a,s,n,l];return l.contains=o,{aliases:["xpath","xq"],case_insensitive:!1,lexemes:/[a-zA-Z\$][a-zA-Z0-9_:\-]*/,illegal:/(proc)|(abstract)|(extends)|(until)|(#)/,keywords:{keyword:e,literal:t},contains:o}}},{name:"yaml",create:function(e){var t={literal:"{ } true false yes no Yes No True False null"},r="^[ \\-]*",a="[a-zA-Z_][\\w\\-]*",i={className:"attr",variants:[{begin:r+a+":"},{begin:r+'"'+a+'"'+":"},{begin:r+"'"+a+"'"+":"}]},n={className:"template-variable",variants:[{begin:"{{",end:"}}"},{begin:"%{",end:"}"}]},s={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/}],contains:[e.BACKSLASH_ESCAPE,n]};return{case_insensitive:!0,aliases:["yml","YAML","yaml"],contains:[i,{className:"meta",begin:"^---s*$",relevance:10},{className:"string",begin:"[\\|>] *$",returnEnd:!0,contains:s.contains,end:i.variants[0].begin},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!!"+e.UNDERSCORE_IDENT_RE},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"^ *-",relevance:0},s,e.HASH_COMMENT_MODE,e.C_NUMBER_MODE],keywords:t}}},{name:"zephir",create:function(e){var t={className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:'b"',end:'"'},{begin:"b'",end:"'"},e.inherit(e.APOS_STRING_MODE,{illegal:null}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null})]},r={variants:[e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE]};return{aliases:["zep"],case_insensitive:!0,keywords:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var let while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally int uint long ulong char uchar double float bool boolean stringlikely unlikely",contains:[e.C_LINE_COMMENT_MODE,e.HASH_COMMENT_MODE,e.COMMENT("/\\*","\\*/",{contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.COMMENT("__halt_compiler.+?;",!1,{endsWithParent:!0,keywords:"__halt_compiler",lexemes:e.UNDERSCORE_IDENT_RE}),{className:"string",begin:"<<<['\"]?\\w+['\"]?$",end:"^\\w+;",contains:[e.BACKSLASH_ESCAPE]},{begin:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{className:"function",beginKeywords:"function",end:/[;{]/,excludeEnd:!0,illegal:"\\$|\\[|%",contains:[e.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)",contains:["self",e.C_BLOCK_COMMENT_MODE,t,r]}]},{className:"class",beginKeywords:"class interface",end:"{",excludeEnd:!0,illegal:/[:\(\$"]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"namespace",end:";",illegal:/[\.']/,contains:[e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"use",end:";",contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"=>"},t,r]}}}],a=0;a"}),e=e.replace(/&/g,"&"),e=e.replace(/"/g,'"')}var n=e("events").EventEmitter,s=e("./highlighter"),l=e("./converter"),o=e("./resources"),c=e("./parser"),d=e("./models/slideshow"),m=e("./views/slideshowView"),u=e("./controllers/defaultController"),h=e("./dom"),p=e("./macros");t.exports=r,r.prototype.highlighter=s,r.prototype.convert=function(e){var t=new c,r=t.parse(e||"",p)[0].content;return l.convertMarkdown(r,{},!0)},r.prototype.create=function(e){var t,r,i,s;return e=a(this.dom,e),t=new n,t.setMaxListeners(0),r=new d(t,e),i=new m(t,this.dom,e.container,r),s=e.controller||new u(t,this.dom,i,e.navigation),r}},{events:1,"./highlighter":7,"./converter":9,"./resources":6,"./parser":10,"./models/slideshow":11,"./views/slideshowView":12,"./controllers/defaultController":13,"./dom":14,"./macros":15}],14:[function(e,t){function r(){}t.exports=r,r.prototype.XMLHttpRequest=XMLHttpRequest,r.prototype.getHTMLElement=function(){return document.getElementsByTagName("html")[0]},r.prototype.getBodyElement=function(){return document.body},r.prototype.getElementById=function(e){return document.getElementById(e)},r.prototype.getLocationHash=function(){return window.location.hash},r.prototype.setLocationHash=function(e){"function"==typeof window.history.replaceState&&"null"!==document.origin?window.history.replaceState(void 0,void 0,e):window.location.hash=e}},{}],15:[function(e,t){var r=t.exports={};r.hello=function(){return"hello!"}},{}],10:[function(e,t){!function(){function r(){}function a(){return{content:[],properties:{continued:"false"},links:{}}}function i(e){return{"class":e.classes.join(" "),block:e.block,content:[]}}function n(e,t){var r=e.content;void 0!==e.notes&&(r=e.notes);var a=r.length-1;"string"==typeof r[a]&&"string"==typeof t?r[a]+=t:r.push(t)}function s(e,t){for(var r,a=/^\n*([-\w]+):([^$\n]*)|\n*(?:)/i;null!==(r=a.exec(e));)e=e.substr(0,r.index)+e.substr(r.index+r[0].length),void 0!==r[1]?t[r[1].trim()]=r[2].trim():t[r[3].trim()]=r[4].trim(),a.lastIndex=r.index;return e}function l(e){var t=function(e,t){for(var r,a=[];null!==(r=t.exec(e));)a.push(r[1]);return a},r=/^([ \t]*)[^ \t\n]/gm,a=t(e,r).map(function(e){return e.length}),i=Math.min.apply(Math,a),n=new RegExp("^[ \\t]{0,"+i+"}","gm");return e.replace(n,"")}var o=e("./lexer");t.exports=r,r.prototype.parse=function(e,t){var r=this,c=new o,d=c.lex(l(e)),m=[],u=[a()];return t=t||{},d.forEach(function(e){switch(e.type){case"text":case"code":case"fences":n(u[u.length-1],e.text);break;case"def":u[0].links[e.id]={href:e.href,title:e.title};break;case"macro":var s=t[e.name];if("function"!=typeof s)throw new Error('Macro "'+e.name+'" not found. '+"You need to define macro using remark.macros['"+e.name+"'] = function () { ... };");var l=s.apply(e.obj,e.args);"string"==typeof l?(l=r.parse(l,t),n(u[u.length-1],l[0].content[0])):n(u[u.length-1],void 0===l?"":l.toString());break;case"content_start":u.push(i(e));break;case"content_end":n(u[u.length-2],u[u.length-1]),u.pop();break;case"separator":m.push(u[0]),u=[a()],u[0].properties.continued=("--"===e.text).toString();break;case"notes_separator":u[0].notes=[]}}),m.push(u[0]),m.forEach(function(e){e.content[0]=s(e.content[0]||"",e.properties)}),m.filter(function(e){var t=(e.properties.exclude||"").toLowerCase();return"true"===t?!1:!0})}}()},{"./lexer":16}],11:[function(e,t){function r(e,t){function r(r){r=r||"",v=a(r,t),i(v),y={},v.forEach(function(e){for(var t in e.links)e.links.hasOwnProperty(t)&&(y[t]=e.links[t])}),e.emit("slidesChanged")}function l(){e.emit("resize")}function o(){return y}function c(){return v.map(function(e){return e})}function d(){return v.length}function m(e){return v.byName[e]}function u(){e.emit("togglePresenterMode")}function h(){e.emit("toggleHelp")}function p(){e.emit("toggleBlackout")}function g(){e.emit("toggleMirrored")}function b(){e.emit("toggleFullscreen")}function _(){e.emit("createClone")}function f(){e.emit("resetTimer")}function I(e,r){return function(){return void 0===t[e]?r:t[e]}}var C=this,v=[],y={};t=t||{},s.call(C,e),n.call(C,e),C.loadFromString=r,C.update=l,C.getLinks=o,C.getSlides=c,C.getSlideCount=d,C.getSlideByName=m,C.togglePresenterMode=u,C.toggleHelp=h,C.toggleBlackout=p,C.toggleMirrored=g,C.toggleFullscreen=b,C.createClone=_,C.resetTimer=f,C.getRatio=I("ratio","4:3"),C.getHighlightStyle=I("highlightStyle","default"),C.getHighlightLines=I("highlightLines",!1),C.getHighlightSpans=I("highlightSpans",!1),C.getHighlightLanguage=I("highlightLanguage",""),C.getSlideNumberFormat=I("slideNumberFormat","%current% / %total%"),r(t.source),e.on("toggleBlackout",function(){C.clone&&!C.clone.closed&&C.clone.postMessage("toggleBlackout","*")})}function a(e,t){var r,a=new o,i=a.parse(e,c),n=[],s={};return n.byName={},i.forEach(function(e,a){var i,o;"true"===e.properties.continued&&a>0?i=n[n.length-1]:s[e.properties.template]?i=s[e.properties.template]:"false"===e.properties.layout?r=void 0:r&&"true"!==e.properties.layout&&(i=r),"true"===e.properties.continued&&t.countIncrementalSlides===!1&&void 0===e.properties.count&&(e.properties.count="false"),o=new l(n.length,e,i),"true"===e.properties.layout&&(r=o),e.properties.name&&(s[e.properties.name]=o),"true"!==e.properties.layout&&(n.push(o),e.properties.name&&(n.byName[e.properties.name]=o))}),n}function i(e){e.forEach(function(e){e.expandVariables()})}var n=e("./slideshow/navigation"),s=e("./slideshow/events"),l=(e("../utils"),e("./slide")),o=e("../parser"),c=e("../macros");t.exports=r},{"./slideshow/navigation":17,"../utils":8,"./slide":18,"./slideshow/events":19,"../parser":10,"../macros":15}],12:[function(e,t){function r(e,t,r,i){var n=this;n.events=e,n.dom=t,n.slideshow=i,n.scaler=new o(e,i),n.slideViews=[],n.configureContainerElement(r),n.configureChildElements(),n.updateDimensions(),n.scaleElements(),n.updateSlideViews(),n.timer=new s(e,n.timerElement),e.on("slidesChanged",function(){n.updateSlideViews()}),e.on("hideSlide",function(e){n.elementArea.getElementsByClassName("remark-fading").forEach(function(e){d.removeClass(e,"remark-fading")}),n.hideSlide(e)}),e.on("showSlide",function(e){n.showSlide(e)}),e.on("forcePresenterMode",function(){d.hasClass(n.containerElement,"remark-presenter-mode")||(d.toggleClass(n.containerElement,"remark-presenter-mode"),n.scaleElements(),m.setPageOrientation("landscape"))}),e.on("togglePresenterMode",function(){d.toggleClass(n.containerElement,"remark-presenter-mode"),n.scaleElements(),e.emit("toggledPresenter",n.slideshow.getCurrentSlideIndex()+1),d.hasClass(n.containerElement,"remark-presenter-mode")?m.setPageOrientation("portrait"):m.setPageOrientation("landscape")}),e.on("toggleHelp",function(){d.toggleClass(n.containerElement,"remark-help-mode")}),e.on("toggleBlackout",function(){d.toggleClass(n.containerElement,"remark-blackout-mode")}),e.on("toggleMirrored",function(){d.toggleClass(n.containerElement,"remark-mirrored-mode")}),e.on("hideOverlay",function(){d.removeClass(n.containerElement,"remark-blackout-mode"),d.removeClass(n.containerElement,"remark-help-mode")}),e.on("pause",function(){d.toggleClass(n.containerElement,"remark-pause-mode")}),e.on("resume",function(){d.toggleClass(n.containerElement,"remark-pause-mode")}),a(n)}function a(e){var t=d.getPrefixedProperty(e.containerElement,"requestFullScreen"),r=d.getPrefixedProperty(document,"cancelFullScreen");e.events.on("toggleFullscreen",function(){var a=d.getPrefixedProperty(document,"fullscreenElement")||d.getPrefixedProperty(document,"fullScreenElement");!a&&t?t.call(e.containerElement,Element.ALLOW_KEYBOARD_INPUT):r&&r.call(document),e.scaleElements()})}function i(e,t,r){r.forEach(function(r){t.addEventListener(r,function(){var t=Array.prototype.slice.call(arguments);e.emit.apply(e,[r].concat(t))})})}var n=e("./slideView"),s=e("components/timer"),l=e("./notesView"),o=e("../scaler"),c=e("../resources"),d=e("../utils"),m=e("components/printing");t.exports=r,r.prototype.isEmbedded=function(){return this.containerElement!==this.dom.getBodyElement()},r.prototype.configureContainerElement=function(e){var t=this;t.containerElement=e,d.addClass(e,"remark-container"),e===t.dom.getBodyElement()?(d.addClass(t.dom.getHTMLElement(),"remark-container"),i(t.events,window,["hashchange","resize","keydown","keypress","mousewheel","message","DOMMouseScroll"]),i(t.events,t.containerElement,["touchstart","touchmove","touchend","click","contextmenu"])):(e.style.position="absolute",e.tabIndex=-1,i(t.events,window,["resize"]),i(t.events,e,["keydown","keypress","mousewheel","touchstart","touchmove","touchend"])),t.events.on("tap",function(e){e-1&&e.showSlide(e.slideshow.getCurrentSlideIndex())},r.prototype.scaleSlideBackgroundImages=function(e){var t=this;t.slideViews.forEach(function(t){t.scaleBackgroundImage(e)})},r.prototype.showSlide=function(e){var t=this,r=t.slideViews[e],a=t.slideViews[e+1];t.events.emit("beforeShowSlide",e),r.show(),t.previewArea.innerHTML=a?a.containerElement.outerHTML:"",t.events.emit("afterShowSlide",e)},r.prototype.hideSlide=function(e){var t=this,r=t.slideViews[e];t.events.emit("beforeHideSlide",e),r.hide(),t.events.emit("afterHideSlide",e)},r.prototype.updateDimensions=function(){var e=this,t=e.scaler.dimensions;e.helpElement.style.width=t.width+"px",e.helpElement.style.height=t.height+"px",e.scaleSlideBackgroundImages(t),e.scaleElements()},r.prototype.scaleElements=function(){var e=this;e.slideViews.forEach(function(t){t.scale(e.elementArea)}),e.previewArea.children.length&&e.scaler.scaleToFit(e.previewArea.children[0].children[0],e.previewArea),e.scaler.scaleToFit(e.helpElement,e.containerElement),e.scaler.scaleToFit(e.pauseElement,e.containerElement)}},{"components/timer":"GFo1Ae","components/printing":"yoGRCZ","./slideView":20,"./notesView":21,"../scaler":22,"../resources":6,"../utils":8}],13:[function(e,t){!function(){function r(e,t,r,c){c=c||{},l.register(e),o.register(e,t,r),i.register(e),n.register(e,c),s.register(e,c),a(e,r,c)}function a(e,t,r){e.on("pause",function(){i.unregister(e),n.unregister(e),s.unregister(e)}),e.on("resume",function(){i.register(e),n.register(e,r),s.register(e,r)})}t.exports=r;var i=e("./inputs/keyboard"),n=e("./inputs/mouse"),s=e("./inputs/touch"),l=e("./inputs/message"),o=e("./inputs/location")}()},{"./inputs/keyboard":23,"./inputs/mouse":24,"./inputs/touch":25,"./inputs/message":26,"./inputs/location":27}],16:[function(e,t){function r(){}function a(e,t,r){var i,I;for(r=r||[];null!==(i=t.exec(e));)i.index>0&&r.push({type:"text",text:e.substring(0,i.index)}),i[l]?r.push({type:"code",text:i[0]}):i[o]?r.push({type:"text",text:i[0]}):i[d]?r.push({type:"fences",text:i[0]}):i[m]?r.push({type:"def",id:i[m],href:i[u],title:i[h]}):i[p]?r.push({type:"macro",name:i[p],args:(i[g]||"").split(",").map(n),obj:i[b]}):i[_]?r.push({type:"separator",text:i[_]}):i[f]?r.push({type:"notes_separator",text:i[f]}):i[c]&&(I=s(e,i.index+i[0].length),void 0!==I?(e=e.substring(I.length+1),"\\"!==i[0][0]?(r.push({type:"content_start",classes:i[c].substring(1).split("."),block:-1!==I.indexOf("\n")}),a(I,v,r),r.push({type:"content_end",block:-1!==I.indexOf("\n")})):r.push({type:"text",text:i[0].substring(1)+I+"]"})):r.push({type:"text",text:i[0]})),e=e.substring(i.index+i[0].length);return(e||!e&&0===r.length)&&r.push({type:"text",text:e}),r}function i(e,t){return new RegExp(e.source.replace(/\w{2,}/g,function(e){return t[e].source}))}function n(e){return"string"==typeof e?e.trim():e}function s(e,t){for(var r,a=1,i=t;a>0&&i]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,MACRO:/!\[:([^\] ]+)([^\]]*)\](?:\(([^\)]*)\))?/,SEPARATOR:/(?:^|\n)(---?)(?:\n|$)/,NOTES_SEPARATOR:/(?:^|\n)(\?{3})(?:\n|$)/},C=i(/CODE|INLINE_CODE|CONTENT|FENCES|DEF|MACRO|SEPARATOR|NOTES_SEPARATOR/,I),v=i(/CODE|INLINE_CODE|CONTENT|FENCES|DEF|MACRO/,I);r.prototype.lex=function(e){var t,r=a(e,C);for(t=r.length-2;t>=0;t--)"text"===r[t].type&&"text"===r[t+1].type&&(r[t].text+=r[t+1].text,r.splice(t+1,1));return r}},{}],17:[function(e,t){function r(e){function t(){e.emit("pause")}function r(){e.emit("resume")}function a(){return u}function i(t,r){var a=t===u,i=0>t||t>m.getSlideCount()-1;void 0===r&&(r=!1),a||i||(-1!==u&&e.emit("hideSlide",u,!1),null===h?h=!1:h===!1&&(e.emit("start"),h=!0),e.emit("showSlide",t),u=t,e.emit("slideChanged",t+1),r||(m.clone&&!m.clone.closed&&m.clone.postMessage("gotoSlide:"+(u+1),"*"),window.opener&&window.opener.postMessage("gotoSlide:"+(u+1),"*")))}function n(e,t){var r=d(e);i(r,t)}function s(){i(u-1)}function l(){i(u+1)}function o(){i(0)}function c(){i(m.getSlideCount()-1)}function d(t){var r,a;return"number"==typeof t?t-1:(r=parseInt(t,10),r.toString()===t?r-1:t.match(/^p\d+$/)?(e.emit("forcePresenterMode"),parseInt(t.substr(1),10)-1):(a=m.getSlideByName(t),a?a.getSlideIndex():0))}var m=this,u=-1,h=null;m.getCurrentSlideIndex=a,m.gotoSlide=n,m.gotoPreviousSlide=s,m.gotoNextSlide=l,m.gotoFirstSlide=o,m.gotoLastSlide=c,m.pause=t,m.resume=r,e.on("gotoSlide",n),e.on("gotoPreviousSlide",s),e.on("gotoNextSlide",l),e.on("gotoFirstSlide",o),e.on("gotoLastSlide",c),e.on("slidesChanged",function(){u>m.getSlideCount()&&(u=m.getSlideCount())}),e.on("createClone",function(){!m.clone||m.clone.closed?m.clone=window.open(location.href,"_blank","location=no"):m.clone.focus()}),e.on("resetTimer",function(){h=!1})}t.exports=r},{}],18:[function(e,t){function r(e,t,r){var i=this;i.properties=t.properties||{},i.links=t.links||{},i.content=t.content||[],i.notes=t.notes||"",i.getSlideIndex=function(){return e},r&&a(i,r)}function a(e,t){i(e,t),s(e,t),l(e,t)}function i(e,t){var r,a;for(r in t.properties)t.properties.hasOwnProperty(r)&&!n(r)&&(a=[t.properties[r]],"class"===r&&e.properties[r]&&a.push(e.properties[r]),("class"===r||void 0===e.properties[r])&&(e.properties[r]=a.join(", ")))}function n(e){return"name"===e||"layout"===e||"count"===e}function s(e,t){var r;e.properties.content=e.content.slice(),e.content=t.content.slice(),r=e.expandVariables(!0),void 0===r.content&&(e.content=e.content.concat(e.properties.content)),delete e.properties.content}function l(e,t){t.notes&&(e.notes=t.notes+"\n\n"+e.notes)}t.exports=r,r.prototype.expandVariables=function(e,t,r){function a(t,a,i,s){var l,o=s.trim();return a?e?t[0]:i:e&&"content"!==o?t:(l=n[o],void 0!==l?(r[o]=l,l):"content"===o?"":i)}var i,n=this.properties;for(t=void 0!==t?t:this.content,r=r||{},i=0;io/d.height?o/m.height:c/m.width,a=m.width*r,i=m.height*r,n=(c-a)/2,s=(o-i)/2,e.style["-webkit-transform"]="scale("+r+")",e.style.MozTransform="scale("+r+")",e.style.left=Math.max(n,0)+"px",e.style.top=Math.max(s,0)+"px"}},{}],23:[function(e,t,r){function a(e){e.on("keydown",function(t){if(!t.metaKey&&!t.ctrlKey)switch(t.keyCode){case 33:case 37:case 38:e.emit("gotoPreviousSlide");break;case 32:case 34:case 39:case 40:e.emit("gotoNextSlide");break;case 36:e.emit("gotoFirstSlide");break;case 35:e.emit("gotoLastSlide");break;case 27:e.emit("hideOverlay")}}),e.on("keypress",function(t){if(!t.metaKey&&!t.ctrlKey)switch(String.fromCharCode(t.which).toLowerCase()){case"j":e.emit("gotoNextSlide");break;case"k":e.emit("gotoPreviousSlide");break;case"b":e.emit("toggleBlackout");break;case"m":e.emit("toggleMirrored");break;case"c":e.emit("createClone");break;case"p":e.emit("togglePresenterMode");break;case"f":e.emit("toggleFullscreen");break;case"t":e.emit("resetTimer");break;case"h":case"?":e.emit("toggleHelp")}})}function i(e){e.removeAllListeners("keydown"),e.removeAllListeners("keypress")}r.register=function(e){a(e)},r.unregister=function(e){i(e)}},{}],24:[function(e,t,r){function a(e,t){if(t.click&&(e.on("click",function(t){"A"!==t.target.nodeName&&0===t.button&&e.emit("gotoNextSlide")}),e.on("contextmenu",function(t){"A"!==t.target.nodeName&&(t.preventDefault(),e.emit("gotoPreviousSlide"))})),t.scroll!==!1){var r=function(t){t.wheelDeltaY>0||t.detail<0?e.emit("gotoPreviousSlide"):(t.wheelDeltaY<0||t.detail>0)&&e.emit("gotoNextSlide")};e.on("mousewheel",r),e.on("DOMMouseScroll",r)}}function i(e){e.removeAllListeners("click"),e.removeAllListeners("contextmenu"),e.removeAllListeners("mousewheel")}r.register=function(e,t){a(e,t)},r.unregister=function(e){i(e)}},{}],25:[function(e,t,r){function a(e,t){var r,a,i;if(t.touch!==!1){var n=function(){return Math.abs(a-i)<10},s=function(){e.emit("tap",i)},l=function(){a>i?e.emit("gotoNextSlide"):e.emit("gotoPreviousSlide")};e.on("touchstart",function(e){r=e.touches[0],a=r.clientX}),e.on("touchend",function(e){"A"!==e.target.nodeName.toUpperCase()&&(r=e.changedTouches[0],i=r.clientX,n()?s():l())}),e.on("touchmove",function(e){e.preventDefault()})}}function i(e){e.removeAllListeners("touchstart"),e.removeAllListeners("touchend"),e.removeAllListeners("touchmove")}r.register=function(e,t){a(e,t)},r.unregister=function(e){i(e)}},{}],26:[function(e,t,r){function a(e){function t(t){var r;null!==(r=/^gotoSlide:(\d+)$/.exec(t.data))?e.emit("gotoSlide",parseInt(r[1],10),!0):"toggleBlackout"===t.data&&e.emit("toggleBlackout")}e.on("message",t)}r.register=function(e){a(e)}},{}],20:[function(e,t){function r(e,t,r,a){var i=this;i.events=e,i.slideshow=t,i.scaler=r,i.slide=a,i.slideNumber=new p(a,t),i.configureElements(),i.updateDimensions(),i.events.on("propertiesChanged",function(e){e.hasOwnProperty("ratio")&&i.updateDimensions()})}function a(e,t,r){var a=document.createElement("div");return r.properties.name&&(a.id="slide-"+r.properties.name),i(t,a,r.properties),a.innerHTML=g.convertMarkdown(r.content,t.getLinks()),c(a,t),a}function i(e,t,r){t.className="",o(t,r),l(t,r,e),s(t,r)}function n(e,t){var r=document.createElement("div");return r.className="remark-slide-notes",r.innerHTML=g.convertMarkdown(t),c(r,e),r}function s(e,t){var r=t["background-image"],a=t["background-color"];r&&(e.style.backgroundImage=r),a&&(e.style.backgroundColor=a)}function l(e,t,r){var a=t["highlight-style"]||r.getHighlightStyle();a&&_.addClass(e,"hljs-"+a)}function o(e,t){_.addClass(e,"remark-slide-content"),(t["class"]||"").split(/,| /).filter(function(e){return""!==e}).forEach(function(t){_.addClass(e,t)})}function c(e,t){var r,a=e.getElementsByTagName("code"),i=t.getHighlightLines(),n=t.getHighlightSpans();a.forEach(function(e){return"PRE"!==e.parentElement.tagName?(_.addClass(e,"remark-inline-code"),void 0):(""===e.className&&(e.className=t.getHighlightLanguage()),i&&(r=d(e)),""!==e.className&&b.engine.highlightBlock(e," "),m(e),i&&u(e,r.highlightedLines),n&&h(e),_.addClass(e,"remark-code"),void 0)})}function d(e){var t=[];return e.innerHTML=e.innerHTML.split(/\r?\n/).map(function(e,r){return 0===e.indexOf("*")?(t.push(r),e.replace(/^\*( )?/,"$1$1")):e}).join("\n"),{highlightedLines:t}}function m(e){var t=e.innerHTML.split(/\r?\n/).map(function(e){return'
'+e+"
"});t.length&&-1!==t[t.length-1].indexOf("><")&&t.pop(),e.innerHTML=t.join("")}function u(e,t){t.forEach(function(t){_.addClass(e.childNodes[t],"remark-code-line-highlighted")})}function h(e){var t=/([^`])`([^`]+?)`/g;e.childNodes.forEach(function(e){e.innerHTML=e.innerHTML.replace(t,function(e,t,r){return"\\"===t?e.substr(1):t+''+r+""})})}var p=e("components/slide-number"),g=e("../converter"),b=e("../highlighter"),_=e("../utils");t.exports=r,r.prototype.updateDimensions=function(){var e=this,t=e.scaler.dimensions;e.scalingElement.style.width=t.width+"px",e.scalingElement.style.height=t.height+"px"},r.prototype.scale=function(e){var t=this;t.scaler.scaleToFit(t.scalingElement,e)},r.prototype.show=function(){_.addClass(this.containerElement,"remark-visible"),_.removeClass(this.containerElement,"remark-fading")},r.prototype.hide=function(){var e=this;_.removeClass(this.containerElement,"remark-visible"),_.addClass(this.containerElement,"remark-fading"),setTimeout(function(){_.removeClass(e.containerElement,"remark-fading")},1e3)},r.prototype.configureElements=function(){var e=this;e.containerElement=document.createElement("div"),e.containerElement.className="remark-slide-container",e.scalingElement=document.createElement("div"),e.scalingElement.className="remark-slide-scaler",e.element=document.createElement("div"),e.element.className="remark-slide",e.contentElement=a(e.events,e.slideshow,e.slide),e.notesElement=n(e.slideshow,e.slide.notes),e.contentElement.appendChild(e.slideNumber.element),e.element.appendChild(e.contentElement),e.scalingElement.appendChild(e.element),e.containerElement.appendChild(e.scalingElement),e.containerElement.appendChild(e.notesElement)},r.prototype.scaleBackgroundImage=function(e){var t,r,a,i=this,n=window.getComputedStyle(this.contentElement),s=n.backgroundImage;null!==(t=/^url\(("?)([^\)]+?)\1\)/.exec(s))&&(r=new Image,r.onload=function(){r.width>e.width||r.height>e.height?i.originalBackgroundSize||(i.originalBackgroundSize=i.contentElement.style.backgroundSize,i.originalBackgroundPosition=i.contentElement.style.backgroundPosition,i.backgroundSizeSet=!0,a=e.width/r.width',c+=r(e[s].content,t,!0),c+="");var d=a.Lexer.lex(c.replace(/^\s+/,""));return d.links=t,o=a.Parser.parse(d),i&&(n.innerHTML=o,1===n.children.length&&"P"===n.children[0].tagName&&(o=n.children[0].innerHTML)),o}var a=e("marked"),i=t.exports={},n=document.createElement("div");a.setOptions({gfm:!0,tables:!0,breaks:!1,pedantic:!0,sanitize:!1,smartLists:!0,langPrefix:""}),i.convertMarkdown=function(e,t,a){return n.innerHTML=r(e,t||{},a),n.innerHTML=n.innerHTML.replace(/

\s*<\/p>/g,""),n.innerHTML.replace(/\n\r?$/,"")}},{marked:28}],28:[function(e,t,r){!function(e){!function(){function e(e){this.tokens=[],this.tokens.links={},this.options=e||m.defaults,this.rules=u.normal,this.options.gfm&&(this.rules=this.options.tables?u.tables:u.gfm)}function a(e,t){if(this.options=t||m.defaults,this.links=e,this.rules=h.normal,this.renderer=this.options.renderer||new i,this.renderer.options=this.options,!this.links)throw new Error("Tokens array requires a `links` property.");this.options.gfm?this.rules=this.options.breaks?h.breaks:h.gfm:this.options.pedantic&&(this.rules=h.pedantic)}function i(e){this.options=e||{}}function n(e){this.tokens=[],this.token=null,this.options=e||m.defaults,this.options.renderer=this.options.renderer||new i,this.renderer=this.options.renderer,this.renderer.options=this.options}function s(e,t){return e.replace(t?/&/g:/&(?!#?\w+;)/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function l(e){return e.replace(/&([#\w]+);/g,function(e,t){return t=t.toLowerCase(),"colon"===t?":":"#"===t.charAt(0)?"x"===t.charAt(1)?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):""})}function o(e,t){return e=e.source,t=t||"",function r(a,i){return a?(i=i.source||i,i=i.replace(/(^|[^\[])\^/g,"$1"),e=e.replace(a,i),r):new RegExp(e,t)}}function c(){}function d(e){for(var t,r,a=1;aAn error occured:

"+s(u.message+"",!0)+"
";throw u}}var u={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:c,hr:/^( *[-*_]){3,} *(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,nptable:c,lheading:/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,blockquote:/^( *>[^\n]+(\n[^\n]+)*\n*)+/,list:/^( *)(bull) [\s\S]+?(?:hr|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:/^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/,def:/^ *\[([^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,table:c,paragraph:/^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,text:/^[^\n]+/};u.bullet=/(?:[*+-]|\d+\.)/,u.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/,u.item=o(u.item,"gm")(/bull/g,u.bullet)(),u.list=o(u.list)(/bull/g,u.bullet)("hr",/\n+(?=(?: *[-*_]){3,} *(?:\n+|$))/)(),u._tag="(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b",u.html=o(u.html)("comment",//)("closed",/<(tag)[\s\S]+?<\/\1>/)("closing",/])*?>/)(/tag/g,u._tag)(),u.paragraph=o(u.paragraph)("hr",u.hr)("heading",u.heading)("lheading",u.lheading)("blockquote",u.blockquote)("tag","<"+u._tag)("def",u.def)(),u.normal=d({},u),u.gfm=d({},u.normal,{fences:/^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n+|$)/,paragraph:/^/}),u.gfm.paragraph=o(u.paragraph)("(?!","(?!"+u.gfm.fences.source.replace("\\1","\\2")+"|"+u.list.source.replace("\\1","\\3")+"|")(),u.tables=d({},u.gfm,{nptable:/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,table:/^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/}),e.rules=u,e.lex=function(t,r){var a=new e(r);return a.lex(t)},e.prototype.lex=function(e){return e=e.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n"),this.token(e,!0) -},e.prototype.token=function(e,t){for(var r,a,i,n,s,l,o,c,d,e=e.replace(/^ +$/gm,"");e;)if((i=this.rules.newline.exec(e))&&(e=e.substring(i[0].length),i[0].length>1&&this.tokens.push({type:"space"})),i=this.rules.code.exec(e))e=e.substring(i[0].length),i=i[0].replace(/^ {4}/gm,""),this.tokens.push({type:"code",text:this.options.pedantic?i:i.replace(/\n+$/,"")});else if(i=this.rules.fences.exec(e))e=e.substring(i[0].length),this.tokens.push({type:"code",lang:i[2],text:i[3]});else if(i=this.rules.heading.exec(e))e=e.substring(i[0].length),this.tokens.push({type:"heading",depth:i[1].length,text:i[2]});else if(t&&(i=this.rules.nptable.exec(e))){for(e=e.substring(i[0].length),l={type:"table",header:i[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:i[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:i[3].replace(/\n$/,"").split("\n")},c=0;c ?/gm,""),this.token(i,t),this.tokens.push({type:"blockquote_end"});else if(i=this.rules.list.exec(e)){for(e=e.substring(i[0].length),n=i[2],this.tokens.push({type:"list_start",ordered:n.length>1}),i=i[0].match(this.rules.item),r=!1,d=i.length,c=0;d>c;c++)l=i[c],o=l.length,l=l.replace(/^ *([*+-]|\d+\.) +/,""),~l.indexOf("\n ")&&(o-=l.length,l=this.options.pedantic?l.replace(/^ {1,4}/gm,""):l.replace(new RegExp("^ {1,"+o+"}","gm"),"")),this.options.smartLists&&c!==d-1&&(s=u.bullet.exec(i[c+1])[0],n===s||n.length>1&&s.length>1||(e=i.slice(c+1).join("\n")+e,c=d-1)),a=r||/\n\n(?!\s*$)/.test(l),c!==d-1&&(r="\n"===l.charAt(l.length-1),a||(a=r)),this.tokens.push({type:a?"loose_item_start":"list_item_start"}),this.token(l,!1),this.tokens.push({type:"list_item_end"});this.tokens.push({type:"list_end"})}else if(i=this.rules.html.exec(e))e=e.substring(i[0].length),this.tokens.push({type:this.options.sanitize?"paragraph":"html",pre:"pre"===i[1]||"script"===i[1]||"style"===i[1],text:i[0]});else if(t&&(i=this.rules.def.exec(e)))e=e.substring(i[0].length),this.tokens.links[i[1].toLowerCase()]={href:i[2],title:i[3]};else if(t&&(i=this.rules.table.exec(e))){for(e=e.substring(i[0].length),l={type:"table",header:i[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:i[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:i[3].replace(/(?: *\| *)?\n$/,"").split("\n")},c=0;c])/,autolink:/^<([^ >]+(@|:\/)[^ >]+)>/,url:c,tag:/^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,link:/^!?\[(inside)\]\(href\)/,reflink:/^!?\[(inside)\]\s*\[([^\]]*)\]/,nolink:/^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,strong:/^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,em:/^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,code:/^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,br:/^ {2,}\n(?!\s*$)/,del:c,text:/^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/,h.link=o(h.link)("inside",h._inside)("href",h._href)(),h.reflink=o(h.reflink)("inside",h._inside)(),h.normal=d({},h),h.pedantic=d({},h.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/}),h.gfm=d({},h.normal,{escape:o(h.escape)("])","~|])")(),url:/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,del:/^~~(?=\S)([\s\S]*?\S)~~/,text:o(h.text)("]|","~]|")("|","|https?://|")()}),h.breaks=d({},h.gfm,{br:o(h.br)("{2,}","*")(),text:o(h.gfm.text)("{2,}","*")()}),a.rules=h,a.output=function(e,t,r){var i=new a(t,r);return i.output(e)},a.prototype.output=function(e){for(var t,r,a,i,n="";e;)if(i=this.rules.escape.exec(e))e=e.substring(i[0].length),n+=i[1];else if(i=this.rules.autolink.exec(e))e=e.substring(i[0].length),"@"===i[2]?(r=":"===i[1].charAt(6)?this.mangle(i[1].substring(7)):this.mangle(i[1]),a=this.mangle("mailto:")+r):(r=s(i[1]),a=r),n+=this.renderer.link(a,null,r);else if(i=this.rules.url.exec(e))e=e.substring(i[0].length),r=s(i[1]),a=r,n+=this.renderer.link(a,null,r);else if(i=this.rules.tag.exec(e))e=e.substring(i[0].length),n+=this.options.sanitize?s(i[0]):i[0];else if(i=this.rules.link.exec(e))e=e.substring(i[0].length),n+=this.outputLink(i,{href:i[2],title:i[3]});else if((i=this.rules.reflink.exec(e))||(i=this.rules.nolink.exec(e))){if(e=e.substring(i[0].length),t=(i[2]||i[1]).replace(/\s+/g," "),t=this.links[t.toLowerCase()],!t||!t.href){n+=i[0].charAt(0),e=i[0].substring(1)+e;continue}n+=this.outputLink(i,t)}else if(i=this.rules.strong.exec(e))e=e.substring(i[0].length),n+=this.renderer.strong(this.output(i[2]||i[1]));else if(i=this.rules.em.exec(e))e=e.substring(i[0].length),n+=this.renderer.em(this.output(i[2]||i[1]));else if(i=this.rules.code.exec(e))e=e.substring(i[0].length),n+=this.renderer.codespan(s(i[2],!0));else if(i=this.rules.br.exec(e))e=e.substring(i[0].length),n+=this.renderer.br();else if(i=this.rules.del.exec(e))e=e.substring(i[0].length),n+=this.renderer.del(this.output(i[1]));else if(i=this.rules.text.exec(e))e=e.substring(i[0].length),n+=s(this.smartypants(i[0]));else if(e)throw new Error("Infinite loop on byte: "+e.charCodeAt(0));return n},a.prototype.outputLink=function(e,t){var r=s(t.href),a=t.title?s(t.title):null;return"!"!==e[0].charAt(0)?this.renderer.link(r,a,this.output(e[1])):this.renderer.image(r,a,s(e[1]))},a.prototype.smartypants=function(e){return this.options.smartypants?e.replace(/--/g,"—").replace(/(^|[-\u2014/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…"):e},a.prototype.mangle=function(e){for(var t,r="",a=e.length,i=0;a>i;i++)t=e.charCodeAt(i),Math.random()>.5&&(t="x"+t.toString(16)),r+="&#"+t+";";return r},i.prototype.code=function(e,t,r){if(this.options.highlight){var a=this.options.highlight(e,t);null!=a&&a!==e&&(r=!0,e=a)}return t?'
'+(r?e:s(e,!0))+"\n
\n":"
"+(r?e:s(e,!0))+"\n
"},i.prototype.blockquote=function(e){return"
\n"+e+"
\n"},i.prototype.html=function(e){return e},i.prototype.heading=function(e,t,r){return"'+e+"\n"},i.prototype.hr=function(){return"
\n"},i.prototype.list=function(e,t){var r=t?"ol":"ul";return"<"+r+">\n"+e+"\n"},i.prototype.listitem=function(e){return"
  • "+e+"
  • \n"},i.prototype.paragraph=function(e){return"

    "+e+"

    \n"},i.prototype.table=function(e,t){return"\n\n"+e+"\n"+"\n"+t+"\n"+"
    \n"},i.prototype.tablerow=function(e){return"
    Tool Standard Error:stderr
    Tool Exit Code:${ job.exit_code | h }
    Job Messages
      + %for job_message in job.job_messages: +
    • ${ job_message['desc'] |h }
    • + %endfor +
      History Content API ID: ${encoded_hda_id} diff --git a/templates/tagging_common.mako b/templates/tagging_common.mako index 303bc0a3290..97ecbd272d9 100644 --- a/templates/tagging_common.mako +++ b/templates/tagging_common.mako @@ -1,259 +1,115 @@ +<%namespace file="/display_common.mako" import="get_controller_name" /> <%! - from cgi import escape - from random import random - from math import floor - import six - from galaxy.model import Tag, ItemTagAssociation - from galaxy.web.framework.helpers import iff - from galaxy.util import unicodify +from cgi import escape %> ## Render a tagging element if there is a tagged_item. %if tagged_item is not None: %if tag_type == "individual": - ${render_individual_tagging_element( user=user, tagged_item=tagged_item, elt_context=elt_context, in_form=in_form, input_size=input_size, tag_click_fn=tag_click_fn, use_toggle_link=use_toggle_link )} + ${render_individual_tagging_element( + user=user, + tagged_item=tagged_item, + elt_context=elt_context, + tag_click_fn=tag_click_fn, + use_toggle_link=use_toggle_link + )} %elif tag_type == "community": - ${render_community_tagging_element(tagged_item=tagged_item, elt_context=elt_context, tag_click_fn=tag_click_fn)} + ${render_community_tagging_element( + tagged_item=tagged_item, + elt_context=elt_context, + tag_click_fn=tag_click_fn + )} %endif %endif -## Render HTML for a list of tags. -<%def name="render_tagging_element_html(elt_id=None, tags=None, editable=True, use_toggle_link=True, input_size='15', in_form=False, tag_type='individual', render_add_tag_button=True)"> - ## Useful attributes. + +<%def name="render_community_tagging_element( + tagged_item=None, + elt_context=None, + use_toggle_link=False, + tag_click_fn='default_tag_click_fn')"> + <% - num_tags = len( tags ) - %> - - - -## Render tool tagging elements -<%def name="render_tool_tagging_elements()"> - <% - elt_id = int ( floor ( random() * six.MAXSIZE ) ) - tags = trans.app.tag_handler.get_tool_tags() - %> - ${self.render_tagging_element_html(elt_id=elt_id, \ - tags=tags, \ - editable=False, \ - use_toggle_link=False )} - - - -## Render community tagging element. -<%def name="render_community_tagging_element(tagged_item=None, elt_context=None, use_toggle_link=False, tag_click_fn='default_tag_click_fn')"> - ## Build HTML. - <% - elt_id = int ( floor ( random() * six.MAXSIZE ) ) - community_tags = trans.app.tag_handler.get_community_tags( item=tagged_item, limit=5 ) - %> - ${self.render_tagging_element_html(elt_id=elt_id, \ - tags=community_tags, \ - use_toggle_link=use_toggle_link, \ - editable=False, tag_type="community")} - - ## Set up tag click function. - - - - -## Render individual tagging element. -<%def name="render_individual_tagging_element(user=None, tagged_item=None, elt_context=None, use_toggle_link=True, in_form=False, input_size='15', tag_click_fn='default_tag_click_fn', get_toggle_link_text_fn='default_get_toggle_link_text_fn', editable=True, render_add_tag_button=True)"> - ## Useful attributes. - <% - # Useful ids. tagged_item_id = str( trans.security.encode_id ( tagged_item.id ) ) - elt_id = int ( floor ( random() * six.MAXSIZE ) ) + controller_name = get_controller_name(tagged_item) + click_url = h.url_for( controller='/' + controller_name , action='list_published') + community_tags = trans.app.tag_handler.get_community_tags( item=tagged_item, limit=5 ) - # Get list of user's item tags. TODO: implement owner_tags for all taggable objects and use here. - item_tags = [ tag for tag in tagged_item.tags if ( tag.user == user ) ] + ## Having trouble converting list of tags into a plain array, this just + ## just plucks out the name + community_tag_names = [] + for tag in community_tags: + community_tag_names.append(escape(tag.name)) %> + +
      - ## Build HTML. - <% - if len(item_tags) > 3: - # If item has more than 3 tags show a link to see tags instead of displaying them all - use_toggle_link = True - else: - use_toggle_link = False - %> - ${self.render_tagging_element_html(elt_id=elt_id, tags=item_tags, editable=editable, use_toggle_link=use_toggle_link, input_size=input_size, in_form=in_form, render_add_tag_button=render_add_tag_button)} - - ## Build script that augments tags using progressive javascript. + - // - // Default function get text to display on the toggle link. - // - var default_get_toggle_link_text_fn = function(tags) - { - var text = ""; - var num_tags = _.size(tags); - if (num_tags != 0) - { - text = num_tags + (num_tags != 1 ? " Tags" : " Tag"); - /* - // Show first N tags; hide the rest. - var max_to_show = 1; - // Build tag string. - var tag_strs = new Array(); - var count = 0; - for (tag_name in tags) - { - tag_value = tags[tag_name]; - tag_strs[tag_strs.length] = build_tag_str(tag_name, tag_value); - if (++count == max_to_show) - break; - } - tag_str = tag_strs.join(", "); +<%def name="render_individual_tagging_element( + user=None, + tagged_item=None, + elt_context=None, + use_toggle_link=True, + tag_click_fn='default_tag_click_fn', + get_toggle_link_text_fn='default_get_toggle_link_text_fn', + editable=True)"> - // Finalize text. - var num_tags_hiding = num_tags - max_to_show; - text = "Tags: " + tag_str + - (num_tags_hiding != 0 ? " and " + num_tags_hiding + " more" : ""); - */ - } - else - { - // No tags. - text = "Add tags"; - } - return text; - }; + <% + tagged_item_id = str( trans.security.encode_id ( tagged_item.id ) ) + item_tags = [ tag for tag in tagged_item.tags if ( tag.user == user ) ] - // Default function to handle a tag click. - var default_tag_click_fn = function(tag_name, tag_value) { }; + item_tag_names = [] + for ta in item_tags: + item_tag_names.append(escape(ta.tag.name)) + %> - <% - ## Build dict of tag name, values. - tag_names_and_values = dict() - for tag in item_tags: - tag_name = escape( tag.user_tname ) - tag_value = "" - if tag.value is not None: - tag_value = escape( tag.user_value ) +
      - ## Tag names and values may be string or unicode object. - if isinstance( tag_name, six.binary_type ): - tag_names_and_values[unicodify(tag_name, 'utf-8')] = unicodify(tag_value, 'utf-8') - else: - tag_names_and_values[tag_name] = tag_value - %> - var options = - { - tags : ${h.dumps(tag_names_and_values)}, - editable : ${iff( editable, 'true', 'false' )}, - get_toggle_link_text_fn: ${get_toggle_link_text_fn}, - tag_click_fn: ${tag_click_fn}, - ## Use forward slash in controller to suppress route memory. - ajax_autocomplete_tag_url: "${h.url_for( controller='/tag', action='tag_autocomplete_data', item_id=tagged_item_id, item_class=tagged_item.__class__.__name__ )}", - ajax_add_tag_url: "${h.url_for( controller='/tag', action='add_tag_async', item_id=tagged_item_id, item_class=tagged_item.__class__.__name__, context=elt_context )}", - ajax_delete_tag_url: "${h.url_for( controller='/tag', action='remove_tag_async', item_id=tagged_item_id, item_class=tagged_item.__class__.__name__, context=elt_context )}", - delete_tag_img: "${h.url_for('/static/images/delete_tag_icon_gray.png')}", - delete_tag_img_rollover: "${h.url_for('/static/images/delete_tag_icon_white.png')}", - use_toggle_link: ${iff( use_toggle_link, 'true', 'false' )} - }; + - ## Use style to hide/display the tag area. - - - <%def name="community_tag_js( controller_name )"> +## TODO: Note that this function no longer has anything to do with community +## tags. the ratings code and tagging initialization were previously co-mingled +## in here. Will remove this script when we write the ratings components + ## set up comminity tag and rating handling - used for page start up / set up ## controller_name: the model controller for the item being tagged - generally gotten with get_controller_name( item ) - - -<%def name="title()">Post Biostar Question - -
      -

      You are now being forwarded to Biostar.

      -

      -

      If you are not automatically forwarded, click the button below:

      -

      - %for input_name, input_value in form_inputs.items(): - - %endfor - -
      -
      -
      diff --git a/templates/webapps/galaxy/galaxy.masthead.mako b/templates/webapps/galaxy/galaxy.masthead.mako index 1c20b43c5d8..473a68a3e92 100644 --- a/templates/webapps/galaxy/galaxy.masthead.mako +++ b/templates/webapps/galaxy/galaxy.masthead.mako @@ -13,8 +13,6 @@ 'remote_user_logout_href' : app.config.remote_user_logout_href, 'enable_cloud_launch' : app.config.get_bool('enable_cloud_launch', False), 'lims_doc_url' : app.config.get("lims_doc_url", "https://usegalaxy.org/u/rkchak/p/sts"), - 'biostar_url' : app.config.biostar_url, - 'biostar_url_redirect' : h.url_for( controller='biostar', action='biostar_redirect', qualified=True ), 'default_locale' : app.config.get("default_locale", "auto"), 'support_url' : app.config.get("support_url", "https://galaxyproject.org/support"), 'search_url' : app.config.get("search_url", "http://galaxyproject.org/search/"), diff --git a/templates/webapps/galaxy/galaxy.panels.mako b/templates/webapps/galaxy/galaxy.panels.mako index 72a42fad563..324ca1d25d8 100644 --- a/templates/webapps/galaxy/galaxy.panels.mako +++ b/templates/webapps/galaxy/galaxy.panels.mako @@ -42,6 +42,8 @@ | ${self.galaxy_config['title']} %endif + ## relative href for site root + ${self.stylesheets()} ${self.javascripts()} @@ -174,7 +176,6 @@ <%def name="javascripts()"> ${h.js( - 'libs/d3', 'bundled/libs.chunk', 'bundled/base.chunk' )} diff --git a/templates/webapps/galaxy/workflow/display.mako b/templates/webapps/galaxy/workflow/display.mako index 476144e6675..68265cfcfea 100644 --- a/templates/webapps/galaxy/workflow/display.mako +++ b/templates/webapps/galaxy/workflow/display.mako @@ -86,14 +86,16 @@ %endif -<%def name="render_item( workflow, steps )"> +<%def name="render_item( workflow, steps, outer_workflow=True )"> <% # HACK: Rendering workflow steps requires that trans have a history; however, if its user's first visit to Galaxy is here, he won't have a history # and an error will occur. To prevent this error, make sure user has a history. trans.get_history( most_recent=True, create=True ) %> + %if outer_workflow: + %endif %for i, step in enumerate( steps ): %endfor + %if outer_workflow:
      StepAnnotation
      %if step.type == 'tool' or step.type is None: @@ -110,6 +112,25 @@
      Step ${int(step.order_index)+1}: Unknown Tool with id '${step.tool_id | h}'
      %endif + %elif step.type == 'subworkflow': +
      +
      Step ${int(step.order_index)+1}: ${step.label or (step.subworkflow.name if step.subworkflow else "Missing workflow") | h}
      + <% errors = step.module.get_errors() %> + %if errors: +
      + Error in Subworkflow: +
        + %for error in errors: +
      • ${error}
      • + %endfor +
      +
      + %else: +
      + ${render_item( step.subworkflow, step.subworkflow.steps, outer_workflow=False )}
      +
      + %endif +
      %else: ## TODO: always input dataset? <% module = step.module %> @@ -128,5 +149,7 @@
      + %endif diff --git a/templates/webapps/galaxy/workflow/editor.mako b/templates/webapps/galaxy/workflow/editor.mako index 223c952406e..aa55bfbd47b 100644 --- a/templates/webapps/galaxy/workflow/editor.mako +++ b/templates/webapps/galaxy/workflow/editor.mako @@ -282,7 +282,7 @@ Tags:
      - ${render_individual_tagging_element(user=trans.get_user(), tagged_item=stored, elt_context="edit_attributes.mako", use_toggle_link=False, input_size="20")} + ${render_individual_tagging_element(user=trans.get_user(), tagged_item=stored, elt_context="edit_attributes.mako", use_toggle_link=False)}
      Apply tags to make it easy to search for and find items with the same tag.
      diff --git a/templates/webapps/reports/base_panels.mako b/templates/webapps/reports/base_panels.mako index bf102f2b4df..52e0c81d349 100644 --- a/templates/webapps/reports/base_panels.mako +++ b/templates/webapps/reports/base_panels.mako @@ -38,13 +38,11 @@
      ## Logo, layered over tabs to be clickable - + diff --git a/templates/webapps/reports/index.mako b/templates/webapps/reports/index.mako index b0667332c90..70e983edac3 100644 --- a/templates/webapps/reports/index.mako +++ b/templates/webapps/reports/index.mako @@ -30,12 +30,12 @@ %>
      Reports - - + +
      -
      +
      diff --git a/templates/webapps/tool_shed/common/common.mako b/templates/webapps/tool_shed/common/common.mako index 2d723462f71..5603b65e688 100644 --- a/templates/webapps/tool_shed/common/common.mako +++ b/templates/webapps/tool_shed/common/common.mako @@ -15,7 +15,7 @@
      <% selected = o[1] in listify(select.value) or o[2] %> ${escape(o[0])}
      @@ -23,7 +23,7 @@ %else:
      @@ -105,24 +101,3 @@ def inherit(context):
      - -<%def name="render_oidc_form( form_action=None )"> - - <% - if form_action is None: - form_action = h.url_for( controller='authnz', action='login', provider='Google' ) - %> - - %if header: - ${header} - %endif -
      -
      OR
      -
      -
      - -
      -
      -
      - - diff --git a/templates/user/logout.mako b/templates/webapps/tool_shed/user/logout.mako similarity index 79% rename from templates/user/logout.mako rename to templates/webapps/tool_shed/user/logout.mako index 83020695a62..20a38c04f36 100644 --- a/templates/user/logout.mako +++ b/templates/webapps/tool_shed/user/logout.mako @@ -38,11 +38,8 @@ def inherit(context): <%def name="javascript_app()"> ${ parent.javascript_app() } \ No newline at end of file diff --git a/templates/user/register.mako b/templates/webapps/tool_shed/user/register.mako similarity index 73% rename from templates/user/register.mako rename to templates/webapps/tool_shed/user/register.mako index 18712d5734a..f4acac17e6b 100644 --- a/templates/user/register.mako +++ b/templates/webapps/tool_shed/user/register.mako @@ -1,12 +1,4 @@ -<%! -#This is a hack, we should restructure templates to avoid this. -def inherit(context): - if context.get('trans').webapp.name == 'galaxy' and context.get( 'use_panels', True ): - return '/webapps/galaxy/base_panels.mako' - else: - return '/base.mako' -%> -<%inherit file="${inherit(context)}"/> +<%inherit file="/base.mako"/> <%namespace file="/webapps/tool_shed/common/common.mako" import="*" /> <%def name="init()"> @@ -60,8 +52,6 @@ def inherit(context): <% if form_action is None: form_action = h.url_for( controller='user', action='create', cntrller=cntrller ) - from galaxy.web.form_builder import CheckboxField - subscribe_check_box = CheckboxField( 'subscribe' ) %> -
      - ## only display the prepopulate form to admins - %if show_user_prepopulate_form: -
      - -
      Pre-populate an account through LDAP
      -
      - - - -
      -
      - - -
      - Depending on your LDAP configuration in your auth_conf.xml you can either provide an - email address or username. -
      -
      -
      - -
      -
      -
      - %endif +
      -
      Create account
      +
      Create account
      @@ -191,14 +157,6 @@ def inherit(context):
      %endif
      - %if trans.app.config.smtp_server and trans.app.config.mailing_join_addr: -
      - - ${render_checkbox(subscribe_check_box)} -

      See - all Galaxy project mailing lists.

      -
      - %endif
      If you see this, please leave following field blank. @@ -207,11 +165,5 @@ def inherit(context):
      - %if registration_warning_message: -
      - ${registration_warning_message} -
      - %endif
      - diff --git a/test-data/1.fasta.gz b/test-data/1.fasta.gz new file mode 100644 index 00000000000..9534b6b1323 Binary files /dev/null and b/test-data/1.fasta.gz differ diff --git a/test-data/exports/1901_two_datasets.tgz b/test-data/exports/1901_two_datasets.tgz new file mode 100644 index 00000000000..7976205b548 Binary files /dev/null and b/test-data/exports/1901_two_datasets.tgz differ diff --git a/test-data/groupby_out1.dat b/test-data/groupby_out1.dat new file mode 100644 index 00000000000..f822b20ca1a --- /dev/null +++ b/test-data/groupby_out1.dat @@ -0,0 +1,20 @@ +chr1 1.48053e+08 +chr10 5.52516e+07 +chr11 8.75888e+07 +chr12 3.84401e+07 +chr13 1.12382e+08 +chr14 9.87102e+07 +chr15 4.16664e+07 +chr16 206638 +chr18 5.05624e+07 +chr19 5.92262e+07 +chr2 1.69276e+08 +chr20 3.35042e+07 +chr21 3.31607e+07 +chr22 3.04712e+07 +chr5 1.31612e+08 +chr6 1.08564e+08 +chr7 1.15958e+08 +chr8 1.18881e+08 +chr9 1.28843e+08 +chrx 1.45195e+08 diff --git a/test-data/groupby_out2.dat b/test-data/groupby_out2.dat new file mode 100644 index 00000000000..f89d723ab44 --- /dev/null +++ b/test-data/groupby_out2.dat @@ -0,0 +1,3 @@ + +chr10 chr10 +chr22 chr22 diff --git a/test-data/groupby_out3.tabular b/test-data/groupby_out3.tabular new file mode 100644 index 00000000000..cc5214529db --- /dev/null +++ b/test-data/groupby_out3.tabular @@ -0,0 +1,20 @@ +chr1 1.48053e+08 1.48031e+08 -,+ 1.48185e+08 1.47962e+08 5.9221e+08 4 1 chr1,chr1,chr1,chr1 +,- 148185136 88085.9 +chr10 5.52516e+07 5.52516e+07 - 5.52516e+07 5.52516e+07 5.52516e+07 1 1 chr10 - 55251623 0 +chr11 8.75888e+07 1.16165e+08 -,+ 1.16212e+08 1.81238e+06 3.50355e+08 4 1 chr11,chr11,chr11,chr11 +,- 1812377 4.9523e+07 +chr12 3.84401e+07 3.84401e+07 - 3.84401e+07 3.84401e+07 3.84401e+07 1 1 chr12 - 38440094 0 +chr13 1.12382e+08 1.12382e+08 + 1.12382e+08 1.12382e+08 1.12382e+08 1 1 chr13 + 112381694 0 +chr14 9.87102e+07 9.87102e+07 - 9.87102e+07 9.87102e+07 9.87102e+07 1 1 chr14 - 98710240 0 +chr15 4.16664e+07 4.16764e+07 -,+ 4.1826e+07 4.14869e+07 1.66666e+08 4 1 chr15,chr15,chr15,chr15 +,- 41673708 120341 +chr16 206638 212188 +,- 259268 142908 826552 4 1 chr16,chr16,chr16,chr16 +,- 244413 47354.9 +chr18 5.05624e+07 5.94314e+07 -,+ 5.96006e+07 2.37861e+07 2.0225e+08 4 1 chr18,chr18,chr18,chr18 +,- 23786114 1.54594e+07 +chr19 5.92262e+07 5.9267e+07 +,- 5.93022e+07 5.90686e+07 2.36905e+08 4 1 chr19,chr19,chr19,chr19 +,- 59236026 94686.3 +chr2 1.69276e+08 1.69292e+08 +,- 2.2023e+08 1.18289e+08 6.77103e+08 4 1 chr2,chr2,chr2,chr2 +,- 220190202 5.09343e+07 +chr20 3.35042e+07 3.35466e+07 -,+ 3.35933e+07 3.33304e+07 1.34017e+08 4 1 chr20,chr20,chr20,chr20 +,- 33579500 104752 +chr21 3.31607e+07 3.30953e+07 +,- 3.3745e+07 3.2707e+07 1.32643e+08 4 1 chr21,chr21,chr21,chr21 +,- 33321040 405475 +chr22 3.04712e+07 3.04128e+07 +,- 3.09391e+07 3.01202e+07 1.21885e+08 4 1 chr22,chr22,chr22,chr22 +,- 30120223 345080 +chr5 1.31612e+08 1.31589e+08 +,- 1.31848e+08 1.31424e+08 5.2645e+08 4 1 chr5,chr5,chr5,chr5 +,- 131556601 153188 +chr6 1.08564e+08 1.08617e+08 -,+ 1.08723e+08 1.083e+08 4.34257e+08 4 1 chr6,chr6,chr6,chr6 +,- 108640045 159611 +chr7 1.15958e+08 1.16613e+08 +,- 1.16946e+08 1.13661e+08 4.63832e+08 4 1 chr7,chr7,chr7,chr7 +,- 113660517 1.33533e+06 +chr8 1.18881e+08 1.18881e+08 - 1.18881e+08 1.18881e+08 1.18881e+08 1 1 chr8 - 118881131 0 +chr9 1.28843e+08 1.28835e+08 +,- 1.28937e+08 1.28764e+08 5.15371e+08 4 1 chr9,chr9,chr9,chr9 +,- 128787519 70228.2 +chrX 1.45195e+08 1.5267e+08 +,- 1.52694e+08 1.22745e+08 5.80779e+08 4 1 chrX,chrX,chrX,chrX +,- 152648964 1.29614e+07 diff --git a/test-data/groupby_out4.tabular b/test-data/groupby_out4.tabular new file mode 100644 index 00000000000..3a9115ca22c --- /dev/null +++ b/test-data/groupby_out4.tabular @@ -0,0 +1,3 @@ + 1600 +chr10 1133.33 +chr22 1500 diff --git a/test-data/matrix.cool b/test-data/matrix.cool new file mode 100644 index 00000000000..91ec17bb85f Binary files /dev/null and b/test-data/matrix.cool differ diff --git a/test-data/safe_relative_symlink.tar b/test-data/safe_relative_symlink.tar new file mode 100644 index 00000000000..19348c1bdd9 Binary files /dev/null and b/test-data/safe_relative_symlink.tar differ diff --git a/test-data/testdir1.tar b/test-data/testdir1.tar new file mode 100644 index 00000000000..d61c2c852de Binary files /dev/null and b/test-data/testdir1.tar differ diff --git a/test-data/testdir1.tar.gz b/test-data/testdir1.tar.gz new file mode 100644 index 00000000000..820a0a5cef0 Binary files /dev/null and b/test-data/testdir1.tar.gz differ diff --git a/test/api/embed_test_1.gxwf.yml b/test/api/embed_test_1.gxwf.yml new file mode 100644 index 00000000000..76461dff6fd --- /dev/null +++ b/test/api/embed_test_1.gxwf.yml @@ -0,0 +1,20 @@ +class: GalaxyWorkflow +inputs: + - label: input1 + +steps: + - tool_id: cat1 + label: first_cat + state: + input1: + $link: input1 + - label: embed1 + run: + "@import": "embed_test_1_tool.gxtool.yml" + - tool_id: cat1 + state: + input1: + $link: first_cat#out_file1 + queries: + - input2: + $link: embed1#output1 diff --git a/test/api/embed_test_1_tool.gxtool.yml b/test/api/embed_test_1_tool.gxtool.yml new file mode 100644 index 00000000000..ff99796a65c --- /dev/null +++ b/test/api/embed_test_1_tool.gxtool.yml @@ -0,0 +1,5 @@ +class: GalaxyTool +command: echo 'hello world 2' > $output1 +outputs: + output1: + format: txt diff --git a/test/api/test_histories.py b/test/api/test_histories.py index bf135b03504..6a5fc16a714 100644 --- a/test/api/test_histories.py +++ b/test/api/test_histories.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- +import time + from requests import ( - get, post, put ) @@ -9,7 +10,6 @@ from base import api # noqa: I100,I202 from base.populators import ( # noqa: I100 DatasetCollectionPopulator, DatasetPopulator, - wait_on ) @@ -178,11 +178,57 @@ class HistoriesApiTestCase(api.ApiTestCase): self._assert_status_code_is(create_response, 403) def test_import_export(self): - history_name = "for_export" + history_name = "for_export_default" history_id = self.dataset_populator.new_history(name=history_name) self.dataset_populator.new_dataset(history_id, content="1 2 3") - imported_history_id = self._reimport_history(history_id, history_name) - self._assert_history_length(imported_history_id, 1) + deleted_hda = self.dataset_populator.new_dataset(history_id, content="1 2 3") + self.dataset_populator.delete_dataset(history_id, deleted_hda["id"]) + deleted_details = self.dataset_populator.get_history_dataset_details(history_id, id=deleted_hda["id"]) + assert deleted_details["deleted"] + imported_history_id = self._reimport_history(history_id, history_name, wait_on_history_length=2) + + def upload_job_check(job): + assert job["tool_id"] == "upload1" + + def check_discarded(hda): + assert hda["deleted"] + assert hda["state"] == "discarded", hda + assert hda["purged"] is True + + self._check_imported_dataset(history_id=imported_history_id, hid=1, job_checker=upload_job_check) + self._check_imported_dataset(history_id=imported_history_id, hid=2, has_job=False, hda_checker=check_discarded, job_checker=upload_job_check) + + imported_content = self.dataset_populator.get_history_dataset_content( + history_id=imported_history_id, + hid=1, + ) + assert imported_content == "1 2 3\n" + + def test_import_1901_histories(self): + f = open(self.test_data_resolver.get_filename("exports/1901_two_datasets.tgz"), 'rb') + import_data = dict(archive_source='', archive_file=f) + self._import_history_and_wait(import_data, "API Test History", wait_on_history_length=2) + + def test_import_export_include_deleted(self): + history_name = "for_export_include_deleted" + history_id = self.dataset_populator.new_history(name=history_name) + self.dataset_populator.new_dataset(history_id, content="1 2 3") + deleted_hda = self.dataset_populator.new_dataset(history_id, content="1 2 3") + self.dataset_populator.delete_dataset(history_id, deleted_hda["id"]) + + imported_history_id = self._reimport_history(history_id, history_name, wait_on_history_length=2, export_kwds={"include_deleted": "True"}) + self._assert_history_length(imported_history_id, 2) + + def upload_job_check(job): + assert job["tool_id"] == "upload1" + + def check_ok(hda): + assert hda["state"] == "ok", hda + assert hda["deleted"] is True, hda + + self._check_imported_dataset(history_id=imported_history_id, hid=1, job_checker=upload_job_check) + self._check_imported_dataset(history_id=imported_history_id, hid=2, hda_checker=check_ok, job_checker=upload_job_check) + imported_content = self.dataset_populator.get_history_dataset_content( history_id=imported_history_id, hid=1, @@ -192,13 +238,22 @@ class HistoriesApiTestCase(api.ApiTestCase): def test_import_metadata_regeneration(self): history_name = "for_import_metadata_regeneration" history_id = self.dataset_populator.new_history(name=history_name) - self.dataset_populator.new_dataset(history_id, content=open(self.test_data_resolver.get_filename("1.bam"), 'rb'), file_type='bam') + self.dataset_populator.new_dataset(history_id, content=open(self.test_data_resolver.get_filename("1.bam"), 'rb'), file_type='bam', wait=True) imported_history_id = self._reimport_history(history_id, history_name) self._assert_history_length(imported_history_id, 1) + self._check_imported_dataset(history_id=imported_history_id, hid=1) import_bam_metadata = self.dataset_populator.get_history_dataset_details( history_id=imported_history_id, hid=1, ) + # The cleanup() method of the __IMPORT_HISTORY__ job (which is executed + # after the job has entered its final state): + # - creates a new dataset with 'ok' state and adds it to the history + # - starts a __SET_METADATA__ job to regenerate the dataset metadata, if + # needed + # We need to wait a bit for the creation of the __SET_METADATA__ job. + time.sleep(1) + self.dataset_populator.wait_for_history_jobs(imported_history_id, assert_ok=True) bai_metadata = import_bam_metadata["meta_files"][0] assert bai_metadata["file_type"] == "bam_index" api_url = bai_metadata["download_url"].split("api/", 1)[1] @@ -207,48 +262,61 @@ class HistoriesApiTestCase(api.ApiTestCase): assert len(bai_response.content) > 4 def test_import_export_collection(self): - from nose.plugins.skip import SkipTest - raise SkipTest("Collection import/export not yet implemented") - history_name = "for_export_with_collections" history_id = self.dataset_populator.new_history(name=history_name) - self.dataset_collection_populator.create_list_in_history(history_id, contents=["Hello", "World"]) - - imported_history_id = self._reimport_history(history_id, history_name) + self.dataset_collection_populator.create_list_in_history(history_id, contents=["Hello", "World"], direct_upload=True) + imported_history_id = self._reimport_history(history_id, history_name, wait_on_history_length=3) self._assert_history_length(imported_history_id, 3) - def _reimport_history(self, history_id, history_name): + def check_elements(elements): + assert len(elements) == 2 + element0 = elements[0]["object"] + element1 = elements[1]["object"] + for element in [element0, element1]: + assert not element["visible"] + assert not element["deleted"] + assert element["state"] == "ok" + + assert element0["hid"] == 2 + assert element1["hid"] == 3 + + self._check_imported_collection(imported_history_id, hid=1, collection_type="list", elements_checker=check_elements) + + def test_import_export_nested_collection(self): + history_name = "for_export_with_nested_collections" + history_id = self.dataset_populator.new_history(name=history_name) + self.dataset_collection_populator.create_list_of_pairs_in_history(history_id) + + imported_history_id = self._reimport_history(history_id, history_name, wait_on_history_length=3) + self._assert_history_length(imported_history_id, 3) + + def check_elements(elements): + assert len(elements) == 1 + element0 = elements[0]["object"] + self._assert_has_keys(element0, "elements", "collection_type") + + child_elements = element0["elements"] + + assert len(child_elements) == 2 + assert element0["collection_type"] == "paired" + + self._check_imported_collection(imported_history_id, hid=1, collection_type="list:paired", elements_checker=check_elements) + + def _reimport_history(self, history_id, history_name, wait_on_history_length=None, export_kwds={}): # Ensure the history is ready to go... self.dataset_populator.wait_for_history(history_id, assert_ok=True) - # Export the history. - download_path = self._export(history_id) + return self.dataset_populator.reimport_history( + history_id, history_name, wait_on_history_length=wait_on_history_length, export_kwds=export_kwds, url=self.url, api_key=self.galaxy_interactor.api_key + ) - # Create download for history - full_download_url = "%s%s?key=%s" % (self.url, download_path, self.galaxy_interactor.api_key) - download_response = get(full_download_url) - self._assert_status_code_is(download_response, 200) + def _import_history_and_wait(self, import_data, history_name, wait_on_history_length=None): - def history_names(): - history_index = self._get("histories") - return dict((h["name"], h) for h in history_index.json()) + imported_history_id = self.dataset_populator.import_history_and_wait_for_name(import_data, history_name) - import_name = "imported from archive: %s" % history_name - assert import_name not in history_names() - - import_data = dict(archive_source=full_download_url, archive_type="url") - import_response = self._post("histories", data=import_data) - - self._assert_status_code_is(import_response, 200) - - def has_history_with_name(): - histories = history_names() - return histories.get(import_name, None) - - imported_history = wait_on(has_history_with_name, desc="import history") - imported_history_id = imported_history["id"] - self.dataset_populator.wait_for_history(imported_history_id) + if wait_on_history_length: + self.dataset_populator.wait_on_history_length(imported_history_id, wait_on_history_length) return imported_history_id @@ -258,6 +326,46 @@ class HistoriesApiTestCase(api.ApiTestCase): contents = contents_response.json() assert len(contents) == n, contents + def _check_imported_dataset(self, history_id, hid, has_job=True, hda_checker=None, job_checker=None): + imported_dataset_metadata = self.dataset_populator.get_history_dataset_details( + history_id=history_id, + hid=hid, + ) + assert imported_dataset_metadata["history_content_type"] == "dataset" + assert imported_dataset_metadata["history_id"] == history_id + + if hda_checker is not None: + hda_checker(imported_dataset_metadata) + + assert "creating_job" in imported_dataset_metadata + job_id = imported_dataset_metadata["creating_job"] + if has_job: + assert job_id + + job_details = self.dataset_populator.get_job_details(job_id, full=True) + assert job_details.status_code == 200, job_details.content + job = job_details.json() + assert 'history_id' in job, job + assert job['history_id'] == history_id, job + + if job_checker is not None: + job_checker(job) + + def _check_imported_collection(self, history_id, hid, collection_type=None, elements_checker=None): + imported_collection_metadata = self.dataset_populator.get_history_collection_details( + history_id=history_id, + hid=hid, + ) + assert imported_collection_metadata["history_content_type"] == "dataset_collection" + assert imported_collection_metadata["history_id"] == history_id + assert "collection_type" in imported_collection_metadata + assert "elements" in imported_collection_metadata + if collection_type is not None: + assert imported_collection_metadata["collection_type"] == collection_type, imported_collection_metadata + + if elements_checker is not None: + elements_checker(imported_collection_metadata["elements"]) + def test_create_tag(self): post_data = dict(name="TestHistoryForTag") history_id = self._post("histories", data=post_data).json()["id"] @@ -266,24 +374,6 @@ class HistoriesApiTestCase(api.ApiTestCase): tag_create_response = self._post(tag_url, data=tag_data) self._assert_status_code_is(tag_create_response, 200) - def _export(self, history_id): - export_url = self._api_url("histories/%s/exports" % history_id, use_key=True) - put_response = put(export_url) - self._assert_status_code_is(put_response, 202) - - def export_ready_response(): - put_response = put(export_url) - if put_response.status_code == 202: - return None - return put_response - - put_response = wait_on(export_ready_response, desc="export ready") - self._assert_status_code_is(put_response, 200) - response = put_response.json() - self._assert_has_keys(response, "download_url") - download_path = response["download_url"] - return download_path - def _show(self, history_id): return self._get("histories/%s" % history_id).json() diff --git a/test/api/test_history_contents.py b/test/api/test_history_contents.py index f575b61b29a..3b994496156 100644 --- a/test/api/test_history_contents.py +++ b/test/api/test_history_contents.py @@ -203,6 +203,29 @@ class HistoryContentsApiTestCase(api.ApiTestCase, TestsDatasets): updated_hda = self.__show(hda1).json() assert updated_hda["name"] == quoted_name, quoted_name + data = { + "dataset_id": hda1["id"], + "name": "moocow", + "dbkey": "?", + "annotation": None, + "info": "my info is", + "operation": "attributes" + } + update_response = self._set_edit_update(data) + # No key or anything supplied, expect a permission problem. + # A bit questionable but I think this is a 400 instead of a 403 so that + # we don't distinguish between this is a valid ID you don't have access to + # and this is an invalid ID. + assert update_response.status_code == 400, update_response.content + + def test_update_batch(self): + hda1 = self._wait_for_new_hda() + assert str(hda1["deleted"]).lower() == "false" + payload = dict(items=[{"history_content_type": "dataset", "id": hda1["id"]}], deleted=True) + update_response = self._raw_update_batch(payload) + objects = update_response.json() + assert objects[0]["deleted"] + def test_update_type_failures(self): hda1 = self._wait_for_new_hda() update_response = self._raw_update(hda1["id"], dict(deleted='not valid')) @@ -213,6 +236,11 @@ class HistoryContentsApiTestCase(api.ApiTestCase, TestsDatasets): self._wait_for_history(self.history_id) return hda1 + def _set_edit_update(self, json): + set_edit_url = "%s/dataset/set_edit" % self.url + update_response = put(set_edit_url, json=json) + return update_response + def _raw_update(self, item_id, data, admin=False, history_id=None): history_id = history_id or self.history_id key_param = "use_admin_key" if admin else "use_key" @@ -226,6 +254,11 @@ class HistoryContentsApiTestCase(api.ApiTestCase, TestsDatasets): update_response = put(update_url, json=data) return update_response + def _raw_update_batch(self, data): + update_url = self._api_url("histories/%s/contents" % (self.history_id), use_key=True) + update_response = put(update_url, json=data) + return update_response + def test_delete(self): hda1 = self._new_dataset(self.history_id) self._wait_for_history(self.history_id) diff --git a/test/api/test_jobs.py b/test/api/test_jobs.py index a1eea668508..22dc29d1d51 100644 --- a/test/api/test_jobs.py +++ b/test/api/test_jobs.py @@ -123,6 +123,12 @@ class JobsApiTestCase(api.ApiTestCase): job_details = show_jobs_response.json() self._assert_has_key(job_details, 'id', 'state', 'exit_code', 'update_time', 'create_time') + show_jobs_response = self._get("jobs/%s" % job_id, {"full": True}) + self._assert_status_code_is(show_jobs_response, 200) + + job_details = show_jobs_response.json() + self._assert_has_key(job_details, 'id', 'state', 'exit_code', 'update_time', 'create_time', 'stdout', 'stderr', 'job_messages') + @uses_test_history(require_new=True) def test_show_security(self, history_id): self.__history_with_new_dataset(history_id) @@ -141,6 +147,49 @@ class JobsApiTestCase(api.ApiTestCase): show_jobs_response = self._get("jobs/%s" % job_id, admin=True) self._assert_has_keys(show_jobs_response.json(), "command_line", "external_id") + def _run_detect_errors(self, history_id, inputs): + payload = self.dataset_populator.run_tool_payload( + tool_id='detect_errors_aggressive', + inputs=inputs, + history_id=history_id, + ) + return self._post("tools", data=payload).json() + + @skip_without_tool("detect_errors_aggressive") + def test_unhide_on_error(self): + with self.dataset_populator.test_history() as history_id: + inputs = {'error_bool': 'true'} + run_response = self._run_detect_errors(history_id=history_id, inputs=inputs) + job_id = run_response['jobs'][0]["id"] + self.dataset_populator.wait_for_job(job_id) + job = self.dataset_populator.get_job_details(job_id).json() + assert job['state'] == 'error' + dataset = self.dataset_populator.get_history_dataset_details(history_id=history_id, + dataset_id=run_response['outputs'][0]['id'], + assert_ok=False) + assert dataset['visible'] + + @skip_without_tool("detect_errors_aggressive") + def test_no_unhide_on_error_if_mapped_over(self): + with self.dataset_populator.test_history() as history_id: + hdca1 = self.dataset_collection_populator.create_list_in_history(history_id, contents=[("sample1-1", "1 2 3")]).json() + inputs = { + 'error_bool': 'true', + 'dataset': { + 'batch': True, + 'values': [{'src': 'hdca', 'id': hdca1['id']}], + } + } + run_response = self._run_detect_errors(history_id=history_id, inputs=inputs) + job_id = run_response['jobs'][0]["id"] + self.dataset_populator.wait_for_job(job_id) + job = self.dataset_populator.get_job_details(job_id).json() + assert job['state'] == 'error' + dataset = self.dataset_populator.get_history_dataset_details(history_id=history_id, + dataset_id=run_response['outputs'][0]['id'], + assert_ok=False) + assert not dataset['visible'] + @skip_without_tool('detect_errors_aggressive') def test_report_error(self): with self.dataset_populator.test_history() as history_id: @@ -159,7 +208,7 @@ class JobsApiTestCase(api.ApiTestCase): @skip_without_tool('detect_errors_aggressive') def test_report_error_anon(self): # Need to get a cookie and use that for anonymous tool runs - cookies = requests.get(self.galaxy_interactor.api_url.rsplit('/api', 1)[0]).cookies + cookies = requests.get(self.url).cookies payload = json.dumps({"tool_id": "detect_errors_aggressive", "inputs": {"error_bool": "true"}}) run_response = requests.post("%s/tools" % self.galaxy_interactor.api_url, data=payload, cookies=cookies).json() diff --git a/test/api/test_libraries.py b/test/api/test_libraries.py index 16813579f44..8535511e480 100644 --- a/test/api/test_libraries.py +++ b/test/api/test_libraries.py @@ -1,4 +1,5 @@ import json +import time import unittest from base import api @@ -135,19 +136,50 @@ class LibrariesApiTestCase(api.ApiTestCase, TestsDatasets): def test_fetch_single_url_to_folder(self): history_id, library, destination = self._setup_fetch_to_folder("single_url") - items = [{"src": "url", "url": "https://raw.githubusercontent.com/galaxyproject/galaxy/dev/test-data/4.bed"}] + items = [{ + "src": "url", + "url": "https://raw.githubusercontent.com/galaxyproject/galaxy/dev/test-data/4.bed", + "MD5": "37b59762b59fff860460522d271bc111" + }] targets = [{ "destination": destination, - "items": items + "items": items, }] payload = { "history_id": history_id, # TODO: Shouldn't be needed :( "targets": json.dumps(targets), + "validate_hashes": True } self.dataset_populator.fetch(payload) dataset = self.library_populator.get_library_contents_with_path(library["id"], "/4.bed") assert dataset["file_size"] == 61, dataset + def test_fetch_failed_validation(self): + # Exception handling is really rough here - we should be creating a dataset in error instead + # of just failing the job like this. + history_id, library, destination = self._setup_fetch_to_folder("single_url") + items = [{ + "src": "url", + "url": "https://raw.githubusercontent.com/galaxyproject/galaxy/dev/test-data/4.bed", + "MD5": "37b59762b59fff860460522d271bc112" + }] + targets = [{ + "destination": destination, + "items": items, + }] + payload = { + "history_id": history_id, # TODO: Shouldn't be needed :( + "targets": json.dumps(targets), + "validate_hashes": True + } + tool_response = self.dataset_populator.fetch(payload, assert_ok=False) + job = self.dataset_populator.check_run(tool_response) + self.dataset_populator.wait_for_job(job["id"]) + + job = tool_response.json()["jobs"][0] + details = self.dataset_populator.get_job_details(job["id"]).json() + assert details["state"] == "error", details + def test_fetch_url_archive_to_folder(self): history_id, library, destination = self._setup_fetch_to_folder("single_url") targets = [{ @@ -200,33 +232,36 @@ class LibrariesApiTestCase(api.ApiTestCase, TestsDatasets): self._assert_has_keys(create_response.json(), "name", "id") def test_update_dataset_in_folder(self): - library = self.library_populator.new_private_library("ForUpdateDataset") - folder_response = self._create_folder(library) - self._assert_status_code_is(folder_response, 200) - folder_id = folder_response.json()[0]['id'] - history_id = self.dataset_populator.new_history() - hda_id = self.dataset_populator.new_dataset(history_id, content="1 2 3")['id'] - payload = {'from_hda_id': hda_id, 'create_type': 'file', 'folder_id': folder_id} - ld = self._post("libraries/%s/contents" % folder_id, payload) + ld = self._create_dataset_in_folder_in_library("ForUpdateDataset") data = {'name': 'updated_name', 'file_ext': 'fastq', 'misc_info': 'updated_info', 'genome_build': 'updated_genome_build'} create_response = self._patch("libraries/datasets/%s" % ld.json()["id"], data=data) self._assert_status_code_is(create_response, 200) self._assert_has_keys(create_response.json(), "name", "file_ext", "misc_info", "genome_build") def test_invalid_update_dataset_in_folder(self): - library = self.library_populator.new_private_library("ForInvalidUpdateDataset") - folder_response = self._create_folder(library) - self._assert_status_code_is(folder_response, 200) - folder_id = folder_response.json()[0]['id'] - history_id = self.dataset_populator.new_history() - hda_id = self.dataset_populator.new_dataset(history_id, content="1 2 3")['id'] - payload = {'from_hda_id': hda_id, 'create_type': 'file', 'folder_id': folder_id} - ld = self._post("libraries/%s/contents" % folder_id, payload) + ld = self._create_dataset_in_folder_in_library("ForInvalidUpdateDataset") data = {'file_ext': 'nonexisting_type'} create_response = self._patch("libraries/datasets/%s" % ld.json()["id"], data=data) self._assert_status_code_is(create_response, 400) assert 'This Galaxy does not recognize the datatype of:' in create_response.json()['err_msg'] + def test_detect_datatype_of_dataset_in_folder(self): + ld = self._create_dataset_in_folder_in_library("ForDetectDataset") + # Wait for metadata job to finish. + time.sleep(2) + data = {'file_ext': 'data'} + create_response = self._patch("libraries/datasets/%s" % ld.json()["id"], data=data) + self._assert_status_code_is(create_response, 200) + self._assert_has_keys(create_response.json(), "file_ext") + assert create_response.json()["file_ext"] == "data" + # Wait for metadata job to finish. + time.sleep(2) + data = {'file_ext': 'auto'} + create_response = self._patch("libraries/datasets/%s" % ld.json()["id"], data=data) + self._assert_status_code_is(create_response, 200) + self._assert_has_keys(create_response.json(), "file_ext") + assert create_response.json()["file_ext"] == "txt" + def test_create_datasets_in_library_from_collection(self): library = self.library_populator.new_private_library("ForCreateDatasetsFromCollection") folder_response = self._create_folder(library) @@ -264,3 +299,14 @@ class LibrariesApiTestCase(api.ApiTestCase, TestsDatasets): name="New Folder", ) return self._post("libraries/%s/contents" % library["id"], data=create_data) + + def _create_dataset_in_folder_in_library(self, library_name): + library = self.library_populator.new_private_library(library_name) + folder_response = self._create_folder(library) + self._assert_status_code_is(folder_response, 200) + folder_id = folder_response.json()[0]['id'] + history_id = self.dataset_populator.new_history() + hda_id = self.dataset_populator.new_dataset(history_id, content="1 2 3")['id'] + payload = {'from_hda_id': hda_id, 'create_type': 'file', 'folder_id': folder_id} + ld = self._post("libraries/%s/contents" % folder_id, payload) + return ld diff --git a/test/api/test_tools.py b/test/api/test_tools.py index 6f98a8dbacb..60f2cd52c35 100644 --- a/test/api/test_tools.py +++ b/test/api/test_tools.py @@ -14,9 +14,33 @@ from base.populators import ( skip_without_tool, uses_test_history, ) +from requests import get from six import BytesIO +MINIMAL_TOOL = { + 'id': "minimal_tool", + 'name': "Minimal Tool", + 'class': "GalaxyTool", + 'version': "1.0.0", + 'command': "echo 'Hello World' > $output1", + 'inputs': [], + 'outputs': dict( + output1=dict(format='txt'), + ) +} +MINIMAL_TOOL_NO_ID = { + 'name': "Minimal Tool", + 'class': "GalaxyTool", + 'version': "1.0.0", + 'command': "echo 'Hello World 2' > $output1", + 'inputs': [], + 'outputs': dict( + output1=dict(format='txt'), + ) +} + + class ToolsTestCase(api.ApiTestCase): def setUp(self): @@ -28,6 +52,12 @@ class ToolsTestCase(api.ApiTestCase): tool_ids = self.__tool_ids() assert "upload1" in tool_ids + @skip_without_tool("cat1") + def test_search(self): + url = self._api_url("tools?q=cat") + get_response = get(url).json() + assert "cat1" in get_response + def test_no_panel_index(self): index = self._get("tools", data=dict(in_panel=False)) tools_index = index.json() @@ -860,6 +890,79 @@ class ToolsTestCase(api.ApiTestCase): output_element_hda_0 = output_element_0["object"] assert output_element_hda_0["metadata_column_types"] is not None + def test_nonadmin_users_cannot_create_tools(self): + payload = dict( + representation=json.dumps(MINIMAL_TOOL), + ) + create_response = self._post("dynamic_tools", data=payload, admin=False) + self._assert_status_code_is(create_response, 403) + + def test_dynamic_tool_1(self): + # Create tool. + self.dataset_populator.create_tool(MINIMAL_TOOL) + + # Run tool. + history_id = self.dataset_populator.new_history() + inputs = {} + self._run("minimal_tool", history_id, inputs) + + self.dataset_populator.wait_for_history(history_id, assert_ok=True) + output_content = self.dataset_populator.get_history_dataset_content(history_id) + self.assertEqual(output_content, "Hello World\n") + + def test_dynamic_tool_no_id(self): + # Create tool. + tool_response = self.dataset_populator.create_tool(MINIMAL_TOOL_NO_ID) + self._assert_has_keys(tool_response, "uuid") + + # Run tool. + history_id = self.dataset_populator.new_history() + inputs = {} + self._run(history_id=history_id, inputs=inputs, tool_uuid=tool_response["uuid"]) + + self.dataset_populator.wait_for_history(history_id, assert_ok=True) + output_content = self.dataset_populator.get_history_dataset_content(history_id) + self.assertEqual(output_content, "Hello World 2\n") + + def test_show_dynamic_tools(self): + # Create tool. + original_list = self.dataset_populator.list_dynamic_tools() + created_dynamic_tool_dict = self.dataset_populator.create_tool(MINIMAL_TOOL_NO_ID) + self._assert_has_keys(created_dynamic_tool_dict, "id", "uuid", "active") + created_id = created_dynamic_tool_dict["id"] + created_uuid = created_dynamic_tool_dict["uuid"] + new_list = self.dataset_populator.list_dynamic_tools() + + for dynamic_tool_dict in original_list: + self._assert_has_keys(dynamic_tool_dict, "id", "uuid", "active") + assert dynamic_tool_dict["id"] != created_id + assert dynamic_tool_dict["uuid"] != created_uuid + + found_id = False + found_uuid = False + for dynamic_tool_dict in new_list: + self._assert_has_keys(dynamic_tool_dict, "id", "uuid", "active") + found_id = found_id or dynamic_tool_dict["id"] == created_id + found_uuid = found_uuid or dynamic_tool_dict["uuid"] == created_uuid + + assert found_id + assert found_uuid + + def test_tool_deactivate(self): + # Create tool. + tool_response = self.dataset_populator.create_tool(MINIMAL_TOOL_NO_ID) + self._assert_has_keys(tool_response, "id", "uuid", "active") + assert tool_response["active"] + deactivate_response = self.dataset_populator.deactivate_dynamic_tool(tool_response["uuid"]) + assert not deactivate_response["active"] + + # Run tool. + history_id = self.dataset_populator.new_history() + inputs = {} + response = self._run(history_id=history_id, inputs=inputs, tool_uuid=tool_response["uuid"], assert_ok=False) + # Get a 404 when trying to run a deactivated tool. + self._assert_status_code_is(response, 404) + @skip_without_tool("cat1") @uses_test_history(require_new=False) def test_run_cat1_with_two_inputs(self, history_id): @@ -1956,12 +2059,16 @@ class ToolsTestCase(api.ApiTestCase): def _run_cat1(self, history_id, inputs, assert_ok=False, **kwargs): return self._run('cat1', history_id, inputs, assert_ok=assert_ok, **kwargs) - def _run(self, tool_id, history_id, inputs, assert_ok=False, tool_version=None, use_cached_job=False, wait_for_job=False): + def _run(self, tool_id=None, history_id=None, inputs={}, tool_uuid=None, assert_ok=False, tool_version=None, use_cached_job=False, wait_for_job=False): + if tool_id is None: + assert tool_uuid is not None payload = self.dataset_populator.run_tool_payload( tool_id=tool_id, inputs=inputs, history_id=history_id, ) + if tool_uuid: + payload['tool_uuid'] = tool_uuid if tool_version is not None: payload["tool_version"] = tool_version if use_cached_job: @@ -2023,6 +2130,48 @@ class ToolsTestCase(api.ApiTestCase): output_content = self.dataset_populator.get_history_dataset_content(history_id, dataset=output) self.assertEqual(output_content.strip(), "123\n456\n456\n0ab") + @skip_without_tool("expression_forty_two") + def test_galaxy_expression_tool_simplest(self): + history_id = self.dataset_populator.new_history() + inputs = { + } + run_response = self._run( + "expression_forty_two", history_id, inputs + ) + self._assert_status_code_is(run_response, 200) + self.dataset_populator.wait_for_history(history_id, assert_ok=True) + output_content = self.dataset_populator.get_history_dataset_content(history_id) + self.assertEqual(output_content, "42") + + @skip_without_tool("expression_parse_int") + def test_galaxy_expression_tool_simple(self): + history_id = self.dataset_populator.new_history() + inputs = { + 'input1': '7', + } + run_response = self._run( + "expression_parse_int", history_id, inputs + ) + self._assert_status_code_is(run_response, 200) + self.dataset_populator.wait_for_history(history_id, assert_ok=True) + output_content = self.dataset_populator.get_history_dataset_content(history_id) + self.assertEqual(output_content, "7") + + @skip_without_tool("expression_log_line_count") + def test_galaxy_expression_metadata(self): + history_id = self.dataset_populator.new_history() + new_dataset1 = self.dataset_populator.new_dataset(history_id, content='1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14') + inputs = { + 'input1': dataset_to_param(new_dataset1), + } + run_response = self._run( + "expression_log_line_count", history_id, inputs + ) + self._assert_status_code_is(run_response, 200) + self.dataset_populator.wait_for_history(history_id, assert_ok=True) + output_content = self.dataset_populator.get_history_dataset_content(history_id) + self.assertEqual(output_content, "3") + def __build_group_list(self, history_id): response = self.dataset_collection_populator.upload_collection(history_id, "list", elements=[ { diff --git a/test/api/test_users.py b/test/api/test_users.py index 4ea433f5fd2..aaf8d091b11 100644 --- a/test/api/test_users.py +++ b/test/api/test_users.py @@ -1,11 +1,13 @@ import json from requests import ( + delete, get, put ) from base import api # noqa: I100,I202 +from base.populators import skip_without_tool TEST_USER_EMAIL = "user_for_users_index_test@bx.psu.edu" @@ -91,6 +93,44 @@ class UsersApiTestCase(api.ApiTestCase): self.assertEqual(len(response["addresses"]), 1) self.assertEqual(response["addresses"][0]["desc"], "_desc") + @skip_without_tool("cat1") + def test_favorites(self): + user = self._setup_user(TEST_USER_EMAIL) + # adding a tool to favorites + url = self._api_url("users/%s/favorites/tools" % user["id"], params=dict(key=self.master_api_key)) + put_response = put(url, data=json.dumps({"object_id" : "cat1"})) + self._assert_status_code_is_ok(put_response) + self.assertEqual(put_response.json()["tools"][0], "cat1") + # not implemented for workflows yet + url = self._api_url("users/%s/favorites/workflows" % user["id"], params=dict(key=self.master_api_key)) + put_response = put(url, data=json.dumps({"object_id" : "14ds68f4sda68gf46dsag4"})) + self._assert_status_code_is(put_response, 400) + # delete existing tool favorite + url = self._api_url("users/%s/favorites/tools/cat1" % user["id"], params=dict(key=self.master_api_key)) + delete_response = delete(url) + self._assert_status_code_is_ok(delete_response) + self.assertEqual(delete_response.json()["tools"], []) + # delete non-existing tool favorite + url = self._api_url("users/%s/favorites/tools/madeuptoolthatdoes/not/exist/in/favs" % user["id"], params=dict(key=self.master_api_key)) + delete_response = delete(url) + self._assert_status_code_is(delete_response, 404) + # delete non existing workflow favorite + url = self._api_url("users/%s/favorites/workflows/1as5das5das56d465" % user["id"], params=dict(key=self.master_api_key)) + delete_response = delete(url) + self._assert_status_code_is(delete_response, 400) + + @skip_without_tool("cat1") + def test_search_favorites(self): + user = self._setup_user(TEST_USER_EMAIL) + user_key = self._setup_user_get_key(TEST_USER_EMAIL) + url = self._api_url("users/%s/favorites/tools" % user["id"], params=dict(key=user_key)) + fav_response = put(url, data=json.dumps({"object_id" : "cat1"})) + self._assert_status_code_is_ok(fav_response) + assert "cat1" in fav_response.json()["tools"] + url = self._api_url("tools", params=dict(q="#favs", key=user_key)) + search_response = get(url).json() + assert "cat1" in search_response + def test_communication(self): user = self._setup_user(TEST_USER_EMAIL) url = self.__url("communication/inputs", user) diff --git a/test/api/test_workflow_extraction.py b/test/api/test_workflow_extraction.py index 3e78c19b532..9ac877e07a2 100644 --- a/test/api/test_workflow_extraction.py +++ b/test/api/test_workflow_extraction.py @@ -24,6 +24,7 @@ class WorkflowExtractionApiTestCase(BaseWorkflowsApiTestCase): contents = self._history_contents() input_hids = [c["hid"] for c in contents[0:2]] downloaded_workflow = self._extract_and_download_workflow( + reimport_as="extract_from_history_basic", dataset_ids=input_hids, job_ids=[cat1_job_id], ) @@ -49,17 +50,45 @@ class WorkflowExtractionApiTestCase(BaseWorkflowsApiTestCase): new_contents = self._history_contents() input_hids = [c["hid"] for c in new_contents[(offset + 0):(offset + 2)]] cat1_job_id = self.__job_id(self.history_id, new_contents[(offset + 2)]["id"]) + + def reimport_jobs_ids(new_history_id): + return [j["id"] for j in self.dataset_populator.history_jobs(new_history_id) if j["tool_id"] == "cat1"] + downloaded_workflow = self._extract_and_download_workflow( dataset_ids=input_hids, job_ids=[cat1_job_id], ) self.__assert_looks_like_cat1_example_workflow(downloaded_workflow) + @summarize_instance_history_on_error + def test_extract_with_copied_inputs_reimported(self): + old_history_id = self.dataset_populator.new_history() + # Run the simple test workflow and extract it back out from history + self.__setup_and_run_cat1_workflow(history_id=old_history_id) + + offset = 0 + old_contents = self._history_contents(old_history_id) + for old_dataset in old_contents: + self.__copy_content_to_history(self.history_id, old_dataset) + new_contents = self._history_contents() + input_hids = [c["hid"] for c in new_contents[(offset + 0):(offset + 2)]] + + def reimport_jobs_ids(new_history_id): + return [j["id"] for j in self.dataset_populator.history_jobs(new_history_id) if j["tool_id"] == "cat1"] + + downloaded_workflow = self._extract_and_download_workflow( + reimport_as="test_extract_with_copied_inputs", + reimport_jobs_ids=reimport_jobs_ids, + dataset_ids=input_hids, + ) + self.__assert_looks_like_cat1_example_workflow(downloaded_workflow) + @skip_without_tool("random_lines1") @summarize_instance_history_on_error def test_extract_mapping_workflow_from_history(self): hdca, job_id1, job_id2 = self.__run_random_lines_mapped_over_pair(self.history_id) downloaded_workflow = self._extract_and_download_workflow( + reimport_as="extract_from_history_with_mapping", dataset_collection_ids=[hdca["hid"]], job_ids=[job_id1, job_id2], ) @@ -81,6 +110,34 @@ class WorkflowExtractionApiTestCase(BaseWorkflowsApiTestCase): ) self.__assert_looks_like_randomlines_mapping_workflow(downloaded_workflow) + def test_extract_copied_mapping_from_history_reimported(self): + import unittest + raise unittest.SkipTest("Mapping connection for copied collections not yet implemented in history import/export") + + old_history_id = self.dataset_populator.new_history() + hdca, job_id1, job_id2 = self.__run_random_lines_mapped_over_singleton(old_history_id) + + old_contents = self._history_contents(old_history_id) + for old_content in old_contents: + self.__copy_content_to_history(self.history_id, old_content) + + def reimport_jobs_ids(new_history_id): + rval = [j["id"] for j in self.dataset_populator.history_jobs(new_history_id) if j["tool_id"] == "random_lines1"] + assert len(rval) == 2 + print(rval) + return rval + + # API test is somewhat contrived since there is no good way + # to retrieve job_id1, job_id2 like this for copied dataset + # collections I don't think. + downloaded_workflow = self._extract_and_download_workflow( + reimport_as="test_extract_from_history_with_mapped_collection_reimport", + reimport_jobs_ids=reimport_jobs_ids, + reimport_wait_on_history_length=9, # see comments in _extract about eliminating this magic constant. + dataset_collection_ids=[hdca["hid"]], + ) + self.__assert_looks_like_randomlines_mapping_workflow(downloaded_workflow) + @skip_without_tool("random_lines1") @skip_without_tool("multi_data_param") def test_extract_reduction_from_history(self): @@ -101,8 +158,10 @@ class WorkflowExtractionApiTestCase(BaseWorkflowsApiTestCase): history_id=self.history_id, ) job_id2 = reduction_run_output["jobs"][0]["id"] + self.dataset_populator.wait_for_job(job_id2, assert_ok=True) self.dataset_populator.wait_for_history(self.history_id, assert_ok=True) downloaded_workflow = self._extract_and_download_workflow( + reimport_as="extract_from_history_with_reduction", dataset_collection_ids=[hdca["hid"]], job_ids=[job_id1, job_id2], ) @@ -111,7 +170,12 @@ class WorkflowExtractionApiTestCase(BaseWorkflowsApiTestCase): tool_steps = self._get_steps_of_type(downloaded_workflow, "tool", expected_len=2) random_lines_map_step = tool_steps[0] reduction_step = tool_steps[1] - random_lines_input = random_lines_map_step["input_connections"]["input"] + assert "tool_id" in random_lines_map_step, random_lines_map_step + assert random_lines_map_step["tool_id"] == "random_lines1", random_lines_map_step + assert "input_connections" in random_lines_map_step, random_lines_map_step + random_lines_input_connections = random_lines_map_step["input_connections"] + assert "input" in random_lines_input_connections, random_lines_map_step + random_lines_input = random_lines_input_connections["input"] assert random_lines_input["id"] == collect_step_idx reduction_step_input = reduction_step["input_connections"]["f1"] assert reduction_step_input["id"] == random_lines_map_step["id"] @@ -133,6 +197,7 @@ test_data: """) job_id = self._job_id_for_tool(jobs_summary.jobs, "collection_paired_test") downloaded_workflow = self._extract_and_download_workflow( + reimport_as="extract_from_history_with_basic_collections", dataset_collection_ids=["1"], job_ids=[job_id], ) @@ -172,10 +237,10 @@ test_data: job1_id = self._job_id_for_tool(jobs_summary.jobs, "cat1") job2_id = self._job_id_for_tool(jobs_summary.jobs, "cat_collection") downloaded_workflow = self._extract_and_download_workflow( + reimport_as="test_extract_workflows_with_subcollection_mapping", dataset_collection_ids=["1"], job_ids=[job1_id, job2_id], ) - print(jobs_summary.inputs["text_input1"]) self.__check_workflow( downloaded_workflow, step_count=3, @@ -222,6 +287,7 @@ test_data: tool_ids = ["cat1", "collection_split_on_column", "cat_list"] job_ids = [functools.partial(self._job_id_for_tool, jobs_summary.jobs)(_) for _ in tool_ids] downloaded_workflow = self._extract_and_download_workflow( + reimport_as="test_extract_workflows_with_output_collections", dataset_ids=["1", "2"], job_ids=job_ids, ) @@ -273,6 +339,7 @@ test_data: tool_ids = ["cat1", "collection_creates_pair", "cat_collection", "cat_list"] job_ids = [functools.partial(self._job_id_for_tool, jobs_summary.jobs)(_) for _ in tool_ids] downloaded_workflow = self._extract_and_download_workflow( + reimport_as="test_extract_workflows_with_mapped_output_collections", dataset_collection_ids=["1"], job_ids=job_ids, ) @@ -311,6 +378,21 @@ test_data: _, job_id2 = self._run_tool_get_collection_and_job_id(history_id, "random_lines1", inputs2) return hdca, job_id1, job_id2 + def __run_random_lines_mapped_over_singleton(self, history_id): + hdca = self.dataset_collection_populator.create_list_in_history(history_id, contents=["1 2 3\n4 5 6"]).json() + hdca_id = hdca["id"] + inputs1 = { + "input": {"batch": True, "values": [{"src": "hdca", "id": hdca_id}]}, + "num_lines": 2 + } + implicit_hdca1, job_id1 = self._run_tool_get_collection_and_job_id(history_id, "random_lines1", inputs1) + inputs2 = { + "input": {"batch": True, "values": [{"src": "hdca", "id": implicit_hdca1["id"]}]}, + "num_lines": 1 + } + _, job_id2 = self._run_tool_get_collection_and_job_id(history_id, "random_lines1", inputs2) + return hdca, job_id1, job_id2 + def __assert_looks_like_randomlines_mapping_workflow(self, downloaded_workflow): # Assert workflow is input connected to a tool step with one output # connected to another tool step. @@ -380,6 +462,54 @@ test_data: return collect_step_idx def _extract_and_download_workflow(self, **extract_payload): + reimport_as = extract_payload.get("reimport_as") + + if reimport_as: + history_name = reimport_as + history_id = self.history_id + self.dataset_populator.wait_for_history(history_id) + self.dataset_populator.rename_history(history_id, history_name) + + history_length = extract_payload.get("reimport_wait_on_history_length") + if history_length is None: + # sometimes this won't be the same (i.e. datasets copied from outside the history + # that need to be included in target history for collections), but we can provide + # a reasonable default for fully in-history imports. + history_length = self.dataset_populator.history_length(history_id) + + new_history_id = self.dataset_populator.reimport_history( + history_id, history_name, wait_on_history_length=history_length, export_kwds={}, url=self.url, api_key=self.galaxy_interactor.api_key + ) + # wait a little more for those jobs, todo fix to wait for history imported false or + # for a specific number of jobs... + import time + time.sleep(1) + + if "reimport_jobs_ids" in extract_payload: + new_history_job_ids = extract_payload["reimport_jobs_ids"](new_history_id) + extract_payload["job_ids"] = new_history_job_ids + else: + # Assume no copying or anything so just straight map job ids by index. + + # Jobs are created after datasets, need to also wait on those... + history_jobs = [j for j in self.dataset_populator.history_jobs(history_id) if j["tool_id"] != "__EXPORT_HISTORY__"] + new_history_jobs = [j for j in self.dataset_populator.history_jobs(new_history_id) if j["tool_id"] != "__EXPORT_HISTORY__"] + + history_job_ids = [j["id"] for j in history_jobs] + new_history_job_ids = [j["id"] for j in new_history_jobs] + + assert len(history_job_ids) == len(new_history_job_ids) + + if "job_ids" in extract_payload: + job_ids = extract_payload["job_ids"] + new_job_ids = [] + for i, job_id in enumerate(job_ids): + new_job_ids.append(new_history_job_ids[history_job_ids.index(job_id)]) + + extract_payload["job_ids"] = new_job_ids + + self.history_id = new_history_id + if "from_history_id" not in extract_payload: extract_payload["from_history_id"] = self.history_id diff --git a/test/api/test_workflows.py b/test/api/test_workflows.py index b0c2d33e1d2..2e8e4f07725 100644 --- a/test/api/test_workflows.py +++ b/test/api/test_workflows.py @@ -203,9 +203,6 @@ class BaseWorkflowsApiTestCase(api.ApiTestCase): # - Much more testing obviously, always more testing. class WorkflowsApiTestCase(BaseWorkflowsApiTestCase): - def setUp(self): - super(WorkflowsApiTestCase, self).setUp() - def test_show_valid(self): workflow_id = self.workflow_populator.simple_workflow("dummy") workflow_id = self.workflow_populator.simple_workflow("test_regular") @@ -385,6 +382,40 @@ class WorkflowsApiTestCase(BaseWorkflowsApiTestCase): self._assert_status_code_is(other_import_response, 200) self._assert_user_has_workflow_with_name("imported: test_import_published_deprecated") + def test_import_export_dynamic(self): + workflow_id = self._upload_yaml_workflow(""" +class: GalaxyWorkflow +steps: + - type: input + label: input1 + - tool_id: cat1 + label: first_cat + state: + input1: + $link: 0 + - label: embed1 + run: + class: GalaxyTool + command: echo 'hello world 2' > $output1 + outputs: + output1: + format: txt + - tool_id: cat1 + state: + input1: + $link: first_cat#out_file1 + queries: + input2: + $link: embed1#output1 +test_data: + input1: "hello world" +""") + downloaded_workflow = self._download_workflow(workflow_id) + # The _upload_yaml_workflow entry point uses an admin key, but if we try to + # do the raw re-import as a regular user we expect a 403 error. + response = self.workflow_populator.create_workflow_response(downloaded_workflow) + self._assert_status_code_is(response, 403) + def test_import_annotations(self): workflow_id = self.workflow_populator.simple_workflow("test_import_annotations", publish=True) with self._different_user(): @@ -491,6 +522,50 @@ class WorkflowsApiTestCase(BaseWorkflowsApiTestCase): 'label', ) + @skip_without_tool('output_filter_with_input') + def test_export_editor_filtered_outputs(self): + template = """ +class: GalaxyWorkflow +steps: + - tool_id: output_filter_with_input + state: + produce_out_1: {produce_out_1} + filter_text_1: {filter_text_1} + produce_collection: false + produce_paired_collection: false +""" + workflow_id = self._upload_yaml_workflow(template.format(produce_out_1='false', filter_text_1='false')) + downloaded_workflow = self._download_workflow(workflow_id, style="editor") + outputs = downloaded_workflow['steps']['0']['outputs'] + assert len(outputs) == 1 + assert outputs[0]['name'] == 'out_3' + workflow_id = self._upload_yaml_workflow(template.format(produce_out_1='true', filter_text_1='false')) + downloaded_workflow = self._download_workflow(workflow_id, style="editor") + outputs = downloaded_workflow['steps']['0']['outputs'] + assert len(outputs) == 2 + assert outputs[0]['name'] == 'out_1' + assert outputs[1]['name'] == 'out_3' + workflow_id = self._upload_yaml_workflow(template.format(produce_out_1='true', filter_text_1='foo')) + downloaded_workflow = self._download_workflow(workflow_id, style="editor") + outputs = downloaded_workflow['steps']['0']['outputs'] + assert len(outputs) == 3 + assert outputs[0]['name'] == 'out_1' + assert outputs[1]['name'] == 'out_2' + assert outputs[2]['name'] == 'out_3' + + @skip_without_tool('output_filter_exception_1') + def test_export_editor_filtered_outputs_exception_handling(self): + workflow_id = self._upload_yaml_workflow(""" +class: GalaxyWorkflow +steps: + - tool_id: output_filter_exception_1 +""") + downloaded_workflow = self._download_workflow(workflow_id, style="editor") + outputs = downloaded_workflow['steps']['0']['outputs'] + assert len(outputs) == 2 + assert outputs[0]['name'] == 'out_1' + assert outputs[1]['name'] == 'out_2' + @skip_without_tool('collection_type_source') def test_export_editor_collection_type_source(self): workflow_id = self._upload_yaml_workflow(""" @@ -507,7 +582,9 @@ steps: downloaded_workflow = self._download_workflow(workflow_id, style="editor") steps = downloaded_workflow['steps'] assert len(steps) == 2 - assert steps['1']['outputs'][0]['collection_type'] == 'list:paired' + # Non-subworkflow collection_type_source tools will be handled by the client, + # so collection_type should be None here. + assert steps['1']['outputs'][0]['collection_type'] is None @skip_without_tool('collection_type_source') def test_export_editor_subworkflow_collection_type_source(self): @@ -745,12 +822,14 @@ test_data: """, history_id=history_id, assert_ok=False, wait=False) self.wait_for_invocation_and_jobs(history_id, job_summary.workflow_id, job_summary.invocation_id, assert_ok=False) history_contents = self.dataset_populator._get_contents_request(history_id=history_id).json() + first_input = history_contents[1] + assert first_input['history_content_type'] == 'dataset' paused_dataset = history_contents[-1] failed_dataset = self.dataset_populator.get_history_dataset_details(history_id, hid=5, assert_ok=False) assert paused_dataset['state'] == 'paused', paused_dataset assert failed_dataset['state'] == 'error', failed_dataset inputs = {"input1": {'values': [{'src': 'hda', - 'id': history_contents[0]['id']}] + 'id': first_input['id']}] }, "failbool": "false", "rerun_remap_job_id": failed_dataset['creating_job']} @@ -766,6 +845,32 @@ test_data: replaced_hda = self.dataset_populator.get_history_dataset_details(history_id, dataset_id=replaced_hda_id, wait=True, assert_ok=False) assert not replaced_hda['visible'], replaced_hda + @skip_without_tool('multi_data_optional') + def test_workflow_list_list_multi_data_map_over(self): + # Test that a list:list is reduced to list with a multiple="true" data input + with self.dataset_populator.test_history() as history_id: + workflow_id = self._upload_yaml_workflow(""" +class: GalaxyWorkflow +inputs: + input_datasets: collection +steps: + multi_data_optional: + tool_id: multi_data_optional + in: + input1: input_datasets +""") + with self.dataset_populator.test_history() as history_id: + hdca_id = self.dataset_collection_populator.create_list_of_list_in_history(history_id).json() + self.dataset_populator.wait_for_history(history_id, assert_ok=True) + inputs = { + '0': self._ds_entry(hdca_id), + } + invocation_id = self.__invoke_workflow(history_id, workflow_id, inputs) + self.wait_for_invocation_and_jobs(history_id, workflow_id, invocation_id) + output_collection = self.dataset_populator.get_history_collection_details(history_id, hid=6) + assert output_collection['collection_type'] == 'list' + assert output_collection['job_source_type'] == 'ImplicitCollectionJobs' + @skip_without_tool("cat_list") @skip_without_tool("collection_creates_pair") def test_workflow_run_output_collection_mapping(self): @@ -778,7 +883,6 @@ test_data: } invocation_id = self.__invoke_workflow(history_id, workflow_id, inputs) self.wait_for_invocation_and_jobs(history_id, workflow_id, invocation_id) - self.dataset_populator.wait_for_history(history_id, assert_ok=True) self.assertEqual("a\nc\nb\nd\ne\ng\nf\nh\n", self.dataset_populator.get_history_dataset_content(history_id, hid=0)) @skip_without_tool("cat_list") @@ -986,19 +1090,19 @@ test_data: assert len([x for x in content.split("\n") if x]) == 2 def test_run_subworkflow_auto_labels(self): - history_id = self.dataset_populator.new_history() - test_data = """ + history_id = self.dataset_populator.new_history() + test_data = """ outer_input: value: 1.bed type: File """ - job_summary = self._run_jobs(NESTED_WORKFLOW_AUTO_LABELS, test_data=test_data, history_id=history_id) - assert len(job_summary.jobs) == 4, "4 jobs expected, got %d jobs" % len(job_summary.jobs) + job_summary = self._run_jobs(NESTED_WORKFLOW_AUTO_LABELS, test_data=test_data, history_id=history_id) + assert len(job_summary.jobs) == 4, "4 jobs expected, got %d jobs" % len(job_summary.jobs) - content = self.dataset_populator.get_history_dataset_content(history_id) - self.assertEqual( - "chrX\t152691446\t152691471\tCCDS14735.1_cds_0_0_chrX_152691447_f\t0\t+\nchrX\t152691446\t152691471\tCCDS14735.1_cds_0_0_chrX_152691447_f\t0\t+\n", - content) + content = self.dataset_populator.get_history_dataset_content(history_id) + self.assertEqual( + "chrX\t152691446\t152691471\tCCDS14735.1_cds_0_0_chrX_152691447_f\t0\t+\nchrX\t152691446\t152691471\tCCDS14735.1_cds_0_0_chrX_152691447_f\t0\t+\n", + content) @skip_without_tool("cat1") @skip_without_tool("collection_paired_test") @@ -1061,6 +1165,41 @@ steps: assert len(identifiers) == 6 assert "oe1-ie1" in identifiers + @skip_without_tool("collection_paired_test") + def test_workflow_flatten_with_mapped_over_execution(self): + with self.dataset_populator.test_history() as history_id: + self._run_jobs(r""" +class: GalaxyWorkflow +inputs: + input_fastqs: collection +steps: + split_up: + tool_id: collection_split_on_column + in: + input1: input_fastqs + flatten: + tool_id: '__FLATTEN__' + in: + input: split_up/split_output + join_identifier: '-' +test_data: + input_fastqs: + type: list + elements: + - identifier: samp1 + content: "0\n1" +""", history_id=history_id) + history = self._get('histories/%s/contents' % history_id).json() + flattened_collection = history[-1] + assert flattened_collection['history_content_type'] == 'dataset_collection' + assert flattened_collection['collection_type'] == 'list' + assert flattened_collection['element_count'] == 2 + nested_collection = self.dataset_populator.get_history_collection_details(history_id, hid=3) + assert nested_collection['collection_type'] == 'list:list' + assert nested_collection['element_count'] == 1 + assert nested_collection['elements'][0]['object']['populated'] + assert nested_collection['elements'][0]['object']['element_count'] == 2 + @skip_without_tool("__APPLY_RULES__") def test_workflow_run_apply_rules(self): with self.dataset_populator.test_history() as history_id: @@ -1541,6 +1680,32 @@ input1: """, history_id=history_id, wait=True) self.assertEqual("0\n", self.dataset_populator.get_history_dataset_content(history_id)) + @skip_without_tool("random_lines1") + def test_change_datatype_collection_map_over(self): + with self.dataset_populator.test_history() as history_id: + jobs_summary = self._run_jobs(""" +class: GalaxyWorkflow +inputs: + text_input1: collection +steps: + map_over: + tool_id: random_lines1 + in: + input: text_input1 + outputs: + out_file1: + change_datatype: csv +""", test_data=""" +text_input1: + type: "list:paired" +""", history_id=history_id) + hdca = self.dataset_populator.get_history_collection_details(history_id=jobs_summary.history_id, hid=4) + assert hdca['collection_type'] == 'list:paired' + assert len(hdca['elements'][0]['object']["elements"]) == 2 + forward, reverse = hdca['elements'][0]['object']["elements"] + assert forward['object']['file_ext'] == 'csv' + assert reverse['object']['file_ext'] == 'csv' + @skip_without_tool("collection_type_source_map_over") def test_mapping_and_subcollection_mapping(self): with self.dataset_populator.test_history() as history_id: @@ -1868,7 +2033,7 @@ text_input: type: raw """, history_id=history_id, wait=True, assert_ok=False) - def test_run_with_text_connection(self): + def test_run_with_text_input_connection(self): with self.dataset_populator.test_history() as history_id: self._run_jobs(""" class: GalaxyWorkflow @@ -1899,6 +2064,33 @@ text_input: content = self.dataset_populator.get_history_dataset_content(history_id) self.assertEqual("chrX\t152691446\t152691471\tCCDS14735.1_cds_0_0_chrX_152691447_f\t0\t+\n", content) + def test_run_with_numeric_input_connection(self): + history_id = self.dataset_populator.new_history() + self._run_jobs(""" +class: GalaxyWorkflow +steps: +- label: forty_two + tool_id: expression_forty_two + state: {} +- label: consume_expression_parameter + tool_id: cheetah_casting + state: + floattest: 3.14 + inttest: + $link: forty_two#out1 +test_data: {} +""", history_id=history_id) + + self.dataset_populator.wait_for_history(history_id, assert_ok=True) + content = self.dataset_populator.get_history_dataset_content(history_id) + lines = content.split("\n") + assert len(lines) == 4 + str_43 = lines[0] + str_4point14 = lines[2] + assert lines[3] == "" + assert int(str_43) == 43 + assert abs(float(str_4point14) - 4.14) < .0001 + @skip_without_tool('cat1') def test_workflow_rerun_with_use_cached_job(self): workflow = self.workflow_populator.load_workflow(name="test_for_run") @@ -2049,6 +2241,23 @@ outer_input: content = self.dataset_populator.get_history_dataset_details(history_id, wait=True, assert_ok=True) assert content["name"] == "foo was replaced" + @skip_without_tool("hidden_param") + def test_hidden_param_in_workflow(self): + with self.dataset_populator.test_history() as history_id: + run_object = self._run_jobs(""" +class: GalaxyWorkflow +steps: + step1: + tool_id: hidden_param +""", test_data={}, history_id=history_id, wait=False) + self.wait_for_invocation_and_jobs(history_id, run_object.workflow_id, run_object.invocation_id) + contents = self.__history_contents(history_id) + assert len(contents) == 1 + okay_dataset = contents[0] + assert okay_dataset["state"] == "ok" + content = self.dataset_populator.get_history_dataset_content(history_id, hid=1) + assert content == '1\n' + @skip_without_tool("output_filter") def test_optional_workflow_output(self): with self.dataset_populator.test_history() as history_id: @@ -2159,7 +2368,121 @@ input1: name: fasta1 """, history_id=history_id) details1 = self.dataset_populator.get_history_collection_details(history_id, hid=4, wait=True, assert_ok=True) + assert details1['elements'][0]['object']['visible'] is False + assert details1["name"] == "my new name", details1 + assert details1["history_content_type"] == "dataset_collection" + @skip_without_tool("__BUILD_LIST__") + def test_run_build_list_hide_collection_output(self): + with self.dataset_populator.test_history() as history_id: + self._run_jobs(""" +class: GalaxyWorkflow +inputs: + input1: data +steps: + - tool_id: __BUILD_LIST__ + in: + datasets_0|input: input1 + outputs: + output: + hide: true +""", test_data=""" +input1: + value: 1.fasta + type: File + name: fasta1 +""", history_id=history_id) + details1 = self.dataset_populator.get_history_collection_details(history_id, hid=3, wait=True, assert_ok=True) + assert details1['elements'][0]['object']['visible'] is False + assert details1["name"] == "data 1 (as list)", details1 + assert details1["visible"] is False + + @skip_without_tool("__BUILD_LIST__") + def test_run_build_list_delete_intermediate_collection_output(self): + with self.dataset_populator.test_history() as history_id: + self._run_jobs(""" +class: GalaxyWorkflow +inputs: + input1: data +steps: + - tool_id: __BUILD_LIST__ + in: + datasets_0|input: input1 + outputs: + output: + delete_intermediate_datasets: true +""", test_data=""" +input1: + value: 1.fasta + type: File + name: fasta1 +""", history_id=history_id) + details1 = self.dataset_populator.get_history_collection_details(history_id, hid=3, wait=True, + assert_ok=True) + assert details1['elements'][0]['object']['visible'] is False + assert details1["name"] == "data 1 (as list)", details1 + # FIXME: this doesn't work because the workflow is still being scheduled + # TODO: Implement a way to run PJAs that couldn't be run during/after the job + # after the workflow has run to completion + assert details1["deleted"] is False + + @skip_without_tool("__BUILD_LIST__") + def test_run_build_list_change_datatype_collection_output(self): + with self.dataset_populator.test_history() as history_id: + self._run_jobs(""" +class: GalaxyWorkflow +inputs: + input1: data +steps: + - tool_id: __BUILD_LIST__ + in: + datasets_0|input: input1 + outputs: + output: + change_datatype: txt + - tool_id: __BUILD_LIST__ + in: + datasets_0|input: input1 +""", test_data=""" +input1: + value: 1.fasta + type: File + file_type: fasta + name: fasta1 +""", history_id=history_id) + details1 = self.dataset_populator.get_history_collection_details(history_id, hid=3, wait=True, + assert_ok=True) + assert details1["name"] == "data 1 (as list)", details1 + assert details1['elements'][0]['object']['visible'] is False + assert details1['elements'][0]['object']['file_ext'] == 'txt' + details2 = self.dataset_populator.get_history_collection_details(history_id, hid=5, wait=True, + assert_ok=True) + # Also check that we don't overwrite the original HDA's datatype + assert details2['elements'][0]['object']['file_ext'] == 'fasta' + + @skip_without_tool("__BUILD_LIST__") + def test_run_build_list_rename_collection_output(self): + with self.dataset_populator.test_history() as history_id: + self._run_jobs(""" +class: GalaxyWorkflow +inputs: + input1: data +steps: + - tool_id: __BUILD_LIST__ + in: + datasets_0|input: input1 + outputs: + output: + rename: "my new name" +""", test_data=""" +input1: + value: 1.fasta + type: File + name: fasta1 +""", history_id=history_id) + details1 = self.dataset_populator.get_history_collection_details(history_id, hid=3, wait=True, + assert_ok=True) + assert details1['elements'][0]['object']['visible'] is False assert details1["name"] == "my new name", details1 assert details1["history_content_type"] == "dataset_collection" @@ -2961,6 +3284,9 @@ input: with self._different_user(): usage_details_response = self._get("workflows/%s/usage/%s" % (workflow_id, usage["id"])) self._assert_status_code_is(usage_details_response, 403) + index_response = self._get("workflows/%s/invocations" % workflow_id) + self._assert_status_code_is(index_response, 200) + assert len(index_response.json()) == 0 invocation_ids = self._all_user_invocation_ids() assert usage["id"] in invocation_ids @@ -3134,18 +3460,18 @@ input_c: self._assert_status_code_is(usage_details_response, 403) def _invoke_paused_workflow(self, history_id): - workflow = self.workflow_populator.load_workflow_from_resource("test_workflow_pause") - workflow_id = self.workflow_populator.create_workflow(workflow) - hda1 = self.dataset_populator.new_dataset(history_id, content="1 2 3") - index_map = { - '0': self._ds_entry(hda1), - } - invocation_id = self.__invoke_workflow( - history_id, - workflow_id, - index_map, - ) - return workflow_id, invocation_id + workflow = self.workflow_populator.load_workflow_from_resource("test_workflow_pause") + workflow_id = self.workflow_populator.create_workflow(workflow) + hda1 = self.dataset_populator.new_dataset(history_id, content="1 2 3") + index_map = { + '0': self._ds_entry(hda1), + } + invocation_id = self.__invoke_workflow( + history_id, + workflow_id, + index_map, + ) + return workflow_id, invocation_id def _wait_for_invocation_non_new(self, workflow_id, invocation_id): target_state_reached = False @@ -3307,3 +3633,49 @@ input_c: self._assert_status_code_is(all_invocations_for_user, 200) invocation_ids = [i["id"] for i in all_invocations_for_user.json()] return invocation_ids + + +class AdminWorkflowsApiTestCase(BaseWorkflowsApiTestCase): + + require_admin_user = True + + def test_import_export_dynamic_tools(self): + workflow_id = self._upload_yaml_workflow(""" +class: GalaxyWorkflow +steps: + - type: input + label: input1 + - tool_id: cat1 + label: first_cat + state: + input1: + $link: 0 + - label: embed1 + run: + class: GalaxyTool + command: echo 'hello world 2' > $output1 + outputs: + output1: + format: txt + - tool_id: cat1 + state: + input1: + $link: first_cat#out_file1 + queries: + - input2: + $link: embed1#output1 +test_data: + input1: "hello world" +""") + downloaded_workflow = self._download_workflow(workflow_id) + response = self.workflow_populator.create_workflow_response(downloaded_workflow) + workflow_id = response.json()["id"] + history_id = self.dataset_populator.new_history() + hda1 = self.dataset_populator.new_dataset(history_id, content="Hello World Second!") + workflow_request = dict( + inputs_by="name", + inputs=json.dumps({'input1': self._ds_entry(hda1)}), + ) + invocation_id = self.workflow_populator.invoke_workflow(history_id, workflow_id, request=workflow_request, assert_ok=True) + self.wait_for_invocation_and_jobs(history_id, workflow_id, invocation_id) + self.assertEqual("Hello World Second!\nhello world 2\n", self.dataset_populator.get_history_dataset_content(history_id, hid=4)) diff --git a/test/api/test_workflows_from_yaml.py b/test/api/test_workflows_from_yaml.py index 71a33d7bd15..f6b7d1e54de 100644 --- a/test/api/test_workflows_from_yaml.py +++ b/test/api/test_workflows_from_yaml.py @@ -275,6 +275,54 @@ steps: assert "no file specified" in content assert "7 7 5" in content + def test_workflow_embed_tool(self): + history_id = self.dataset_populator.new_history() + self._run_jobs(""" +class: GalaxyWorkflow +steps: + - type: input + label: input1 + - tool_id: cat1 + label: first_cat + state: + input1: + $link: 0 + - label: embed1 + run: + class: GalaxyTool + command: echo 'hello world 2' > $output1 + outputs: + output1: + format: txt + - tool_id: cat1 + state: + input1: + $link: first_cat#out_file1 + queries: + - input2: + $link: embed1#output1 +test_data: + input1: "hello world" +""", history_id=history_id) + + content = self.dataset_populator.get_history_dataset_content(history_id) + self.assertEquals(content, "hello world\nhello world 2\n") + + def test_workflow_import_tool(self): + history_id = self.dataset_populator.new_history() + workflow_path = os.path.join(WORKFLOWS_DIRECTORY, "embed_test_1.gxwf.yml") + jobs_descriptions = { + "test_data": {"input1": "hello world"} + } + self._run_jobs( + workflow_path, + source_type="path", + jobs_descriptions=jobs_descriptions, + history_id=history_id + ) + content = self.dataset_populator.get_history_dataset_content(history_id) + self.assertEquals(content, "hello world\nhello world 2\n") + def _steps_by_label(self, workflow_as_dict): by_label = {} assert "steps" in workflow_as_dict, workflow_as_dict diff --git a/test/base/api.py b/test/base/api.py index 9ec0a7e44a2..8b98da97abf 100644 --- a/test/base/api.py +++ b/test/base/api.py @@ -7,6 +7,7 @@ from .api_asserts import ( assert_has_keys, assert_not_has_keys, assert_status_code_is, + assert_status_code_is_ok, ) from .api_util import ( ADMIN_TEST_USER, @@ -84,6 +85,9 @@ class UsesApiTestCaseMixin(object): def _patch(self, *args, **kwds): return self.galaxy_interactor.patch(*args, **kwds) + def _assert_status_code_is_ok(self, response): + assert_status_code_is_ok(response) + def _assert_status_code_is(self, response, expected_status_code): assert_status_code_is(response, expected_status_code) diff --git a/test/base/driver_util.py b/test/base/driver_util.py index fe9fae1cf78..42b275cb9b0 100644 --- a/test/base/driver_util.py +++ b/test/base/driver_util.py @@ -25,9 +25,15 @@ from six.moves import ( shlex_quote ) from six.moves.urllib.parse import urlparse +from sqlalchemy_utils import ( + create_database, + database_exists, +) from galaxy.app import UniverseApplication as GalaxyUniverseApplication from galaxy.config import LOGGING_CONFIG_DEFAULT +from galaxy.model import mapping +from galaxy.model.tool_shed_install import mapping as toolshed_mapping from galaxy.tools.verify.interactor import GalaxyInteractorApi, verify_tool from galaxy.util import asbool, download_to_file from galaxy.util.properties import load_app_properties @@ -201,7 +207,6 @@ def setup_galaxy_config( cleanup_job='onsuccess', data_manager_config_file=data_manager_config_file, enable_beta_tool_formats=True, - enable_beta_workflow_format=True, expose_dataset_path=True, file_path=file_path, ftp_upload_purge=False, @@ -229,6 +234,7 @@ def setup_galaxy_config( webhooks_dir=TEST_WEBHOOKS_DIR, logging=LOGGING_CONFIG_DEFAULT, monitor_thread_join_timeout=5, + object_store_store_by="uuid", ) config.update(database_conf(tmpdir, prefer_template_database=prefer_template_database)) config.update(install_database_conf(tmpdir, default_merged=default_install_db_merged)) @@ -311,6 +317,7 @@ def copy_database_template(source, db_path): def database_conf(db_path, prefix="GALAXY", prefer_template_database=False): """Find (and populate if needed) Galaxy database connection.""" database_auto_migrate = False + check_migrate_databases = True dburi_var = "%s_TEST_DBURI" % prefix template_name = None if dburi_var in os.environ: @@ -323,6 +330,12 @@ def database_conf(db_path, prefix="GALAXY", prefer_template_database=False): actual_db = "gxtest" + ''.join(random.choice(string.ascii_uppercase) for _ in range(10)) actual_database_parsed = database_template_parsed._replace(path="/%s" % actual_db) database_connection = actual_database_parsed.geturl() + if not database_exists(database_connection): + # We pass by migrations and instantiate the current table + create_database(database_connection) + mapping.init('/tmp', database_connection, create_tables=True, map_install_models=True) + toolshed_mapping.init(database_connection, create_tables=True) + check_migrate_databases = False else: default_db_filename = "%s.sqlite" % prefix.lower() template_var = "%s_TEST_DB_TEMPLATE" % prefix @@ -337,6 +350,7 @@ def database_conf(db_path, prefix="GALAXY", prefer_template_database=False): database_auto_migrate = True database_connection = 'sqlite:///%s' % db_path config = { + "check_migrate_databases": check_migrate_databases, "database_connection": database_connection, "database_auto_migrate": database_auto_migrate } diff --git a/test/base/integration_util.py b/test/base/integration_util.py index 185fb3c26cd..36e9aa44b0c 100644 --- a/test/base/integration_util.py +++ b/test/base/integration_util.py @@ -7,6 +7,8 @@ tessting configuration. import os from unittest import skip, SkipTest, TestCase +import pytest + from galaxy.tools.deps.commands import which from galaxy.tools.verify.test_data import TestDataResolver from .api import UsesApiTestCaseMixin @@ -33,11 +35,11 @@ def skip_unless_docker(): return skip_unless_executable("docker") -def skip_unless_singularity(): - return skip_unless_executable("singularity") +def skip_unless_kubernetes(): + return skip_unless_executable("kubectl") -class IntegrationTestCase(TestCase, UsesApiTestCaseMixin): +class IntegrationInstance(UsesApiTestCaseMixin): """Unit test case with utilities for spinning up Galaxy.""" prefer_template_database = True @@ -119,3 +121,27 @@ class IntegrationTestCase(TestCase, UsesApiTestCaseMixin): def _run_tool_test(self, *args, **kwargs): return self._test_driver.run_tool_test(*args, **kwargs) + + +class IntegrationTestCase(IntegrationInstance, TestCase): + """Unit TestCase with utilities for spinning up Galaxy.""" + + +def integration_module_instance(clazz): + + def _instance(): + instance = clazz() + instance.setUpClass() + instance.setUp() + yield instance + instance.tearDownClass() + + return pytest.fixture(scope='module')(_instance) + + +def integration_tool_runner(tool_ids): + + def test_tools(instance, tool_id): + instance._run_tool_test(tool_id) + + return pytest.mark.parametrize("tool_id", tool_ids)(test_tools) diff --git a/test/base/populators.py b/test/base/populators.py index 6d2b51f180b..60621657961 100644 --- a/test/base/populators.py +++ b/test/base/populators.py @@ -254,6 +254,34 @@ class BaseDatasetPopulator(object): def cancel_job(self, job_id): return self._delete("jobs/%s" % job_id) + def delete_dataset(self, history_id, content_id): + delete_response = self._delete("histories/%s/contents/%s" % (history_id, content_id)) + return delete_response + + def create_tool(self, representation): + if isinstance(representation, dict): + representation = json.dumps(representation) + payload = dict( + representation=representation, + ) + create_response = self._post("dynamic_tools", data=payload, admin=True) + assert create_response.status_code == 200, create_response + return create_response.json() + + def list_dynamic_tools(self): + list_response = self._get("dynamic_tools", admin=True) + assert list_response.status_code == 200, list_response + return list_response.json() + + def show_dynamic_tool(self, uuid): + show_response = self._get("dynamic_tools/%s" % uuid, admin=True) + assert show_response.status_code == 200, show_response + return show_response.json() + + def deactivate_dynamic_tool(self, uuid): + delete_response = self._delete("dynamic_tools/%s" % uuid, admin=True) + return delete_response.json() + def _summarize_history(self, history_id): pass @@ -486,6 +514,100 @@ class BaseDatasetPopulator(object): assert update_response.status_code == 200, update_response.content return update_response.json() + def export_url(self, history_id, data, check_download=True): + url = "histories/%s/exports" % history_id + put_response = self._put(url, data) + api_asserts.assert_status_code_is(put_response, 202) + + def export_ready_response(): + put_response = self._put(url) + if put_response.status_code == 202: + return None + return put_response + + put_response = wait_on(export_ready_response, desc="export ready") + api_asserts.assert_status_code_is(put_response, 200) + response = put_response.json() + api_asserts.assert_has_keys(response, "download_url") + download_url = response["download_url"] + + if check_download: + self.get_export_url(download_url) + + return download_url + + def get_export_url(self, export_url): + full_download_url = "%s?key=%s" % (export_url, self._api_key) + download_response = self._get(full_download_url) + api_asserts.assert_status_code_is(download_response, 200) + return download_response + + def import_history(self, import_data): + files = {} + archive_file = import_data.pop("archive_file", None) + if archive_file: + files["archive_file"] = archive_file + import_response = self._post("histories", data=import_data, files=files) + api_asserts.assert_status_code_is(import_response, 200) + + def import_history_and_wait_for_name(self, import_data, history_name): + def history_names(): + return dict((h["name"], h) for h in self.get_histories()) + + import_name = "imported from archive: %s" % history_name + assert import_name not in history_names() + + self.import_history(import_data) + + def has_history_with_name(): + histories = history_names() + return histories.get(import_name, None) + + imported_history = wait_on(has_history_with_name, desc="import history") + imported_history_id = imported_history["id"] + self.wait_for_history(imported_history_id) + return imported_history_id + + def rename_history(self, history_id, new_name): + update_url = "histories/%s" % history_id + put_response = self._put(update_url, {"name": new_name}) + return put_response + + def get_histories(self): + history_index_response = self._get("histories") + api_asserts.assert_status_code_is(history_index_response, 200) + return history_index_response.json() + + def wait_on_history_length(self, history_id, wait_on_history_length): + + def history_has_length(): + history_length = self.history_length(history_id) + return None if history_length != wait_on_history_length else True + + wait_on(history_has_length, desc="import history population") + + def history_length(self, history_id): + contents_response = self._get("histories/%s/contents" % history_id) + api_asserts.assert_status_code_is(contents_response, 200) + contents = contents_response.json() + return len(contents) + + def reimport_history(self, history_id, history_name, wait_on_history_length, export_kwds, url, api_key): + # Export the history. + download_path = self.export_url(history_id, export_kwds, check_download=True) + + # Create download for history + full_download_url = "%s%s?key=%s" % (url, download_path, api_key) + + import_data = dict(archive_source=full_download_url, archive_type="url") + + imported_history_id = self.import_history_and_wait_for_name(import_data, history_name) + + if wait_on_history_length: + self.wait_on_history_length(imported_history_id, wait_on_history_length) + + return imported_history_id + def get_random_name(self, prefix=None, suffix=None, len=10): # stolen from navigates_galaxy.py return '%s%s%s' % ( @@ -500,27 +622,38 @@ class DatasetPopulator(BaseDatasetPopulator): def __init__(self, galaxy_interactor): self.galaxy_interactor = galaxy_interactor - def _post(self, route, data=None, files=None): + @property + def _api_key(self): + return self.galaxy_interactor.api_key + + def _post(self, route, data=None, files=None, admin=False): if data is None: data = {} - files = data.get("__files", None) - if files is not None: - del data["__files"] + if files is None: + files = data.get("__files", None) + if files is not None: + del data["__files"] - return self.galaxy_interactor.post(route, data, files=files) + return self.galaxy_interactor.post(route, data, files=files, admin=admin) - def _get(self, route, data=None): + def _put(self, route, data=None): if data is None: data = {} - return self.galaxy_interactor.get(route, data=data) + return self.galaxy_interactor.put(route, data) - def _delete(self, route, data=None): + def _get(self, route, data=None, admin=False): if data is None: data = {} - return self.galaxy_interactor.delete(route, data=data) + return self.galaxy_interactor.get(route, data=data, admin=admin) + + def _delete(self, route, data=None, admin=False): + if data is None: + data = {} + + return self.galaxy_interactor.delete(route, data=data, admin=admin) def _summarize_history(self, history_id): self.galaxy_interactor._summarize_history(history_id) @@ -650,13 +783,6 @@ class BaseWorkflowPopulator(object): def run_workflow(self, has_workflow, test_data=None, history_id=None, wait=True, source_type=None, jobs_descriptions=None, expected_response=200, assert_ok=True, client_convert=None, round_trip_format_conversion=False, raw_yaml=False): """High-level wrapper around workflow API, etc. to invoke format 2 workflows.""" workflow_populator = self - - def read_test_data(test_dict): - test_data_resolver = TestDataResolver() - filename = test_data_resolver.get_filename(test_dict["value"]) - content = open(filename, "r").read() - return content - if client_convert is None: client_convert = not round_trip_format_conversion @@ -726,11 +852,11 @@ class WorkflowPopulator(BaseWorkflowPopulator, ImporterGalaxyInterface): self.dataset_populator = DatasetPopulator(galaxy_interactor) self.dataset_collection_populator = DatasetCollectionPopulator(galaxy_interactor) - def _post(self, route, data=None): + def _post(self, route, data=None, admin=False): if data is None: data = {} - return self.galaxy_interactor.post(route, data) + return self.galaxy_interactor.post(route, data, admin=admin) def _get(self, route, data=None): if data is None: @@ -750,6 +876,22 @@ class WorkflowPopulator(BaseWorkflowPopulator, ImporterGalaxyInterface): assert upload_response.status_code == 200, upload_response.content return upload_response.json() + def import_tool(self, tool): + """ Import a workflow via POST /api/workflows or + comparable interface into Galaxy. + """ + upload_response = self._import_tool_response(tool) + assert upload_response.status_code == 200, upload_response + return upload_response.json() + + def _import_tool_response(self, tool): + tool_str = json.dumps(tool, indent=4) + data = { + 'representation': tool_str + } + upload_response = self._post("dynamic_tools", data=data, admin=True) + return upload_response + class LibraryPopulator(object): @@ -1246,24 +1388,39 @@ def wait_on_state(state_func, desc="state", skip_states=["running", "queued", "n class GiPostGetMixin(object): """Mixin for adapting Galaxy testing populators helpers to bioblend.""" + @property + def _api_key(self): + return self._gi.key + + def _api_url(self): + return self._gi.url + def _get(self, route, data=None): if data is None: data = {} - return self._gi.make_get_request(self.__url(route), data=data) + return self._gi.make_get_request(self._url(route), data=data) def _post(self, route, data={}): data = data.copy() data['key'] = self._gi.key - return requests.post(self.__url(route), data=data) + return requests.post(self._url(route), data=data) + + def _put(self, route, data={}): + data = data.copy() + data['key'] = self._gi.key + return requests.put(self._url(route), data=data) def _delete(self, route, data={}): data = data.copy() data['key'] = self._gi.key - return requests.delete(self.__url(route), data=data) + return requests.delete(self._url(route), data=data) - def __url(self, route): - return self._gi.url + "/" + route + def _url(self, route): + if route.startswith("/api/"): + route = route[len("/api/"):] + + return self._api_url() + "/" + route class GiDatasetPopulator(BaseDatasetPopulator, GiPostGetMixin): diff --git a/test/base/testcase.py b/test/base/testcase.py index 02cda1500c4..c73824250f6 100644 --- a/test/base/testcase.py +++ b/test/base/testcase.py @@ -4,8 +4,8 @@ import logging import os import unittest +from galaxy.security import idencoding from galaxy.tools.verify.test_data import TestDataResolver -from galaxy.web import security from .driver_util import GalaxyTestDriver, setup_keep_outdir, target_url_parts log = logging.getLogger(__name__) @@ -14,8 +14,7 @@ log = logging.getLogger(__name__) class FunctionalTestCase(unittest.TestCase): def setUp(self): - # Security helper - self.security = security.SecurityHelper(id_secret='changethisinproductiontoo') + self.security = idencoding.IdEncodingHelper(id_secret='changethisinproductiontoo') self.history_id = os.environ.get('GALAXY_TEST_HISTORY_ID', None) self.host, self.port, self.url = target_url_parts() self.test_data_resolver = TestDataResolver() diff --git a/test/docker/base/Dockerfile b/test/docker/base/Dockerfile index 2399868d3ff..cc917f67f8e 100644 --- a/test/docker/base/Dockerfile +++ b/test/docker/base/Dockerfile @@ -19,20 +19,17 @@ ENV DEBIAN_FRONTEND=noninteractive \ RUN apt-get update -y && apt-get install -y software-properties-common apt-transport-https curl && \ apt-add-repository -y ppa:ansible/ansible && \ curl -s https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \ - curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - && \ - curl -sL https://deb.nodesource.com/setup_8.x | bash - && \ echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list && \ + curl -s http://neuro.debian.net/lists/xenial.us-ca.full > /etc/apt/sources.list.d/neurodebian.sources.list && \ + apt-key adv --recv-keys --keyserver hkp://pool.sks-keyservers.net:80 0xA5D32F012649A5A9 && \ apt-get update -y && \ - apt-get install -y libpq-dev postgresql postgresql-client ansible wget \ - slurm-llnl libmunge-dev slurm-drmaa-dev ant cmake curl \ - g++ gcc gfortran git-core libffi-dev liblapack-dev \ - libncurses5-dev libopenblas-dev libpam0g-dev libpq-dev libsparsehash-dev make \ - patch postgresql postgresql \ - postgresql-client python-dev python3-dev \ - python-prettytable python-psycopg2 python-virtualenv python-pip \ - rsync slurm-drmaa-dev swig sysstat unzip \ - autoconf automake build-essential libatlas-base-dev libblas-dev openssl \ - bzip2 uuid-dev libssl-dev libgpgme11-dev squashfs-tools libseccomp-dev pkg-config \ + apt-get install -y --no-install-recommends postgresql postgresql-client ansible wget \ + python3-dev \ + git-core \ + python-prettytable python-virtualenv python-pip \ + rsync swig sysstat unzip \ + openssl \ + bzip2 \ ca-certificates \ openjdk-8-jre-headless \ tzdata \ @@ -40,13 +37,12 @@ RUN apt-get update -y && apt-get install -y software-properties-common apt-trans locales \ xvfb \ ${CHROME_VERSION:-google-chrome-stable} \ - wget zlib1g-dev nodejs \ + ffmpeg \ + bcftools \ + singularity-container \ libnss3 libgconf-2-4 && \ apt-get autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* -# Install golang -RUN export VERSION=1.11 OS=linux ARCH=amd64 && curl -sL https://dl.google.com/go/go$VERSION.$OS-$ARCH.tar.gz \ - | tar -C /usr/local -xzf - RUN mkdir -p /tmp/ansible && \ mkdir -p /opt/galaxy/db && \ @@ -76,7 +72,7 @@ RUN cd $GALAXY_ROOT && \ virtualenv -p /usr/bin/python3 $GALAXY_VIRTUAL_ENV_3 && \ for VENV in $GALAXY_VIRTUAL_ENV_2 $GALAXY_VIRTUAL_ENV_3; do \ export GALAXY_VIRTUAL_ENV=$VENV && \ - ./scripts/common_startup.sh || { echo "common_startup.sh failed"; exit 1; } && \ + ./scripts/common_startup.sh && \ dev_requirements=./lib/galaxy/dependencies/dev-requirements.txt && \ [ -f $dev_requirements ] && $VENV/bin/pip install -r $dev_requirements; done @@ -96,16 +92,6 @@ RUN for VENV in $GALAXY_VIRTUAL_ENV_3 $GALAXY_VIRTUAL_ENV_2; do \ echo "Prepopulating toolshed sqlite database" && \ TOOL_SHED_CONFIG_DATABASE_CONNECTION="sqlite:////opt/galaxy/toolshed.sqlite" bash create_db.sh tool_shed -# bcftools for Galaxy. -RUN mkdir -p /tmp/install && \ - cd /tmp/install && \ - wget https://github.com/samtools/bcftools/releases/download/1.2/bcftools-1.2.tar.bz2 && \ - tar xvjf bcftools-1.2.tar.bz2 && \ - cd bcftools-1.2 && \ - make && \ - make install && \ - cd && rm -rf /tmp/install - #======================================== # Add Selenium user with passwordless sudo #======================================== @@ -126,18 +112,6 @@ RUN sudo mkdir -p /opt/selenium \ && wget --no-verbose https://selenium-release.storage.googleapis.com/3.6/selenium-server-standalone-3.6.0.jar \ -O /opt/selenium/selenium-server-standalone.jar -USER seluser - -RUN export GOPATH=${HOME}/go && export PATH=/usr/local/go/bin:${PATH}:${GOPATH}/bin && \ - mkdir -p $GOPATH/src/github.com/sylabs && \ - cd $GOPATH/src/github.com/sylabs && \ - git clone https://github.com/sylabs/singularity.git && \ - cd singularity && \ - git checkout v3.0.1 && \ - ./mconfig && \ - cd builddir && make && sudo make install && \ - rm -Rf ${HOME}/go - USER root #============================== diff --git a/test/docker/base/ansible_vars.yml b/test/docker/base/ansible_vars.yml index 139809f7a73..d92d87f6f41 100644 --- a/test/docker/base/ansible_vars.yml +++ b/test/docker/base/ansible_vars.yml @@ -3,7 +3,7 @@ add_system_users: no galaxyFS_base_dir: /opt/galaxy # TODO: use GALAXY_ROOT via cmd line instead galaxy_server_dir: "/galaxy" -galaxy_venv_dir: "{{ galaxyFS_base_dir }}/.venv" +galaxy_venv_dir: "/galaxy_venv" galaxy_vcs: git galaxy_git_repo: https://github.com/galaxyproject/galaxy.git galaxy_changeset_id: dev @@ -24,6 +24,7 @@ install_maintainance_packages: false galaxy_user_name: "root" galaxy_extras_install_packages: true galaxy_job_conf_path: "/etc/galaxy/job_conf.xml" +galaxy_build_client: no galaxy_web_processes: 1 galaxy_handler_processes: 2 diff --git a/test/functional/test_toolbox.py b/test/functional/test_toolbox.py index ea8114ab889..7531e68b4a4 100644 --- a/test/functional/test_toolbox.py +++ b/test/functional/test_toolbox.py @@ -82,7 +82,7 @@ def build_tests(app=None, # Create a new subclass of ToolTestCase, dynamically adding methods # named test_tool_XXX that run each test defined in the tool config. if contains and contains not in tool_id: - continue + continue name = name_prefix + tool_id.replace(' ', '_') baseclasses = (baseclass, ) namespace = dict() diff --git a/test/functional/tools/cheetah_casting.xml b/test/functional/tools/cheetah_casting.xml index d12bb6569e8..ab9c7606158 100644 --- a/test/functional/tools/cheetah_casting.xml +++ b/test/functional/tools/cheetah_casting.xml @@ -1,9 +1,11 @@ #set $int_val_inc = int($inttest) + 1 + #set $int_flot_val_inc = int($floattest) + 1 #set $float_val_inc = float($floattest) + 1 - echo $int_val_inc >> $out_file1; - echo $float_val_inc >> $out_file1; + echo $int_val_inc >> '$out_file1'; + echo $int_flot_val_inc >> '$out_file1'; + echo $float_val_inc >> '$out_file1'; @@ -19,6 +21,7 @@ + diff --git a/test/functional/tools/collection_paired_test.xml b/test/functional/tools/collection_paired_test.xml index 329a84111b0..644913262a0 100644 --- a/test/functional/tools/collection_paired_test.xml +++ b/test/functional/tools/collection_paired_test.xml @@ -1,4 +1,4 @@ - + cat $f1.forward $f1['reverse'] >> $out1; diff --git a/test/functional/tools/detect_errors.xml b/test/functional/tools/detect_errors.xml new file mode 100644 index 00000000000..724c5f7f8e8 --- /dev/null +++ b/test/functional/tools/detect_errors.xml @@ -0,0 +1,139 @@ + + + + + + + + + + + + + 2& echo "$stderrmsg"; + #end if + sh -c "exit $exit_code" + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/functional/tools/detect_errors_aggressive.xml b/test/functional/tools/detect_errors_aggressive.xml index 3ece4c2705d..bb0c161abbe 100644 --- a/test/functional/tools/detect_errors_aggressive.xml +++ b/test/functional/tools/detect_errors_aggressive.xml @@ -11,6 +11,7 @@ + diff --git a/test/functional/tools/expression_forty_two.xml b/test/functional/tools/expression_forty_two.xml new file mode 100644 index 00000000000..af6afd0d997 --- /dev/null +++ b/test/functional/tools/expression_forty_two.xml @@ -0,0 +1,15 @@ + + Parse Int + + {return {'output': + 42}; + } + + + + + + + Produces the integer 42. + diff --git a/test/functional/tools/expression_log_line_count.xml b/test/functional/tools/expression_log_line_count.xml new file mode 100644 index 00000000000..b5811769906 --- /dev/null +++ b/test/functional/tools/expression_log_line_count.xml @@ -0,0 +1,14 @@ + + Log Lines + + {return {'output': Math.max(Math.round(Math.log(parseInt($job.input1.metadata.data_lines))), 1)};} + + + + + + + + + diff --git a/test/functional/tools/expression_parse_int.xml b/test/functional/tools/expression_parse_int.xml new file mode 100644 index 00000000000..3e2080aac0c --- /dev/null +++ b/test/functional/tools/expression_parse_int.xml @@ -0,0 +1,14 @@ + + Parse Int + + {return {'output': parseInt($job.input1)};} + + + + + + + + Parse an integer from text. + diff --git a/test/functional/tools/hidden_param.xml b/test/functional/tools/hidden_param.xml new file mode 100644 index 00000000000..b19890f05e4 --- /dev/null +++ b/test/functional/tools/hidden_param.xml @@ -0,0 +1,23 @@ + + Hidden Parameter + '$out_file1' + ]]> + + + + + + + + + + + + + + + + + + diff --git a/test/functional/tools/implicit_collection_conversion.xml b/test/functional/tools/implicit_collection_conversion.xml new file mode 100644 index 00000000000..bf693159ba3 --- /dev/null +++ b/test/functional/tools/implicit_collection_conversion.xml @@ -0,0 +1,30 @@ + + + #for $key in $input1.keys() + cut -f 1 '$input1[key]' > '$output1[key]' + #end for + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/functional/tools/implicit_conversion.xml b/test/functional/tools/implicit_conversion.xml index 1a9e52c899c..0f2d957e169 100644 --- a/test/functional/tools/implicit_conversion.xml +++ b/test/functional/tools/implicit_conversion.xml @@ -6,13 +6,22 @@ - + - + + + + + + + + + + diff --git a/test/functional/tools/implicit_nested_list_conversion.xml b/test/functional/tools/implicit_nested_list_conversion.xml new file mode 100644 index 00000000000..d73c447475e --- /dev/null +++ b/test/functional/tools/implicit_nested_list_conversion.xml @@ -0,0 +1,39 @@ + + + #for $outer_key in $input1.keys() + #for $inner_key in $input1[$outer_key].keys(): + cut -f 1 '$input1[$outer_key][$inner_key]' > '$output1[$outer_key][$inner_key]' + #end for + #end for + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/functional/tools/inheritance_simple.xml b/test/functional/tools/inheritance_simple.xml index 218e9653112..e169e7dcae4 100644 --- a/test/functional/tools/inheritance_simple.xml +++ b/test/functional/tools/inheritance_simple.xml @@ -1,24 +1,33 @@ - - (demonstrates subtypes are usable for parent format) - - cat $input1 > $out_file1 - - - - - - - - - - - - - - - - - - - - + + (demonstrates subtypes are usable for parent format) + + cat '$input1' > '$out_file1' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/functional/tools/job_environment_default.xml b/test/functional/tools/job_environment_default.xml index 22c884657f1..61604b73e03 100644 --- a/test/functional/tools/job_environment_default.xml +++ b/test/functional/tools/job_environment_default.xml @@ -3,6 +3,8 @@ busybox:ubuntu-14.04 &2 echo 'Example tool stderr output.'); echo `id -u` > '$user_id'; echo `id -g` > '$group_id'; echo `pwd` > '$pwd'; diff --git a/test/functional/tools/output_filter.xml b/test/functional/tools/output_filter.xml index 7de19cf3cf2..dfb179ef4ec 100644 --- a/test/functional/tools/output_filter.xml +++ b/test/functional/tools/output_filter.xml @@ -1,15 +1,21 @@ + test for output filtering and expect_num_outputs echo "test" > 1; echo "test" > 2; echo "test" > 3; echo "test" > 4; echo "test" > 5; + echo "test" > p1.forward; + echo "test" > p1.reverse; + echo "test" > p2.forward; + echo "test" > p2.reverse; + @@ -25,6 +31,10 @@ produce_collection is True + + + produce_paired_collection is True + @@ -52,11 +62,34 @@ - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/functional/tools/output_filter_with_input.xml b/test/functional/tools/output_filter_with_input.xml index 9eb7dbc20d1..c8b115d05e4 100644 --- a/test/functional/tools/output_filter_with_input.xml +++ b/test/functional/tools/output_filter_with_input.xml @@ -22,6 +22,8 @@ filter_text_1 == "foo" + + input_1.ext != 'xyz' diff --git a/test/functional/tools/sample_datatypes_conf.xml b/test/functional/tools/sample_datatypes_conf.xml index 3391bf8a50c..6b9c6d5326c 100644 --- a/test/functional/tools/sample_datatypes_conf.xml +++ b/test/functional/tools/sample_datatypes_conf.xml @@ -7,7 +7,7 @@ - + diff --git a/test/functional/tools/samples_tool_conf.xml b/test/functional/tools/samples_tool_conf.xml index e2060ddce3b..28798011dcb 100644 --- a/test/functional/tools/samples_tool_conf.xml +++ b/test/functional/tools/samples_tool_conf.xml @@ -48,6 +48,7 @@ + @@ -60,6 +61,7 @@ + @@ -91,16 +93,24 @@ + + + + + + + + @@ -139,8 +149,10 @@ + + + - diff --git a/test/functional/tools/validation_hdf5.xml b/test/functional/tools/validation_hdf5.xml new file mode 100644 index 00000000000..f66fa618835 --- /dev/null +++ b/test/functional/tools/validation_hdf5.xml @@ -0,0 +1,23 @@ + + + cp '$input' '$output' + + + + + + + + + + + + + + + + + + + + diff --git a/test/functional/tools/validation_metadata_in_range.xml b/test/functional/tools/validation_metadata_in_range.xml new file mode 100644 index 00000000000..327333817b3 --- /dev/null +++ b/test/functional/tools/validation_metadata_in_range.xml @@ -0,0 +1,30 @@ + + + echo "Hello World" > out1; + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/functional/tools/validation_tar.xml b/test/functional/tools/validation_tar.xml new file mode 100644 index 00000000000..58ebd6ac0d6 --- /dev/null +++ b/test/functional/tools/validation_tar.xml @@ -0,0 +1,58 @@ + + + cat '$input' > '$output' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/functional/tools/validation_tar_gz.xml b/test/functional/tools/validation_tar_gz.xml new file mode 100644 index 00000000000..cd18f43d247 --- /dev/null +++ b/test/functional/tools/validation_tar_gz.xml @@ -0,0 +1,58 @@ + + + cat '$input' > '$output' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/functional/tools/validation_zip.xml b/test/functional/tools/validation_zip.xml new file mode 100644 index 00000000000..6e512074540 --- /dev/null +++ b/test/functional/tools/validation_zip.xml @@ -0,0 +1,62 @@ + + + cat '$input' > '$output' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/functional/webhooks/masthead_test/config.yml b/test/functional/webhooks/masthead_test/config.yml index dfb14084885..22672d6af9e 100644 --- a/test/functional/webhooks/masthead_test/config.yml +++ b/test/functional/webhooks/masthead_test/config.yml @@ -4,7 +4,7 @@ type: activate: true icon: fa-star-half-o -tooltip: Biostars Galaxy +tooltip: Galaxy Help function: > - window.open('https://biostar.usegalaxy.org', '_blank'); + window.open('https://help.galaxyproject.org', '_blank'); diff --git a/test/galaxy_selenium/cli.py b/test/galaxy_selenium/cli.py index 93048dc26dc..e46a6f3a32a 100644 --- a/test/galaxy_selenium/cli.py +++ b/test/galaxy_selenium/cli.py @@ -3,7 +3,7 @@ BROWSER_DESCRIPTION = "Use specific browser for selenium (e.g. firefox or chrome REMOTE_HOST_DESCRIPTION = "Selenium hub remote host to use (if remote driver in use)." REMOTE_PORT_DESCRIPTION = "Selenium hub remote port to use (if remote driver in use)." GALAXY_URL_DESCRIPTION = "URL of Galaxy instance to target." -HEADLESS_DESCRIPTION = "Use local selenium headlessly (requires pyvirtualdisplay)." +HEADLESS_DESCRIPTION = "Use local selenium headlessly (native in chrome, otherwise this requires pyvirtualdisplay)." from six.moves.urllib.parse import urljoin diff --git a/test/galaxy_selenium/driver_factory.py b/test/galaxy_selenium/driver_factory.py index 1e9b729225c..8ddb0cd4953 100644 --- a/test/galaxy_selenium/driver_factory.py +++ b/test/galaxy_selenium/driver_factory.py @@ -6,6 +6,7 @@ except ImportError: Display = None from selenium import webdriver +from selenium.webdriver.chrome.options import Options as ChromeOptions from selenium.webdriver.common.desired_capabilities import DesiredCapabilities @@ -15,15 +16,18 @@ LOGGING_PREFS = { } -def get_local_driver(browser=DEFAULT_BROWSER): +def get_local_browser(browser): if browser == "auto": if _which("chromedriver"): - browser = "CHROME" + return "CHROME" elif _which("geckodriver"): - browser = "FIREFOX" + return "FIREFOX" else: raise Exception("Selenium browser is 'auto' but neither geckodriver or chromedriver are found on PATH.") + +def get_local_driver(browser=DEFAULT_BROWSER, headless=False): + browser = get_local_browser(browser) assert browser in ["CHROME", "FIREFOX", "OPERA", "PHANTOMJS"] driver_to_class = { "CHROME": webdriver.Chrome, @@ -32,7 +36,12 @@ def get_local_driver(browser=DEFAULT_BROWSER): "PHANTOMJS": webdriver.PhantomJS, } driver_class = driver_to_class[browser] - return driver_class(desired_capabilities={"loggingPrefs": LOGGING_PREFS}) + if browser == 'CHROME' and headless: + options = ChromeOptions() + options.add_argument('--headless') + return driver_class(desired_capabilities={"loggingPrefs": LOGGING_PREFS}, chrome_options=options) + else: + return driver_class(desired_capabilities={"loggingPrefs": LOGGING_PREFS}) def get_remote_driver( diff --git a/test/galaxy_selenium/navigates_galaxy.py b/test/galaxy_selenium/navigates_galaxy.py index 3856880a246..10ab5a61429 100644 --- a/test/galaxy_selenium/navigates_galaxy.py +++ b/test/galaxy_selenium/navigates_galaxy.py @@ -245,7 +245,7 @@ class NavigatesGalaxy(HasDriver): def history_panel_create_new(self): """Click create new and pause a bit for the history to begin to refresh.""" - self.click_history_option('Create New') + self.history_click_create_new() self.sleep_for(WAIT_TYPES.UX_RENDER) def history_panel_wait_for_hid_ok(self, hid, allowed_force_refreshes=0): @@ -367,21 +367,16 @@ class NavigatesGalaxy(HasDriver): def submit_login(self, email, password=None, assert_valid=True, retries=0): if password is None: password = self.default_password - login_info = { 'login': email, 'password': password, } - self.click_masthead_user() - self.wait_for_and_click(self.navigation.masthead.labels.login) - self.sleep_for(WAIT_TYPES.UX_RENDER) form = self.wait_for_visible(self.navigation.login.selectors.form) self.fill(form, login_info) self.snapshot("logging-in") self.wait_for_and_click(self.navigation.login.selectors.submit) - self.snapshot("login-submitted") if assert_valid: try: @@ -406,26 +401,25 @@ class NavigatesGalaxy(HasDriver): self.home() self.click_masthead_user() - self.wait_for_and_click(self.navigation.masthead.labels.register) - with self.main_panel(): - form = self.wait_for_visible(self.navigation.registration.selectors.form) - self.fill(form, dict( - email=email, - password=password, - username=username, - confirm=confirm - )) - self.wait_for_and_click(self.navigation.registration.selectors.submit) - # Give the browser a bit of time to submit the request. - # It would be good to eliminate this sleep, but it can't be because Galaxy - # doesn't swap the "User" menu automatically after it registers a user and - # and the donemessage visible comment below doesn't work when using Selenium. - # Something about the Selenium session or quickness of registering causes the - # following in the Galaxy logs which gets propaged to the GUI as a generic error: - # /api/histories/cfc05ccec54895e2/contents?keys=type_id%2Celement_count&order=hid&v=dev&q=history_content_type&q=deleted&q=purged&q=visible&qv=dataset_collection&qv=False&qv=False&qv=True HTTP/1.1" 403 - "http://localhost:8080/" - # Like the logged in user doesn't have permission to the previously anonymous user's - # history, it is odd but I cannot replicate this outside of Selenium. - time.sleep(1.35) + self.wait_for_and_click(self.navigation.registration.selectors.toggle) + form = self.wait_for_visible(self.navigation.registration.selectors.form) + self.fill(form, dict( + email=email, + password=password, + username=username, + confirm=confirm + )) + self.wait_for_and_click(self.navigation.registration.selectors.submit) + # Give the browser a bit of time to submit the request. + # It would be good to eliminate this sleep, but it can't be because Galaxy + # doesn't swap the "User" menu automatically after it registers a user and + # and the donemessage visible comment below doesn't work when using Selenium. + # Something about the Selenium session or quickness of registering causes the + # following in the Galaxy logs which gets propaged to the GUI as a generic error: + # /api/histories/cfc05ccec54895e2/contents?keys=type_id%2Celement_count&order=hid&v=dev&q=history_content_type&q=deleted&q=purged&q=visible&qv=dataset_collection&qv=False&qv=False&qv=True HTTP/1.1" 403 - "http://localhost:8080/" + # Like the logged in user doesn't have permission to the previously anonymous user's + # history, it is odd but I cannot replicate this outside of Selenium. + time.sleep(1.35) if assert_valid: # self.wait_for_selector_visible(".donemessage") @@ -991,7 +985,7 @@ class NavigatesGalaxy(HasDriver): def workflow_index_tags(self, workflow_index=0): workflow_row_element = self.workflow_index_table_row(workflow_index) tag_display = workflow_row_element.find_element_by_css_selector(".tags-display") - tag_spans = tag_display.find_elements_by_css_selector("span.badge-tags") + tag_spans = tag_display.find_elements_by_css_selector(".tag-name") tags = [] for tag_span in tag_spans: tags.append(tag_span.text) @@ -1089,6 +1083,9 @@ class NavigatesGalaxy(HasDriver): menu_selection_element = self.wait_for_sizzle_selector_clickable(menu_item_sizzle_selector) menu_selection_element.click() + def history_click_create_new(self): + self.components.history_panel.new_history_button.wait_for_and_click() + def history_panel_click_copy_elements(self): self.click_history_option("Copy Datasets") @@ -1257,7 +1254,7 @@ class NavigatesGalaxy(HasDriver): self.home() self.click_masthead_user() self.wait_for_and_click(self.navigation.masthead.labels.logout) - self.click_label('go to the home page') + self.sleep_for(WAIT_TYPES.UX_TRANSITION) assert not self.is_logged_in() def run_tour(self, path, skip_steps=None, sleep_on_steps=None, tour_callback=None): diff --git a/test/galaxy_selenium/navigation.yml b/test/galaxy_selenium/navigation.yml index 879a7253a77..57395ed628f 100644 --- a/test/galaxy_selenium/navigation.yml +++ b/test/galaxy_selenium/navigation.yml @@ -57,8 +57,6 @@ masthead: admin: 'Admin' # user menu - register: 'Register' - login: 'Login' logout: 'Logout' custom_builds: 'Custom Builds' saved_histories: 'Saved Histories' @@ -145,6 +143,7 @@ history_panel: options_button: '#history-options-button' options_button_icon: '#history-options-button span.fa-cog' options_menu: '#history-options-button-menu' + new_history_button: '#history-new-button' multi_view_button: '#history-view-multi-button' pagination_pages: '.list-pagination .pages' @@ -208,14 +207,15 @@ login: form: 'form#login' submit: type: xpath - selector: "//button[@type='submit']" + selector: "//button[@name='login']" registration: selectors: - form: '#registration' + toggle: '#register-toggle' + form: 'form#registration' submit: type: xpath - selector: "//input[@value='Submit']" + selector: "//button[@name='create']" tool_form: selectors: @@ -238,7 +238,7 @@ workflows: workflow_run: - selectors: + selectors: input_div: "[step-label='${label}']" input_data_div: "[step-label='${label}'] .select2-container" # TODO: put step labels in the DOM ideally @@ -324,7 +324,7 @@ admin: users_grid: '#users-grid' users_grid_create_button: '.manage-table-actions .action-button' groups_grid_create_button: '.manage-table-actions .action-button' - registration_form: 'form[name="registration"]' + registration_form: 'form#registration' groups_grid: '#groups-grid' roles_grid: '#roles-grid' groups_create_view: '#create-group' diff --git a/test/integration/io_injection_job_conf.xml b/test/integration/io_injection_job_conf.xml new file mode 100644 index 00000000000..ebecf1652f9 --- /dev/null +++ b/test/integration/io_injection_job_conf.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/test/integration/test_containerized_jobs.py b/test/integration/test_containerized_jobs.py index 49e30430d54..ccff74a7b7f 100644 --- a/test/integration/test_containerized_jobs.py +++ b/test/integration/test_containerized_jobs.py @@ -17,7 +17,21 @@ SINGULARITY_JOB_CONFIG_FILE = os.path.join(SCRIPT_DIRECTORY, "singularity_job_co EXTENDED_TIMEOUT = 120 -class DockerizedJobsIntegrationTestCase(integration_util.IntegrationTestCase, RunsEnvironmentJobs): +class MulledJobTestCases(object): + def test_explicit(self): + self.dataset_populator.run_tool("mulled_example_explicit", {}, self.history_id) + self.dataset_populator.wait_for_history(self.history_id, assert_ok=True) + output = self.dataset_populator.get_history_dataset_content(self.history_id, timeout=EXTENDED_TIMEOUT) + assert "0.7.15-r1140" in output + + def test_mulled_simple(self): + self.dataset_populator.run_tool("mulled_example_simple", {}, self.history_id) + self.dataset_populator.wait_for_history(self.history_id, assert_ok=True) + output = self.dataset_populator.get_history_dataset_content(self.history_id, timeout=EXTENDED_TIMEOUT) + assert "0.7.15-r1140" in output + + +class DockerizedJobsIntegrationTestCase(integration_util.IntegrationTestCase, RunsEnvironmentJobs, MulledJobTestCases): framework_tool_and_types = True job_config_file = DOCKERIZED_JOB_CONFIG_FILE @@ -47,19 +61,7 @@ class DockerizedJobsIntegrationTestCase(integration_util.IntegrationTestCase, Ru self.dataset_populator = DatasetPopulator(self.galaxy_interactor) self.history_id = self.dataset_populator.new_history() - def test_explicit(self): - self.dataset_populator.run_tool("mulled_example_explicit", {}, self.history_id) - self.dataset_populator.wait_for_history(self.history_id, assert_ok=True) - output = self.dataset_populator.get_history_dataset_content(self.history_id, timeout=EXTENDED_TIMEOUT) - assert "0.7.15-r1140" in output - - def test_mulled_simple(self): - self.dataset_populator.run_tool("mulled_example_simple", {}, self.history_id) - self.dataset_populator.wait_for_history(self.history_id, assert_ok=True) - output = self.dataset_populator.get_history_dataset_content(self.history_id, timeout=EXTENDED_TIMEOUT) - assert "0.7.15-r1140" in output - - def test_container_job_enviornment(self): + def test_container_job_environment(self): job_env = self._run_and_get_environment_properties("job_environment_default") euid = os.geteuid() diff --git a/test/integration/test_datatype_upload.py b/test/integration/test_datatype_upload.py index 990b7606386..b743cea5075 100644 --- a/test/integration/test_datatype_upload.py +++ b/test/integration/test_datatype_upload.py @@ -4,9 +4,10 @@ import tempfile import pytest -from galaxy.datatypes.registry import Registry +from base import integration_util # noqa: I100,I202 +from galaxy.datatypes.registry import Registry # noqa: I201 from galaxy.util.hash_util import md5_hash_file -from .test_upload_configuration_options import BaseUploadContentConfigurationTestCase +from .test_upload_configuration_options import BaseUploadContentConfigurationInstance SCRIPT_DIRECTORY = os.path.abspath(os.path.dirname(__file__)) TEST_FILE_DIR = '%s/../../lib/galaxy/datatypes/test' % SCRIPT_DIRECTORY @@ -36,22 +37,12 @@ def collect_test_data(): return {os.path.basename(data.path): data for data in test_data_description} -class UploadTestDatatypeDataTestCase(BaseUploadContentConfigurationTestCase): +class UploadTestDatatypeDataTestCase(BaseUploadContentConfigurationInstance): framework_tool_and_types = False datatypes_conf_override = DATATYPES_CONFIG - def runTest(self): - # we don't want to run the standard unittest tests when we setup UploadTestDatatypeDataTestCase - pass - -@pytest.fixture(scope='module') -def instance(): - instance = UploadTestDatatypeDataTestCase() - instance.setUpClass() - instance.setUp() - yield instance - instance.tearDownClass() +instance = integration_util.integration_module_instance(UploadTestDatatypeDataTestCase) @pytest.fixture diff --git a/test/integration/test_job_environments.py b/test/integration/test_job_environments.py index 07385518669..6622ada24a2 100644 --- a/test/integration/test_job_environments.py +++ b/test/integration/test_job_environments.py @@ -12,6 +12,7 @@ from base.populators import ( SCRIPT_DIRECTORY = os.path.abspath(os.path.dirname(__file__)) SIMPLE_JOB_CONFIG_FILE = os.path.join(SCRIPT_DIRECTORY, "simple_job_conf.xml") +IO_INJECTION_JOB_CONFIG_FILE = os.path.join(SCRIPT_DIRECTORY, "io_injection_job_conf.xml") SETS_TMP_DIR_TO_TRUE_JOB_CONFIG = os.path.join(SCRIPT_DIRECTORY, "sets_tmp_dir_to_true_job_conf.xml") SETS_TMP_DIR_AS_EXPRESSION_JOB_CONFIG = os.path.join(SCRIPT_DIRECTORY, "sets_tmp_dir_expression_job_conf.xml") @@ -31,6 +32,7 @@ class RunsEnvironmentJobs(object): with self.dataset_populator.test_history() as history_id: self.dataset_populator.run_tool(tool_id, {}, history_id) self.dataset_populator.wait_for_history(history_id, assert_ok=True) + self._check_completed_history(history_id) return self._environment_properties(history_id) def _environment_properties(self, history_id): @@ -42,6 +44,9 @@ class RunsEnvironmentJobs(object): some_env = self.dataset_populator.get_history_dataset_content(history_id, hid=6).strip() return JobEnviromentProperties(user_id, group_id, pwd, home, tmp, some_env) + def _check_completed_history(self, history_id): + """Extension point that lets subclasses investigate the completed job.""" + class BaseJobEnvironmentIntegrationTestCase(integration_util.IntegrationTestCase, RunsEnvironmentJobs): @@ -164,3 +169,28 @@ class SharedHomeJobEnvironmentIntegrationTestCase(BaseJobEnvironmentIntegrationT # shared_home_dir used for newer tools if forced in tool XML job_env = self._run_and_get_environment_properties("job_environment_explicit_shared_home") assert job_env.home == self.shared_home_directory, job_env.home + + +class JobIOEnvironmentIntegrationTestCase(BaseJobEnvironmentIntegrationTestCase): + + @classmethod + def handle_galaxy_config_kwds(cls, config): + cls.jobs_directory = tempfile.mkdtemp() + config["jobs_directory"] = cls.jobs_directory + config["job_config_file"] = IO_INJECTION_JOB_CONFIG_FILE + + @skip_without_tool("job_environment_default") + def test_io_separation(self): + self._run_and_get_environment_properties() + + def _check_completed_history(self, history_id): + jobs = self.dataset_populator.history_jobs(history_id) + assert len(jobs) == 1 + job = jobs[0] + job_details = self.dataset_populator.get_job_details(job["id"], full=True) + job_details = job_details.json() + print(job_details) + assert 'moo std cow' in job_details['job_stdout'] + assert 'moo err cow' in job_details['job_stderr'] + assert 'Writing environment properties to output files.' in job_details['tool_stdout'] + assert 'Example tool stderr output.' in job_details['tool_stderr'] diff --git a/test/integration/test_job_recovery.py b/test/integration/test_job_recovery.py index 6abafcd5cd4..3bb54a54055 100644 --- a/test/integration/test_job_recovery.py +++ b/test/integration/test_job_recovery.py @@ -18,6 +18,7 @@ class JobRecoveryBeforeHandledIntegerationTestCase(integration_util.IntegrationT def setUp(self): super(JobRecoveryBeforeHandledIntegerationTestCase, self).setUp() self.dataset_populator = DatasetPopulator(self.galaxy_interactor) + self.history_id = self.dataset_populator.new_history() @classmethod def handle_galaxy_config_kwds(cls, config): diff --git a/test/integration/test_kubernetes_runner.py b/test/integration/test_kubernetes_runner.py new file mode 100644 index 00000000000..4554b1ddfe1 --- /dev/null +++ b/test/integration/test_kubernetes_runner.py @@ -0,0 +1,255 @@ +"""Integration tests for the CLI shell plugins and runners.""" +# Tested on docker for mac 18.06.1-ce-mac73 using the default kubernetes setup +import collections +import json +import os +import string +import subprocess +import tempfile +import time + +from base import integration_util # noqa: I100,I202 +from base.populators import skip_without_tool +from .test_containerized_jobs import MulledJobTestCases # noqa: I201 +from .test_job_environments import BaseJobEnvironmentIntegrationTestCase # noqa: I201 + +PERSISTENT_VOLUME_NAME = 'pv-galaxy-integration-test' +PERSISTENT_VOLUME_CLAIM_NAME = 'galaxy-pvc-integration-test' +Config = collections.namedtuple('ConfigTuple', 'path') +TOOL_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 'tools')) + + +class KubeSetupConfigTuple(Config): + + def setup(self): + subprocess.check_call(['kubectl', 'create', '-f', self.path]) + + def teardown(self): + subprocess.check_call(['kubectl', 'delete', '-f', self.path]) + + +def persistent_volume(path, persistent_volume_name): + volume_yaml = string.Template(""" +kind: PersistentVolume +apiVersion: v1 +metadata: + name: $persistent_volume_name + labels: + type: local +spec: + capacity: + storage: 5Gi + accessModes: + - ReadWriteMany + persistentVolumeReclaimPolicy: Retain + storageClassName: manual + local: + path: $path + nodeAffinity: + required: + nodeSelectorTerms: + - matchExpressions: + - key: app + operator: NotIn + values: + - 'i-do-not-exist' + """).substitute(path=path, + persistent_volume_name=persistent_volume_name) + with tempfile.NamedTemporaryFile(suffix="_persistent_volume.yml", mode="w", delete=False) as volume: + volume.write(volume_yaml) + return KubeSetupConfigTuple(path=volume.name) + + +def persistent_volume_claim(persistent_volume_name, persistent_volum_claim_name): + peristent_volume_claim_yaml = string.Template(""" +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: $persistent_volume_claim_name +spec: + accessModes: + - ReadWriteMany + volumeName: $persistent_volume_name + resources: + requests: + storage: 2Gi + storageClassName: manual +""").substitute(persistent_volume_name=persistent_volume_name, + persistent_volume_claim_name=persistent_volum_claim_name) + with tempfile.NamedTemporaryFile(suffix="_persistent_volume_claim.yml", mode="w", delete=False) as volume_claim: + volume_claim.write(peristent_volume_claim_yaml) + return KubeSetupConfigTuple(path=volume_claim.name) + + +def job_config(jobs_directory): + job_conf_template = string.Template(""" + + + + jobs-directory-claim:$jobs_directory,tool-directory-claim:$tool_directory + $k8s_config_path + gx-short-id + + + + + 1.9 + 10M + true + busybox:ubuntu-14.04 + 42 + + + + + + + + +""") + job_conf_str = job_conf_template.substitute(jobs_directory=jobs_directory, + tool_directory=TOOL_DIR, + k8s_config_path=os.environ.get('GALAXY_TEST_KUBE_CONFIG_PATH', '~/.kube/config'), + ) + with tempfile.NamedTemporaryFile(suffix="_kubernetes_integration_job_conf", mode="w", delete=False) as job_conf: + job_conf.write(job_conf_str) + return Config(job_conf.name) + + +@integration_util.skip_unless_kubernetes() +class BaseKubernetesIntegrationTestCase(BaseJobEnvironmentIntegrationTestCase, MulledJobTestCases): + + def setUp(self): + super(BaseKubernetesIntegrationTestCase, self).setUp() + self.history_id = self.dataset_populator.new_history() + + @classmethod + def setUpClass(cls): + cls.jobs_directory = tempfile.mkdtemp() + cls.volumes = [ + [cls.jobs_directory, 'jobs-directory-volume', 'jobs-directory-claim'], + [TOOL_DIR, 'tool-directory-volume', 'tool-directory-claim'], + ] + cls.persistent_volumes = [] + cls.persistent_volume_claims = [] + for (path, volume, claim) in cls.volumes: + volume_obj = persistent_volume(path, volume) + volume_obj.setup() + cls.persistent_volumes.append(volume_obj) + claim_obj = persistent_volume_claim(volume, claim) + claim_obj.setup() + cls.persistent_volume_claims.append(claim_obj) + cls.job_config = job_config(jobs_directory=cls.jobs_directory) + super(BaseKubernetesIntegrationTestCase, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + for claim in cls.persistent_volume_claims: + claim.teardown() + for volume in cls.persistent_volumes: + volume.teardown() + super(BaseKubernetesIntegrationTestCase, cls).tearDownClass() + + @classmethod + def handle_galaxy_config_kwds(cls, config, ): + config["jobs_directory"] = cls.jobs_directory + config["file_path"] = cls.jobs_directory + config["job_config_file"] = cls.job_config.path + config["default_job_shell"] = '/bin/sh' + # Disable tool dependency resolution. + config["tool_dependency_dir"] = "none" + config["enable_beta_mulled_containers"] = "true" + + @skip_without_tool("job_environment_default") + def test_job_environment(self): + job_env = self._run_and_get_environment_properties() + assert job_env.some_env == '42' + + @skip_without_tool('cat_data_and_sleep') + def test_kill_process(self): + with self.dataset_populator.test_history() as history_id: + hda1 = self.dataset_populator.new_dataset(history_id, content="1 2 3") + running_inputs = { + "input1": {"src": "hda", "id": hda1["id"]}, + "sleep_time": 240, + } + running_response = self.dataset_populator.run_tool( + "cat_data_and_sleep", + running_inputs, + history_id, + assert_ok=False, + ).json() + job_dict = running_response["jobs"][0] + + app = self._app + sa_session = app.model.context.current + external_id = None + state = False + + job = sa_session.query(app.model.Job).filter_by(tool_id="cat_data_and_sleep").one() + # Not checking the state here allows the change from queued to running to overwrite + # the change from queued to deleted_new in the API thread - this is a problem because + # the job will still run. See issue https://github.com/galaxyproject/galaxy/issues/4960. + max_tries = 60 + while max_tries > 0 and external_id is None or state != app.model.Job.states.RUNNING: + sa_session.refresh(job) + assert not job.finished + external_id = job.job_runner_external_id + state = job.state + time.sleep(1) + max_tries -= 1 + + status = json.loads(subprocess.check_output(['kubectl', 'get', 'job', external_id, '-o', 'json'])) + assert status['status']['active'] == 1 + + delete_response = self.dataset_populator.cancel_job(job_dict["id"]) + assert delete_response.json() is True + + # Wait for job to be cancelled in kubernetes + time.sleep(2) + status = json.loads(subprocess.check_output(['kubectl', 'get', 'job', external_id, '-o', 'json'])) + assert 'active' not in status['status'] + + @skip_without_tool('job_properties') + def test_exit_code_127(self): + inputs = { + 'failbool': True + } + running_response = self.dataset_populator.run_tool( + "job_properties", + inputs, + self.history_id, + assert_ok=False, + ) + result = self.dataset_populator.wait_for_tool_run(run_response=running_response, history_id=self.history_id, assert_ok=False).json() + details = self.dataset_populator.get_job_details(result['jobs'][0]['id'], full=True).json() + + assert details['state'] == 'error', details + assert details['stdout'] == 'The bool is not true\n', details + assert details['stderr'] == 'Fatal error: Exit code 127 (Failing exit code.)\nThe bool is very not true\n' + + @skip_without_tool('Count1') + def test_python_dep(self): + with self.dataset_populator.test_history() as history_id: + hda1 = self.dataset_populator.new_dataset(history_id, content="1\t2\t3", file_type='tabular', wait=True) + self.dataset_populator.run_tool( + 'Count1', + {'input': {"src": "hda", "id": hda1["id"]}, + 'column': [1]}, + self.history_id, + assert_ok=True, + ) + + @skip_without_tool('galaxy_slots_and_memory') + def test_slots_and_memory(self): + self.dataset_populator.run_tool( + 'galaxy_slots_and_memory', + {}, + self.history_id, + assert_ok=True + ) + dataset_content = self.dataset_populator.get_history_dataset_content(self.history_id, hid=1).strip() + CPU = '2' + MEM = '10' + MEM_PER_SLOT = '5' + assert [CPU, MEM, MEM_PER_SLOT] == dataset_content.split('\n'), dataset_content diff --git a/test/integration/test_pulsar_embedded.py b/test/integration/test_pulsar_embedded.py index 03998092d61..eaf685d0971 100644 --- a/test/integration/test_pulsar_embedded.py +++ b/test/integration/test_pulsar_embedded.py @@ -8,8 +8,8 @@ SCRIPT_DIRECTORY = os.path.abspath(os.path.dirname(__file__)) EMBEDDED_PULSAR_JOB_CONFIG_FILE = os.path.join(SCRIPT_DIRECTORY, "embedded_pulsar_job_conf.xml") -class EmbeddedPulsarIntegrationTestCase(integration_util.IntegrationTestCase): - """Start a Pulsar job.""" +class EmbeddedPulsarIntegrationInstance(integration_util.IntegrationInstance): + """Describe a Galaxy test instance with embedded pulsar configured.""" framework_tool_and_types = True @@ -17,11 +17,7 @@ class EmbeddedPulsarIntegrationTestCase(integration_util.IntegrationTestCase): def handle_galaxy_config_kwds(cls, config): config["job_config_file"] = EMBEDDED_PULSAR_JOB_CONFIG_FILE - def test_tool_simple_constructs(self): - self._run_tool_test("simple_constructs") - def test_multi_data_param(self): - self._run_tool_test("multi_data_param") +instance = integration_util.integration_module_instance(EmbeddedPulsarIntegrationInstance) - def test_work_dir_outputs(self): - self._run_tool_test("output_filter") +test_tools = integration_util.integration_tool_runner(["simple_constructs", "multi_data_param", "output_filter"]) diff --git a/test/integration/test_pulsar_embedded_metadata.py b/test/integration/test_pulsar_embedded_metadata.py new file mode 100644 index 00000000000..d5b5582dbcb --- /dev/null +++ b/test/integration/test_pulsar_embedded_metadata.py @@ -0,0 +1,23 @@ +"""Integration tests for the Pulsar embedded runner.""" + +import os + +from base import integration_util + +SCRIPT_DIRECTORY = os.path.abspath(os.path.dirname(__file__)) +EMBEDDED_PULSAR_JOB_CONFIG_FILE = os.path.join(SCRIPT_DIRECTORY, "embedded_pulsar_metadata_job_conf.xml") + + +class EmbeddedPulsarIntegrationInstance(integration_util.IntegrationInstance): + """Describe a Galaxy test instance with embedded pulsar configured.""" + + framework_tool_and_types = True + + @classmethod + def handle_galaxy_config_kwds(cls, config): + config["job_config_file"] = EMBEDDED_PULSAR_JOB_CONFIG_FILE + + +instance = integration_util.integration_module_instance(EmbeddedPulsarIntegrationInstance) + +test_tools = integration_util.integration_tool_runner(["simple_constructs"]) diff --git a/test/integration/test_upload_configuration_options.py b/test/integration/test_upload_configuration_options.py index 41f21a97cf6..dd076d4e060 100644 --- a/test/integration/test_upload_configuration_options.py +++ b/test/integration/test_upload_configuration_options.py @@ -24,6 +24,7 @@ import os import re import shutil import tempfile +import unittest from base import integration_util from base.api_util import ( @@ -45,12 +46,12 @@ SCRIPT_DIR = os.path.normpath(os.path.dirname(__file__)) TEST_DATA_DIRECTORY = os.path.join(SCRIPT_DIR, os.pardir, os.pardir, "test-data") -class BaseUploadContentConfigurationTestCase(integration_util.IntegrationTestCase): +class BaseUploadContentConfigurationInstance(integration_util.IntegrationInstance): framework_tool_and_types = True def setUp(self): - super(BaseUploadContentConfigurationTestCase, self).setUp() + super(BaseUploadContentConfigurationInstance, self).setUp() self.dataset_populator = DatasetPopulator(self.galaxy_interactor) self.library_populator = LibraryPopulator(self.galaxy_interactor) self.history_id = self.dataset_populator.new_history() @@ -84,6 +85,10 @@ class BaseUploadContentConfigurationTestCase(integration_util.IntegrationTestCas os.makedirs(path) +class BaseUploadContentConfigurationTestCase(BaseUploadContentConfigurationInstance, unittest.TestCase): + pass + + class InvalidFetchRequestsTestCase(BaseUploadContentConfigurationTestCase): def test_in_place_not_allowed(self): diff --git a/test/manual/gen_history_export_test_artifacts.py b/test/manual/gen_history_export_test_artifacts.py new file mode 100644 index 00000000000..d02c20178fa --- /dev/null +++ b/test/manual/gen_history_export_test_artifacts.py @@ -0,0 +1,62 @@ +import os +import random +import sys +from argparse import ArgumentParser + +from bioblend import galaxy + +galaxy_root = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir)) +sys.path[1:1] = [os.path.join(galaxy_root, "lib"), os.path.join(galaxy_root, "test")] + +from base.populators import ( # noqa: I100,I202 + GiDatasetPopulator, +) + +DESCRIPTION = "Script to generate history export test artifacts." + + +def main(argv=None): + arg_parser = ArgumentParser(description=DESCRIPTION) + arg_parser.add_argument("--api_key", default="testmasterapikey") + arg_parser.add_argument("--host", default="http://localhost:8080/") + + args = arg_parser.parse_args(argv) + gi = _gi(args) + + _run(args, gi) + + +def _run(args, gi): + dataset_populator = GiDatasetPopulator(gi) + history_id = dataset_populator.new_history() + input_hda = dataset_populator.new_dataset(history_id, content="1 2 3") + deleted_hda = dataset_populator.new_dataset(history_id, content="1 2 3") + dataset_populator.delete_dataset(history_id, deleted_hda["id"]) + inputs = { + "input1": {"src": "hda", "id": input_hda["id"]}, + "queries_0|input2": {"src": "hda", "id": input_hda["id"]}, + } + dataset_populator.run_tool("cat", inputs, history_id) + dataset_populator.wait_for_history(history_id, assert_ok=True) + + # Export the history. + export_kwds = {} + export_url = dataset_populator.export_url(history_id, export_kwds) + export_response = dataset_populator.get_export_url(export_url) + with open("download.tar.gz", "wb") as f: + f.write(export_response.content) + + +def _gi(args): + gi = galaxy.GalaxyInstance(args.host, key=args.api_key) + name = "histexport-user-%d" % random.randint(0, 1000000) + + user = gi.users.create_local_user(name, "%s@galaxytesting.dev" % name, "pass123") + user_id = user["id"] + api_key = gi.users.create_user_apikey(user_id) + user_gi = galaxy.GalaxyInstance(args.host, api_key) + return user_gi + + +if __name__ == "__main__": + main() diff --git a/test/selenium_tests/framework.py b/test/selenium_tests/framework.py index d0166dcd846..480b9e801d8 100644 --- a/test/selenium_tests/framework.py +++ b/test/selenium_tests/framework.py @@ -287,7 +287,7 @@ class SeleniumTestCase(FunctionalTestCase, NavigatesGalaxy, UsesApiTestCaseMixin self._try_setup_with_driver() def setup_driver_and_session(self): - self.display = driver_factory.virtual_display_if_enabled(headless_selenium()) + self.display = driver_factory.virtual_display_if_enabled(use_virtual_display()) self.driver = get_driver() # New workflow index page does not degrade well to smaller sizes, needed # to increase this. @@ -488,7 +488,20 @@ def headless_selenium(): return False if GALAXY_TEST_SELENIUM_HEADLESS == "auto": - if driver_factory.is_virtual_display_available(): + if driver_factory.is_virtual_display_available() or driver_factory.get_local_browser(GALAXY_TEST_SELENIUM_BROWSER) == "CHROME": + return True + else: + return False + else: + return asbool(GALAXY_TEST_SELENIUM_HEADLESS) + + +def use_virtual_display(): + if asbool(GALAXY_TEST_SELENIUM_REMOTE): + return False + + if GALAXY_TEST_SELENIUM_HEADLESS == "auto": + if driver_factory.is_virtual_display_available() and not driver_factory.get_local_browser(GALAXY_TEST_SELENIUM_BROWSER) == "CHROME": return True else: return False @@ -497,7 +510,10 @@ def headless_selenium(): def get_local_driver(): - return driver_factory.get_local_driver(GALAXY_TEST_SELENIUM_BROWSER) + return driver_factory.get_local_driver( + GALAXY_TEST_SELENIUM_BROWSER, + headless_selenium() + ) def get_remote_driver(): diff --git a/test/selenium_tests/test_anon_history.py b/test/selenium_tests/test_anon_history.py index 2f14fa29638..95caafb10fe 100644 --- a/test/selenium_tests/test_anon_history.py +++ b/test/selenium_tests/test_anon_history.py @@ -44,8 +44,9 @@ class AnonymousHistoriesTestCase(SeleniumTestCase): def test_clean_anon_history_after_logout(self): self._upload_file_anonymous_then_register_user() self.logout_if_needed() - # Give Galaxy a chance to load the new empty history for that now + # Give Galaxy the chance to load a new empty history for that now # anonymous user. Make sure this new history is empty. + self.home() self.history_panel_wait_for_history_loaded() history_contents = self.current_history_contents() assert len(history_contents) == 0 diff --git a/test/selenium_tests/test_history_panel_collections.py b/test/selenium_tests/test_history_panel_collections.py index 9aa1d373157..2cd5d175dfd 100644 --- a/test/selenium_tests/test_history_panel_collections.py +++ b/test/selenium_tests/test_history_panel_collections.py @@ -158,7 +158,8 @@ class HistoryPanelCollectionsTestCase(SeleniumTestCase): collection_hid = input_collection["hid"] collection_view = self._click_and_wait_for_collection_view(collection_hid) - self.tagging_add(["#moo"]) + # the space on the end of the parent_selector is important + self.tagging_add(["#moo"], parent_selector="#current-history-panel .dataset-collection-panel .controls ") self.screenshot("history_panel_collection_view_add_nametag") collection_view.back.wait_for_and_click() diff --git a/test/selenium_tests/test_library_contents.py b/test/selenium_tests/test_library_contents.py index fc0df067d2d..2dc1f5b0c25 100644 --- a/test/selenium_tests/test_library_contents.py +++ b/test/selenium_tests/test_library_contents.py @@ -35,6 +35,7 @@ class LibraryContentsTestCase(SeleniumTestCase): self.libraries_dataset_import_from_history() # Need to select the right item on the dropdown + self.sleep_for(self.wait_types.UX_RENDER) self._select_history_option("dataset_add_bulk", "Unnamed history") self.sleep_for(self.wait_types.UX_RENDER) self.libraries_dataset_import_from_history_select(["1.txt"]) diff --git a/test/selenium_tests/test_published_histories_grid.py b/test/selenium_tests/test_published_histories_grid.py index e694d4038b7..f5f1777a2ff 100644 --- a/test/selenium_tests/test_published_histories_grid.py +++ b/test/selenium_tests/test_published_histories_grid.py @@ -95,7 +95,7 @@ class HistoryGridTestCase(SharedStateSeleniumTestCase): if tags is None: raise AssertionError('Failed to find history with name [%s]' % self.history1_name) - tag_button_selector = '.tag-area > .tag-button > .tag-name' + tag_button_selector = 'div.tag-name' tag_buttons = tags.find_elements_by_css_selector(tag_button_selector) tag_button_text = None target_tag_button_text = self.history1_tags[0] diff --git a/test/selenium_tests/test_registration.py b/test/selenium_tests/test_registration.py index 8f1ad0a2f45..8b2bd40624f 100644 --- a/test/selenium_tests/test_registration.py +++ b/test/selenium_tests/test_registration.py @@ -38,8 +38,7 @@ class RegistrationTestCase(SeleniumTestCase): self.register(email, password, username, confirm) self.logout_if_needed() self.register(email, password, username, confirm, assert_valid=False) - with self.main_panel(): - self.assert_error_message() + self.assert_error_message() @selenium_test def test_reregister_username_fails(self): @@ -52,8 +51,7 @@ class RegistrationTestCase(SeleniumTestCase): self.register(email1, password, username, confirm) self.logout_if_needed() self.register(email2, password, username, confirm, assert_valid=False) - with self.main_panel(): - self.assert_error_message(contains='Public name is taken; please choose another') + self.assert_error_message(contains='Public name is taken') @selenium_test def test_bad_emails(self): @@ -62,30 +60,25 @@ class RegistrationTestCase(SeleniumTestCase): password = self.default_password confirm = password username = good_email.split("@")[0] - for bad_email in bad_emails: self.register(bad_email, password, username, confirm, assert_valid=False) - with self.main_panel(): - self.assert_error_message(contains='The format of the email address is not correct.') + self.assert_error_message(contains='The format of the email address is not correct.') @selenium_test def test_short_password(self): self.register(password="1234", assert_valid=False) - with self.main_panel(): - self.assert_error_message(contains='Use a password of at least 6 characters') + self.assert_error_message(contains='Use a password of at least 6 characters') @selenium_test def test_password_confirmation(self): - bad_confirms = ['1234', '12345678', '123456 7', ''] + bad_confirms = ['1234', '12345678', '123456 7'] for bad_confirm in bad_confirms: self.register(confirm=bad_confirm, assert_valid=False) - with self.main_panel(): - self.assert_error_message(contains='Passwords don\'t match') + self.assert_error_message(contains='Passwords do not match') @selenium_test def test_bad_usernames(self): bad_usernames = ['BOBERT', 'Robert Paulson', 'bobert!'] for bad_username in bad_usernames: self.register(username=bad_username, assert_valid=False) - with self.main_panel(): - self.assert_error_message(contains='Public name must contain only ') + self.assert_error_message(contains='Public name must contain only ') diff --git a/test/selenium_tests/test_saved_histories.py b/test/selenium_tests/test_saved_histories.py index 9cfea43272c..a0821224d34 100644 --- a/test/selenium_tests/test_saved_histories.py +++ b/test/selenium_tests/test_saved_histories.py @@ -211,20 +211,16 @@ class SavedHistoriesTestCase(SharedStateSeleniumTestCase): self._login() self.navigate_to_saved_histories_page() - # Click the add tag button - tags_cell = self.get_history_tags_cell(self.history2_name) - add_tag_button = tags_cell.find_element_by_css_selector('.add-tag-button') - add_tag_button.click() - # Insert a tag tags_cell = self.get_history_tags_cell(self.history2_name) - tag_area = tags_cell.find_element_by_tag_name('textarea') + tag_area = tags_cell.find_element_by_css_selector('.ti-new-tag-input-wrapper input') + tag_area.click() tag_area.send_keys(self.history2_tags[0]) self.send_enter(tag_area) # Search by tag tags_cell = self.get_history_tags_cell(self.history2_name) - tag = tags_cell.find_element_by_css_selector('span.tag-name') + tag = tags_cell.find_element_by_css_selector('.ti-tag-center') tag.click() self.assert_grid_histories_are([self.history2_name], False) diff --git a/test/shed_functional/base/twilltestcase.py b/test/shed_functional/base/twilltestcase.py index 4151f36ba01..33d2f80e87f 100644 --- a/test/shed_functional/base/twilltestcase.py +++ b/test/shed_functional/base/twilltestcase.py @@ -29,8 +29,8 @@ import galaxy.model.tool_shed_install as galaxy_model import galaxy.util import galaxy.webapps.tool_shed.util.hgweb_config from base.testcase import FunctionalTestCase # noqa: I100,I201,I202 -from galaxy.util import unicodify # noqa: I201 -from galaxy.web import security # noqa: I201 +from galaxy.security import idencoding # noqa: I201 +from galaxy.util import smart_str, unicodify # noqa: I201 from tool_shed.util import hg_util, xml_util from tool_shed.util.encoding_util import tool_shed_encode from . import common, test_db_util @@ -52,7 +52,7 @@ class ShedTwillTestCase(FunctionalTestCase): def setUp(self): # Security helper - self.security = security.SecurityHelper(id_secret='changethisinproductiontoo') + self.security = idencoding.IdEncodingHelper(id_secret='changethisinproductiontoo') self.history_id = None self.hgweb_config_dir = os.environ.get('TEST_HG_WEB_CONFIG_DIR') self.hgweb_config_manager = galaxy.webapps.tool_shed.util.hgweb_config.HgWebConfigManager() @@ -465,11 +465,9 @@ class ShedTwillTestCase(FunctionalTestCase): return self.wait_for(lambda: self.get_running_datasets(), **kwds) def write_temp_file(self, content, suffix='.html'): - fd, fname = tempfile.mkstemp(suffix=suffix, prefix='twilltestcase-') - f = os.fdopen(fd, "w") - f.write(content) - f.close() - return fname + with tempfile.NamedTemporaryFile(suffix=suffix, prefix='twilltestcase-', delete=False) as fh: + fh.write(smart_str(content)) + return fh.name def add_repository_review_component(self, **kwd): params = { @@ -785,33 +783,13 @@ class ShedTwillTestCase(FunctionalTestCase): def create_user_in_galaxy(self, cntrller='user', email='test@bx.psu.edu', password='testuser', username='admin-user', redirect=''): params = { - 'cntrller': cntrller, - 'use_panels': False + 'username': username, + 'email': email, + 'password': password, + 'confirm': password, + 'session_csrf_token': self.galaxy_token() } - self.visit_galaxy_url('/user/create', params=params) - self.submit_form('1', 'create_user_button', email=email, password=password, confirm=password, username=username, redirect=redirect) - previously_created = False - username_taken = False - invalid_username = False - try: - self.check_page_for_string("Created new user account") - except Exception: - try: - # May have created the account in a previous test run... - self.check_page_for_string("User with that email already exists") - previously_created = True - except Exception: - try: - self.check_page_for_string('Public name is taken; please choose another') - username_taken = True - except Exception: - try: - # Note that we're only checking if the usr name is >< 4 chars here... - self.check_page_for_string('Public name must be at least 4 characters in length') - invalid_username = True - except Exception: - pass - return previously_created, username_taken, invalid_username + self.visit_galaxy_url('/user/create', params=params, allowed_codes=[200, 400]) def deactivate_repository(self, installed_repository, strings_displayed=None, strings_not_displayed=None): params = { @@ -1070,19 +1048,7 @@ class ShedTwillTestCase(FunctionalTestCase): strings_displayed.append('Reviews were saved') self.check_for_strings(strings_displayed, strings_not_displayed) - def galaxy_login(self, email='test@bx.psu.edu', password='testuser', username='admin-user', redirect='', logout_first=True): - if logout_first: - self.galaxy_logout() - previously_created, username_taken, invalid_username = \ - self.create_user_in_galaxy(email=email, password=password, username=username, redirect=redirect) - if previously_created: - params = { - "login": email, - "password": password - } - self.visit_galaxy_url('/user/login', params=params) - - def galaxy_logout(self): + def galaxy_token(self): self.visit_galaxy_url("/") html = self.last_page() token_def_index = html.find("session_csrf_token") @@ -1090,8 +1056,21 @@ class ShedTwillTestCase(FunctionalTestCase): token_quote_start_index = html.find('"', token_sep_index) token_quote_end_index = html.find('"', token_quote_start_index + 1) token = html[(token_quote_start_index + 1):token_quote_end_index] - self.visit_galaxy_url("/user/logout", params=dict(session_csrf_token=token)) - self.check_page_for_string("You have been logged out") + return token + + def galaxy_login(self, email='test@bx.psu.edu', password='testuser', username='admin-user', redirect='', logout_first=True): + if logout_first: + self.galaxy_logout() + self.create_user_in_galaxy(email=email, password=password, username=username, redirect=redirect) + params = { + "login": email, + "password": password, + "session_csrf_token": self.galaxy_token() + } + self.visit_galaxy_url('/user/login', params=params) + + def galaxy_logout(self): + self.visit_galaxy_url("/user/logout", params=dict(session_csrf_token=self.galaxy_token())) tc.browser.cj.clear() def generate_complex_dependency_xml(self, filename, filepath, repository_tuples, package, version): @@ -2048,9 +2027,9 @@ class ShedTwillTestCase(FunctionalTestCase): self.visit_galaxy_url('/admin_toolshed/view_workflow', params=params) self.check_for_strings(strings_displayed, strings_not_displayed) - def visit_galaxy_url(self, url, params=None, doseq=False): + def visit_galaxy_url(self, url, params=None, doseq=False, allowed_codes=[200]): url = '%s%s' % (self.galaxy_url, url) - self.visit_url(url, params=params, doseq=doseq) + self.visit_url(url, params=params, doseq=doseq, allowed_codes=allowed_codes) def wait_for_repository_installation(self, repository_ids): final_states = [galaxy_model.ToolShedRepository.installation_status.ERROR, diff --git a/test/unit/authnz/__init__.py b/test/unit/authnz/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/unit/authnz/test_custos_authnz.py b/test/unit/authnz/test_custos_authnz.py new file mode 100644 index 00000000000..07b15cbca9b --- /dev/null +++ b/test/unit/authnz/test_custos_authnz.py @@ -0,0 +1,476 @@ +import hashlib +import os +import unittest +import uuid +from datetime import datetime, timedelta + +import jwt +import requests +from six.moves.urllib.parse import parse_qs, urlparse + +from galaxy.authnz import custos_authnz +from galaxy.model import CustosAuthnzToken, User + + +class CustosAuthnzTestCase(unittest.TestCase): + + _create_oauth2_session_called = False + _fetch_token_called = False + _get_userinfo_called = False + _raw_token = None + + def _get_base_idp_url(self): + # it would be ideal is we can use a URI as the following: + # https://test_base_uri/auth + return 'https://iam.scigap.org/auth' + + def _get_idp_url(self): + return "{}/realms/test-realm/.well-known/openid-configuration".format(self._get_base_idp_url()) + + def setUp(self): + self.orig_requests_get = requests.get + requests.get = self.mockRequest(self._get_idp_url(), { + "authorization_endpoint": "https://test-auth-endpoint", + "token_endpoint": "https://test-token-endpoint", + "userinfo_endpoint": "https://test-userinfo-endpoint" + }) + self.custos_authnz = custos_authnz.CustosAuthnz('Custos', { + 'VERIFY_SSL': True + }, { + 'url': self._get_base_idp_url(), + 'client_id': 'test-client-id', + 'client_secret': 'test-client-secret', + 'redirect_uri': 'https://test-redirect-uri', + 'realm': 'test-realm' + }) + self.setupMocks() + self.test_state = "abc123" + self.test_nonce = b"4662892146306485421546981092" + self.test_nonce_hash = hashlib.sha256(self.test_nonce).hexdigest() + self.test_code = "test-code" + self.test_username = "test-username" + self.test_email = "test-email" + self.test_alt_username = "test-alt-username" + self.test_alt_email = "test-alt-email" + self.test_access_token = "test_access_token" + self.test_refresh_token = "test_refresh_token" + self.test_expires_in = 30 + self.test_refresh_expires_in = 1800 + self.test_user_id = str(uuid.uuid4()) + self.test_alt_user_id = str(uuid.uuid4()) + self.trans.request.url = "https://localhost:8000/authnz/custos/oidc/callback?state={test_state}&code={test_code}".format(test_state=self.test_state, test_code=self.test_code) + + def setupMocks(self): + self.mock_fetch_token(self.custos_authnz) + self.mock_get_userinfo(self.custos_authnz) + self.trans = self.mockTrans() + + @property + def test_id_token(self): + return jwt.encode({'nonce': self.test_nonce_hash}, key=None, algorithm=None).decode() + + def mock_create_oauth2_session(self, custos_authnz): + orig_create_oauth2_session = custos_authnz._create_oauth2_session + + def create_oauth2_session(state=None): + self._create_oauth2_session_called = True + assert state == self.test_state + return orig_create_oauth2_session(state) + custos_authnz._create_oauth2_session = create_oauth2_session + + def mock_fetch_token(self, custos_authnz): + def fetch_token(oauth2_session, trans): + self._fetch_token_called = True + self._raw_token = { + "access_token": self.test_access_token, + "id_token": self.test_id_token, + "refresh_token": self.test_refresh_token, + "expires_in": self.test_expires_in, + "refresh_expires_in": self.test_refresh_expires_in + } + return self._raw_token + custos_authnz._fetch_token = fetch_token + + def mock_get_userinfo(self, custos_authnz): + def get_userinfo(oauth2_session): + self._get_userinfo_called = True + return { + "preferred_username": self.test_username, + "email": self.test_email, + "sub": self.test_user_id, + "alt_username": self.test_alt_username, + "alt_email": self.test_alt_email, + "alt_id": self.test_alt_user_id + } + custos_authnz._get_userinfo = get_userinfo + + def mockRequest(self, url, resp): + def get(x, **kwargs): + assert x == url + return Response() + + class Response(object): + def json(self): + return resp + + return get + + def mockTrans(self): + + class Request: + url = None + + class QueryResult: + results = [] + + def __init__(self, results=None): + if results: + self.results = results + + def first(self): + if len(self.results) > 0: + return self.results[0] + else: + return None + + def one_or_none(self): + if len(self.results) == 1: + return self.results[0] + elif len(self.results) == 0: + return None + else: + raise Exception("More than one result!") + + class Query: + external_user_id = None + provider = None + custos_authnz_token = None + + def filter_by(self, external_user_id=None, provider=None): + self.external_user_id = external_user_id + self.provider = provider + if self.custos_authnz_token: + return QueryResult([self.custos_authnz_token]) + else: + return QueryResult() + + class Session: + items = [] + flush_called = False + _query = Query() + deleted = [] + + def add(self, item): + self.items.append(item) + + def delete(self, item): + self.deleted.append(item) + + def flush(self): + self.flush_called = True + + def query(self, cls): + return self._query + + class Trans: + cookies = {} + cookies_args = {} + request = Request() + sa_session = Session() + user = None + + def set_cookie(self, value, name=None, **kwargs): + self.cookies[name] = value + self.cookies_args[name] = kwargs + + def get_cookie(self, name): + return self.cookies[name] + + return Trans() + + def tearDown(self): + requests.get = self.orig_requests_get + + def test_parse_config(self): + self.assertTrue(self.custos_authnz.config['verify_ssl']) + self.assertEqual(self.custos_authnz.config['client_id'], 'test-client-id') + self.assertEqual(self.custos_authnz.config['client_secret'], 'test-client-secret') + self.assertEqual(self.custos_authnz.config['redirect_uri'], 'https://test-redirect-uri') + self.assertEqual(self.custos_authnz.config['well_known_oidc_config_uri'], self._get_idp_url()) + self.assertEqual(self.custos_authnz.config['authorization_endpoint'], 'https://test-auth-endpoint') + self.assertEqual(self.custos_authnz.config['token_endpoint'], 'https://test-token-endpoint') + self.assertEqual(self.custos_authnz.config['userinfo_endpoint'], 'https://test-userinfo-endpoint') + + def test_authenticate_set_state_cookie(self): + """Verify that authenticate() sets a state cookie.""" + authorization_url = self.custos_authnz.authenticate(self.trans) + parsed = urlparse(authorization_url) + state = parse_qs(parsed.query)['state'][0] + self.assertEqual(state, self.trans.cookies[custos_authnz.STATE_COOKIE_NAME]) + + def test_authenticate_set_nonce_cookie(self): + """Verify that authenticate() sets a nonce cookie.""" + authorization_url = self.custos_authnz.authenticate(self.trans) + parsed = urlparse(authorization_url) + hashed_nonce_in_url = parse_qs(parsed.query)['nonce'][0] + nonce_in_cookie = self.trans.cookies[custos_authnz.NONCE_COOKIE_NAME] + hashed_nonce = self.custos_authnz._hash_nonce(nonce_in_cookie) + self.assertEqual(hashed_nonce, hashed_nonce_in_url) + + def test_authenticate_adds_extra_params(self): + """Verify that authenticate() adds configured extra params.""" + authorization_url = self.custos_authnz.authenticate(self.trans) + parsed = urlparse(authorization_url) + param1_value = parse_qs(parsed.query)['kc_idp_hint'][0] + self.assertEqual(param1_value, 'cilogon') + + def test_authenticate_sets_env_var_when_localhost_redirect(self): + """Verify that OAUTHLIB_INSECURE_TRANSPORT var is set with localhost redirect.""" + self.custos_authnz = custos_authnz.CustosAuthnz('Custos', { + 'VERIFY_SSL': True + }, { + 'url': self._get_base_idp_url(), + 'client_id': 'test-client-id', + 'client_secret': 'test-client-secret', + 'redirect_uri': 'http://localhost/auth/callback', + 'realm': 'test-realm' + }) + self.setupMocks() + self.assertIsNone(os.environ.get('OAUTHLIB_INSECURE_TRANSPORT', None)) + self.custos_authnz.authenticate(self.trans) + self.assertEqual("1", os.environ['OAUTHLIB_INSECURE_TRANSPORT']) + + def test_authenticate_does_not_set_env_var_when_https_redirect(self): + self.assertTrue(self.custos_authnz.config['redirect_uri'].startswith("https:")) + self.assertIsNone(os.environ.get('OAUTHLIB_INSECURE_TRANSPORT', None)) + self.custos_authnz.authenticate(self.trans) + self.assertIsNone(os.environ.get('OAUTHLIB_INSECURE_TRANSPORT', None)) + + def test_callback_verify_with_state_cookie(self): + """Verify that state from cookie is passed to OAuth2Session constructor.""" + self.trans.set_cookie(value=self.test_state, name=custos_authnz.STATE_COOKIE_NAME) + self.trans.set_cookie(value=self.test_nonce, name=custos_authnz.NONCE_COOKIE_NAME) + self.trans.sa_session._query.user = User(email=self.test_email, username=self.test_username) + + # Mock _create_oauth2_session to make sure it is created with cookie state token + self.mock_create_oauth2_session(self.custos_authnz) + + # Intentionally passing a bad state_token to make sure that code under + # test uses the state cookie instead when creating the OAuth2Session + login_redirect_url, user = self.custos_authnz.callback( + state_token="xxx", + authz_code=self.test_code, trans=self.trans, + login_redirect_url="http://localhost:8000/") + self.assertTrue(self._create_oauth2_session_called) + self.assertTrue(self._fetch_token_called) + self.assertTrue(self._get_userinfo_called) + self.assertEqual(login_redirect_url, "http://localhost:8000/") + self.assertIsNotNone(user) + + def test_callback_nonce_validation_with_bad_nonce(self): + self.trans.set_cookie(value=self.test_state, name=custos_authnz.STATE_COOKIE_NAME) + self.trans.set_cookie(value=self.test_nonce, name=custos_authnz.NONCE_COOKIE_NAME) + self.trans.sa_session._query.user = User(email=self.test_email, username=self.test_username) + + # Intentionally create a bad nonce + self.test_nonce_hash = self.test_nonce_hash + "Z" + + # self.custos_authnz._fetch_token = fetch_token + with self.assertRaises(Exception): + self.custos_authnz.callback(state_token="xxx", + authz_code=self.test_code, trans=self.trans, + login_redirect_url="http://localhost:8000/") + self.assertTrue(self._fetch_token_called) + self.assertFalse(self._get_userinfo_called) + + def test_callback_galaxy_user_created_when_no_custos_authnz_token_exists(self): + self.trans.set_cookie(value=self.test_state, name=custos_authnz.STATE_COOKIE_NAME) + self.trans.set_cookie(value=self.test_nonce, name=custos_authnz.NONCE_COOKIE_NAME) + + self.assertIsNone( + self.trans.sa_session.query(CustosAuthnzToken) + .filter_by(external_user_id=self.test_user_id, + provider=self.custos_authnz.config['provider']) + .one_or_none() + ) + self.assertEqual(0, len(self.trans.sa_session.items)) + login_redirect_url, user = self.custos_authnz.callback( + state_token="xxx", + authz_code=self.test_code, trans=self.trans, + login_redirect_url="http://localhost:8000/") + self.assertTrue(self._fetch_token_called) + self.assertTrue(self._get_userinfo_called) + self.assertEqual(2, len(self.trans.sa_session.items), "Session has new User and new CustosAuthnzToken") + added_user = self.trans.sa_session.items[0] + self.assertIsInstance(added_user, User) + self.assertEqual(self.test_username, added_user.username) + self.assertEqual(self.test_email, added_user.email) + self.assertIsNotNone(added_user.password) + # Verify added_custos_authnz_token + added_custos_authnz_token = self.trans.sa_session.items[1] + self.assertIsInstance(added_custos_authnz_token, CustosAuthnzToken) + self.assertIs(user, added_custos_authnz_token.user) + self.assertEqual(self.test_access_token, added_custos_authnz_token.access_token) + self.assertEqual(self.test_id_token, added_custos_authnz_token.id_token) + self.assertEqual(self.test_refresh_token, added_custos_authnz_token.refresh_token) + expected_expiration_time = datetime.now() + timedelta(seconds=self.test_expires_in) + expiration_timedelta = expected_expiration_time - added_custos_authnz_token.expiration_time + self.assertTrue(expiration_timedelta.total_seconds() < 1) + expected_refresh_expiration_time = datetime.now() + timedelta(seconds=self.test_refresh_expires_in) + refresh_expiration_timedelta = expected_refresh_expiration_time - added_custos_authnz_token.refresh_expiration_time + self.assertTrue(refresh_expiration_timedelta.total_seconds() < 1) + self.assertEqual(self.custos_authnz.config['provider'], added_custos_authnz_token.provider) + self.assertTrue(self.trans.sa_session.flush_called) + + def test_callback_galaxy_user_not_created_when_user_logged_in_and_no_custos_authnz_token_exists(self): + """ + Galaxy user is already logged in and trying to associate external + identity with their Galaxy user account. No new user should be created. + """ + self.trans.set_cookie(value=self.test_state, name=custos_authnz.STATE_COOKIE_NAME) + self.trans.set_cookie(value=self.test_nonce, name=custos_authnz.NONCE_COOKIE_NAME) + self.trans.user = User() + + self.assertIsNone( + self.trans.sa_session.query(CustosAuthnzToken) + .filter_by(external_user_id=self.test_user_id, + provider=self.custos_authnz.config['provider']) + .one_or_none() + ) + self.assertEqual(0, len(self.trans.sa_session.items)) + login_redirect_url, user = self.custos_authnz.callback( + state_token="xxx", + authz_code=self.test_code, trans=self.trans, + login_redirect_url="http://localhost:8000/") + self.assertTrue(self._fetch_token_called) + self.assertTrue(self._get_userinfo_called) + self.assertEqual(1, len(self.trans.sa_session.items), "Session has new CustosAuthnzToken") + # Verify added_custos_authnz_token + added_custos_authnz_token = self.trans.sa_session.items[0] + self.assertIsInstance(added_custos_authnz_token, CustosAuthnzToken) + self.assertIs(user, added_custos_authnz_token.user) + self.assertIs(user, self.trans.user) + self.assertTrue(self.trans.sa_session.flush_called) + + def test_callback_galaxy_user_not_created_when_custos_authnz_token_exists(self): + self.trans.set_cookie(value=self.test_state, name=custos_authnz.STATE_COOKIE_NAME) + self.trans.set_cookie(value=self.test_nonce, name=custos_authnz.NONCE_COOKIE_NAME) + old_access_token = "old-access-token" + old_id_token = "old-id-token" + old_refresh_token = "old-refresh-token" + old_expiration_time = datetime.now() - timedelta(days=1) + old_refresh_expiration_time = datetime.now() - timedelta(hours=3) + existing_custos_authnz_token = CustosAuthnzToken( + user=User(email=self.test_email, username=self.test_username), + external_user_id=self.test_user_id, + provider=self.custos_authnz.config['provider'], + access_token=old_access_token, + id_token=old_id_token, + refresh_token=old_refresh_token, + expiration_time=old_expiration_time, + refresh_expiration_time=old_refresh_expiration_time, + ) + + self.trans.sa_session._query.custos_authnz_token = existing_custos_authnz_token + + self.assertIsNotNone( + self.trans.sa_session.query(CustosAuthnzToken) + .filter_by(external_user_id=self.test_user_id, + provider=self.custos_authnz.config['provider']) + .one_or_none() + ), + self.assertEqual(0, len(self.trans.sa_session.items)) + login_redirect_url, user = self.custos_authnz.callback( + state_token="xxx", + authz_code=self.test_code, trans=self.trans, + login_redirect_url="http://localhost:8000/") + self.assertTrue(self._fetch_token_called) + self.assertTrue(self._get_userinfo_called) + # Make sure query was called with correct parameters + self.assertEqual(self.test_user_id, self.trans.sa_session._query.external_user_id) + self.assertEqual(self.custos_authnz.config['provider'], self.trans.sa_session._query.provider) + self.assertEqual(1, len(self.trans.sa_session.items), "Session has updated CustosAuthnzToken") + session_custos_authnz_token = self.trans.sa_session.items[0] + self.assertIsInstance(session_custos_authnz_token, CustosAuthnzToken) + self.assertIs(existing_custos_authnz_token, session_custos_authnz_token, "existing CustosAuthnzToken should be updated") + # Verify both that existing CustosAuthnzToken has the correct values and different values than before + self.assertEqual(self.test_access_token, session_custos_authnz_token.access_token) + self.assertNotEqual(old_access_token, session_custos_authnz_token.access_token) + self.assertEqual(self.test_id_token, session_custos_authnz_token.id_token) + self.assertNotEqual(old_id_token, session_custos_authnz_token.id_token) + self.assertEqual(self.test_refresh_token, session_custos_authnz_token.refresh_token) + self.assertNotEqual(old_refresh_token, session_custos_authnz_token.refresh_token) + expected_expiration_time = datetime.now() + timedelta(seconds=self.test_expires_in) + expiration_timedelta = expected_expiration_time - session_custos_authnz_token.expiration_time + self.assertTrue(expiration_timedelta.total_seconds() < 1) + self.assertNotEqual(old_expiration_time, session_custos_authnz_token.expiration_time) + expected_refresh_expiration_time = datetime.now() + timedelta(seconds=self.test_refresh_expires_in) + refresh_expiration_timedelta = expected_refresh_expiration_time - session_custos_authnz_token.refresh_expiration_time + self.assertTrue(refresh_expiration_timedelta.total_seconds() < 1) + self.assertNotEqual(old_refresh_expiration_time, session_custos_authnz_token.refresh_expiration_time) + self.assertTrue(self.trans.sa_session.flush_called) + + def test_disconnect(self): + custos_authnz_token = CustosAuthnzToken( + user=User(email=self.test_email, username=self.test_username), + external_user_id=self.test_user_id, + provider=self.custos_authnz.config['provider'], + access_token=self.test_access_token, + id_token=self.test_id_token, + refresh_token=self.test_refresh_token, + expiration_time=datetime.now() + timedelta(seconds=self.test_refresh_expires_in), + refresh_expiration_time=datetime.now() + timedelta(seconds=self.test_refresh_expires_in), + ) + self.trans.user = custos_authnz_token.user + self.trans.user.custos_auth = [custos_authnz_token] + + success, message, redirect_uri = self.custos_authnz.disconnect("Custos", self.trans, "/") + + self.assertEqual(1, len(self.trans.sa_session.deleted)) + deleted_token = self.trans.sa_session.deleted[0] + self.assertIs(custos_authnz_token, deleted_token) + self.assertTrue(self.trans.sa_session.flush_called) + self.assertTrue(success) + self.assertEqual("", message) + self.assertEqual("/", redirect_uri) + + def test_disconnect_when_no_associated_provider(self): + self.trans.user = User() + success, message, redirect_uri = self.custos_authnz.disconnect("Custos", self.trans, "/") + self.assertEqual(0, len(self.trans.sa_session.deleted)) + self.assertFalse(self.trans.sa_session.flush_called) + self.assertFalse(success) + self.assertNotEqual("", message) + self.assertIsNone(redirect_uri) + + def test_disconnect_when_more_than_one_associated_token_for_provider(self): + self.trans.user = User(email=self.test_email, username=self.test_username) + custos_authnz_token1 = CustosAuthnzToken( + user=self.trans.user, + external_user_id=self.test_user_id + "1", + provider=self.custos_authnz.config['provider'], + access_token=self.test_access_token, + id_token=self.test_id_token, + refresh_token=self.test_refresh_token, + expiration_time=datetime.now() + timedelta(seconds=self.test_refresh_expires_in), + refresh_expiration_time=datetime.now() + timedelta(seconds=self.test_refresh_expires_in), + ) + custos_authnz_token2 = CustosAuthnzToken( + user=self.trans.user, + external_user_id=self.test_user_id + "2", + provider=self.custos_authnz.config['provider'], + access_token=self.test_access_token, + id_token=self.test_id_token, + refresh_token=self.test_refresh_token, + expiration_time=datetime.now() + timedelta(seconds=self.test_refresh_expires_in), + refresh_expiration_time=datetime.now() + timedelta(seconds=self.test_refresh_expires_in), + ) + self.trans.user.custos_auth = [custos_authnz_token1, custos_authnz_token2] + + success, message, redirect_uri = self.custos_authnz.disconnect("Custos", self.trans, "/") + + self.assertEqual(0, len(self.trans.sa_session.deleted)) + self.assertFalse(self.trans.sa_session.flush_called) + self.assertFalse(success) + self.assertNotEqual("", message) + self.assertIsNone(redirect_uri) diff --git a/test/unit/dataset_collections/test_matching.py b/test/unit/dataset_collections/test_matching.py index b3aa022479d..b9484ce9c4e 100644 --- a/test/unit/dataset_collections/test_matching.py +++ b/test/unit/dataset_collections/test_matching.py @@ -1,10 +1,10 @@ -from galaxy.dataset_collections import ( +from galaxy.model.dataset_collections import ( matching, registry, type_description, ) -TYPE_REGISTRY = registry.DatasetCollectionTypesRegistry(None) +TYPE_REGISTRY = registry.DatasetCollectionTypesRegistry() TYPE_DESCRIPTION_FACTORY = type_description.CollectionTypeDescriptionFactory(TYPE_REGISTRY) diff --git a/test/unit/datatypes/test_data.py b/test/unit/datatypes/test_data.py index db0885dccb3..969916d57b9 100644 --- a/test/unit/datatypes/test_data.py +++ b/test/unit/datatypes/test_data.py @@ -2,10 +2,12 @@ Unit tests for base DataTypes. .. seealso:: galaxy.datatypes.data """ +import os from galaxy.datatypes.data import get_file_peek +from galaxy.util import galaxy_directory def test_get_file_peek(): # should get the first 5 lines of the file without a trailing newline character - assert get_file_peek('test-data/1.tabular', line_wrap=False) == 'chr22\t1000\tNM_17\nchr22\t2000\tNM_18\nchr10\t2200\tNM_10\nchr10\thap\ttest\nchr10\t1200\tNM_11' + assert get_file_peek(os.path.join(galaxy_directory(), 'test-data/1.tabular'), line_wrap=False) == 'chr22\t1000\tNM_17\nchr22\t2000\tNM_18\nchr10\t2200\tNM_10\nchr10\thap\ttest\nchr10\t1200\tNM_11' diff --git a/test/unit/jobs/test_command_factory.py b/test/unit/jobs/test_command_factory.py index e1f533495eb..b5493c3698a 100644 --- a/test/unit/jobs/test_command_factory.py +++ b/test/unit/jobs/test_command_factory.py @@ -32,49 +32,49 @@ class TestCommandFactory(TestCase): def test_simplest_command(self): self.include_work_dir_outputs = False - self.__assert_command_is(_surrond_command(MOCK_COMMAND_LINE + "; return_code=$?")) + self.__assert_command_is(_surround_command(MOCK_COMMAND_LINE + "; return_code=$?")) def test_shell_commands(self): self.include_work_dir_outputs = False dep_commands = [". /opt/galaxy/tools/bowtie/default/env.sh"] self.job_wrapper.dependency_shell_commands = dep_commands - self.__assert_command_is(_surrond_command("%s; %s; return_code=$?" % (dep_commands[0], MOCK_COMMAND_LINE))) + self.__assert_command_is(_surround_command("%s; %s; return_code=$?" % (dep_commands[0], MOCK_COMMAND_LINE))) def test_shell_commands_external(self): self.job_wrapper.commands_in_new_shell = True self.include_work_dir_outputs = False dep_commands = [". /opt/galaxy/tools/bowtie/default/env.sh"] self.job_wrapper.dependency_shell_commands = dep_commands - self.__assert_command_is(_surrond_command("%s/tool_script.sh; return_code=$?" % self.job_wrapper.working_directory)) + self.__assert_command_is(_surround_command("%s/tool_script.sh > ../tool_stdout 2> ../tool_stderr; return_code=$?" % self.job_wrapper.working_directory)) self.__assert_tool_script_is("#!/bin/sh\n%s; %s" % (dep_commands[0], MOCK_COMMAND_LINE)) def test_remote_dependency_resolution(self): self.include_work_dir_outputs = False dep_commands = [". /opt/galaxy/tools/bowtie/default/env.sh"] self.job_wrapper.dependency_shell_commands = dep_commands - self.__assert_command_is(_surrond_command(MOCK_COMMAND_LINE + "; return_code=$?"), remote_command_params=dict(dependency_resolution="remote")) + self.__assert_command_is(_surround_command(MOCK_COMMAND_LINE + "; return_code=$?"), remote_command_params=dict(dependency_resolution="remote")) def test_explicit_local_dependency_resolution(self): self.include_work_dir_outputs = False dep_commands = [". /opt/galaxy/tools/bowtie/default/env.sh"] self.job_wrapper.dependency_shell_commands = dep_commands - self.__assert_command_is(_surrond_command("%s; %s; return_code=$?" % (dep_commands[0], MOCK_COMMAND_LINE)), + self.__assert_command_is(_surround_command("%s; %s; return_code=$?" % (dep_commands[0], MOCK_COMMAND_LINE)), remote_command_params=dict(dependency_resolution="local")) def test_task_prepare_inputs(self): self.include_work_dir_outputs = False self.job_wrapper.prepare_input_files_cmds = ["/opt/split1", "/opt/split2"] - self.__assert_command_is(_surrond_command("/opt/split1; /opt/split2; %s; return_code=$?") % MOCK_COMMAND_LINE) + self.__assert_command_is(_surround_command("/opt/split1; /opt/split2; %s; return_code=$?") % MOCK_COMMAND_LINE) def test_workdir_outputs(self): self.include_work_dir_outputs = True self.workdir_outputs = [("foo", "bar")] - self.__assert_command_is(_surrond_command('%s; return_code=$?; if [ -f foo ] ; then cp foo bar ; fi' % MOCK_COMMAND_LINE)) + self.__assert_command_is(_surround_command('%s; return_code=$?; if [ -f foo ] ; then cp foo bar ; fi' % MOCK_COMMAND_LINE)) def test_set_metadata_skipped_if_unneeded(self): self.include_metadata = True self.include_work_dir_outputs = False - self.__assert_command_is(_surrond_command(MOCK_COMMAND_LINE + "; return_code=$?")) + self.__assert_command_is(_surround_command(MOCK_COMMAND_LINE + "; return_code=$?")) def test_set_metadata(self): self._test_set_metadata() @@ -87,7 +87,7 @@ class TestCommandFactory(TestCase): self.include_metadata = True self.include_work_dir_outputs = False self.job_wrapper.metadata_line = TEST_METADATA_LINE - expected_command = _surrond_command("%s; return_code=$?; cd '%s'; %s%s" % (MOCK_COMMAND_LINE, self.job_dir, SETUP_GALAXY_FOR_METADATA, TEST_METADATA_LINE)) + expected_command = _surround_command("%s; return_code=$?; cd '%s'; %s%s" % (MOCK_COMMAND_LINE, self.job_dir, SETUP_GALAXY_FOR_METADATA, TEST_METADATA_LINE)) self.__assert_command_is(expected_command) def test_empty_metadata(self): @@ -98,7 +98,7 @@ class TestCommandFactory(TestCase): self.include_work_dir_outputs = False self.job_wrapper.metadata_line = ' ' # Empty metadata command do not touch command line. - expected_command = _surrond_command("%s; return_code=$?; cd '%s'" % (MOCK_COMMAND_LINE, self.job_dir)) + expected_command = _surround_command("%s; return_code=$?; cd '%s'" % (MOCK_COMMAND_LINE, self.job_dir)) self.__assert_command_is(expected_command) def test_metadata_kwd_defaults(self): @@ -152,7 +152,7 @@ class TestCommandFactory(TestCase): return build_command(**kwds) -def _surrond_command(command): +def _surround_command(command): return '''rm -rf working; mkdir -p working; cd working; %s; sh -c "exit $return_code"''' % command diff --git a/test/unit/jobs/test_expression_run.py b/test/unit/jobs/test_expression_run.py new file mode 100644 index 00000000000..c23455d6e07 --- /dev/null +++ b/test/unit/jobs/test_expression_run.py @@ -0,0 +1,48 @@ +import json +import os +import shutil +import subprocess +import tempfile + +from galaxy.tools import expressions + +THIS_DIRECTORY = os.path.abspath(os.path.dirname(__file__)) +TEST_DIRECTORY = os.path.join(THIS_DIRECTORY, os.path.pardir, os.path.pardir) +ROOT_DIRECTORY = os.path.join(TEST_DIRECTORY, os.path.pardir) +LIB_DIRECTORY = os.path.join(ROOT_DIRECTORY, "lib") + + +def test_run_simple(): + test_directory = tempfile.mkdtemp() + try: + environment_path = os.path.join(test_directory, "env.json") + environment = { + 'job': {'input1': '7'}, + 'outputs': [ + {'name': 'out1', 'from_expression': "output1", 'path': 'moo'} + ], + 'script': "{return {'output1': parseInt($job.input1)};}", + } + with open(environment_path, "w") as f: + json.dump(environment, f) + expressions.write_evalute_script( + test_directory, + ) + new_env = os.environ.copy() + if "PYTHONPATH" in new_env: + new_env['PYTHONPATH'] = "%s:%s" % (LIB_DIRECTORY, new_env["PYTHONPATH"]) + else: + new_env['PYTHONPATH'] = "%s" % (LIB_DIRECTORY) + new_env['GALAXY_EXPRESSION_INPUTS'] = environment_path + p = subprocess.Popen( + args=expressions.EXPRESSION_SCRIPT_CALL, + shell=True, + cwd=test_directory, + env=new_env, + ) + assert p.wait() == 0 + with open(os.path.join(test_directory, 'moo')) as f: + out_content = f.read() + assert out_content == '7', out_content + finally: + shutil.rmtree(test_directory) diff --git a/test/unit/jobs/test_job_output_checker.py b/test/unit/jobs/test_job_output_checker.py index 471d59fda53..eb6a76cdcea 100644 --- a/test/unit/jobs/test_job_output_checker.py +++ b/test/unit/jobs/test_job_output_checker.py @@ -1,7 +1,6 @@ from unittest import TestCase from galaxy.jobs.output_checker import check_output, DETECTED_JOB_STATE -from galaxy.model import Job from galaxy.tools.parser.error_level import StdioErrorLevel from galaxy.tools.parser.interface import ToolStdioRegex from galaxy.util.bunch import Bunch @@ -14,8 +13,6 @@ class OutputCheckerTestCase(TestCase): stdio_regexes=[], stdio_exit_codes=[], ) - self.job = Job() - self.job.id = "test_id" self.stdout = '' self.stderr = '' self.tool_exit_code = None @@ -87,10 +84,10 @@ class OutputCheckerTestCase(TestCase): self.tool.stdio_regexes.append(regex) def __assertSuccessful(self): - assert self.__check_output() == DETECTED_JOB_STATE.OK + assert self.__check_output()[0] == DETECTED_JOB_STATE.OK def __assertNotSuccessful(self): - assert self.__check_output() != DETECTED_JOB_STATE.OK + assert self.__check_output()[0] != DETECTED_JOB_STATE.OK def __check_output(self): - return check_output(self.tool, self.stdout, self.stderr, self.tool_exit_code, self.job) + return check_output(self.tool.stdio_regexes, self.tool.stdio_exit_codes, self.stdout, self.stderr, self.tool_exit_code, "job_id") diff --git a/test/unit/managers/test_TagManager.py b/test/unit/managers/test_TagHandler.py similarity index 74% rename from test/unit/managers/test_TagManager.py rename to test/unit/managers/test_TagHandler.py index a11d7b0dd75..2f0afded17f 100644 --- a/test/unit/managers/test_TagManager.py +++ b/test/unit/managers/test_TagHandler.py @@ -2,7 +2,7 @@ from galaxy.managers import hdas from galaxy.managers.datasets import DatasetManager from galaxy.managers.histories import HistoryManager -from galaxy.managers.tags import GalaxyTagManager +from galaxy.model.tags import GalaxyTagHandler from galaxy.util import unicodify from .base import BaseTestCase @@ -12,14 +12,14 @@ user2_data = dict(email='user2@user2.user2', username='user2', password=default_ # ============================================================================= -class TagManagerTestCase(BaseTestCase): +class TagHandlerTestCase(BaseTestCase): def set_up_managers(self): - super(TagManagerTestCase, self).set_up_managers() + super(TagHandlerTestCase, self).set_up_managers() self.hda_manager = hdas.HDAManager(self.app) self.history_manager = HistoryManager(self.app) self.dataset_manager = DatasetManager(self.app) - self.tag_manager = GalaxyTagManager(self.trans.sa_session) + self.tag_handler = GalaxyTagHandler(self.trans.sa_session) self.user = self.user_manager.create(**user2_data) def _create_vanilla_hda(self, user=None): @@ -59,49 +59,49 @@ class TagManagerTestCase(BaseTestCase): ] for tag_string, expected_tag in zip(tag_strings, expected_tags): hda = self._create_vanilla_hda() - self.tag_manager.apply_item_tags(user=self.user, item=hda, tags_str=tag_string) + self.tag_handler.apply_item_tags(user=self.user, item=hda, tags_str=tag_string) self._check_tag_list(hda.tags, expected_tag) def test_set_tag_from_list(self): hda = self._create_vanilla_hda() tags = ['tag1', 'tag2'] - self.tag_manager.set_tags_from_list(self.user, hda, tags) + self.tag_handler.set_tags_from_list(self.user, hda, tags) self._check_tag_list(hda.tags, tags) # Setting tags should erase previous tags - self.tag_manager.set_tags_from_list(self.user, hda, ['tag1']) + self.tag_handler.set_tags_from_list(self.user, hda, ['tag1']) self._check_tag_list(hda.tags, expected_tags=['tag1']) def test_add_tag_from_list(self): hda = self._create_vanilla_hda() tags = ['tag1', 'tag2'] - self.tag_manager.add_tags_from_list(self.user, hda, tags) + self.tag_handler.add_tags_from_list(self.user, hda, tags) self._check_tag_list(tags=hda.tags, expected_tags=tags) # Adding tags should keep previous tags - self.tag_manager.add_tags_from_list(self.user, hda, ['tag3']) + self.tag_handler.add_tags_from_list(self.user, hda, ['tag3']) self._check_tag_list(hda.tags, expected_tags=['tag1', 'tag2', 'tag3']) def test_remove_tag_from_list(self): hda = self._create_vanilla_hda() - tags = ['tag1', 'tag2'] - self.tag_manager.set_tags_from_list(self.user, hda, tags) + tags = ['tag1', 'tag2', 'tag3'] + self.tag_handler.set_tags_from_list(self.user, hda, tags) self._check_tag_list(hda.tags, tags) - self.tag_manager.remove_tags_from_list(self.user, hda, ['tag1']) + self.tag_handler.remove_tags_from_list(self.user, hda, ['tag1', 'tag3']) self._check_tag_list(hda.tags, ['tag2']) def test_delete_item_tags(self): hda = self._create_vanilla_hda() tags = ['tag1'] - self.tag_manager.set_tags_from_list(self.user, hda, tags) - self.tag_manager.delete_item_tags(user=self.user, item=hda) + self.tag_handler.set_tags_from_list(self.user, hda, tags) + self.tag_handler.delete_item_tags(user=self.user, item=hda) self.assertEqual(hda.tags, []) def test_item_has_tag(self): hda = self._create_vanilla_hda() tags = ['tag1'] - self.tag_manager.set_tags_from_list(self.user, hda, tags) - self.assertTrue(self.tag_manager.item_has_tag(self.user, item=hda, tag='tag1')) + self.tag_handler.set_tags_from_list(self.user, hda, tags) + self.assertTrue(self.tag_handler.item_has_tag(self.user, item=hda, tag='tag1')) # ItemTagAssociation - self.assertTrue(self.tag_manager.item_has_tag(self.user, item=hda, tag=hda.tags[0])) + self.assertTrue(self.tag_handler.item_has_tag(self.user, item=hda, tag=hda.tags[0])) # Tag - self.assertTrue(self.tag_manager.item_has_tag(self.user, item=hda, tag=hda.tags[0].tag)) - self.assertFalse(self.tag_manager.item_has_tag(self.user, item=hda, tag='tag2')) + self.assertTrue(self.tag_handler.item_has_tag(self.user, item=hda, tag=hda.tags[0].tag)) + self.assertFalse(self.tag_handler.item_has_tag(self.user, item=hda, tag='tag2')) diff --git a/test/unit/queue_worker/__init__.py b/test/unit/queue_worker/__init__.py new file mode 100644 index 00000000000..9009d803301 --- /dev/null +++ b/test/unit/queue_worker/__init__.py @@ -0,0 +1,3 @@ +""" +Tests for Galaxy Queue Worker +""" diff --git a/test/unit/queue_worker/conftest.py b/test/unit/queue_worker/conftest.py new file mode 100644 index 00000000000..603265ebcc8 --- /dev/null +++ b/test/unit/queue_worker/conftest.py @@ -0,0 +1,63 @@ +import os +import tempfile + +import pytest + +from galaxy.util import which +from ..unittest_utils import galaxy_mock + + +def create_base_test(connection, amqp_type, amqp_connection=None): + app = galaxy_mock.MockApp(database_connection=connection) + app.config.database_connection = connection + app.config.amqp_internal_connection = amqp_connection or "sqlalchemy+%s" % app.config.database_connection + app.amqp_type = amqp_type + return app + + +@pytest.fixture() +def sqlite_connection(request): + fd, path = tempfile.mkstemp() + os.close(fd) + request.addfinalizer(lambda: os.remove(path)) + return 'sqlite:////%s' % path + + +@pytest.fixture() +def sqlite_rabbitmq_app(sqlite_connection): + + def create_app(): + amqp_connection = os.environ.get('GALAXY_TEST_AMQP_INTERNAL_CONNECTION') + return create_base_test(sqlite_connection, amqp_type='rabbitmq', amqp_connection=amqp_connection) + + return create_app + + +@pytest.fixture() +def sqlite_app(sqlite_connection): + + def create_app(): + return create_base_test(sqlite_connection, amqp_type='sqlite') + + return create_app + + +@pytest.fixture() +def postgres_app(postgresql_proc): + connection = "postgresql://{p.user}@{p.host}:{p.port}/".format(p=postgresql_proc) + + def create_app(): + return create_base_test(connection, amqp_type='postgres') + + return create_app + + +@pytest.fixture(params=['postgres_app', 'sqlite_app', 'sqlite_rabbitmq_app']) +def database_app(request): + if request.param == 'postgres_app': + if not which('initdb'): + pytest.skip("initdb must be on PATH for postgresql fixture") + if request.param == 'sqlite_rabbitmq_app': + if not os.environ.get('GALAXY_TEST_AMQP_INTERNAL_CONNECTION'): + pytest.skip("rabbitmq tests will be skipped if GALAXY_TEST_AMQP_INTERNAL_CONNECTION env var is unset") + return request.getfixturevalue(request.param) diff --git a/test/unit/queue_worker/test_database_heartbeat.py b/test/unit/queue_worker/test_database_heartbeat.py new file mode 100644 index 00000000000..1f86ae4f849 --- /dev/null +++ b/test/unit/queue_worker/test_database_heartbeat.py @@ -0,0 +1,64 @@ +import contextlib +import time + +import pytest + +from galaxy.web.stack import application_stack_instance +from galaxy.web.stack.database_heartbeat import DatabaseHeartbeat + + +@pytest.fixture +def heartbeat_app(database_app): + with setup_heartbeat_app(database_app()) as heartbeat_app: + yield heartbeat_app + + +@contextlib.contextmanager +def setup_heartbeat_app(app): + app.config.server_name = 'test_heartbeat' + app.config.attach_to_pools = False + app.application_stack = application_stack_instance(app=app) + app.database_heartbeat = DatabaseHeartbeat(application_stack=app.application_stack, heartbeat_interval=0.1) + yield app + app.database_heartbeat.shutdown() + + +def test_database_heartbeat(heartbeat_app): + active_processes = heartbeat_app.database_heartbeat.get_active_processes() + assert len(active_processes) == 0 + heartbeat_app.database_heartbeat.start() + + def one_active_process(): + active_processes = heartbeat_app.database_heartbeat.get_active_processes() + assert len(active_processes) == 1 + process = active_processes[0] + return process + + # thread needs to start + process = wait_for_assertion(one_active_process) + update_time = process.update_time + + def process_updated(): + heartbeat_app.model.context.refresh(process) + next_update_time = process.update_time + assert update_time < next_update_time + + wait_for_assertion(process_updated) + + heartbeat_app.database_heartbeat.shutdown() + time.sleep(0.5) + assert len(heartbeat_app.database_heartbeat.get_active_processes(last_seen_seconds=5)) == 1 + assert len(heartbeat_app.database_heartbeat.get_active_processes(last_seen_seconds=0.4)) == 0 + + +def wait_for_assertion(assert_f): + assertion_error = None + for i in range(10): + try: + v = assert_f() + return v + except AssertionError as e: + assertion_error = e + time.sleep(.2) + + raise assertion_error diff --git a/test/unit/queue_worker/test_queue_worker.py b/test/unit/queue_worker/test_queue_worker.py new file mode 100644 index 00000000000..c943eb95987 --- /dev/null +++ b/test/unit/queue_worker/test_queue_worker.py @@ -0,0 +1,113 @@ +import datetime +import time + +import pytest + +from galaxy.queue_worker import ( + GalaxyQueueWorker, + send_control_task, + send_local_control_task, +) +from galaxy.queues import connection_from_config +from galaxy.web.stack import application_stack_instance +from galaxy.web.stack.database_heartbeat import DatabaseHeartbeat + + +def bar(app, **kwargs): + app.some_var = 'bar' + app.tasks_executed.append('echo') + return 'bar' + + +control_message_to_task = {'echo': bar} + + +@pytest.fixture() +def queue_worker_factory(request, database_app): + + def app_factory(): + app = setup_queue_worker_test(database_app()) + request.addfinalizer(app.control_worker.shutdown) + request.addfinalizer(app.database_heartbeat.shutdown) + return app + + return app_factory + + +def setup_queue_worker_test(app): + app.some_var = 'foo' + app.tasks_executed = [] + server_name = "%s.%s" % (app.amqp_type, datetime.datetime.now()) + app.config.server_name = server_name + app.config.server_names = [server_name] + app.config.attach_to_pools = False + app.amqp_internal_connection_obj = connection_from_config(app.config) + app.application_stack = application_stack_instance(app=app) + app.database_heartbeat = DatabaseHeartbeat(application_stack=app.application_stack, heartbeat_interval=10) + app.database_heartbeat.start() + time.sleep(0.2) + app.control_worker = GalaxyQueueWorker(app=app, task_mapping=control_message_to_task) + app.control_worker.bind_and_start() + time.sleep(0.5) + return app + + +def test_send_control_task(queue_worker_factory): + app = queue_worker_factory() + send_control_task(app=app, task='echo') + wait_for_var(app, 'some_var', 'bar') + assert len(app.tasks_executed) == 1 + + +def test_send_control_task_to_many_listeners(queue_worker_factory): + app1 = queue_worker_factory() + app2 = queue_worker_factory() + app3 = queue_worker_factory() + app4 = queue_worker_factory() + app5 = queue_worker_factory() + send_control_task(app=app1, task='echo') + for app in [app1, app2, app3, app4, app5]: + wait_for_var(app, 'some_var', 'bar') + assert len(app.tasks_executed) == 1 + + +def test_send_control_task_get_result(queue_worker_factory): + app = queue_worker_factory() + response = send_control_task(app=app, task='echo', get_response=True) + assert response == 'bar' + assert app.some_var == 'bar' + assert len(app.tasks_executed) == 1 + + +def test_send_local_control_task(queue_worker_factory): + app = queue_worker_factory() + send_local_control_task(app=app, task='echo') + wait_for_var(app, 'some_var', 'bar') + assert len(app.tasks_executed) == 1 + + +def test_send_local_control_task_with_non_target_listeners(queue_worker_factory): + app1 = queue_worker_factory() + app2 = queue_worker_factory() + assert app2.some_var == 'foo' + send_local_control_task(app=app1, task='echo') + wait_for_var(app1, 'some_var', 'bar') + assert app2.some_var == 'foo' + assert len(app1.tasks_executed) == 1 + assert len(app2.tasks_executed) == 0 + + +def test_send_control_task_noop_self(queue_worker_factory): + app = queue_worker_factory() + assert app.some_var == 'foo' + response = send_control_task(app=app, task='echo', noop_self=True, get_response=True) + assert response == 'NO_OP' + assert app.some_var == 'foo' + assert len(app.tasks_executed) == 0 + + +def wait_for_var(obj, var, value, tries=10, sleep=0.25): + while getattr(obj, var) != value and tries >= 0: + tries -= 1 + time.sleep(sleep) + assert getattr(obj, var) == value diff --git a/test/unit/shed_unit/test_tool_panel_manager.py b/test/unit/shed_unit/test_tool_panel_manager.py index bf0ed4b319d..e8fd74ecfb5 100644 --- a/test/unit/shed_unit/test_tool_panel_manager.py +++ b/test/unit/shed_unit/test_tool_panel_manager.py @@ -128,7 +128,7 @@ class ToolPanelManagerTestCase(BaseToolBoxTestCase): assert "github.com/galaxyproject/example/test_tool/0.2" not in open(os.path.join(self.test_directory, "tool_conf.xml"), "r").read() self._verify_tool_confs() - self._remove_guids(["github.com/galaxyproject/example/test_tool/0.1"], uninstall=True) + self._remove_repository_contents("github.com/galaxyproject/example/test_tool/0.1", uninstall=True) # Now no versions of this tool are returned by new toolbox. new_toolbox = self.get_new_toolbox() @@ -144,7 +144,7 @@ class ToolPanelManagerTestCase(BaseToolBoxTestCase): self._setup_two_versions_in_config(section=True) self._setup_two_versions() self.toolbox - self._remove_guids(["github.com/galaxyproject/example/test_tool/0.2"], uninstall=uninstall) + self._remove_repository_contents("github.com/galaxyproject/example/test_tool/0.2", uninstall=uninstall) def _verify_version_2_removed_from_panel(self, section=True): # Check that test_tool now only has one version... @@ -164,9 +164,11 @@ class ToolPanelManagerTestCase(BaseToolBoxTestCase): next(iter(self.toolbox._tool_panel.values())).id == "github.com/galaxyproject/example/test_tool/0.1" assert "github.com/galaxyproject/example/test_tool/0.2" not in new_toolbox._integrated_tool_panel - def _remove_guids(self, guids, uninstall, shed_tool_conf="tool_conf.xml"): - self.tpm.remove_guids( - guids_to_remove=guids, + def _remove_repository_contents(self, guid, uninstall, shed_tool_conf="tool_conf.xml"): + tool = self.toolbox.get_tool(guid) + repository = tool.tool_shed_repository + self.tpm.remove_repository_contents( + repository=repository, shed_tool_conf=shed_tool_conf, uninstall=uninstall, ) diff --git a/test/unit/test_compression_util.py b/test/unit/test_compression_util.py index c68c094083d..582190d5def 100644 --- a/test/unit/test_compression_util.py +++ b/test/unit/test_compression_util.py @@ -1,23 +1,28 @@ +import shutil import tempfile +import unittest from galaxy.util.compression_utils import CompressedFile -def test_compression_safety(): - assert_safety("test-data/unsafe.tar", expected_to_be_safe=False) - assert_safety("test-data/unsafe_relative_symlink.tar", expected_to_be_safe=False) - assert_safety("test-data/unsafe.zip", expected_to_be_safe=False) - assert_safety("test-data/4.bed.zip", expected_to_be_safe=True) - assert_safety("test-data/testdir.tar", expected_to_be_safe=True) - assert_safety("test-data/safetar_with_symlink.tar", expected_to_be_safe=True) +class CompressionUtilTestCase(unittest.TestCase): + def test_compression_safety(self): + self.assert_safety("test-data/unsafe.tar", False) + self.assert_safety("test-data/unsafe_relative_symlink.tar", False) + self.assert_safety("test-data/unsafe.zip", False) + self.assert_safety("test-data/4.bed.zip", True) + self.assert_safety("test-data/testdir.tar", True) + self.assert_safety("test-data/safetar_with_symlink.tar", True) + self.assert_safety("test-data/safe_relative_symlink.tar", True) -def assert_safety(path, expected_to_be_safe=False): - d = tempfile.mkdtemp() - is_safe = True - try: - CompressedFile(path).extract(d) - except Exception: - is_safe = False - - assert is_safe is expected_to_be_safe + def assert_safety(self, path, expected_to_be_safe): + temp_dir = tempfile.mkdtemp() + try: + if expected_to_be_safe: + CompressedFile(path).extract(temp_dir) + else: + with self.assertRaises(Exception): + CompressedFile(path).extract(temp_dir) + finally: + shutil.rmtree(temp_dir, ignore_errors=True) diff --git a/test/unit/test_security_helper.py b/test/unit/test_id_encode_decode.py similarity index 88% rename from test/unit/test_security_helper.py rename to test/unit/test_id_encode_decode.py index c87497ea58e..d7aa10e7383 100644 --- a/test/unit/test_security_helper.py +++ b/test/unit/test_id_encode_decode.py @@ -1,23 +1,23 @@ # -*- coding: utf-8 -*- -from galaxy.web import security +from galaxy.security import idencoding -test_helper_1 = security.SecurityHelper(id_secret="secu1") -test_helper_2 = security.SecurityHelper(id_secret="secu2") +test_helper_1 = idencoding.IdEncodingHelper(id_secret="secu1") +test_helper_2 = idencoding.IdEncodingHelper(id_secret="secu2") def test_maximum_length_handling_ascii(): # Test that id secrets can be up to 56 characters long. - longest_id_secret = "m" * security.MAXIMUM_ID_SECRET_LENGTH - helper = security.SecurityHelper(id_secret=longest_id_secret) + longest_id_secret = "m" * idencoding.MAXIMUM_ID_SECRET_LENGTH + helper = idencoding.IdEncodingHelper(id_secret=longest_id_secret) helper.encode_id(1) # Test that security helper will catch if the id secret is too long. threw_exception = False - longer_id_secret = "m" * (security.MAXIMUM_ID_SECRET_LENGTH + 1) + longer_id_secret = "m" * (idencoding.MAXIMUM_ID_SECRET_LENGTH + 1) try: - security.SecurityHelper(id_secret=longer_id_secret) + idencoding.IdEncodingHelper(id_secret=longer_id_secret) except Exception: threw_exception = True @@ -44,14 +44,14 @@ def test_maximum_length_handling_ascii(): def test_maximum_length_handling_nonascii(): longest_id_secret = "◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎" - helper = security.SecurityHelper(id_secret=longest_id_secret) + helper = idencoding.IdEncodingHelper(id_secret=longest_id_secret) helper.encode_id(1) # Test that security helper will catch if the id secret is too long. threw_exception = False longer_id_secret = "◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎" try: - security.SecurityHelper(id_secret=longer_id_secret) + idencoding.IdEncodingHelper(id_secret=longer_id_secret) except Exception: threw_exception = True diff --git a/test/unit/test_model_store.py b/test/unit/test_model_store.py new file mode 100644 index 00000000000..79f133c7cb0 --- /dev/null +++ b/test/unit/test_model_store.py @@ -0,0 +1,399 @@ +import json +import os +from tempfile import mkdtemp + +from galaxy import model +from galaxy.model import store +from galaxy.tools.imp_exp import unpack_tar_gz_archive +from .tools.test_history_imp_exp import _create_datasets, _mock_app, Dummy + + +def test_import_export_history(): + """Test a simple job import/export after decompressing an archive (like history import/export tool).""" + app = _mock_app() + + u, h, d1, d2, j = _setup_simple_cat_job(app) + + imported_history = _import_export_history(app, h, export_files="copy") + + _assert_simple_cat_job_imported(imported_history) + + +def test_import_export_bag_archive(): + """Test a simple job import/export using a BagIt archive.""" + dest_parent = mkdtemp() + dest_export = os.path.join(dest_parent, "moo.tgz") + + app = _mock_app() + + u, h, d1, d2, j = _setup_simple_cat_job(app) + + with store.BagArchiveModelExportStore(dest_export, app=app, bag_archiver="tgz", export_files="copy") as export_store: + export_store.export_history(h) + + model_store = store.BagArchiveImportModelStore(dest_export, app=app, user=u) + with model_store.target_history(default_history=None) as imported_history: + model_store.perform_import(imported_history) + + _assert_simple_cat_job_imported(imported_history) + + +def test_import_export_datasets(): + """Test a simple job import/export using a directory.""" + app, h, temp_directory, import_history = _setup_simple_export({"for_edit": False}) + u = h.user + + _perform_import_from_directory(temp_directory, app, u, import_history) + + datasets = import_history.datasets + assert len(datasets) == 2 + imported_job = datasets[1].creating_job + assert imported_job + assert imported_job.output_datasets + assert imported_job.output_datasets[0].dataset == datasets[1] + + assert imported_job.input_datasets + assert imported_job.input_datasets[0].dataset == datasets[0] + + +def test_import_library_require_permissions(): + """Verify library creation (import) is off by default.""" + app = _mock_app() + sa_session = app.model.context + + u = model.User(email="collection@example.com", password="password") + + library = model.Library(name="my library 1", description="my library description", synopsis="my synopsis") + root_folder = model.LibraryFolder(name="my library 1", description='folder description') + library.root_folder = root_folder + sa_session.add_all((library, root_folder)) + sa_session.flush() + + temp_directory = mkdtemp() + with store.DirectoryModelExportStore(temp_directory, app=app) as export_store: + export_store.export_library(library) + + error_caught = False + try: + import_model_store = store.get_import_model_store_for_directory(temp_directory, app=app, user=u) + import_model_store.perform_import() + except AssertionError: + # TODO: throw and catch a better exception... + error_caught = True + + assert error_caught + + +def test_import_export_library(): + """Test basics of library, library folder, and library dataset import/export.""" + app = _mock_app() + sa_session = app.model.context + + u = model.User(email="collection@example.com", password="password") + + library = model.Library(name="my library 1", description="my library description", synopsis="my synopsis") + root_folder = model.LibraryFolder(name="my library 1", description='folder description') + library.root_folder = root_folder + sa_session.add_all((library, root_folder)) + sa_session.flush() + + subfolder = model.LibraryFolder(name="sub folder 1", description="sub folder") + root_folder.add_folder(subfolder) + sa_session.add(subfolder) + + ld = model.LibraryDataset(folder=root_folder, name="my name", info="my library dataset") + ldda = model.LibraryDatasetDatasetAssociation( + create_dataset=True, flush=False + ) + ld.library_dataset_dataset_association = ldda + root_folder.add_library_dataset(ld) + + sa_session.add(ld) + sa_session.add(ldda) + + sa_session.flush() + assert len(root_folder.datasets) == 1 + assert len(root_folder.folders) == 1 + + temp_directory = mkdtemp() + with store.DirectoryModelExportStore(temp_directory, app=app) as export_store: + export_store.export_library(library) + + import_model_store = store.get_import_model_store_for_directory(temp_directory, app=app, user=u, import_options=store.ImportOptions(allow_library_creation=True)) + import_model_store.perform_import() + + all_libraries = sa_session.query(model.Library).all() + assert len(all_libraries) == 2, len(all_libraries) + all_lddas = sa_session.query(model.LibraryDatasetDatasetAssociation).all() + assert len(all_lddas) == 2, len(all_lddas) + + new_library = [l for l in all_libraries if l.id != library.id][0] + assert new_library.name == "my library 1" + assert new_library.description == "my library description" + assert new_library.synopsis == "my synopsis" + + new_root = new_library.root_folder + assert new_root + assert new_root.name == "my library 1" + + assert len(new_root.folders) == 1 + assert len(new_root.datasets) == 1 + + +def test_finalize_job_state(): + """Verify jobs are given finalized states on import.""" + app, h, temp_directory, import_history = _setup_simple_export({"for_edit": False}) + u = h.user + + with open(os.path.join(temp_directory, store.ATTRS_FILENAME_JOBS), "r") as f: + job_attrs = json.load(f) + + for job in job_attrs: + job["state"] = "queued" + + with open(os.path.join(temp_directory, store.ATTRS_FILENAME_JOBS), "w") as f: + json.dump(job_attrs, f) + + _perform_import_from_directory(temp_directory, app, u, import_history) + + datasets = import_history.datasets + assert len(datasets) == 2 + imported_job = datasets[1].creating_job + assert imported_job + assert imported_job.state == model.Job.states.ERROR + + +def test_import_export_edit_datasets(): + """Test modifying existing HDA and dataset metadata with import.""" + app, h, temp_directory, import_history = _setup_simple_export({"for_edit": True}) + u = h.user + + # Fabric editing metadata... + datasets_metadata_path = os.path.join(temp_directory, store.ATTRS_FILENAME_DATASETS) + with open(datasets_metadata_path, "r") as f: + datasets_metadata = json.load(f) + + datasets_metadata[0]["name"] = "my new name 0" + datasets_metadata[1]["name"] = "my new name 1" + + assert "dataset" in datasets_metadata[0] + datasets_metadata[0]["dataset"]["object_store_id"] = "foo1" + + with open(datasets_metadata_path, "w") as f: + json.dump(datasets_metadata, f) + + _perform_import_from_directory(temp_directory, app, u, import_history, store.ImportOptions(allow_edit=True)) + + datasets = import_history.datasets + assert len(datasets) == 0 + + d1 = h.datasets[0] + d2 = h.datasets[1] + + assert d1.name == "my new name 0", d1.name + assert d2.name == "my new name 1", d2.name + assert d1.dataset.object_store_id == "foo1", d1.dataset.object_store_id + + +def test_import_export_edit_collection(): + """Test modifying existing collections with imports.""" + app = _mock_app() + sa_session = app.model.context + + u = model.User(email="collection@example.com", password="password") + h = model.History(name="Test History", user=u) + + c1 = model.DatasetCollection(collection_type="list", populated=False) + hc1 = model.HistoryDatasetCollectionAssociation(history=h, hid=1, collection=c1, name="HistoryCollectionTest1") + + sa_session.add(hc1) + sa_session.add(h) + sa_session.flush() + + import_history = model.History(name="Test History for Import", user=u) + sa_session.add(import_history) + + temp_directory = mkdtemp() + with store.DirectoryModelExportStore(temp_directory, app=app, for_edit=True) as export_store: + export_store.add_dataset_collection(hc1) + + # Fabric editing metadata for collection... + collections_metadata_path = os.path.join(temp_directory, store.ATTRS_FILENAME_COLLECTIONS) + datasets_metadata_path = os.path.join(temp_directory, store.ATTRS_FILENAME_DATASETS) + with open(collections_metadata_path, "r") as f: + hdcas_metadata = json.load(f) + + assert len(hdcas_metadata) == 1 + hdca_metadata = hdcas_metadata[0] + assert hdca_metadata + assert "id" in hdca_metadata + assert "collection" in hdca_metadata + collection_metadata = hdca_metadata["collection"] + assert "populated_state" in collection_metadata + assert collection_metadata["populated_state"] == model.DatasetCollection.populated_states.NEW + + collection_metadata["populated_state"] = model.DatasetCollection.populated_states.OK + + d1 = model.HistoryDatasetAssociation(extension="txt", create_dataset=True, flush=False) + d1.hid = 1 + d2 = model.HistoryDatasetAssociation(extension="txt", create_dataset=True, flush=False) + d2.hid = 2 + serialization_options = model.SerializationOptions(for_edit=True) + dataset_list = [d1.serialize(app.security, serialization_options), + d2.serialize(app.security, serialization_options)] + + dc = model.DatasetCollection( + id=collection_metadata["id"], + collection_type="list", + element_count=2, + ) + dc.populated_state = model.DatasetCollection.populated_states.OK + dce1 = model.DatasetCollectionElement( + element=d1, + element_index=0, + element_identifier="first", + ) + dce2 = model.DatasetCollectionElement( + element=d2, + element_index=1, + element_identifier="second", + ) + dc.elements = [dce1, dce2] + with open(datasets_metadata_path, "w") as datasets_f: + json.dump(dataset_list, datasets_f) + + hdca_metadata["collection"] = dc.serialize(app.security, serialization_options) + with open(collections_metadata_path, "w") as collections_f: + json.dump(hdcas_metadata, collections_f) + + _perform_import_from_directory(temp_directory, app, u, import_history, store.ImportOptions(allow_edit=True)) + + sa_session.refresh(c1) + assert c1.populated_state == model.DatasetCollection.populated_states.OK, c1.populated_state + assert len(c1.elements) == 2 + + +def test_sessionless_import_edit_datasets(): + app, h, temp_directory, import_history = _setup_simple_export({"for_edit": True}) + # Create a model store without a session and import it. + import_model_store = store.get_import_model_store_for_directory(temp_directory, import_options=store.ImportOptions(allow_dataset_object_edit=True, allow_edit=True)) + import_model_store.perform_import() + # Not using app.sa_session but a session mock that has a query/find pattern emulating usage + # of real sa_session. + d1 = import_model_store.sa_session.query(model.HistoryDatasetAssociation).find(h.datasets[0].id) + d2 = import_model_store.sa_session.query(model.HistoryDatasetAssociation).find(h.datasets[1].id) + assert d1 is not None + assert d2 is not None + + +def test_import_datasets_with_ids_fails_if_not_editing_models(): + app, h, temp_directory, import_history = _setup_simple_export({"for_edit": True}) + u = h.user + + caught = None + try: + _perform_import_from_directory(temp_directory, app, u, import_history, store.ImportOptions(allow_edit=False)) + except AssertionError as e: + # TODO: catch a better exception + caught = e + assert caught + + +def _setup_simple_export(export_kwds): + app = _mock_app() + + u, h, d1, d2, j = _setup_simple_cat_job(app) + + sa_session = app.model.context + + import_history = model.History(name="Test History for Import", user=u) + sa_session.add(import_history) + + temp_directory = mkdtemp() + with store.DirectoryModelExportStore(temp_directory, app=app, **export_kwds) as export_store: + export_store.add_dataset(d1) + export_store.add_dataset(d2) + + return app, h, temp_directory, import_history + + +def _assert_simple_cat_job_imported(imported_history): + assert imported_history.name == "imported from archive: Test History" + + datasets = imported_history.datasets + assert len(datasets) == 2 + imported_job = datasets[1].creating_job + assert imported_job + assert imported_job.output_datasets + assert imported_job.output_datasets[0].dataset == datasets[1] + + assert imported_job.input_datasets + assert imported_job.input_datasets[0].dataset == datasets[0] + + with open(datasets[0].file_name, "r") as f: + assert f.read().startswith("chr1 4225 19670") + with open(datasets[1].file_name, "r") as f: + assert f.read().startswith("chr1\t147962192\t147962580\tNM_005997_cds_0_0_chr1_147962193_r\t0\t-") + + +def _setup_simple_cat_job(app): + sa_session = app.model.context + + u = model.User(email="collection@example.com", password="password") + h = model.History(name="Test History", user=u) + + d1, d2 = _create_datasets(sa_session, h, 2) + + j = model.Job() + j.user = u + j.tool_id = "cat1" + + j.add_input_dataset("input1", d1) + j.add_output_dataset("out_file1", d2) + + sa_session.add_all((d1, d2, h, j)) + sa_session.flush() + + app.object_store.update_from_file(d1, file_name="test-data/1.txt", create=True) + app.object_store.update_from_file(d2, file_name="test-data/2.bed", create=True) + + return u, h, d1, d2, j + + +def _import_export_history(app, h, dest_export=None, export_files=None): + if dest_export is None: + dest_parent = mkdtemp() + dest_export = os.path.join(dest_parent, "moo.tgz") + + with store.TarModelExportStore(dest_export, app=app, export_files=export_files) as export_store: + export_store.export_history(h) + + imported_history = import_archive(dest_export, app, h.user) + assert imported_history + return imported_history + + +def _perform_import_from_directory(directory, app, user, import_history, import_options=None): + import_model_store = store.get_import_model_store_for_directory(directory, app=app, user=user, import_options=import_options) + with import_model_store.target_history(default_history=import_history): + import_model_store.perform_import(import_history) + + +def import_archive(archive_path, app, user): + dest_parent = mkdtemp() + dest_dir = os.path.join(dest_parent, 'dest') + + options = Dummy() + options.is_url = False + options.is_file = True + options.is_b64encoded = False + + args = (archive_path, dest_dir) + unpack_tar_gz_archive.main(options, args) + + new_history = None + model_store = store.get_import_model_store_for_directory(dest_dir, app=app, user=user) + with model_store.target_history(default_history=None) as new_history: + model_store.perform_import(new_history) + + return new_history diff --git a/test/unit/test_objectstore.py b/test/unit/test_objectstore.py index 34e06245197..afd8998e7bc 100644 --- a/test/unit/test_objectstore.py +++ b/test/unit/test_objectstore.py @@ -508,7 +508,7 @@ def test_config_parse_s3(): assert len(extra_dirs) == 2 -CLOUD_TEST_CONFIG = """ +CLOUD_AWS_TEST_CONFIG = """ @@ -518,8 +518,9 @@ CLOUD_TEST_CONFIG = """ """ -CLOUD_TEST_CONFIG_YAML = """ -type: s3 +CLOUD_AWS_TEST_CONFIG_YAML = """ +type: cloud +provider: aws auth: access_key: access_moo secret_key: secret_cow @@ -540,13 +541,86 @@ extra_dirs: """ -def test_config_parse_cloud(): - for config_str in [CLOUD_TEST_CONFIG, CLOUD_TEST_CONFIG_YAML]: - with TestConfig(config_str, clazz=UnitializedCloudObjectStore) as (directory, object_store): - assert object_store.access_key == "access_moo" - assert object_store.secret_key == "secret_cow" +CLOUD_AZURE_TEST_CONFIG = """ + + + + + + +""" - assert object_store.bucket == "unique_bucket_name_all_lowercase" +CLOUD_AZURE_TEST_CONFIG_YAML = """ +type: cloud +provider: azure +auth: + subscription_id: a_sub_id + client_id: and_a_client_id + secret: and_a_secret_key + tenant: and_some_tenant_info + +bucket: + name: unique_bucket_name_all_lowercase + use_reduced_redundancy: false + +cache: + path: database/object_store_cache + size: 1000 + +extra_dirs: +- type: job_work + path: database/job_working_directory_cloud +- type: temp + path: database/tmp_cloud +""" + + +CLOUD_GOOGLE_TEST_CONFIG = """ + + + + + + +""" + +CLOUD_GOOGLE_TEST_CONFIG_YAML = """ +type: cloud +provider: google +auth: + credentials_file: gcp.config + +bucket: + name: unique_bucket_name_all_lowercase + use_reduced_redundancy: false + +cache: + path: database/object_store_cache + size: 1000 + +extra_dirs: +- type: job_work + path: database/job_working_directory_cloud +- type: temp + path: database/tmp_cloud +""" + + +def test_config_parse_cloud(): + for config_str in [CLOUD_AWS_TEST_CONFIG, CLOUD_AWS_TEST_CONFIG_YAML, + CLOUD_AZURE_TEST_CONFIG, CLOUD_AZURE_TEST_CONFIG_YAML, + CLOUD_GOOGLE_TEST_CONFIG, CLOUD_GOOGLE_TEST_CONFIG_YAML]: + if "google" in config_str: + tmpdir = mkdtemp() + if not os.path.exists(tmpdir): + os.makedirs(tmpdir) + path = os.path.join(tmpdir, "gcp.config") + open(path, "w").write("some_gcp_config") + config_str = config_str.replace("gcp.config", path) + with TestConfig(config_str, clazz=UnitializedCloudObjectStore) as (directory, object_store): + + assert object_store.bucket_name == "unique_bucket_name_all_lowercase" assert object_store.use_rr is False assert object_store.host is None @@ -555,13 +629,13 @@ def test_config_parse_cloud(): assert object_store.is_secure is True assert object_store.conn_path == "/" - assert object_store.cache_size == 1000 + assert object_store.cache_size == 1000.0 assert object_store.staging_path == "database/object_store_cache" assert object_store.extra_dirs["job_work"] == "database/job_working_directory_cloud" assert object_store.extra_dirs["temp"] == "database/tmp_cloud" as_dict = object_store.to_dict() - _assert_has_keys(as_dict, ["auth", "bucket", "connection", "cache", "extra_dirs", "type"]) + _assert_has_keys(as_dict, ["provider", "auth", "bucket", "connection", "cache", "extra_dirs", "type"]) _assert_key_has_value(as_dict, "type", "cloud") @@ -570,8 +644,17 @@ def test_config_parse_cloud(): connection_dict = as_dict["connection"] cache_dict = as_dict["cache"] - _assert_key_has_value(auth_dict, "access_key", "access_moo") - _assert_key_has_value(auth_dict, "secret_key", "secret_cow") + provider = as_dict["provider"] + if provider == "aws": + _assert_key_has_value(auth_dict, "access_key", "access_moo") + _assert_key_has_value(auth_dict, "secret_key", "secret_cow") + elif provider == "azure": + _assert_key_has_value(auth_dict, "subscription_id", "a_sub_id") + _assert_key_has_value(auth_dict, "client_id", "and_a_client_id") + _assert_key_has_value(auth_dict, "secret", "and_a_secret_key") + _assert_key_has_value(auth_dict, "tenant", "and_some_tenant_info") + elif provider == "google": + _assert_key_has_value(auth_dict, "credentials_file", path) _assert_key_has_value(bucket_dict, "name", "unique_bucket_name_all_lowercase") _assert_key_has_value(bucket_dict, "use_reduced_redundancy", False) @@ -581,7 +664,7 @@ def test_config_parse_cloud(): _assert_key_has_value(connection_dict, "multipart", True) _assert_key_has_value(connection_dict, "is_secure", True) - _assert_key_has_value(cache_dict, "size", 1000) + _assert_key_has_value(cache_dict, "size", 1000.0) _assert_key_has_value(cache_dict, "path", "database/object_store_cache") extra_dirs = as_dict["extra_dirs"] @@ -657,7 +740,7 @@ def test_config_parse_azure(): class TestConfig(object): - def __init__(self, config_str, clazz=None): + def __init__(self, config_str=DISK_TEST_CONFIG, clazz=None): self.temp_directory = mkdtemp() if config_str.startswith("<"): config_file = "store.xml" diff --git a/test/unit/test_utils.py b/test/unit/test_utils.py index 536275c65e3..1c5ca1e5ac2 100644 --- a/test/unit/test_utils.py +++ b/test/unit/test_utils.py @@ -1,5 +1,17 @@ +from tempfile import NamedTemporaryFile + from galaxy import util +SECTION_XML = """ +
      + + + toolshed.g2.bx.psu.edu + + +
      +""" + def test_strip_control_characters(): s = '\x00bla' @@ -15,3 +27,45 @@ def test_strip_control_characters_nested(): assert util.strip_control_characters_nested(l)[0] == stripped_s assert util.strip_control_characters_nested(t)[0] == stripped_s assert util.strip_control_characters_nested(d)[42] == stripped_s + + +def test_parse_xml_string(): + section = util.parse_xml_string(SECTION_XML) + _verify_section(section) + + +def test_parse_xml_file(): + with NamedTemporaryFile(mode='w') as tmp: + tmp.write(SECTION_XML) + tmp.flush() + section = util.parse_xml(tmp.name).getroot() + _verify_section(section) + + +def _verify_section(section): + tool = next(iter(section)) + assert sorted(tool.items()) == [ + ('file', + 'toolshed.g2.bx.psu.edu/repos/peterjc/seq_filter_by_id/fb1313d79396/seq_filter_by_id/tools/seq_filter_by_id/seq_filter_by_id.xml'), + ('guid', + 'toolshed.g2.bx.psu.edu/repos/peterjc/seq_filter_by_id/seq_filter_by_id/0.2.5') + ] + assert next(iter(tool)).text == 'toolshed.g2.bx.psu.edu' + + +def test_xml_to_string(): + section = util.parse_xml_string(SECTION_XML) + s = util.xml_to_string(section) + assert len(s.split('\n')) == 1 + + +def test_xml_to_string_pretty(): + section = util.parse_xml_string(SECTION_XML) + s = util.xml_to_string(section, pretty=True) + PRETTY = """ +
      + + toolshed.g2.bx.psu.edu + +
      """ + assert s == PRETTY diff --git a/test/unit/tools/test_collect_primary_datasets.py b/test/unit/tools/test_collect_primary_datasets.py index 6c97e519ab9..0f1814680b3 100644 --- a/test/unit/tools/test_collect_primary_datasets.py +++ b/test/unit/tools/test_collect_primary_datasets.py @@ -6,8 +6,8 @@ from galaxy import ( model, util ) -from galaxy.tools.parameters.output_collect import LegacyToolProvidedMetadata, NullToolProvidedMetadata from galaxy.tools.parser import output_collection_def +from galaxy.tools.provided_metadata import LegacyToolProvidedMetadata, NullToolProvidedMetadata from .. import tools_support DEFAULT_TOOL_OUTPUT = "out1" @@ -329,9 +329,9 @@ class CollectPrimaryDatasetsTestCase(unittest.TestCase, tools_support.UsesApp, t if not os.path.exists(meta_file): tool_provided_metadata = NullToolProvidedMetadata() else: - tool_provided_metadata = LegacyToolProvidedMetadata(None, meta_file) + tool_provided_metadata = LegacyToolProvidedMetadata(meta_file) - return self.tool.collect_primary_datasets(self.outputs, tool_provided_metadata, job_working_directory, "txt", input_dbkey="btau") + return self.tool.discover_outputs(self.outputs, {}, tool_provided_metadata, job_working_directory, job=self.job, input_ext="txt", input_dbkey="btau") def _replace_output_collectors(self, xml_str): # Rewrite tool as if it had been created with output containing @@ -383,6 +383,7 @@ class CollectPrimaryDatasetsTestCase(unittest.TestCase, tools_support.UsesApp, t self.app.model.context.add(job) self.job = job self.history = self._new_history(hdas=[self.hda]) + self.job.history = self.history self.outputs = {DEFAULT_TOOL_OUTPUT: self.hda} def _new_history(self, hdas=[], flush=True): diff --git a/test/unit/tools/test_expression_basics.py b/test/unit/tools/test_expression_basics.py new file mode 100644 index 00000000000..51c7293ac3f --- /dev/null +++ b/test/unit/tools/test_expression_basics.py @@ -0,0 +1,8 @@ +from galaxy.tools.expressions import evaluate + + +def test_evaluate(): + input = { + "script": "{return 5;}" + } + assert evaluate(None, input) == 5 diff --git a/test/unit/tools/test_history_imp_exp.py b/test/unit/tools/test_history_imp_exp.py index 2c792d90992..a62062acab5 100644 --- a/test/unit/tools/test_history_imp_exp.py +++ b/test/unit/tools/test_history_imp_exp.py @@ -1,3 +1,4 @@ +import json import os import shutil import tarfile @@ -6,53 +7,75 @@ from tempfile import mkdtemp from galaxy import model from galaxy.exceptions import MalformedContents -from galaxy.tools.imp_exp import JobImportHistoryArchiveWrapper, unpack_tar_gz_archive +from galaxy.tools.imp_exp import JobExportHistoryArchiveWrapper, JobImportHistoryArchiveWrapper, unpack_tar_gz_archive from galaxy.tools.imp_exp.export_history import create_archive +from ..test_objectstore import TestConfig from ..unittest_utils.galaxy_mock import MockApp # good enough for the very specific tests we're writing as of now... DATASETS_ATTRS = '''[{{"info": "\\nuploaded txt file", "peek": "foo\\n\\n\\n\\n\\n\\n", "update_time": "2016-02-08 18:39:22.937474", "name": "Pasted Entry", "extension": "txt", "tags": {{}}, "__HistoryDatasetAssociation__": true, "file_name": "{file_name}", "deleted": false, "designation": null, "visible": true, "create_time": "2016-02-08 18:38:38.682087", "hid": 1, "parent_id": null, "extra_files_path": "", "uuid": "406d913e-925d-4ccd-800d-06c9b32df309", "metadata": {{"dbkey": "?", "data_lines": 1}}, "annotation": null, "blurb": "1 line", "exported": true}}]''' +DATASETS_ATTRS_EXPORT = '''[{{"info": "\\nuploaded txt file", "peek": "foo\\n\\n\\n\\n\\n\\n", "update_time": "2016-02-08 18:39:22.937474", "name": "Pasted Entry", "extension": "txt", "tags": {{}}, "__HistoryDatasetAssociation__": true, "file_name": "{file_name}", "deleted": false, "designation": null, "visible": true, "create_time": "2016-02-08 18:38:38.682087", "hid": 1, "parent_id": null, "_extra_files_path": "", "uuid": "406d913e-925d-4ccd-800d-06c9b32df309", "metadata": {{"dbkey": "?", "data_lines": 1}}, "annotation": null, "blurb": "1 line"}}]''' DATASETS_ATTRS_PROVENANCE = '''[]''' -HISTORY_ATTRS = '''{"hid_counter": 2, "update_time": "2016-02-08 18:38:38.705058", "create_time": "2016-02-08 18:38:20.790057", "includes_deleted_datasets": false, "name": "paste", "tags": {}, "includes_hidden_datasets": false, "genome_build": "?", "annotation": null}''' -JOBS_ATTRS = '''[{"info": null, "tool_id": "upload1", "update_time": "2016-02-08T18:39:23.356482", "stdout": "", "input_mapping": {}, "tool_version": "1.1.4", "input_datasets": [], "traceback": null, "command_line": "python /galaxy/tools/data_source/upload.py /galaxy /scratch/tmppwU9rD /scratch/tmpP4_45Y 1:/scratch/jobs/000/dataset_1_files:/data/000/dataset_1.dat", "exit_code": 0, "output_datasets": [1], "state": "ok", "create_time": "2016-02-08T18:38:39.153873", "params": {"files": [{"to_posix_lines": "Yes", "NAME": "None", "file_data": null, "space_to_tab": null, "url_paste": "/scratch/strio_url_paste_o6nrv8", "__index__": 0, "ftp_files": "", "uuid": "None"}], "paramfile": "/scratch/tmpP4_45Y", "file_type": "auto", "files_metadata": {"file_type": "auto", "__current_case__": 41}, "async_datasets": "None", "dbkey": "?"}, "stderr": ""}]''' +HISTORY_ATTRS = '''{"hid_counter": 2, "update_time": "2016-02-08 18:38:38.705058", "create_time": "2016-02-08 18:38:20.790057", "name": "paste", "tags": {}, "genome_build": "?", "annotation": null}''' +JOBS_ATTRS = '''[{"info": null, "tool_id": "upload1", "update_time": "2016-02-08T18:39:23.356482", "stdout": "", "input_mapping": {}, "tool_version": "1.1.4", "traceback": null, "command_line": "python /galaxy/tools/data_source/upload.py /galaxy /scratch/tmppwU9rD /scratch/tmpP4_45Y 1:/scratch/jobs/000/dataset_1_files:/data/000/dataset_1.dat", "exit_code": 0, "output_datasets": [1], "state": "ok", "create_time": "2016-02-08T18:38:39.153873", "params": {"files": [{"to_posix_lines": "Yes", "NAME": "None", "file_data": null, "space_to_tab": null, "url_paste": "/scratch/strio_url_paste_o6nrv8", "__index__": 0, "ftp_files": "", "uuid": "None"}], "paramfile": "/scratch/tmpP4_45Y", "file_type": "auto", "files_metadata": {"file_type": "auto", "__current_case__": 41}, "async_datasets": "None", "dbkey": "?"}, "stderr": ""}]''' -def _run_jihaw_cleanup(history_archive, msg): - app = MockApp() +class MockSetExternalTool(object): + + def regenerate_imported_metadata_if_needed(self, *args, **kwds): + pass + + +def _run_jihaw_cleanup(archive_dir, app=None): + app = app or _mock_app() job = model.Job() - job.stderr = '' - jiha = model.JobImportHistoryArchive(job=job, archive_dir=history_archive.arc_directory) + job.tool_stderr = '' + jiha = model.JobImportHistoryArchive(job=job, archive_dir=archive_dir) app.model.context.current.add_all([job, jiha]) app.model.context.flush() - jihaw = JobImportHistoryArchiveWrapper(app, 1) # yeehaw! + jihaw = JobImportHistoryArchiveWrapper(app, job.id) # yeehaw! + return app, jihaw.cleanup_after_job() + + +def _mock_app(): + app = MockApp() + test_object_store_config = TestConfig() + app.object_store = test_object_store_config.object_store + app.model.Dataset.object_store = app.object_store + app.datatypes_registry.set_external_metadata_tool = MockSetExternalTool() + return app + + +def _run_jihaw_cleanup_check_secure(history_archive, msg): + malformed = False try: - jihaw.cleanup_after_job() - data = app.object_store.get_data(model.Dataset(1)) - assert data != 'insecure', msg + app, _ = _run_jihaw_cleanup(history_archive.arc_directory) except MalformedContents: - pass + malformed = True + assert malformed def test_create_archive(): tempdir = mkdtemp() - dataset = os.path.join(tempdir, 'dataset_1.dat') - history_attrs_file = os.path.join(tempdir, 'history_attrs_file.txt') - datasets_attrs_file = os.path.join(tempdir, 'dataset_attrs_file.txt') - jobs_attrs_file = os.path.join(tempdir, 'jobs_attrs_file.txt') + dataset = os.path.join(tempdir, 'datasets/Pasted_Entry_1.txt') + history_attrs_file = os.path.join(tempdir, 'history_attrs.txt') + datasets_attrs_file = os.path.join(tempdir, 'datasets_attrs.txt') + jobs_attrs_file = os.path.join(tempdir, 'jobs_attrs.txt') out_file = os.path.join(tempdir, 'out.tar.gz') + os.makedirs(os.path.join(tempdir, 'datasets')) with open(dataset, 'w') as out: out.write('Hello\n') with open(history_attrs_file, 'w') as out: out.write(HISTORY_ATTRS) with open(datasets_attrs_file, 'w') as out: - out.write(DATASETS_ATTRS.format(file_name=dataset)) + out.write(DATASETS_ATTRS_EXPORT.format(file_name=dataset)) with open(jobs_attrs_file, 'w') as out: out.write(JOBS_ATTRS) try: - create_archive(history_attrs_file, datasets_attrs_file, jobs_attrs_file, out_file, gzip=True) + create_archive(tempdir, out_file, gzip=True) with tarfile.open(out_file) as t: - assert t.getnames() == ['datasets/Pasted_Entry_1.txt', 'history_attrs.txt', 'datasets_attrs.txt', 'jobs_attrs.txt'] + assert sorted(t.getnames()) == sorted(['datasets', 'datasets/Pasted_Entry_1.txt', 'history_attrs.txt', 'datasets_attrs.txt', 'jobs_attrs.txt']), t.getnames() finally: shutil.rmtree(tempdir) @@ -64,7 +87,7 @@ def test_history_import_symlink(): history_archive.write_metafiles() history_archive.write_link('datasets/Pasted_Entry_1.txt', '../target.txt') history_archive.write_file('target.txt', 'insecure') - _run_jihaw_cleanup(history_archive, 'Symlink dataset in import archive allowed') + _run_jihaw_cleanup_check_secure(history_archive, 'Symlink dataset in import archive allowed') def test_history_import_relpath_in_metadata(): @@ -74,7 +97,7 @@ def test_history_import_relpath_in_metadata(): history_archive.write_metafiles(dataset_file_name='../outside.txt') history_archive.write_file('datasets/Pasted_Entry_1.txt', 'foo') history_archive.write_outside() - _run_jihaw_cleanup(history_archive, 'Relative parent path in datasets_attrs.txt allowed') + _run_jihaw_cleanup_check_secure(history_archive, 'Relative parent path in datasets_attrs.txt allowed') def test_history_import_abspath_in_metadata(): @@ -85,7 +108,534 @@ def test_history_import_abspath_in_metadata(): dataset_file_name=os.path.join(history_archive.temp_directory, 'outside.txt')) history_archive.write_file('datasets/Pasted_Entry_1.txt', 'foo') history_archive.write_outside() - _run_jihaw_cleanup(history_archive, 'Absolute path in datasets_attrs.txt allowed') + _run_jihaw_cleanup_check_secure(history_archive, 'Absolute path in datasets_attrs.txt allowed') + + +def test_export_dataset(): + app, sa_session, h = _setup_history_for_export("Datasets History") + + d1, d2 = _create_datasets(sa_session, h, 2) + + j = model.Job() + j.user = h.user + j.tool_id = "cat1" + + j.add_input_dataset("input1", d1) + j.add_output_dataset("out_file1", d2) + + sa_session.add(d1) + sa_session.add(d2) + sa_session.add(h) + sa_session.add(j) + sa_session.flush() + + app.object_store.update_from_file(d1, file_name="test-data/1.txt", create=True) + app.object_store.update_from_file(d2, file_name="test-data/2.bed", create=True) + + imported_history = _import_export(app, h) + + datasets = list(imported_history.contents_iter(types=["dataset"])) + assert len(datasets) == 2 + imported_job = datasets[1].creating_job + assert imported_job + assert imported_job.output_datasets + assert imported_job.output_datasets[0].dataset == datasets[1] + + assert imported_job.input_datasets + assert imported_job.input_datasets[0].dataset == datasets[0] + + assert datasets[0].state == 'ok' + assert datasets[1].state == 'ok' + + with open(datasets[0].file_name, "r") as f: + assert f.read().startswith("chr1 4225 19670") + with open(datasets[1].file_name, "r") as f: + assert f.read().startswith("chr1\t147962192\t147962580\tNM_005997_cds_0_0_chr1_147962193_r\t0\t-") + + +def test_export_dataset_with_deleted_and_purged(): + app, sa_session, h = _setup_history_for_export("Datasets History with deleted") + + d1, d2 = _create_datasets(sa_session, h, 2) + + # Maybe use abstractions for deleting? + d1.deleted = True + d1.dataset.deleted = True + d1.dataset.purged = False + + d2.deleted = True + d2.dataset.deleted = True + d2.dataset.purged = True + + j1 = model.Job() + j1.user = h.user + j1.tool_id = "cat1" + + j1.add_output_dataset("out_file1", d1) + + j2 = model.Job() + j2.user = h.user + j2.tool_id = "cat1" + + j2.add_output_dataset("out_file1", d2) + + sa_session.add(d1) + sa_session.add(d2) + sa_session.add(j1) + sa_session.add(j2) + sa_session.add(h) + sa_session.flush() + + assert d1.deleted + + app.object_store.update_from_file(d1, file_name="test-data/1.txt", create=True) + app.object_store.update_from_file(d2, file_name="test-data/2.bed", create=True) + + imported_history = _import_export(app, h) + + datasets = list(imported_history.contents_iter(types=["dataset"])) + assert len(datasets) == 1 + + assert datasets[0].state == 'discarded' + assert datasets[0].deleted + assert datasets[0].dataset.deleted + assert datasets[0].creating_job + + +def test_multi_inputs(): + app, sa_session, h = _setup_history_for_export("Datasets History") + + d1, d2, d3 = _create_datasets(sa_session, h, 3) + + j = model.Job() + j.user = h.user + j.tool_id = "cat_multi" + + # Emulate multiple data inputs into multi data input parameter... + j.add_input_dataset("input1", d1) + j.add_input_dataset("input11", d1) + j.add_input_dataset("input12", d2) + j.add_output_dataset("out_file1", d3) + + sa_session.add(d1) + sa_session.add(d2) + sa_session.add(d3) + sa_session.add(h) + sa_session.add(j) + sa_session.flush() + + app.object_store.update_from_file(d1, file_name="test-data/1.txt", create=True) + app.object_store.update_from_file(d2, file_name="test-data/2.bed", create=True) + app.object_store.update_from_file(d3, file_name="test-data/4.bed", create=True) + + imported_history = _import_export(app, h) + + datasets = list(imported_history.contents_iter(types=["dataset"])) + assert len(datasets) == 3 + imported_job = datasets[2].creating_job + assert imported_job + assert imported_job.output_datasets + assert imported_job.output_datasets[0].dataset.hid == 3 + assert imported_job.output_datasets[0].dataset == datasets[2] + + assert imported_job.input_datasets + assert len(imported_job.input_datasets) == 3 + names = [d.name for d in imported_job.input_datasets] + hids = [d.dataset.hid for d in imported_job.input_datasets] + _assert_distinct(names) + for name in ["input1", "input11", "input12"]: + assert name in names + for hid in [1, 2]: + assert hid in hids + + with open(datasets[0].file_name, "r") as f: + assert f.read().startswith("chr1 4225 19670") + with open(datasets[1].file_name, "r") as f: + assert f.read().startswith("chr1\t147962192\t147962580\tNM_005997_cds_0_0_chr1_147962193_r\t0\t-") + + +def test_export_collection_history(): + app, sa_session, h = _setup_history_for_export("Collection History") + + d1, d2, d3, d4 = _create_datasets(sa_session, h, 4) + + c1 = model.DatasetCollection(collection_type="paired") + hc1 = model.HistoryDatasetCollectionAssociation(history=h, hid=1, collection=c1, name="HistoryCollectionTest1") + + dce1 = model.DatasetCollectionElement(collection=c1, element=d1, element_identifier="forward", element_index=0) + dce2 = model.DatasetCollectionElement(collection=c1, element=d2, element_identifier="reverse", element_index=1) + + c2 = model.DatasetCollection(collection_type="list:paired") + hc2 = model.HistoryDatasetCollectionAssociation(history=h, hid=2, collection=c2, name="HistoryCollectionTest2") + + cleaf = model.DatasetCollection(collection_type="paired") + dce2leaf1 = model.DatasetCollectionElement(collection=cleaf, element=d3, element_identifier="forward", element_index=0) + dce2leaf2 = model.DatasetCollectionElement(collection=cleaf, element=d4, element_identifier="reverse", element_index=1) + + dce21 = model.DatasetCollectionElement(collection=c2, element=cleaf, element_identifier="listel", element_index=0) + + j = model.Job() + j.user = h.user + j.tool_id = "cat1" + j.add_input_dataset_collection("input1_collect", hc1) + j.add_output_dataset_collection("output_collect", hc2) + + sa_session.add(dce1) + sa_session.add(dce2) + sa_session.add(dce21) + sa_session.add(dce2leaf1) + sa_session.add(dce2leaf2) + sa_session.add(hc1) + sa_session.add(hc2) + sa_session.add(j) + sa_session.flush() + + imported_history = _import_export(app, h) + + datasets = imported_history.datasets + assert len(datasets) == 4 + + dataset_collections = list(imported_history.contents_iter(types=["dataset_collection"])) + assert len(dataset_collections) == 2 + + imported_hdca1 = dataset_collections[0] + imported_hdca2 = dataset_collections[1] + + imported_collection_2 = imported_hdca2.collection + assert imported_hdca1.collection.collection_type == "paired" + assert imported_collection_2.collection_type == "list:paired" + + assert len(imported_collection_2.elements) == 1 + imported_top_level_element = imported_collection_2.elements[0] + assert imported_top_level_element.element_identifier == "listel", imported_top_level_element.element_identifier + assert imported_top_level_element.element_index == 0, imported_top_level_element.element_index + imported_nested_collection = imported_top_level_element.child_collection + assert len(imported_nested_collection.elements) == 2 + assert imported_nested_collection.collection_type == "paired", imported_nested_collection.collection_type + + assert len(imported_history.jobs) == 1 + imported_job = imported_history.jobs[0] + assert imported_job + assert len(imported_job.input_dataset_collections) == 1, len(imported_job.input_dataset_collections) + assert len(imported_job.output_dataset_collection_instances) == 1 + assert imported_job.id != j.id + + +def test_export_collection_with_mapping_history(): + app, sa_session, h = _setup_history_for_export("Collection Mapping History") + + d1, d2, d3, d4 = _create_datasets(sa_session, h, 4) + + c1 = model.DatasetCollection(collection_type="list") + hc1 = model.HistoryDatasetCollectionAssociation(history=h, hid=1, collection=c1, name="HistoryCollectionTest1") + dce1 = model.DatasetCollectionElement(collection=c1, element=d1, element_identifier="el1", element_index=0) + dce2 = model.DatasetCollectionElement(collection=c1, element=d2, element_identifier="el2", element_index=1) + + c2 = model.DatasetCollection(collection_type="list") + hc2 = model.HistoryDatasetCollectionAssociation(history=h, hid=2, collection=c2, name="HistoryCollectionTest2") + dce3 = model.DatasetCollectionElement(collection=c2, element=d3, element_identifier="el1", element_index=0) + dce4 = model.DatasetCollectionElement(collection=c2, element=d4, element_identifier="el2", element_index=1) + + hc2.add_implicit_input_collection("input1", hc1) + + j1 = model.Job() + j1.user = h.user + j1.tool_id = "cat1" + j1.add_input_dataset("input1", d1) + j1.add_output_dataset("out_file1", d3) + + j2 = model.Job() + j2.user = h.user + j2.tool_id = "cat1" + j2.add_input_dataset("input1", d2) + j2.add_output_dataset("out_file1", d4) + + sa_session.add(dce1) + sa_session.add(dce2) + sa_session.add(dce3) + sa_session.add(dce4) + sa_session.add(hc1) + sa_session.add(hc2) + sa_session.add(j1) + sa_session.add(j2) + sa_session.flush() + + implicit_collection_jobs = model.ImplicitCollectionJobs() + j1.add_output_dataset_collection("out_file1", hc2) # really? + ija1 = model.ImplicitCollectionJobsJobAssociation() + ija1.order_index = 0 + ija1.implicit_collection_jobs = implicit_collection_jobs + ija1.job = j1 + + j2.add_output_dataset_collection("out_file1", hc2) # really? + ija2 = model.ImplicitCollectionJobsJobAssociation() + ija2.order_index = 1 + ija2.implicit_collection_jobs = implicit_collection_jobs + ija2.job = j2 + + sa_session.add(implicit_collection_jobs) + sa_session.add(ija1) + sa_session.add(ija2) + sa_session.flush() + + imported_history = _import_export(app, h) + assert len(imported_history.jobs) == 2 + imported_job0 = imported_history.jobs[0] + + imported_icj = imported_job0.implicit_collection_jobs_association.implicit_collection_jobs + assert imported_icj + assert len(imported_icj.jobs) == 2, len(imported_icj.jobs) + + +def test_export_collection_with_datasets_from_other_history(): + app, sa_session, h = _setup_history_for_export("Collection History with dataset from other history") + + dataset_history = model.History(name="Dataset History", user=h.user) + + d1, d2 = _create_datasets(sa_session, dataset_history, 2) + + c1 = model.DatasetCollection(collection_type="paired") + hc1 = model.HistoryDatasetCollectionAssociation(history=h, hid=1, collection=c1, name="HistoryCollectionTest1") + h.hid_counter = 2 + dce1 = model.DatasetCollectionElement(collection=c1, element=d1, element_identifier="forward", element_index=0) + dce2 = model.DatasetCollectionElement(collection=c1, element=d2, element_identifier="reverse", element_index=1) + + sa_session.add(dce1) + sa_session.add(dce2) + sa_session.add(d1) + sa_session.add(d2) + sa_session.add(hc1) + sa_session.flush() + + imported_history = _import_export(app, h) + + assert imported_history.hid_counter == 4, imported_history.hid_counter + assert len(imported_history.dataset_collections) == 1 + assert len(imported_history.datasets) == 2 + for hdca in imported_history.dataset_collections: + assert hdca.hid == 1, hdca.hid + for hda in imported_history.datasets: + assert hda.hid in [2, 3] + _assert_distinct_hids(imported_history) + + +def test_export_collection_with_copied_datasets_and_overlapping_hids(): + app, sa_session, h = _setup_history_for_export("Collection History with dataset from other history") + + dataset_history = model.History(name="Dataset History", user=h.user) + + d1, d2 = _create_datasets(sa_session, dataset_history, 2) + + sa_session.add(d1) + sa_session.add(d2) + sa_session.add(dataset_history) + sa_session.flush() + + app.object_store.update_from_file(d1, file_name="test-data/1.txt", create=True) + app.object_store.update_from_file(d2, file_name="test-data/2.bed", create=True) + + d1_copy = d1.copy() + d2_copy = d2.copy() + + d1_copy.history = h + d2_copy.history = h + + c1 = model.DatasetCollection(collection_type="paired") + hc1 = model.HistoryDatasetCollectionAssociation(history=h, hid=3, collection=c1, name="HistoryCollectionTest1") + h.hid_counter = 5 + dce1 = model.DatasetCollectionElement(collection=c1, element=d1, element_identifier="forward", element_index=0) + dce2 = model.DatasetCollectionElement(collection=c1, element=d2, element_identifier="reverse", element_index=1) + + sa_session.add(dce1) + sa_session.add(dce2) + sa_session.add(d1_copy) + sa_session.add(d2_copy) + sa_session.add(hc1) + sa_session.flush() + + _import_export(app, h) + # Currently d1 and d1_copy would have conflicting paths in the tar file... this test verifies at least + # this doesn't prevent the tar ball creation. A lot more work could be done here - such as making sure + # they map to the same dataset on import and making sure the resulting datasets all look good in the + # imported history. + + +def test_export_copied_collection(): + app, sa_session, h = _setup_history_for_export("Collection History with dataset from other history") + + d1, d2 = _create_datasets(sa_session, h, 2) + + c1 = model.DatasetCollection(collection_type="paired") + hc1 = model.HistoryDatasetCollectionAssociation(history=h, hid=3, collection=c1, name="HistoryCollectionTest1") + h.hid_counter = 4 + dce1 = model.DatasetCollectionElement(collection=c1, element=d1, element_identifier="forward", element_index=0) + dce2 = model.DatasetCollectionElement(collection=c1, element=d2, element_identifier="reverse", element_index=1) + + sa_session.add_all((dce1, dce2, d1, d2, hc1)) + sa_session.flush() + + hc2 = hc1.copy(element_destination=h) + h.add_dataset_collection(hc2) + assert h.hid_counter == 7 + + sa_session.add(hc2) + sa_session.flush() + + assert hc2.copied_from_history_dataset_collection_association == hc1 + + imported_history = _import_export(app, h) + assert imported_history.hid_counter == 7 + + assert len(imported_history.dataset_collections) == 2 + assert len(imported_history.datasets) == 4 + + _assert_distinct_hids(imported_history) + imported_by_hid = _hid_dict(imported_history) + assert imported_by_hid[4].copied_from_history_dataset_association == imported_by_hid[1] + assert imported_by_hid[5].copied_from_history_dataset_association == imported_by_hid[2] + assert imported_by_hid[6].copied_from_history_dataset_collection_association == imported_by_hid[3] + + +def test_export_collection_hids(): + app, sa_session, h = _setup_history_for_export("Collection History with dataset from this history") + + d1, d2 = _create_datasets(sa_session, h, 2) + + c1 = model.DatasetCollection(collection_type="paired") + hc1 = model.HistoryDatasetCollectionAssociation(history=h, hid=3, collection=c1, name="HistoryCollectionTest1") + h.hid_counter = 4 + dce1 = model.DatasetCollectionElement(collection=c1, element=d1, element_identifier="forward", element_index=0) + dce2 = model.DatasetCollectionElement(collection=c1, element=d2, element_identifier="reverse", element_index=1) + + sa_session.add(dce1) + sa_session.add(dce2) + sa_session.add(d1) + sa_session.add(d2) + sa_session.add(hc1) + sa_session.flush() + + imported_history = _import_export(app, h) + + assert imported_history.hid_counter == 4, imported_history.hid_counter + assert len(imported_history.dataset_collections) == 1 + assert len(imported_history.datasets) == 2 + for hdca in imported_history.dataset_collections: + assert hdca.hid == 3, hdca.hid + for hda in imported_history.datasets: + assert hda.hid in [1, 2], hda.hid + _assert_distinct_hids(imported_history) + + +def _assert_distinct_hids(history): + hids = [] + for hdca in history.dataset_collections: + hids.append(hdca.hid) + for hda in history.datasets: + hids.append(hda.hid) + _assert_distinct(hids) + + +def _hid_dict(history): + hids = {} + for hdca in history.dataset_collections: + hids[hdca.hid] = hdca + for hda in history.datasets: + hids[hda.hid] = hda + return hids + + +def _assert_distinct(l): + assert len(l) == len(set(l)) + + +def _create_datasets(sa_session, history, n): + return [model.HistoryDatasetAssociation(extension="txt", history=history, create_dataset=True, sa_session=sa_session, hid=i + 1) for i in range(n)] + + +def _setup_history_for_export(history_name): + app = _mock_app() + sa_session = app.model.context + + email = history_name.replace(" ", "-") + "-user@example.org" + u = model.User(email=email, password="password") + h = model.History(name=history_name, user=u) + + return app, sa_session, h + + +def _import_export(app, h, dest_export=None): + if dest_export is None: + dest_parent = mkdtemp() + dest_export = os.path.join(dest_parent, "moo.tgz") + + jeha = model.JobExportHistoryArchive(job=None, history=h, + dataset=None, + compressed=True) + wrapper = JobExportHistoryArchiveWrapper(app, 1) + wrapper.setup_job(jeha) + + from galaxy.tools.imp_exp import export_history + ret = export_history.main(["--gzip", jeha.temp_directory, dest_export]) + assert ret == 0, ret + + _, imported_history = import_archive(dest_export, app=app) + assert imported_history + return imported_history + + +def test_import_1901_default(): + app, new_history = import_archive('test-data/exports/1901_two_datasets.tgz') + assert new_history + + datasets = new_history.datasets + assert len(datasets) == 2 + dataset0 = datasets[0] + dataset1 = datasets[1] + + assert dataset0.hid == 1 + # There was a deleted dataset so skip to 3 + assert dataset1.hid == 3, dataset1.hid + + jobs = app.model.context.query(model.Job) \ + .filter_by(history_id=new_history.id).order_by(model.Job.table.c.id).all() + assert len(jobs) == 2 + assert jobs[0].tool_id == 'upload1' + assert jobs[1].tool_id == 'cat' + + cat_job = jobs[1] + assert len(cat_job.input_datasets) == 2 + assert len(cat_job.output_datasets) == 1 + + assert cat_job.input_datasets[0].dataset == dataset0 + assert cat_job.input_datasets[1].dataset == dataset0 + assert cat_job.output_datasets[0].dataset == dataset1 + + param_dict = cat_job.raw_param_dict() + # Not sure these shouldn't be {"values": [{"src": "hda", "id": dataset0.id}]} + # but parameter processing for pre-19.05 exports/imports didn't work anyway so + # perhaps not a problem? + input1_param = json.loads(param_dict['input1']) + assert input1_param["src"] == "hda" + assert input1_param["id"] == dataset0.id + + input2_param = json.loads(param_dict['queries'])[0]['input2'] + assert input2_param["src"] == "hda" + assert input2_param["id"] == dataset0.id + + +def import_archive(archive_path, app=None): + dest_parent = mkdtemp() + dest_dir = os.path.join(dest_parent, 'dest') + + options = Dummy() + options.is_url = False + options.is_file = True + options.is_b64encoded = False + + args = (archive_path, dest_dir) + unpack_tar_gz_archive.main(options, args) + app, new_history = _run_jihaw_cleanup(dest_dir, app=app) + return app, new_history def _run_unpack(history_archive, dest_parent, msg): @@ -110,6 +660,7 @@ def test_history_import_relpath_in_archive(): """ dest_parent = mkdtemp() with HistoryArchive(arcname_prefix='../insecure') as history_archive: + history_archive.write_metafiles() history_archive.write_file('datasets/Pasted_Entry_1.txt', 'foo') history_archive.finalize() @@ -122,6 +673,7 @@ def test_history_import_abspath_in_archive(): """ dest_parent = mkdtemp() arcname_prefix = os.path.abspath(os.path.join(dest_parent, 'insecure')) + with HistoryArchive(arcname_prefix=arcname_prefix) as history_archive: history_archive.write_metafiles() history_archive.write_file('datasets/Pasted_Entry_1.txt', 'foo') diff --git a/test/unit/tools/test_metadata.py b/test/unit/tools/test_metadata.py new file mode 100644 index 00000000000..2c7e2e99fd0 --- /dev/null +++ b/test/unit/tools/test_metadata.py @@ -0,0 +1,197 @@ +import os +import subprocess +import unittest + +from galaxy import model +from galaxy.jobs.datasets import DatasetPath +from galaxy.metadata import get_metadata_compute_strategy +from galaxy.objectstore import ObjectStorePopulator +from .. import tools_support + + +class MetadataTestCase(unittest.TestCase, tools_support.UsesApp, tools_support.UsesTools): + + def setUp(self): + super(MetadataTestCase, self).setUp() + self.setup_app() + model.Dataset.object_store = self.app.object_store + job = model.Job() + sa_session = self.app.model.session + sa_session.add(job) + history = model.History() + job.history = history + sa_session.flush() + self.job = job + self.history = history + self.job_working_directory = os.path.join(self.test_directory, "job_working") + self.tool_working_directory = os.path.join(self.job_working_directory, "working") + os.mkdir(self.job_working_directory) + os.mkdir(self.tool_working_directory) + + def tearDown(self): + super(MetadataTestCase, self).tearDown() + self.metadata_compute_strategy = None + + def test_simple_output_legacy(self): + self.app.config.metadata_strategy = "legacy" + self._test_simple_output() + + def test_simple_output_directory(self): + self.app.config.metadata_strategy = "directory" + self._test_simple_output() + + def _test_simple_output(self): + source_file_name = os.path.join(os.getcwd(), "test/functional/tools/for_workflows/cat.xml") + self._init_tool_for_path(source_file_name) + output_dataset = self._create_output_dataset( + extension="fasta", + ) + sa_session = self.app.model.session + sa_session.flush() + output_datasets = { + "out_file1": output_dataset, + } + command = self.metadata_command(output_datasets) + self._write_output_dataset_contents(output_dataset, ">seq1\nGCTGCATG\n") + self.exec_metadata_command(command) + metadata_set_successfully = self.metadata_compute_strategy.external_metadata_set_successfully(output_dataset, "out_file1", sa_session, working_directory=self.job_working_directory) + assert metadata_set_successfully + self.metadata_compute_strategy.load_metadata(output_dataset, "out_file1", sa_session, working_directory=self.job_working_directory) + assert output_dataset.metadata.data_lines == 2 + assert output_dataset.metadata.sequences == 1 + + def test_primary_dataset_output_extension_legacy(self): + self.app.config.metadata_strategy = "legacy" + self._test_primary_dataset_output_extension() + + def test_primary_dataset_output_extension_directory(self): + self.app.config.metadata_strategy = "directory" + self._test_primary_dataset_output_extension() + + def _test_primary_dataset_output_extension(self): + source_file_name = os.path.join(os.getcwd(), "test/functional/tools/for_workflows/cat.xml") + self._init_tool_for_path(source_file_name) + # setting extension to 'auto' here, results in the extension specified in + # galaxy.json (below) being respected. + output_dataset = self._create_output_dataset( + extension="auto", + ) + sa_session = self.app.model.session + sa_session.flush() + output_datasets = { + "out_file1": output_dataset, + } + command = self.metadata_command(output_datasets) + self._write_galaxy_json("""{"type": "dataset", "dataset_id": "%s", "name": "my dynamic name", "ext": "fasta", "info": "my dynamic info"}""" % output_dataset.dataset.id) + self._write_output_dataset_contents(output_dataset, ">seq1\nGCTGCATG\n") + self.exec_metadata_command(command) + metadata_set_successfully = self.metadata_compute_strategy.external_metadata_set_successfully(output_dataset, "out_file1", sa_session, working_directory=self.job_working_directory) + assert metadata_set_successfully + output_dataset.extension = "fasta" # gets done in job finish... + self.metadata_compute_strategy.load_metadata(output_dataset, "out_file1", sa_session, working_directory=self.job_working_directory) + assert output_dataset.metadata.data_lines == 2 + assert output_dataset.metadata.sequences == 1 + + def test_primary_dataset_output_metadata_override_legacy(self): + self.app.config.metadata_strategy = "legacy" + self._test_primary_dataset_output_metadata_override() + + def test_primary_dataset_output_metadata_override_directory(self): + self.app.config.metadata_strategy = "directory" + self._test_primary_dataset_output_metadata_override() + + def _test_primary_dataset_output_metadata_override(self): + source_file_name = os.path.join(os.getcwd(), "test/functional/tools/for_workflows/cat.xml") + self._init_tool_for_path(source_file_name) + output_dataset = self._create_output_dataset( + extension="auto", + ) + sa_session = self.app.model.session + sa_session.flush() + output_datasets = { + "out_file1": output_dataset, + } + command = self.metadata_command(output_datasets) + self._write_galaxy_json("""{"type": "dataset", "dataset_id": "%s", "name": "my dynamic name", "ext": "fasta", "info": "my dynamic info", "metadata": {"sequences": 42}}""" % output_dataset.dataset.id) + self._write_output_dataset_contents(output_dataset, ">seq1\nGCTGCATG\n") + self.exec_metadata_command(command) + metadata_set_successfully = self.metadata_compute_strategy.external_metadata_set_successfully(output_dataset, "out_file1", sa_session, working_directory=self.job_working_directory) + assert metadata_set_successfully + output_dataset.extension = "fasta" # get done in job finish... + self.metadata_compute_strategy.load_metadata(output_dataset, "out_file1", sa_session, working_directory=self.job_working_directory) + assert output_dataset.metadata.data_lines == 2 + assert output_dataset.metadata.sequences == 42 + + def _create_output_dataset(self, **kwd): + output_dataset = model.HistoryDatasetAssociation( + sa_session=self.app.model.session, + create_dataset=True, + flush=True, + **kwd + ) + self.history.add_dataset(output_dataset) + ObjectStorePopulator(self.app).set_object_store_id(output_dataset) + return output_dataset + + def _write_output_dataset_contents(self, output_dataset, contents): + with open(output_dataset.dataset.file_name, "w") as f: + f.write(contents) + + def _write_galaxy_json(self, contents): + job_metadata = os.path.join(self.tool_working_directory, self.tool.provided_metadata_file) + with open(job_metadata, "w") as f: + f.write(contents) + + def metadata_command(self, output_datasets): + metadata_compute_strategy = get_metadata_compute_strategy(self.app, self.job.id) + self.metadata_compute_strategy = metadata_compute_strategy + + exec_dir = None + dataset_files_path = self.app.model.Dataset.file_path + config_root = self.app.config.root + config_file = None + datatypes_config = os.path.join(self.job_working_directory, 'registry.xml') + self.app.datatypes_registry.to_xml_file(path=datatypes_config) + job_metadata = os.path.join(self.tool_working_directory, self.tool.provided_metadata_file) + output_fnames = [DatasetPath(o.dataset.id, o.dataset.file_name, None) for o in output_datasets.values()] + command = metadata_compute_strategy.setup_external_metadata(output_datasets, + self.app.model.session, + exec_dir=exec_dir, + tmp_dir=self.job_working_directory, # set in jobs/runners.py - better if was default. + dataset_files_path=dataset_files_path, + config_root=config_root, + config_file=config_file, + datatypes_config=datatypes_config, + job_metadata=job_metadata, + output_fnames=output_fnames, + max_metadata_value_size=10000) + return command + + def exec_metadata_command(self, command): + with open(self.stdout_path, "wb") as stdout_file, open(self.stderr_path, "wb") as stderr_file: + _environ = os.environ.copy() + _environ["PYTHONPATH"] = os.path.abspath("lib") + proc = subprocess.Popen(args=command, + shell=True, + cwd=self.job_working_directory, + env=_environ, + stdout=stdout_file, + stderr=stderr_file) + ret = proc.wait() + self.print_command_output() + assert ret == 0 + return ret + + def print_command_output(self): + print(">unit test of external metadata setting (command standard output)") + print(open(self.stdout_path, "r").read()) + print(">unit test of external metadata setting (command standard error)") + print(open(self.stderr_path, "r").read()) + + @property + def stdout_path(self): + return os.path.join(self.test_directory, "stdout") + + @property + def stderr_path(self): + return os.path.join(self.test_directory, "stderr") diff --git a/test/unit/tools/test_parsing.py b/test/unit/tools/test_parsing.py index 1734dadbca3..772a4b98418 100644 --- a/test/unit/tools/test_parsing.py +++ b/test/unit/tools/test_parsing.py @@ -92,6 +92,39 @@ tests: compare: sim_size """ +TOOL_EXPRESSION_XML_1 = """ + + Parse Int + + {return {'output': parseInt($job.input1)};} + + + + + + + + Parse an integer from text. + +""" + + +TOOL_EXPRESSION_YAML_1 = """ +class: GalaxyExpressionTool +name: "parse_int" +id: parse_int +version: 1.0.2 +expression: "{return {'output': parseInt($job.input1)};}" +inputs: + - name: input1 + label: Text to parse + type: text +outputs: + out1: + type: integer + from: "#output" +""" + class BaseLoaderTestCase(unittest.TestCase): @@ -119,6 +152,22 @@ class BaseLoaderTestCase(unittest.TestCase): return tool_source +class XmlExpressionLoaderTestCase(BaseLoaderTestCase): + source_file_name = "expression.xml" + source_contents = TOOL_EXPRESSION_XML_1 + + def test_expression(self): + assert self._tool_source.parse_expression().strip() == "{return {'output': parseInt($job.input1)};}" + + def test_tool_type(self): + assert self._tool_source.parse_tool_type() == "expression" + + +class YamlExpressionLoaderTestCase(BaseLoaderTestCase): + source_file_name = "expression.yml" + source_contents = TOOL_EXPRESSION_XML_1 + + class XmlLoaderTestCase(BaseLoaderTestCase): source_file_name = "bwa.xml" source_contents = TOOL_XML_1 @@ -485,6 +534,7 @@ class CollectionTestCase(BaseLoaderTestCase): tests = tests_dict["tests"] assert len(tests) == 2 assert len(tests[0]["inputs"]) == 3, tests[0] + outputs, output_collections = self._tool_source.parse_outputs(None) assert len(output_collections) == 0 @@ -505,3 +555,16 @@ class CollectionOutputYamlTestCase(BaseLoaderTestCase): def test_tests(self): outputs, output_collections = self._tool_source.parse_outputs(None) assert len(output_collections) == 1 + + +class ExpectationsTestCase(BaseLoaderTestCase): + source_file_name = os.path.join(os.getcwd(), "test/functional/tools/detect_errors.xml") + source_contents = None + + def test_tests(self): + tests_dict = self._tool_source.parse_tests_to_dict() + tests = tests_dict["tests"] + assert len(tests) == 10 + test_0 = tests[0] + assert len(test_0["stderr"]) == 1 + assert len(test_0["stdout"]) == 2 diff --git a/test/unit/tools/test_toolbox.py b/test/unit/tools/test_toolbox.py index 94b0a4e446d..d9787714246 100644 --- a/test/unit/tools/test_toolbox.py +++ b/test/unit/tools/test_toolbox.py @@ -1,4 +1,5 @@ import json +import logging import os import string import time @@ -20,6 +21,8 @@ from .test_tool_loader import ( from .test_toolbox_filters import mock_trans from ..tools_support import UsesApp, UsesTools +log = logging.getLogger(__name__) + CONFIG_TEST_TOOL_VERSION_TEMPLATE = string.Template( """ @@ -51,11 +54,9 @@ class BaseToolBoxTestCase(unittest.TestCase, UsesApp, UsesTools): @property def toolbox(self): - if self.__toolbox is None: - self.__toolbox = SimplifiedToolBox(self) - # wire app with this new toolbox - self.app.toolbox = self.__toolbox - return self.__toolbox + if self._toolbox is None: + self.app.toolbox = self._toolbox = SimplifiedToolBox(self) + return self._toolbox def setUp(self): self.reindexed = False @@ -66,12 +67,21 @@ class BaseToolBoxTestCase(unittest.TestCase, UsesApp, UsesTools): self.app.reindex_tool_search = self.__reindex itp_config = os.path.join(self.test_directory, "integrated_tool_panel.xml") self.app.config.integrated_tool_panel_config = itp_config - self.app.watchers = ConfigWatchers(self.app) - self.__toolbox = None + self.app.watchers = ConfigWatchers(self.app, start_thread=False) + self._toolbox = None self.config_files = [] + def tearDown(self): + self.app.watchers.shutdown() + def _repo_install(self, changeset, config_filename=None): - metadata = {} + metadata = { + 'tools': [{ + 'add_to_tool_panel': False, # to have repository.includes_tools_for_display_in_tool_panel=False in InstalledRepositoryManager.activate_repository() + 'guid': "github.com/galaxyproject/example/test_tool/0.%s" % changeset, + 'tool_config': 'tool.xml' + }], + } if config_filename: metadata['shed_config_filename'] = config_filename repository = tool_shed_install.ToolShedRepository(metadata=metadata) @@ -98,7 +108,6 @@ class BaseToolBoxTestCase(unittest.TestCase, UsesApp, UsesTools): version2 = tool_shed_install.ToolVersion() version2.tool_id = "github.com/galaxyproject/example/test_tool/0.2" version2.repository = repository2 - self.app.install_model.context.add(version2) self.app.install_model.context.flush() @@ -112,21 +121,17 @@ class BaseToolBoxTestCase(unittest.TestCase, UsesApp, UsesTools): def _setup_two_versions_in_config(self, section=False): if section: template = """ -
      - %s -
      -
      - %s -
      +
      + %s +
      +
      + %s +
      """ else: template = """ -
      %s -
      -
      %s -
      """ self._add_config(template % (self.test_directory, CONFIG_TEST_TOOL_VERSION_1, CONFIG_TEST_TOOL_VERSION_2)) @@ -192,13 +197,14 @@ class ToolBoxTestCase(BaseToolBoxTestCase): assert tool is not None assert len(tool._macro_paths) == 1 macro_path = tool._macro_paths[0] - time.sleep(1.5) with open(macro_path, 'w') as macro_out: macro_out.write(SIMPLE_MACRO.substitute(tool_version="3.0")) - time.sleep(1.5) - tool = self.app.toolbox.get_tool("tool_with_macro") - assert tool.version == "3.0" + def check_tool_macro(): + tool = self.toolbox.get_tool("tool_with_macro") + assert tool.version == "3.0" + + self._try_until_no_errors(check_tool_macro) def test_tool_reload_for_broken_tool(self): self._init_tool(filename="simple_tool.xml", version="1.0") @@ -214,7 +220,7 @@ class ToolBoxTestCase(BaseToolBoxTestCase): out.write('certainly not a valid tool') def check_tool_errors(): - tool = self.app.toolbox.get_tool("test_tool") + tool = self.toolbox.get_tool("test_tool") assert tool is not None assert tool.version == "1.0" assert tool.tool_errors == 'Current on-disk tool is not valid' @@ -225,7 +231,7 @@ class ToolBoxTestCase(BaseToolBoxTestCase): self._init_tool(filename="simple_tool.xml", version="2.0") def check_no_tool_errors(): - tool = self.app.toolbox.get_tool("test_tool") + tool = self.toolbox.get_tool("test_tool") assert tool is not None assert tool.version == "2.0" assert tool.tool_errors is None @@ -235,7 +241,7 @@ class ToolBoxTestCase(BaseToolBoxTestCase): def _try_until_no_errors(self, f): e = None - for i in range(300): + for i in range(30): try: f() return @@ -553,6 +559,8 @@ class SimplifiedToolBox(ToolBox): tool_root_dir, app, ) + # Need to start thread now for new reload callback to take effect + self.app.watchers.start() def handle_panel_update(self, section_dict): self.create_section(section_dict) @@ -560,4 +568,6 @@ class SimplifiedToolBox(ToolBox): def reload_callback(test_case): test_case.app.tool_cache.cleanup() - test_case.__toolbox = test_case.app.toolbox = SimplifiedToolBox(test_case) + log.debug("Reload callback called, toolbox contains %s", test_case._toolbox._tool_versions_by_id) + test_case._toolbox = test_case.app.toolbox = SimplifiedToolBox(test_case) + log.debug("After callback toolbox contains %s", test_case._toolbox._tool_versions_by_id) diff --git a/test/unit/tools/test_toolbox_filters.py b/test/unit/tools/test_toolbox_filters.py index 564339fbe7b..f6212bf4337 100644 --- a/test/unit/tools/test_toolbox_filters.py +++ b/test/unit/tools/test_toolbox_filters.py @@ -14,16 +14,17 @@ def test_stock_filtering_requires_login_tools(): def test_stock_filtering_hidden_tools(): - filters = filter_factory({}).build_filters(mock_trans())['tool'] - assert not is_filtered(filters, mock_trans(), mock_tool(hidden=False)) - assert is_filtered(filters, mock_trans(), mock_tool(hidden=True)) + trans = mock_trans() + filters = filter_factory({}).build_filters(trans)['tool'] + assert not is_filtered(filters, trans, mock_tool(hidden=False)) + assert is_filtered(filters, trans, mock_tool(hidden=True)) def test_trackster_filtering(): - filters = filter_factory({}).build_filters(mock_trans(), trackster=True)['tool'] - # Ekkk... is trackster_conf broken? Why is it showing up. - # assert is_filtered( filters, mock_trans(), mock_tool( trackster_conf=False ) ) - assert not is_filtered(filters, mock_trans(), mock_tool(trackster_conf=True)) + trans = mock_trans() + filters = filter_factory({}).build_filters(trans, trackster=True)['tool'] + assert is_filtered(filters, trans, mock_tool(trackster_conf=False)) + assert not is_filtered(filters, trans, mock_tool(trackster_conf=True)) def test_custom_filters(): @@ -57,7 +58,7 @@ def filter_factory(config_dict=None): def is_filtered(filters, trans, tool): context = Bunch(trans=trans) - return not all(map(lambda filter: filter(context, tool), filters)) + return not all(_(context, tool) for _ in filters) def mock_tool(require_login=False, hidden=False, trackster_conf=False, allow_access=True): diff --git a/test/unit/tools_support.py b/test/unit/tools_support.py index 0f9e9d617fa..70ec4965a78 100644 --- a/test/unit/tools_support.py +++ b/test/unit/tools_support.py @@ -84,6 +84,11 @@ class UsesTools(object): self.__write_tool(extra_file_contents, path=os.path.join(self.test_directory, extra_file_path)) return self.__setup_tool() + def _init_tool_for_path(self, tool_file): + self._init_app_for_tools() + self.tool_file = tool_file + return self.__setup_tool() + def _init_app_for_tools(self): self.app.config.drmaa_external_runjob_script = "" self.app.config.tool_secret = "testsecret" @@ -102,7 +107,8 @@ class UsesTools(object): def __write_tool(self, contents, path=None): path = path or self.tool_file - open(path, "w").write(contents) + with open(path, "w") as out: + out.write(contents) class MockContext(object): diff --git a/test/unit/unittest_utils/galaxy_mock.py b/test/unit/unittest_utils/galaxy_mock.py index 9a28c8e4174..dcc931cb274 100644 --- a/test/unit/unittest_utils/galaxy_mock.py +++ b/test/unit/unittest_utils/galaxy_mock.py @@ -13,12 +13,11 @@ from galaxy import ( from galaxy.auth import AuthManager from galaxy.datatypes import registry from galaxy.jobs.manager import NoopManager -from galaxy.managers import tags -from galaxy.model import mapping +from galaxy.model import mapping, tags +from galaxy.security import idencoding from galaxy.tools.deps.containers import NullContainerFinder from galaxy.util.bunch import Bunch from galaxy.util.dbkeys import GenomeBuilds -from galaxy.web import security from galaxy.web.stack import ApplicationStack @@ -62,10 +61,10 @@ class MockApp(object): self.security = self.config.security self.name = kwargs.get('name', 'galaxy') self.object_store = objectstore.build_object_store_from_config(self.config) - self.model = mapping.init("/tmp", "sqlite:///:memory:", create_tables=True, object_store=self.object_store) + self.model = mapping.init("/tmp", self.config.database_connection, create_tables=True, object_store=self.object_store) self.security_agent = self.model.security_agent self.visualizations_registry = MockVisualizationsRegistry() - self.tag_handler = tags.GalaxyTagManager(self.model.context) + self.tag_handler = tags.GalaxyTagHandler(self.model.context) self.quota_agent = quota.QuotaAgent(self.model) self.init_datatypes() self.job_config = Bunch( @@ -76,11 +75,16 @@ class MockApp(object): self.dataset_collections_service = None self.container_finder = NullContainerFinder() self._toolbox_lock = MockLock() + self.tool_shed_registry = Bunch(tool_sheds={}) self.genome_builds = GenomeBuilds(self) self.job_manager = NoopManager() self.application_stack = ApplicationStack() self.auth_manager = AuthManager(self) + def url_for(*args, **kwds): + return "/mock/url" + self.url_for = url_for + def init_datatypes(self): datatypes_registry = registry.Registry() datatypes_registry.load_datatypes() @@ -106,12 +110,14 @@ class MockAppConfig(Bunch): def __init__(self, root=None, **kwargs): Bunch.__init__(self, **kwargs) root = root or '/tmp' - self.security = security.SecurityHelper(id_secret='6e46ed6483a833c100e68cc3f1d0dd76') + self.security = idencoding.IdEncodingHelper(id_secret='6e46ed6483a833c100e68cc3f1d0dd76') + self.database_connection = kwargs.get('database_connection', "sqlite:///:memory:") self.use_remote_user = kwargs.get('use_remote_user', False) self.file_path = '/tmp' self.jobs_directory = '/tmp' self.new_file_path = '/tmp' self.tool_data_path = '/tmp' + self.metadata_strategy = 'legacy' self.object_store_config_file = '' self.object_store = 'disk' @@ -150,7 +156,7 @@ class MockWebapp(object): def __init__(self, **kwargs): self.name = kwargs.get('name', 'galaxy') - self.security = security.SecurityHelper(id_secret='6e46ed6483a833c100e68cc3f1d0dd76') + self.security = idencoding.IdEncodingHelper(id_secret='6e46ed6483a833c100e68cc3f1d0dd76') class MockTrans(object): @@ -173,6 +179,9 @@ class MockTrans(object): self.request = Bunch(headers={}, body=None) self.response = Bunch(headers={}, set_content_type=lambda i : None) + def check_csrf_token(self, payload): + pass + def handle_user_login(self, user): pass diff --git a/test/unit/workflows/test_modules.py b/test/unit/workflows/test_modules.py index 64d479b50be..4ad43a127f2 100644 --- a/test/unit/workflows/test_modules.py +++ b/test/unit/workflows/test_modules.py @@ -1,8 +1,11 @@ import json +from collections import namedtuple import mock +import pytest from galaxy import model +from galaxy.managers.workflows import WorkflowContentsManager from galaxy.util import bunch from galaxy.workflow import modules from .workflow_support import MockTrans, yaml_to_model @@ -192,6 +195,20 @@ steps: - output_name: "out_file1" """ +COLLECTION_TYPE_WORKLFOW_YAML = """ +steps: + - type: "data_collection_input" + label: "input1" + collection_type: "list:list" + - type: "tool" + tool_id: "cat1" + inputs: + input1: + connections: + - "@output_step": 0 + output_name: "output" +""" + def test_subworkflow_new_inputs(): subworkflow_module = __new_subworkflow_module() @@ -204,6 +221,12 @@ def test_subworkflow_new_inputs(): assert input2["name"] == "input2", input2["name"] +def test_subworkflow_new_inputs_collection_type(): + subworkflow_module = __new_subworkflow_module(COLLECTION_TYPE_WORKLFOW_YAML) + inputs = subworkflow_module.get_data_inputs() + assert inputs[0]['collection_type'] == 'list:list' + + def test_subworkflow_new_outputs(): subworkflow_module = __new_subworkflow_module() outputs = subworkflow_module.get_data_outputs() @@ -216,11 +239,155 @@ def test_subworkflow_new_outputs(): assert output2["label"] == "4:out_file1", output2["label"] -def __new_subworkflow_module(): +def _construct_steps_for_map_over(): + test_case = namedtuple('MapOverTestCase', 'data_input step_input_def step_output_def expected_collection_type steps') + # these are the cartesian product of + # data_input = ['dataset', 'list', 'list:pair', 'list:list'] + # step_input_definition = ['dataset', 'dataset_multiple', 'list', ['list', 'pair']] + # step_output_definition = ['dataset', 'list', 'list:list'] + # list(itertools.product(data_input, step_input_definition, step_output_definition, [None])), + # with the last item filled in manually + test_case_args = [ + ('dataset', 'dataset', 'dataset', None), + ('dataset', 'dataset', 'list', 'list'), + ('dataset', 'dataset', 'list:list', 'list:list'), + ('dataset', 'dataset_multiple', 'dataset', None), + ('dataset', 'dataset_multiple', 'list', 'list'), + ('dataset', 'dataset_multiple', 'list:list', 'list:list'), + # Can't feed a dataset into a list or pair input + # ('dataset', 'list', 'dataset', None), + # ('dataset', 'list', 'list', None), + # ('dataset', 'list', 'list:list', None), + # ('dataset', ['list', 'pair'], 'dataset', None), + # ('dataset', ['list', 'pair'], 'list', None), + # ('dataset', ['list', 'pair'], 'list:list', None), + ('list', 'dataset', 'dataset', 'list'), + ('list', 'dataset', 'list', 'list:list'), + ('list', 'dataset', 'list:list', 'list:list:list'), + ('list', 'dataset_multiple', 'dataset', None), + ('list', 'dataset_multiple', 'list', 'list'), + ('list', 'dataset_multiple', 'list:list', 'list:list'), + ('list', 'list', 'dataset', None), + ('list', 'list', 'list', 'list'), + ('list', 'list', 'list:list', 'list:list'), + ('list', ['list', 'pair'], 'dataset', None), + ('list', ['list', 'pair'], 'list', 'list'), + ('list', ['list', 'pair'], 'list:list', 'list:list'), + ('list:pair', 'dataset', 'dataset', 'list:pair'), + ('list:pair', 'dataset', 'list', 'list:pair:list'), + ('list:pair', 'dataset', 'list:list', 'list:pair:list:list'), + # Pair into multiple="True" is not allowed + # ('list:pair', 'dataset_multiple', 'dataset', None), + # ('list:pair', 'dataset_multiple', 'list', None), + # ('list:pair', 'dataset_multiple', 'list:list', None), + # list:pair into list is not allowed + # ('list:pair', 'list', 'dataset', None), + # ('list:pair', 'list', 'list', None), + # ('list:pair', 'list', 'list:list', None), + ('list:pair', ['list', 'pair'], 'dataset', 'list'), + ('list:pair', ['list', 'pair'], 'list', 'list:list'), + ('list:pair', ['list', 'pair'], 'list:list', 'list:list:list'), + ('list:list', 'dataset', 'dataset', 'list:list'), + ('list:list', 'dataset', 'list', 'list:list:list'), + ('list:list', 'dataset', 'list:list', 'list:list:list:list'), + ('list:list', 'dataset_multiple', 'dataset', 'list'), + ('list:list', 'dataset_multiple', 'list', 'list:list'), + ('list:list', 'dataset_multiple', 'list:list', 'list:list:list'), + ('list:list', 'list', 'dataset', 'list'), + ('list:list', 'list', 'list', 'list:list'), + ('list:list', 'list', 'list:list', 'list:list:list'), + ('list:list', ['list', 'pair'], 'dataset', 'list'), + ('list:list', ['list', 'pair'], 'list', 'list:list'), + ('list:list', ['list', 'pair'], 'list:list', 'list:list:list') + ] + test_cases = [] + for (data_input, step_input_def, step_output_def, expected_collection_type) in test_case_args: + steps = { + 0: _input_step(collection_type=data_input), + 1: _output_step(step_input_def=step_input_def, step_output_def=step_output_def) + } + test_cases.append( + test_case( + data_input=data_input, + step_input_def=step_input_def, + step_output_def=step_output_def, + expected_collection_type=expected_collection_type, + steps=steps, + ) + ) + return test_cases + + +def _input_step(collection_type): + output = {'name': 'output', 'extensions': ['input_collection']} + if collection_type != 'dataset': + output['collection'] = True + output['collection_type'] = collection_type + step_type = 'data_colletion_input' if collection_type == 'dataset' else 'data_input' + return { + 'id': 0, + 'type': step_type, + 'inputs': [], + 'outputs': [output], + 'workflow_outputs': [], + 'input_connections': {}, + } + + +def _output_step(step_input_def, step_output_def): + multiple = False + if step_input_def in ['dataset', 'dataset_multiple']: + input_type = 'dataset' + collection_types = None + if step_input_def == 'dataset_multiple': + multiple = True + else: + input_type = 'dataset_collection' + collection_types = step_input_def if isinstance(step_input_def, list) else [step_input_def] + output = {'name': 'output', 'extensions': ['data']} + if step_output_def != 'dataset': + output['collection'] = True + output['collection_type'] = step_output_def + input_connection_input = [{'id': 0, 'output_name': 'output', 'input_type': input_type}] + if step_input_def == 'dataset': + # For whatever reason multiple = False inputs are not wrapped in a list. + input_connection_input = input_connection_input[0] + return { + 'id': 1, + 'type': 'tool', + 'inputs': [ + {'name': 'input', + 'multiple': multiple, + 'input_type': input_type, + 'collection_types': collection_types, + 'extensions': ['data'] + } + ], + 'input_connections': {'input': input_connection_input}, + 'outputs': [output], + 'workflow_outputs': [{'output_name': 'output'}] + } + + +@pytest.mark.parametrize('test_case', _construct_steps_for_map_over()) +def test_subworkflow_map_over_type(test_case): + trans = MockTrans() + new_steps = WorkflowContentsManager(app=trans.app)._resolve_collection_type(test_case.steps) + assert new_steps[1]['outputs'][0].get('collection_type') == test_case.expected_collection_type, \ + "Expected collection_type '%s' for a '%s' input module, a '%s' input and a '%s' output, got collection_type '%s' instead" % ( + test_case.expected_collection_type, + test_case.data_input, + test_case.step_input_def, + test_case.step_output_def, + new_steps[1]['outputs'][0].get('collection_type'), + ) + + +def __new_subworkflow_module(workflow=TEST_WORKFLOW_YAML): trans = MockTrans() mock_tool = __mock_tool(id="cat1", version="1.0") trans.app.toolbox.tools["cat1"] = mock_tool - workflow = yaml_to_model(TEST_WORKFLOW_YAML) + workflow = yaml_to_model(workflow) stored_workflow = trans.save_workflow(workflow) workflow_id = trans.app.security.encode_id(stored_workflow.id) subworkflow_module = modules.module_factory.from_dict(trans, {"type": "subworkflow", "content_id": workflow_id}) @@ -284,7 +451,9 @@ def __mock_tool( format='input', format_source=None, change_format=[], - label=None)}, + filters=[], + label=None, + output_type='data')}, params_from_strings=mock.Mock(), check_and_update_param_values=mock.Mock(), to_json=_to_json diff --git a/test/unit/workflows/workflow_support.py b/test/unit/workflows/workflow_support.py index b460bfe1061..534b71bb29f 100644 --- a/test/unit/workflows/workflow_support.py +++ b/test/unit/workflows/workflow_support.py @@ -4,8 +4,8 @@ import yaml from galaxy import model from galaxy.model import mapping +from galaxy.security.idencoding import IdEncodingHelper from galaxy.util import bunch -from galaxy.web.security import SecurityHelper class MockTrans(object): @@ -47,7 +47,7 @@ class TestApp(object): ) self.toolbox = TestToolbox() self.datatypes_registry = TestDatatypesRegistry() - self.security = SecurityHelper(id_secret="testing") + self.security = IdEncodingHelper(id_secret="testing") class TestDatatypesRegistry(object): @@ -64,7 +64,7 @@ class TestToolbox(object): def __init__(self): self.tools = {} - def get_tool(self, tool_id, tool_version=None, exact=False): + def get_tool(self, tool_id, tool_version=None, exact=False, tool_uuid=None): # Real tool box returns None of missing tool also return self.tools.get(tool_id, None) @@ -123,6 +123,9 @@ def yaml_to_model(has_dict, id_offset=100): value = inputs if key == "workflow_outputs": value = [partial(_dict_to_workflow_output, workflow_step)(_) for _ in value] + if key == 'collection_type': + key = 'tool_inputs' + value = {'collection_type': value} setattr(workflow_step, key, value) workflow.steps.append(workflow_step) diff --git a/tool-data/intermine_simple_display.loc.sample b/tool-data/intermine_simple_display.loc.sample index db9fc70c3c0..7583ec9b343 100644 --- a/tool-data/intermine_simple_display.loc.sample +++ b/tool-data/intermine_simple_display.loc.sample @@ -1,3 +1,3 @@ # Table used for listing simple Intermine display servers # -main_registry Registry http://registry.intermine.org/galaxy-to-im?URL=%(intermine_file_url_qp)s +main_registry Registry http://registry.intermine.org/galaxy-to-im?URL=%(intermine_file_url_qp)s diff --git a/tools/cloud/send.py b/tools/cloud/send.py index 91bf34d4f2b..7f3708e0a2e 100644 --- a/tools/cloud/send.py +++ b/tools/cloud/send.py @@ -21,7 +21,7 @@ from galaxy.managers.cloud import CloudManager try: - from cloudbridge.cloud.factory import CloudProviderFactory, ProviderList + from cloudbridge.factory import CloudProviderFactory, ProviderList except ImportError: CloudProviderFactory = None ProviderList = None diff --git a/tools/cloud/send.xml b/tools/cloud/send.xml index 9aa854abb84..c5dd5c70fc3 100644 --- a/tools/cloud/send.xml +++ b/tools/cloud/send.xml @@ -1,9 +1,11 @@ + 0: - sed_header = "sed -n '1,%dp' %s > %s" % (header_lines, input, output) - subprocess.call(sed_header, shell=True) + with open(output, 'w') as out: + # sed header + if header_lines > 0: + sed_header = ['sed', '-n', "1,%dp" % header_lines, input] + subprocess.check_call(sed_header, stdout=out) - # grep comments - grep_comments = "(grep '^#' %s) >> %s" % (input, output) - subprocess.call(grep_comments, shell=True) + # grep comments + grep_comments = ['grep', '^#', input] + exit_code = subprocess.call(grep_comments, stdout=out) + if exit_code not in [0, 1]: + stop_err('Searching for comment lines failed') - # grep and sort columns - sed_header_restore = "" - if header_lines > 0: - sed_header_restore = "sed '1,%dd' | " % (header_lines) - sort_columns = "(cat %s | %s grep '^[^#]' | sort -f -t '\t' %s) >> %s" % (input, sed_header_restore, ' '.join(key), output) - subprocess.call(sort_columns, shell=True) + # grep and sort columns + sed_header_restore = "" + if header_lines > 0: + sed_header_restore = "sed '1,%dd' | " % (header_lines) + sort_columns = "(cat %s | %s grep '^[^#]' | sort -f -t '\t' %s)" % (input, sed_header_restore, ' '.join(key)) + subprocess.check_call(sort_columns, stdout=out, shell=True) except Exception as ex: stop_err('Error running sorter.py\n' + str(ex)) diff --git a/tools/filters/sorter.xml b/tools/filters/sorter.xml index b1ab491fc0f..53ecd9ec94d 100644 --- a/tools/filters/sorter.xml +++ b/tools/filters/sorter.xml @@ -5,40 +5,40 @@ grep sed - - python '$__tool_directory__/sorter.py' + +python '$__tool_directory__/sorter.py' - --input='$input' - --output='$out_file1' +--input='$input' +--output='$out_file1' - #if (str($style) == 'num'): - #set $style = 'n' - #elif (str($style) == 'gennum'): - #set $style = 'g' - #else: - #set $style = '' - #end if +#if (str($style) == 'num'): + #set $style = 'n' +#elif (str($style) == 'gennum'): + #set $style = 'g' +#else: + #set $style = '' +#end if - #set $order = '' if (str($order) == 'ASC') else 'r' +#set $order = '' if (str($order) == 'ASC') else 'r' - --key=${column},${column}${style}${order} +--key=${column},${column}${style}${order} - #for $col in $column_set: - #set $other_column = str($col.other_column) +#for $col in $column_set: + #set $other_column = str($col.other_column) - #if (str($col.other_style) == 'num'): - #set $other_style = 'n' - #elif (str($col.other_style) == 'gennum'): - #set $other_style = 'g' - #else: - #set $other_style = '' - #end if + #if (str($col.other_style) == 'num'): + #set $other_style = 'n' + #elif (str($col.other_style) == 'gennum'): + #set $other_style = 'g' + #else: + #set $other_style = '' + #end if - #set $other_order = '' if (str($col.other_order) == "ASC") else 'r' - --key=${other_column},${other_column}${other_style}${other_order} - #end for - --header $header_lines + #set $other_order = '' if (str($col.other_order) == "ASC") else 'r' + --key=${other_column},${other_column}${other_style}${other_order} +#end for +--header $header_lines diff --git a/tools/genomespace/genomespace_exporter.xml b/tools/genomespace/genomespace_exporter.xml index 70b1d794062..392d3eef55c 100644 --- a/tools/genomespace/genomespace_exporter.xml +++ b/tools/genomespace/genomespace_exporter.xml @@ -1,5 +1,5 @@ - + - send data to GenomeSpace @@ -8,7 +8,7 @@ $token - python $__tool_directory__/genomespace_exporter.py + python $__tool_directory__/genomespace_exporter.py #set $target_folder = $genomespace_browser.split('^')[0] #assert $target_folder, Exception('You must select a valid target folder.') diff --git a/tools/genomespace/genomespace_importer.xml b/tools/genomespace/genomespace_importer.xml index 48bd215ac6b..543a540a3e2 100644 --- a/tools/genomespace/genomespace_importer.xml +++ b/tools/genomespace/genomespace_importer.xml @@ -1,5 +1,5 @@ - + - receive data from GenomeSpace diff --git a/tools/maf/maf_to_bed.py b/tools/maf/maf_to_bed.py index 6347924cf2a..fdb59219886 100644 --- a/tools/maf/maf_to_bed.py +++ b/tools/maf/maf_to_bed.py @@ -66,7 +66,7 @@ def __main__(): for c in l: spec, chrom = maf.src_split(c.src) if not spec or not chrom: - spec = chrom = c.src + spec = chrom = c.src if spec not in out_files.keys(): out_files[spec] = open(os.path.join(database_tmp_dir, 'primary_%s_%s_visible_bed_%s' % (output_id, spec, spec)), 'wb+') diff --git a/tools/phenotype_association/senatag.py b/tools/phenotype_association/senatag.py index 8a7a8c08dcd..fa6ae6f496b 100755 --- a/tools/phenotype_association/senatag.py +++ b/tools/phenotype_association/senatag.py @@ -139,7 +139,7 @@ def check_output(g, tagsnps): for n in tagsnps: for m in n.edges: - mysnps.append(m.name) + mysnps.append(m.name) mysnps = list(set(mysnps)) diff --git a/tools/stats/grouping.py b/tools/stats/grouping.py index ad9038de1b4..6dad86e9a2c 100644 --- a/tools/stats/grouping.py +++ b/tools/stats/grouping.py @@ -40,7 +40,7 @@ def float_wdefault(s, d, c): def stop_err(msg): sys.stderr.write(msg) - sys.exit() + sys.exit(1) def mode(data): @@ -115,7 +115,7 @@ def main(): case = '' if ignorecase == 1: case = '-f' - command_line = "sort -t ' ' %s -k%s,%s -o %s %s" % (case, group_col + 1, group_col + 1, tmpfile.name, inputfile) + command_line = "sort -t $'\\t' %s -k%s,%s -o %s %s" % (case, group_col + 1, group_col + 1, tmpfile.name, inputfile) except Exception as exc: stop_err('Initialization error -> %s' % str(exc)) @@ -128,7 +128,7 @@ def main(): def is_new_item(line): try: - item = line.strip().split("\t")[group_col] + item = line.split("\t")[group_col] except IndexError: stop_err("The following line didn't have %s columns: %s" % (group_col + 1, line)) diff --git a/tools/stats/grouping.xml b/tools/stats/grouping.xml index c0947769e57..9546ce139ae 100644 --- a/tools/stats/grouping.xml +++ b/tools/stats/grouping.xml @@ -1,12 +1,15 @@ - + data by a column and perform aggregate operation on other columns. - - grouping.py - "${out_file1}" - "${input1}" - "${groupcol}" - "${ignorecase}" - "${ignorelines}" + + numpy + + + python '$__tool_directory__/grouping.py' + '${out_file1}' + '${input1}' + '${groupcol}' + '${ignorecase}' + '${ignorelines}' #for $op in $operations '${op.optype},${op.opcol},${op.opround},${op.opdefault}' #end for @@ -61,40 +64,123 @@ - - numpy - - - - + + + + + - - - - - + + + + + - --> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/stats/gsummary.py b/tools/stats/gsummary.py index b5dc153a949..5acb74458bd 100755 --- a/tools/stats/gsummary.py +++ b/tools/stats/gsummary.py @@ -22,7 +22,7 @@ def S3_METHODS(all="key"): "acosh", "asinh", "atanh", "lgamma", "gamma", "gammaCody", "digamma", "trigamma", "cumsum", "cumprod", "cummax", "cummin", "c"] Group_Ops = ["+", "-", "*", "/", "^", "%%", "%/%", "&", "|", "!", "==", "!=", "<", "<=", ">=", ">", "(", ")", "~", ","] - if all is "key": + if all == "key": return {'Math': Group_Math, 'Ops': Group_Ops} diff --git a/tox.ini b/tox.ini index 6dcf0416f17..9904d2a82f1 100644 --- a/tox.ini +++ b/tox.ini @@ -1,12 +1,26 @@ [tox] # envlist is the list of environments that are tested when `tox` is run without any option # hyphens in an environment name are used to delimit factors -envlist = check_py3_compatibility, py27-first_startup, py27-lint, py27-lint_docstring_include_list, py27-unit, py34-first_startup, py34-lint, py34-unit, validate_test_tools +envlist = check_py3_compatibility, py{27,35}-first_startup, py{27,35}-lint, py27-lint_docstring_include_list, py{27,35}-unit, validate_test_tools skipsdist = True +[testenv] +commands = + first_startup: bash .ci/first_startup.sh + lint: bash .ci/flake8_wrapper.sh + unit: bash run_tests.sh -u +whitelist_externals = bash +setenv = + py{35,36,37}-first_startup: GALAXY_VIRTUAL_ENV=.venv3 + unit: GALAXY_VIRTUAL_ENV={envdir} + unit: GALAXY_ENABLE_BETA_COMPRESSED_GENBANK_SNIFFING=1 +deps = + lint,lint_docstring,lint_docstring_include_list: -rlib/galaxy/dependencies/pipfiles/flake8/pinned-requirements.txt + unit: mock + unit: mock-ssh-server + [testenv:check_py3_compatibility] commands = bash .ci/check_py3_compatibility.sh -whitelist_externals = bash [testenv:check_python_dependencies] commands = make list-dependency-updates # someday change exit code on this. @@ -14,67 +28,15 @@ whitelist_externals = make [testenv:mako_count] commands = bash .ci/check_mako.sh -whitelist_externals = bash - -[testenv:py27-first_startup] -commands = bash .ci/first_startup.sh -whitelist_externals = bash - -[testenv:py27-lint] -commands = bash .ci/flake8_wrapper.sh -whitelist_externals = bash -deps = -rlib/galaxy/dependencies/pipfiles/flake8/pinned-requirements.txt [testenv:py27-lint_docstring] commands = bash .ci/flake8_wrapper_docstrings.sh --exclude -whitelist_externals = bash -deps = -rlib/galaxy/dependencies/pipfiles/flake8/pinned-requirements.txt [testenv:py27-lint_docstring_include_list] commands = bash .ci/flake8_wrapper_docstrings.sh --include -whitelist_externals = bash -deps = -rlib/galaxy/dependencies/pipfiles/flake8/pinned-requirements.txt - -[testenv:py27-unit] -commands = bash run_tests.sh -u -whitelist_externals = bash -setenv = - GALAXY_VIRTUAL_ENV={envdir} - GALAXY_ENABLE_BETA_COMPRESSED_GENBANK_SNIFFING=1 -deps = - mock - mock-ssh-server - -[testenv:py34-first_startup] -commands = - bash .ci/first_startup.sh -setenv = - GALAXY_VIRTUAL_ENV=.venv3 -whitelist_externals = bash - -[testenv:py34-lint] -commands = bash .ci/flake8_wrapper.sh -whitelist_externals = bash -deps = -rlib/galaxy/dependencies/pipfiles/flake8/pinned-requirements.txt - -[testenv:py34-unit] -commands = bash run_tests.sh -u -whitelist_externals = bash -setenv = - GALAXY_VIRTUAL_ENV={envdir} -deps = - mock - mock-ssh-server - -[testenv:py35-lint] -commands = bash .ci/flake8_wrapper.sh -whitelist_externals = bash -deps = -rlib/galaxy/dependencies/pipfiles/flake8/pinned-requirements.txt [testenv:validate_test_tools] commands = bash .ci/validate_test_tools.sh -whitelist_externals = bash [testenv:web_controller_line_count] commands = bash .ci/check_controller.sh -whitelist_externals = bash