From 4b3a48ce394a64bffb7d6f77d39385e51c8651f8 Mon Sep 17 00:00:00 2001 From: Nuwan Goonasekera <2070605+nuwang@users.noreply.github.com> Date: Mon, 25 Jan 2021 22:46:19 +0530 Subject: [PATCH 001/255] Deal with empty claim names in k8s runner --- lib/galaxy/jobs/runners/kubernetes.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/jobs/runners/kubernetes.py b/lib/galaxy/jobs/runners/kubernetes.py index d68a3abdc67..f525d523a09 100644 --- a/lib/galaxy/jobs/runners/kubernetes.py +++ b/lib/galaxy/jobs/runners/kubernetes.py @@ -97,9 +97,11 @@ class KubernetesJobRunner(AsynchronousJobRunner): volume_claims = dict(volume.split(":") for volume in self.runner_params['k8s_persistent_volume_claims'].split(',')) else: volume_claims = {} - mountable_volumes = [{'name': claim_name, 'persistentVolumeClaim': {'claimName': claim_name}} for claim_name in volume_claims] + mountable_volumes = [{'name': claim_name, 'persistentVolumeClaim': {'claimName': claim_name}} + for claim_name in volume_claims if claim_name] 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()] + volume_mounts = [{'name': claim_name, 'mountPath': mount_path} + for claim_name, mount_path in volume_claims.items() if claim_name] self.runner_params['k8s_volume_mounts'] = volume_mounts def queue_job(self, job_wrapper): From ec9270467ded6e44d59aa283107b3ebc83192eea Mon Sep 17 00:00:00 2001 From: mtangaro Date: Tue, 26 Jan 2021 09:16:23 +0000 Subject: [PATCH 002/255] add reworked chronos job runner --- .../sample/job_conf.xml.sample_advanced | 12 ++-- .../dependencies/conditional-requirements.txt | 2 +- lib/galaxy/jobs/runners/chronos.py | 64 ++++++++++++++++++- 3 files changed, 68 insertions(+), 10 deletions(-) diff --git a/lib/galaxy/config/sample/job_conf.xml.sample_advanced b/lib/galaxy/config/sample/job_conf.xml.sample_advanced index 3a82b4add0a..63d3df96685 100644 --- a/lib/galaxy/config/sample/job_conf.xml.sample_advanced +++ b/lib/galaxy/config/sample/job_conf.xml.sample_advanced @@ -346,8 +346,8 @@ password - true - + false + - 2 + directories. Other directories of the data used by tools can + be mounted as well, separated by commas.--> + 0 diff --git a/lib/galaxy/dependencies/conditional-requirements.txt b/lib/galaxy/dependencies/conditional-requirements.txt index a9c27c187e9..daeaa7d5d81 100644 --- a/lib/galaxy/dependencies/conditional-requirements.txt +++ b/lib/galaxy/dependencies/conditional-requirements.txt @@ -17,7 +17,7 @@ fs.webdavfs fs.dropboxfs # Chronos client -chronos-python==0.38.0 +chronos-python==1.2.1 # Kubernetes job runner pykube==0.15.0 diff --git a/lib/galaxy/jobs/runners/chronos.py b/lib/galaxy/jobs/runners/chronos.py index 5e5c241025b..fa80731cda5 100644 --- a/lib/galaxy/jobs/runners/chronos.py +++ b/lib/galaxy/jobs/runners/chronos.py @@ -1,5 +1,6 @@ import functools import logging +import os from galaxy import model from galaxy.jobs.runners import AsynchronousJobRunner, AsynchronousJobState @@ -55,6 +56,27 @@ def _write_logfile(logfile, msg): fil.write(msg) +def _parse_job_volumes_list(li): + # Convert comma separated string to list + volume_list = list(li.split(',')) + # Create the list with right mountpoint and permissions + mountpoint_list = [] + # Convert each element to right format + path = lambda x,y,z: {'hostPath': x, 'containerPath': y, 'mode': z} + for i in volume_list: + hpath,cpath,mode = i.split(':') + mountpoint_list.append(path(hpath,cpath,mode)) + return mountpoint_list + + +def _add_galaxy_environment_variables(cpus,memory): + # Set: + # GALAXY_SLOTS: to docker_cpu + # GALAXY_MEMORY_MB to docker_memory + add_var = lambda x,y: { 'name': x, 'value': y } + li = [ add_var('GALAXY_SLOTS', cpus), add_var('GALAXY_MEMORY_MB', memory) ] + return li + class ChronosJobRunner(AsynchronousJobRunner): runner_name = 'ChronosRunner' RUNNER_PARAM_SPEC_KEY = 'runner_param_specs' @@ -99,7 +121,7 @@ class ChronosJobRunner(AsynchronousJobRunner): 'default': None, 'map_name': 'container/volumes', 'map': ( - lambda x: [{'containerPath': x, 'hostPath': x, 'mode': 'RW'}] + lambda x: _parse_job_volumes_list(x) if x is not None else []) }, 'max_retries': { @@ -180,6 +202,18 @@ class ChronosJobRunner(AsynchronousJobRunner): ajs.running = False self.monitor_queue.put(ajs) + def fail_job(self, job_state, exception=False): + if getattr(job_state, 'stop_job', True): + self.stop_job(job_state.job_wrapper) + job_state.job_wrapper.reclaim_ownership() + self._handle_runner_state('failure', job_state) + if not job_state.runner_state_handled: + job_state.job_wrapper.fail(getattr(job_state, 'fail_message', 'Job failed'), exception=exception) + self._finish_or_resubmit_job(job_state,'',job_state.fail_message, job_id=job_state.job_id) + if job_state.job_wrapper.cleanup_job == "always": + job_state.cleanup() + + @handle_exception_call def check_watched_item(self, job_state): job_name = job_state.job_id @@ -194,7 +228,10 @@ class ChronosJobRunner(AsynchronousJobRunner): return self._mark_as_active(job_state) elif errors: max_retries = job['retries'] - msg = 'Job {name!r} failed more than {retries!s} times' + if max_retries == 0: + msg = 'Job {name!r} failed. No retries performed.' + else: + msg = 'Job {name!r} failed more than {retries!s} times.' reason = msg.format(name=job_name, retries=str(max_retries)) return self._mark_as_failed(job_state, reason) reason = f'Job {job_name!r} not found' @@ -239,16 +276,35 @@ class ChronosJobRunner(AsynchronousJobRunner): parsed_params.update(to_dict(segments, mapper(value))) return parsed_params + def write_command(self, job_wrapper): + # Create command script instead passing it in the container + # preventing wrong characters parsing. + if not os.path.exists(job_wrapper.working_directory): + LOGGER.error("No working directory found") + + path = job_wrapper.working_directory + '/chronos_' + job_wrapper.get_id_tag() + '.sh' + mode=0o755 + + with open(path, 'w', encoding='utf-8') as f: + f.write('#!/bin/bash\n') + f.write(job_wrapper.runner_command_line) + os.chmod(path, mode) + return path + def _get_job_spec(self, job_wrapper): job_name = self.JOB_NAME_PREFIX + job_wrapper.get_id_tag() job_destination = job_wrapper.job_destination + command_script_path = self.write_command(job_wrapper) template = { 'async': False, - 'command': job_wrapper.runner_command_line, + #'command': job_wrapper.runner_command_line, + 'command': '$SHELL ' + command_script_path, 'owner': self.runner_params['owner'], 'disabled': False, 'schedule': 'R1//PT1S', 'name': job_name, + # Add Galaxy environemnt variables to json + 'environmentVariables': _add_galaxy_environment_variables(job_destination.params.get('docker_cpu'), job_destination.params.get('docker_memory')), } if not job_destination.params.get('docker_enabled'): raise ChronosRunnerException( @@ -259,6 +315,8 @@ class ChronosJobRunner(AsynchronousJobRunner): template['container']['type'] = 'DOCKER' template['container']['image'] = self._find_container( job_wrapper).container_id + # Fix the working directory inside the container + template['container']['parameters'] = [{"key": "workdir", "value": job_wrapper.working_directory }] return template def _retrieve_job(self, job_id): From 0ba2f540b4f635020f5e220cfa2b40dcb84be858 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Mon, 1 Feb 2021 11:45:54 +0100 Subject: [PATCH 003/255] Decrease verbosity of urllib connectinpool logging --- lib/galaxy/config/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/galaxy/config/__init__.py b/lib/galaxy/config/__init__.py index 353510c68eb..d026720c1e4 100644 --- a/lib/galaxy/config/__init__.py +++ b/lib/galaxy/config/__init__.py @@ -66,6 +66,10 @@ LOGGING_CONFIG_DEFAULT = { 'level': 'WARN', 'qualname': 'paste.httpserver.ThreadPool', }, + 'urllib3.connectionpool': { + 'level': 'WARN', + 'qualname': 'urllib3.connectionpool', + }, 'routes.middleware': { 'level': 'WARN', 'qualname': 'routes.middleware', From dd4de637c72e86fead98e1c6a2d23d5b1a3e8b0d Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Mon, 1 Feb 2021 15:40:31 +0100 Subject: [PATCH 004/255] Fix dbkey setup Fixes vsnp* tool test failures (`Input staging problem: Invalid dbkey specified`) and https://sentry.galaxyproject.org/sentry/test/issues/44049/. Minor bug introduced in https://github.com/galaxyproject/galaxy/pull/10667/files. --- lib/galaxy/tools/parameters/basic.py | 7 +-- lib/galaxy/util/__init__.py | 53 ----------------------- lib/galaxy/util/dbkeys.py | 64 ++++++++++++++++++++++++++-- 3 files changed, 65 insertions(+), 59 deletions(-) diff --git a/lib/galaxy/tools/parameters/basic.py b/lib/galaxy/tools/parameters/basic.py index 4ec7dbb9836..9de12bfea84 100644 --- a/lib/galaxy/tools/parameters/basic.py +++ b/lib/galaxy/tools/parameters/basic.py @@ -15,6 +15,7 @@ import galaxy.model from galaxy import util from galaxy.tool_util.parser import get_input_source as ensure_input_source from galaxy.util import ( + dbkeys, sanitize_param, string_as_bool, string_as_bool_or_none, @@ -1038,7 +1039,7 @@ class GenomeBuildParameter(SelectToolParameter): >>> # Create a mock transaction with 'hg17' as the current build >>> from galaxy.util.bunch import Bunch - >>> trans = Bunch(app=None, history=Bunch(genome_build='hg17'), db_builds=util.read_dbnames(None)) + >>> trans = Bunch(app=None, history=Bunch(genome_build='hg17'), db_builds=dbkeys.read_dbnames(None)) >>> p = GenomeBuildParameter(None, XML('')) >>> print(p.name) _name @@ -1092,7 +1093,7 @@ class GenomeBuildParameter(SelectToolParameter): def _get_dbkey_names(self, trans=None): if not self.tool: # Hack for unit tests, since we have no tool - return util.read_dbnames(None) + return dbkeys.read_dbnames(None) return self.tool.app.genome_builds.get_genome_build_names(trans=trans) @@ -1388,7 +1389,7 @@ class DrillDownSelectToolParameter(SelectToolParameter): Creating a hierarchical select menu, which allows users to 'drill down' a tree-like set of options. >>> from galaxy.util.bunch import Bunch - >>> trans = Bunch(app=None, history=Bunch(genome_build='hg17'), db_builds=util.read_dbnames(None)) + >>> trans = Bunch(app=None, history=Bunch(genome_build='hg17'), db_builds=dbkeys.read_dbnames(None)) >>> p = DrillDownSelectToolParameter(None, XML( ... ''' ... diff --git a/lib/galaxy/util/__init__.py b/lib/galaxy/util/__init__.py index e65c00dc1f2..ce9f4c0260c 100644 --- a/lib/galaxy/util/__init__.py +++ b/lib/galaxy/util/__init__.py @@ -1168,59 +1168,6 @@ def compare_urls(url1, url2, compare_scheme=True, compare_hostname=True, compare return True -def read_dbnames(filename): - """ Read build names from file """ - db_names = [] - try: - ucsc_builds = {} - man_builds = [] # assume these are integers - name_to_db_base = {} - if filename is None: - # Should only be happening with the galaxy.tools.parameters.basic:GenomeBuildParameter docstring unit test - filename = os.path.join(galaxy_directory(), 'tool-data', 'shared', 'ucsc', 'builds.txt.sample') - for line in open(filename): - try: - if line[0:1] == "#": - continue - fields = line.replace("\r", "").replace("\n", "").split("\t") - # Special case of unspecified build is at top of list - if fields[0] == "?": - db_names.insert(0, (fields[0], fields[1])) - continue - try: # manual build (i.e. microbes) - int(fields[0]) - man_builds.append((fields[1], fields[0])) - except Exception: # UCSC build - db_base = fields[0].rstrip('0123456789') - if db_base not in ucsc_builds: - ucsc_builds[db_base] = [] - name_to_db_base[fields[1]] = db_base - # we want to sort within a species numerically by revision number - build_rev = re.compile(r'\d+$') - try: - build_rev = int(build_rev.findall(fields[0])[0]) - except Exception: - build_rev = 0 - ucsc_builds[db_base].append((build_rev, fields[0], fields[1])) - except Exception: - continue - sort_names = sorted(name_to_db_base.keys()) - for name in sort_names: - db_base = name_to_db_base[name] - ucsc_builds[db_base].sort() - ucsc_builds[db_base].reverse() - ucsc_builds[db_base] = [(build, name) for _, build, name in ucsc_builds[db_base]] - db_names = list(db_names + ucsc_builds[db_base]) - if len(db_names) > 1 and len(man_builds) > 0: - db_names.append((db_names.default_value, '----- Additional Species Are Below -----')) - man_builds.sort() - man_builds = [(build, name) for name, build in man_builds] - db_names = list(db_names + man_builds) - except Exception as e: - log.error("ERROR: Unable to read builds file: %s", unicodify(e)) - return db_names - - def read_build_sites(filename, check_builds=True): """ read db names to ucsc mappings from file, this file should probably be merged with the one above """ build_sites = [] diff --git a/lib/galaxy/util/dbkeys.py b/lib/galaxy/util/dbkeys.py index 926b67e9e31..27b4b1d4fcc 100644 --- a/lib/galaxy/util/dbkeys.py +++ b/lib/galaxy/util/dbkeys.py @@ -1,14 +1,72 @@ """ Functionality for dealing with dbkeys. """ -# dbkeys read from disk using builds.txt - +import logging import os.path +import re from json import loads -from galaxy.util import read_dbnames +from galaxy.util import ( + galaxy_directory, + unicodify, +) from galaxy.util.object_wrapper import sanitize_lists_to_string +log = logging.getLogger(__name__) + + +def read_dbnames(filename): + """ Read build names from file """ + db_names = [] + try: + ucsc_builds = {} + man_builds = [] # assume these are integers + name_to_db_base = {} + if filename is None: + # Should only be happening with the galaxy.tools.parameters.basic:GenomeBuildParameter docstring unit test + filename = os.path.join(galaxy_directory(), 'tool-data', 'shared', 'ucsc', 'builds.txt.sample') + for line in open(filename): + try: + if line[0:1] == "#": + continue + fields = line.replace("\r", "").replace("\n", "").split("\t") + # Special case of unspecified build is at top of list + if fields[0] == "?": + db_names.insert(0, (fields[0], fields[1])) + continue + try: # manual build (i.e. microbes) + int(fields[0]) + man_builds.append((fields[1], fields[0])) + except Exception: # UCSC build + db_base = fields[0].rstrip('0123456789') + if db_base not in ucsc_builds: + ucsc_builds[db_base] = [] + name_to_db_base[fields[1]] = db_base + # we want to sort within a species numerically by revision number + build_rev = re.compile(r'\d+$') + try: + build_rev = int(build_rev.findall(fields[0])[0]) + except Exception: + build_rev = 0 + ucsc_builds[db_base].append((build_rev, fields[0], fields[1])) + except Exception: + continue + sort_names = sorted(name_to_db_base.keys()) + for name in sort_names: + db_base = name_to_db_base[name] + ucsc_builds[db_base].sort() + ucsc_builds[db_base].reverse() + ucsc_builds[db_base] = [(build, name) for _, build, name in ucsc_builds[db_base]] + db_names = list(db_names + ucsc_builds[db_base]) + if len(db_names) > 1 and len(man_builds) > 0: + db_names.append((GenomeBuilds.default_value, '----- Additional Species Are Below -----')) + man_builds.sort() + man_builds = [(build, name) for name, build in man_builds] + db_names = list(db_names + man_builds) + except Exception as e: + log.error("ERROR: Unable to read builds file: %s", unicodify(e)) + return db_names + class GenomeBuilds: default_value = "?" From d8fd3ddfe8d6ba00cd509caf29078cbff7f3ff18 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Mon, 1 Feb 2021 16:55:11 +0100 Subject: [PATCH 005/255] Set correct status code in old history route `err_code.code` is our internal error, more specific code that looks like `403003` for instance, while we need to set a standard webob.exc status code here. Fixes https://sentry.galaxyproject.org/sentry/test/issues/1216796/: ``` AttributeError: 'NoneType' object has no attribute 'code' File "galaxy/web/framework/middleware/sentry.py", line 43, in __call__ iterable = self.application(environ, start_response) File "/cvmfs/test.galaxyproject.org/venv/lib/python3.6/site-packages/paste/recursive.py", line 85, in __call__ return self.application(environ, start_response) File "galaxy/web/framework/middleware/statsd.py", line 33, in __call__ req = self.application(environ, start_response) File "/cvmfs/test.galaxyproject.org/venv/lib/python3.6/site-packages/paste/httpexceptions.py", line 640, in __call__ return self.application(environ, start_response) File "galaxy/web/framework/base.py", line 136, in __call__ return self.handle_request(environ, start_response) File "galaxy/web/framework/base.py", line 221, in handle_request return body_renderer(trans, body, environ, start_response) File "galaxy/web/framework/base.py", line 239, in _render_body start_response(trans.response.wsgi_status(), File "galaxy/web/framework/base.py", line 473, in wsgi_status return "%d %s" % (exception.code, exception.title) ``` --- lib/galaxy/webapps/galaxy/controllers/history.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/history.py b/lib/galaxy/webapps/galaxy/controllers/history.py index 3d83ea8dfbd..1adc7bbe97b 100644 --- a/lib/galaxy/webapps/galaxy/controllers/history.py +++ b/lib/galaxy/webapps/galaxy/controllers/history.py @@ -1208,7 +1208,7 @@ class HistoryController(BaseUIController, SharableMixin, UsesAnnotations, UsesIt trans.set_history(history) return self.history_data(trans, history) except exceptions.MessageException as msg_exc: - trans.response.status = msg_exc.err_code.code + trans.response.status = msg_exc.status_code return {'err_msg': msg_exc.err_msg, 'err_code': msg_exc.err_code.code} @web.json From 0287de0cc9c93a06bb27ed06d31f7c32aafce9df Mon Sep 17 00:00:00 2001 From: Nicola Soranzo Date: Mon, 1 Feb 2021 19:15:35 +0000 Subject: [PATCH 006/255] Don't fail silently in `User.system_user_pwent()` --- lib/galaxy/model/__init__.py | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 643aa8628c3..699600b751a 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -437,24 +437,17 @@ class User(Dictifiable, RepresentById): Gives the system user pwent entry based on e-mail or username depending on the value in real_system_username """ - system_user_pwent = None if real_system_username == 'user_email': - try: - system_user_pwent = pwd.getpwnam(self.email.split('@')[0]) - except KeyError: - pass + username = self.email.split('@')[0] elif real_system_username == 'username': - try: - system_user_pwent = pwd.getpwnam(self.username) - except KeyError: - pass + username = self.username else: - try: - system_user_pwent = pwd.getpwnam(real_system_username) - except KeyError: - log.warning("invalid configuration of real_system_username") - system_user_pwent = None - return system_user_pwent + username = real_system_username + try: + return pwd.getpwnam(username) + except Exception: + log.exception(f"Error getting the password database entry for user {username}") + raise def all_roles(self): """ From 3eb46ac6f635ad1874db27ef5d0a3c84490d4a2f Mon Sep 17 00:00:00 2001 From: Nicola Soranzo Date: Mon, 1 Feb 2021 19:46:40 +0000 Subject: [PATCH 007/255] Run `external_chown` only when `external_chown_script` is defined --- lib/galaxy/jobs/__init__.py | 4 ++-- lib/galaxy/tools/actions/upload_common.py | 2 +- lib/galaxy/tools/imp_exp/__init__.py | 28 +++++++++++++++++------ lib/galaxy/util/path/__init__.py | 2 +- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/lib/galaxy/jobs/__init__.py b/lib/galaxy/jobs/__init__.py index 3ab20ea94e6..cdd7d2cfd71 100644 --- a/lib/galaxy/jobs/__init__.py +++ b/lib/galaxy/jobs/__init__.py @@ -2227,7 +2227,7 @@ class JobWrapper(HasResourceParameters): def change_ownership_for_run(self): job = self.get_job() external_chown_script = self.get_destination_configuration("external_chown_script", None) - if job.user is not None and external_chown_script is not None: + if job.user is not None and external_chown_script: ret = external_chown(self.working_directory, self.user_system_pwent, external_chown_script, description="working directory") if not ret: @@ -2236,7 +2236,7 @@ class JobWrapper(HasResourceParameters): def reclaim_ownership(self): job = self.get_job() external_chown_script = self.get_destination_configuration("external_chown_script", None) - if job.user is not None: + if job.user is not None and external_chown_script: external_chown(self.working_directory, self.galaxy_system_pwent, external_chown_script, description="working directory") diff --git a/lib/galaxy/tools/actions/upload_common.py b/lib/galaxy/tools/actions/upload_common.py index ce763c00c6c..1fe1c6e1190 100644 --- a/lib/galaxy/tools/actions/upload_common.py +++ b/lib/galaxy/tools/actions/upload_common.py @@ -377,7 +377,7 @@ def create_paramfile(trans, uploaded_datasets): # TODO: This will have to change when we start bundling inputs. # Also, in_place above causes the file to be left behind since the # user cannot remove it unless the parent directory is writable. - if link_data_only == 'copy_files' and trans.user: + if link_data_only == 'copy_files' and trans.user and trans.app.config.external_chown_script: external_chown(uploaded_dataset.path, trans.user.system_user_pwent(trans.app.config.real_system_username), trans.app.config.external_chown_script, description="uploaded file") diff --git a/lib/galaxy/tools/imp_exp/__init__.py b/lib/galaxy/tools/imp_exp/__init__.py index e8b74b87191..6de6442dda1 100644 --- a/lib/galaxy/tools/imp_exp/__init__.py +++ b/lib/galaxy/tools/imp_exp/__init__.py @@ -23,11 +23,20 @@ class JobImportHistoryArchiveWrapper: self.sa_session = self.app.model.context def setup_job(self, jiha, archive_source, archive_type): - if archive_type != "url": - external_chown(archive_source, jiha.job.user.system_user_pwent(self.app.config.real_system_username), - self.app.config.external_chown_script, "history import archive") - external_chown(jiha.archive_dir, jiha.job.user.system_user_pwent(self.app.config.real_system_username), - self.app.config.external_chown_script, "history import archive directory") + if self.app.config.external_chown_script: + if archive_type != "url": + external_chown( + archive_source, + jiha.job.user.system_user_pwent(self.app.config.real_system_username), + self.app.config.external_chown_script, + "history import archive" + ) + external_chown( + jiha.archive_dir, + jiha.job.user.system_user_pwent(self.app.config.real_system_username), + self.app.config.external_chown_script, + "history import archive directory" + ) def cleanup_after_job(self): """ Set history, datasets, collections and jobs' attributes @@ -46,8 +55,13 @@ class JobImportHistoryArchiveWrapper: new_history = None try: archive_dir = jiha.archive_dir - external_chown(archive_dir, jiha.job.user.system_user_pwent(getpass.getuser()), - self.app.config.external_chown_script, "history import archive directory") + if self.app.config.external_chown_script: + external_chown( + archive_dir, + jiha.job.user.system_user_pwent(getpass.getuser()), + self.app.config.external_chown_script, + "history import archive directory" + ) 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: diff --git a/lib/galaxy/util/path/__init__.py b/lib/galaxy/util/path/__init__.py index 2d6c1463ba3..c012fdc03f9 100644 --- a/lib/galaxy/util/path/__init__.py +++ b/lib/galaxy/util/path/__init__.py @@ -352,7 +352,7 @@ def external_chown(path, pwent, external_chown_script, description="file"): - True in case of success - False in case of failure """ - if external_chown_script is None: + if not external_chown_script: return None try: From 4c8f94514bccd729a518a3e9baeccde370a1d46c Mon Sep 17 00:00:00 2001 From: Nicola Soranzo Date: Tue, 2 Feb 2021 12:19:55 +0000 Subject: [PATCH 008/255] Return False if `external_chown_script` is not defined --- lib/galaxy/util/path/__init__.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/galaxy/util/path/__init__.py b/lib/galaxy/util/path/__init__.py index c012fdc03f9..9022aaabb1b 100644 --- a/lib/galaxy/util/path/__init__.py +++ b/lib/galaxy/util/path/__init__.py @@ -343,22 +343,18 @@ extensions = Extensions({ def external_chown(path, pwent, external_chown_script, description="file"): """ - call the external chown script (if not None) to change + call the external chown script to change the user and group of the given path, and additional description of the file/path for the log message can be given - return - - None if external_chown_script is None - - True in case of success - - False in case of failure + return True in case of success """ - if not external_chown_script: - return None - try: + if not external_chown_script: + raise ValueError('external_chown_script is not defined') cmd = shlex.split(external_chown_script) cmd.extend([path, pwent[0], str(pwent[3])]) - log.debug('Changing ownership of {} with: {}'.format(path, ' '.join(cmd))) + log.debug('Changing ownership of {} with: {}'.format(path, ' '.join(map(shlex.quote, cmd)))) galaxy.util.commands.execute(cmd) return True except galaxy.util.commands.CommandLineException as e: From 8e11ed5ebf8d0a695bac0419b885d5388b588cf1 Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Tue, 2 Feb 2021 15:45:38 +0100 Subject: [PATCH 009/255] Fix error on Admin panel when creating new quota --- lib/galaxy/webapps/galaxy/controllers/admin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/admin.py b/lib/galaxy/webapps/galaxy/controllers/admin.py index 7fe0ca4de33..36b1e38e098 100644 --- a/lib/galaxy/webapps/galaxy/controllers/admin.py +++ b/lib/galaxy/webapps/galaxy/controllers/admin.py @@ -677,8 +677,8 @@ class AdminGalaxy(controller.JSAppLauncher, AdminActions, UsesQuotaMixin, QuotaP .order_by(trans.app.model.Group.table.c.name): all_groups.append((group.name, trans.security.encode_id(group.id))) default_options = [('No', 'no')] - for typ in trans.app.model.DefaultQuotaAssociation.types.__dict__.values(): - default_options.append(('Yes, ' + typ, typ)) + for type_ in trans.app.model.DefaultQuotaAssociation.types: + default_options.append(('Yes, ' + type_, type_)) return {'title': 'Create Quota', 'inputs': [ { From 8afffbd2f99d18ebcdcc2989026f12d94fa100d0 Mon Sep 17 00:00:00 2001 From: Nate Coraor Date: Tue, 2 Feb 2021 17:20:15 -0500 Subject: [PATCH 010/255] Update pulsar lib to 0.14.1 --- lib/galaxy/dependencies/pipfiles/default/Pipfile | 2 +- .../dependencies/pipfiles/default/pinned-requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/dependencies/pipfiles/default/Pipfile b/lib/galaxy/dependencies/pipfiles/default/Pipfile index 5804df16ea9..1e04dcf0216 100644 --- a/lib/galaxy/dependencies/pipfiles/default/Pipfile +++ b/lib/galaxy/dependencies/pipfiles/default/Pipfile @@ -75,7 +75,7 @@ bioblend = "*" boto = "*" kombu = "*" psutil = "*" -pulsar-galaxy-lib = "==0.14.0.dev4" +pulsar-galaxy-lib = "==0.14.1" sqlalchemy-migrate = "*" sqlitedict = "*" sqlparse = "*" diff --git a/lib/galaxy/dependencies/pipfiles/default/pinned-requirements.txt b/lib/galaxy/dependencies/pipfiles/default/pinned-requirements.txt index 09324d1cd95..a75bd2a035d 100644 --- a/lib/galaxy/dependencies/pipfiles/default/pinned-requirements.txt +++ b/lib/galaxy/dependencies/pipfiles/default/pinned-requirements.txt @@ -117,7 +117,7 @@ prettytable==0.7.2 protobuf==3.14.0 prov==1.5.1 psutil==5.7.3 -pulsar-galaxy-lib==0.14.0.dev4 +pulsar-galaxy-lib==0.14.1 pyasn1-modules==0.2.8 pyasn1==0.4.8 pycparser==2.20; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' From e4f4731e086bcbd4a42cafa77930077a279f997d Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Wed, 3 Feb 2021 10:53:05 +0100 Subject: [PATCH 011/255] Don't flush for each failed output dataset I noticed that main's job handler is spending a lot of time here: ``` Thread 21942 (active+gil): "SlurmRunner.work_thread-1" _remove_snapshot (sqlalchemy/orm/session.py:396) commit (sqlalchemy/orm/session.py:514) _flush (sqlalchemy/orm/session.py:2674) flush (sqlalchemy/orm/session.py:2536) do (sqlalchemy/orm/scoping.py:163) fail (galaxy/jobs/__init__.py:1314) fail_job (galaxy/jobs/runners/__init__.py:474) run_next (galaxy/jobs/runners/__init__.py:136) run (threading.py:864) _bootstrap_inner (threading.py:916) _bootstrap (threading.py:884) ``` We flush just a few lines below anyway, so this flush shouldn't be needed and could have a big impact if there are a lot of output datasets. --- lib/galaxy/jobs/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/galaxy/jobs/__init__.py b/lib/galaxy/jobs/__init__.py index 3ab20ea94e6..6a50aa34d81 100644 --- a/lib/galaxy/jobs/__init__.py +++ b/lib/galaxy/jobs/__init__.py @@ -1310,8 +1310,6 @@ class JobWrapper(HasResourceParameters): # Pause any dependent jobs (and those jobs' outputs) for dep_job_assoc in dataset.dependent_jobs: self.pause(dep_job_assoc.job, "Execution of this dataset's job is paused because its input datasets are in an error state.") - self.sa_session.add(dataset) - self.sa_session.flush() job.set_final_state(job.states.ERROR) job.command_line = unicodify(self.command_line) job.info = message From a72850ee75a5643e4b225f54700678d42005739a Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Wed, 3 Feb 2021 11:44:34 +0100 Subject: [PATCH 012/255] Don't check if item is in list Just do set intersection. This is surprisingly something that takes up a good amount of CPU time on job handlers if there are a lot of jobs. With cumulative line-based profiling this is one of the top lines in Galaxy code. --- lib/galaxy/jobs/handler.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/galaxy/jobs/handler.py b/lib/galaxy/jobs/handler.py index 4cff86a0308..4de1b62e7f5 100644 --- a/lib/galaxy/jobs/handler.py +++ b/lib/galaxy/jobs/handler.py @@ -436,9 +436,8 @@ class JobHandlerQueue(Monitors): if not self.track_jobs_in_database: self.waiting_jobs = new_waiting_jobs # Remove cached wrappers for any jobs that are no longer being tracked - for id in list(self.job_wrappers.keys()): - if id not in new_waiting_jobs: - del self.job_wrappers[id] + for id in set(self.job_wrappers.keys()) - set(new_waiting_jobs): + del self.job_wrappers[id] # Flush, if we updated the state self.sa_session.flush() # Done with the session From 60efa8e094b4b2e70f59b8edda7c87998e6ecd99 Mon Sep 17 00:00:00 2001 From: Mason Houtz Date: Wed, 3 Feb 2021 08:23:53 -0800 Subject: [PATCH 013/255] history load fixes, rxjs provider debugging, upload init isolation --- client/package.json | 2 +- .../src/components/DataDialog/DataDialog.vue | 4 +- client/src/components/DebouncedInput.js | 6 - .../ContentItem/CollectionContentItem.js | 6 - .../History/ContentItem/ContentItem.js | 32 +- .../History/ContentItem/Dataset/Dataset.vue | 57 +++ .../ContentItem/Dataset/DatasetMenu.vue | 31 +- .../History/ContentItem/Dataset/DatasetUI.vue | 61 ++- .../History/ContentItem/Dataset/index.js | 5 + .../History/ContentItem/Dataset/index.vue | 49 --- .../DatasetCollection/DatasetCollection.vue | 57 +++ .../ContentItem/DatasetCollection/DscMenu.vue | 6 +- .../ContentItem/DatasetCollection/DscUI.vue | 37 +- .../ContentItem/DatasetCollection/index.js | 6 + .../ContentItem/DatasetCollection/index.vue | 67 ---- .../History/ContentItem/HistoryContentItem.js | 4 - .../History/ContentItem/Subcollection.vue | 12 +- .../components/History/ContentOperations.vue | 6 +- client/src/components/History/CopyModal.vue | 13 +- .../Details.vue | 10 +- .../History/CurrentCollection/Panel.vue | 109 ++++++ .../TopNav.vue | 16 - .../History/CurrentHistoryPanel.vue | 54 +-- .../src/components/History/HistoriesMenu.vue | 53 +++ client/src/components/History/History.vue | 159 ++++---- .../src/components/History/HistoryDetails.vue | 125 +++--- .../src/components/History/HistoryEmpty.vue | 9 +- .../src/components/History/HistoryPanel.vue | 48 ++- .../components/History/HistorySelector.vue | 22 +- client/src/components/History/HistoryTags.vue | 2 +- .../src/components/History/HistoryTopNav.vue | 91 ----- client/src/components/History/ListMixin.js | 73 ---- .../History/SelectedCollection/Panel.vue | 123 ------ .../History/adapters/HistoryPanelProxy.js | 1 - .../src/components/History/caching/index.js | 1 + .../History/caching/loadDscContent.js | 5 +- .../src/components/History/caching/queue.js | 41 -- .../src/components/History/model/Content.js | 6 + .../components/History/model/historyStore.js | 213 +++++----- .../src/components/History/model/queries.js | 39 +- .../CollectionContentProvider.js | 172 +-------- .../CollectionContentProvider.test.js | 7 +- .../loadCollectionContents.js | 30 +- .../processCollectionStreams.js | 102 +++++ .../History/providers/ContentProvider.js | 38 +- .../providers/DscProvider/DscProvider.test.js | 7 +- .../History/providers/ExpandedItems.js | 39 ++ .../HistoryContentProvider.js | 342 +---------------- .../HistoryContentProvider.test.js | 6 +- .../processHistoryStreams.js | 224 +++++++++++ .../watchHistoryContents.js | 13 +- .../History/providers/SelectedItems.js | 86 +++++ .../components/History/providers/UpdateMap.js | 59 --- .../History/providers/UpdateMap.test.js | 56 --- .../UserHistories/MockCurrentHistory.js | 10 + .../providers/UserHistories/UserHistories.js | 95 +++++ .../UserHistories/UserHistories.test.js | 201 ++++++++++ .../History/providers/UserHistories/index.js | 2 + .../src/components/History/providers/index.js | 7 + .../components/Panels/Common/ToolSection.vue | 23 +- client/src/components/Panels/ToolBox.vue | 6 +- client/src/components/PriorityMenu/Menu.vue | 2 +- .../src/components/PriorityMenu/MenuItem.vue | 4 +- .../src/components/RuleCollectionBuilder.vue | 28 +- client/src/components/Tags/Tags.test.js | 11 +- client/src/components/Upload/Collection.vue | 10 +- client/src/components/Upload/Composite.vue | 8 +- client/src/components/Upload/Default.vue | 10 +- client/src/components/Upload/RulesInput.vue | 44 +-- .../Buttons => Upload}/UploadButton.vue | 33 +- .../src/components/Upload/UploadModal.test.js | 46 ++- client/src/components/Upload/UploadModal.vue | 363 +++--------------- .../components/Upload/UploadModalContent.vue | 243 ++++++++++++ client/src/components/Upload/config.js | 6 +- client/src/components/Upload/helpers.js | 52 +++ client/src/components/Upload/index.js | 5 +- client/src/components/Upload/mount.js | 34 +- .../DatasetCollectionUIWrapper.test.js | 8 +- .../DatasetCollectionUIWrapper.vue | 10 +- .../DatasetUIWrapper.test.js | 8 +- .../DatasetUIWrapper.vue | 24 +- .../WorkflowInvocationDetails.vue | 31 +- .../providers/rxProviders.js | 3 - client/src/components/plugins/eventHub.js | 2 +- client/src/components/plugins/index.js | 6 +- client/src/components/plugins/localization.js | 9 + .../components/providers/ConfigProvider.js | 23 ++ .../src/components/providers/CurrentUser.js | 18 + .../components/providers/MockCurrentUser.js | 9 + .../providers/SingleQueryProvider.js | 30 ++ client/src/entry/panels/tool-panel.js | 19 - .../mvc/history/history-view-edit-current.js | 4 +- client/src/mvc/upload/upload-utils.js | 71 ++-- client/src/store/configStore/configStore.js | 42 ++ client/src/store/configStore/index.js | 46 +-- .../store/configStore/syncConfigToGalaxy.js | 4 +- client/src/store/index.js | 16 +- client/src/store/syncVuextoGalaxy.js | 6 +- .../src/store/userStore/syncUserToGalaxy.js | 5 +- client/src/store/userStore/userStore.js | 24 +- client/src/utils/data.js | 5 +- client/src/utils/mountVueComponent.js | 4 +- client/src/utils/observable/activity.js | 36 +- client/src/utils/observable/debugging.js | 34 -- client/src/utils/observable/lastValueFrom.js | 15 - client/src/utils/observable/nth.js | 4 + client/src/utils/observable/shareButDie.js | 7 + client/src/utils/observable/waitForInit.js | 5 + client/src/utils/proxy.js | 57 +-- client/src/utils/redirect.js | 11 + client/src/utils/sorting.js | 15 + client/tests/jest/helpers.js | 10 +- 112 files changed, 2425 insertions(+), 2281 deletions(-) create mode 100644 client/src/components/History/ContentItem/Dataset/Dataset.vue create mode 100644 client/src/components/History/ContentItem/Dataset/index.js delete mode 100644 client/src/components/History/ContentItem/Dataset/index.vue create mode 100644 client/src/components/History/ContentItem/DatasetCollection/DatasetCollection.vue create mode 100644 client/src/components/History/ContentItem/DatasetCollection/index.js delete mode 100644 client/src/components/History/ContentItem/DatasetCollection/index.vue rename client/src/components/History/{SelectedCollection => CurrentCollection}/Details.vue (77%) create mode 100644 client/src/components/History/CurrentCollection/Panel.vue rename client/src/components/History/{SelectedCollection => CurrentCollection}/TopNav.vue (80%) create mode 100644 client/src/components/History/HistoriesMenu.vue delete mode 100644 client/src/components/History/HistoryTopNav.vue delete mode 100644 client/src/components/History/ListMixin.js delete mode 100644 client/src/components/History/SelectedCollection/Panel.vue delete mode 100644 client/src/components/History/caching/queue.js create mode 100644 client/src/components/History/providers/CollectionContentProvider/processCollectionStreams.js create mode 100644 client/src/components/History/providers/ExpandedItems.js create mode 100644 client/src/components/History/providers/HistoryContentProvider/processHistoryStreams.js create mode 100644 client/src/components/History/providers/SelectedItems.js delete mode 100644 client/src/components/History/providers/UpdateMap.js delete mode 100644 client/src/components/History/providers/UpdateMap.test.js create mode 100644 client/src/components/History/providers/UserHistories/MockCurrentHistory.js create mode 100644 client/src/components/History/providers/UserHistories/UserHistories.js create mode 100644 client/src/components/History/providers/UserHistories/UserHistories.test.js create mode 100644 client/src/components/History/providers/UserHistories/index.js rename client/src/components/{Panels/Buttons => Upload}/UploadButton.vue (61%) create mode 100644 client/src/components/Upload/UploadModalContent.vue create mode 100644 client/src/components/Upload/helpers.js create mode 100644 client/src/components/providers/ConfigProvider.js create mode 100644 client/src/components/providers/CurrentUser.js create mode 100644 client/src/components/providers/MockCurrentUser.js create mode 100644 client/src/components/providers/SingleQueryProvider.js create mode 100644 client/src/store/configStore/configStore.js delete mode 100644 client/src/utils/observable/debugging.js delete mode 100644 client/src/utils/observable/lastValueFrom.js create mode 100644 client/src/utils/observable/nth.js create mode 100644 client/src/utils/observable/shareButDie.js create mode 100644 client/src/utils/sorting.js diff --git a/client/package.json b/client/package.json index 7f3ac4610ed..04561397485 100644 --- a/client/package.json +++ b/client/package.json @@ -74,11 +74,11 @@ "underscore": "^1.10.2", "underscore.string": "^3.3.5", "vue": "^2.6.11", - "vuedraggable": "2.24.1", "vue-infinite-scroll": "^2.0.2", "vue-multiselect": "^2.1.0", "vue-router": "^3.3.1", "vue-rx": "^6.2.0", + "vuedraggable": "2.24.1", "vueisotope": "^3.1.2", "vuex": "^3.4.0", "vuex-cache": "^3.2.0", diff --git a/client/src/components/DataDialog/DataDialog.vue b/client/src/components/DataDialog/DataDialog.vue index f1b498fcde5..a123cda6429 100644 --- a/client/src/components/DataDialog/DataDialog.vue +++ b/client/src/components/DataDialog/DataDialog.vue @@ -49,7 +49,7 @@ import { UrlTracker } from "./utilities"; import { Model } from "./model"; import { Services } from "./services"; import { getAppRoot } from "onload/loadConfig"; -import { mountUploadModal } from "components/Upload"; +import { openUploadModal } from "components/Upload"; Vue.use(BootstrapVue); @@ -135,7 +135,7 @@ export default { callback: this.callback, modalShow: true, }; - mountUploadModal(propsData); + openUploadModal(propsData); this.modalShow = false; }, /** Called when selection is complete, values are formatted and parsed to external callback **/ diff --git a/client/src/components/DebouncedInput.js b/client/src/components/DebouncedInput.js index dd3e1cba62f..03a4ee9de5f 100644 --- a/client/src/components/DebouncedInput.js +++ b/client/src/components/DebouncedInput.js @@ -2,15 +2,9 @@ * Renderless component, used to debounce various types of form inputs */ -import Vue from "vue"; -import VueRx from "vue-rx"; import { filter, debounceTime, distinctUntilChanged, finalize } from "rxjs/operators"; -import { vueRxShortcuts } from "./plugins"; - -Vue.use(VueRx); export default { - mixins: [vueRxShortcuts], props: { value: { required: true }, delay: { type: Number, required: false, default: 500 }, diff --git a/client/src/components/History/ContentItem/CollectionContentItem.js b/client/src/components/History/ContentItem/CollectionContentItem.js index e88605af117..efa92a9083a 100644 --- a/client/src/components/History/ContentItem/CollectionContentItem.js +++ b/client/src/components/History/ContentItem/CollectionContentItem.js @@ -5,12 +5,6 @@ export default { computed: { contentItemComponent() { - if (this.item === null) { - return "Loading"; - } - if (this.scrolling) { - return "Placeholder"; - } const { history_content_type } = this.item; switch (history_content_type) { case "dataset": diff --git a/client/src/components/History/ContentItem/ContentItem.js b/client/src/components/History/ContentItem/ContentItem.js index afe62bf8e10..0adfea54b42 100644 --- a/client/src/components/History/ContentItem/ContentItem.js +++ b/client/src/components/History/ContentItem/ContentItem.js @@ -11,22 +11,17 @@ import Placeholder from "./Placeholder"; import Dataset from "./Dataset"; import DatasetCollection from "./DatasetCollection"; +// import SubDataset from "./SubDataset"; import Subcollection from "./Subcollection"; export default { - inject: ["listState", "isSelected", "isExpanded", "setSelected", "setExpanded"], - template: ` + + + + diff --git a/client/src/components/History/ContentItem/Dataset/DatasetMenu.vue b/client/src/components/History/ContentItem/Dataset/DatasetMenu.vue index bc96b65eba6..3982752bf4a 100644 --- a/client/src/components/History/ContentItem/Dataset/DatasetMenu.vue +++ b/client/src/components/History/ContentItem/Dataset/DatasetMenu.vue @@ -72,6 +72,14 @@ icon="fas fa-file-download" /> + +
import { mapGetters } from "vuex"; -import { Dataset } from "../../model"; +import { Dataset, STATES } from "../../model"; import { PriorityMenu, PriorityMenuItem } from "components/PriorityMenu"; -import { legacyNavigationMixin } from "components/plugins"; +import { legacyNavigationMixin } from "components/plugins/legacyNavigation"; export default { - inject: ["STATES"], mixins: [legacyNavigationMixin], components: { @@ -156,6 +163,11 @@ export default { }; }, + created() { + // make available to template + this.STATES = STATES; + }, + computed: { ...mapGetters("user", ["currentUser"]), ...mapGetters("config", ["config"]), @@ -168,10 +180,10 @@ export default { if (this.dataset.purged) { return "Cannot display datasets removed from disk"; } - if (this.dataset.state == this.STATES.UPLOAD) { + if (this.dataset.state == STATES.UPLOAD) { return "This dataset must finish uploading before it can be viewed"; } - if (this.dataset.state == this.STATES.NEW) { + if (this.dataset.state == STATES.NEW) { return "This dataset is not yet viewable"; } return "View data"; @@ -184,7 +196,7 @@ export default { if (this.dataset.purged) { return "Cannot edit attributes of datasets removed from disk"; } - const unreadyStates = new Set([this.STATES.UPLOAD, this.STATES.NEW]); + const unreadyStates = new Set([STATES.UPLOAD, STATES.NEW]); if (unreadyStates.has(this.dataset.state)) { return "This dataset is not yet editable"; } @@ -206,7 +218,7 @@ export default { if (!this.dataset.hasData) { return false; } - const okStates = new Set([this.STATES.OK, this.STATES.FAILED_METADATA, this.STATES.ERROR]); + const okStates = new Set([STATES.OK, STATES.FAILED_METADATA, STATES.ERROR]); return okStates.has(this.dataset.state); }, @@ -261,9 +273,8 @@ export default { }, onDeleteClick() { - const eventName = this.dataset.deleted ? "undeleteDataset" : "deleteDataset"; - // console.log("emitting", eventName, this.dataset); - this.$emit(eventName, this.dataset); + const eventName = this.dataset.deleted ? "undelete" : "delete"; + this.$emit(eventName); }, }, }; diff --git a/client/src/components/History/ContentItem/Dataset/DatasetUI.vue b/client/src/components/History/ContentItem/Dataset/DatasetUI.vue index 8314d8c6d92..36c2da3b28e 100644 --- a/client/src/components/History/ContentItem/Dataset/DatasetUI.vue +++ b/client/src/components/History/ContentItem/Dataset/DatasetUI.vue @@ -14,6 +14,7 @@ either through the props, and make updates through the events --> @keydown.arrow-right.self.stop="$emit('update:expanded', true)" @keydown.space.self.stop.prevent="$emit('update:selected', !selected)" > +
@@ -46,25 +46,30 @@ either through the props, and make updates through the events --> {{ dataset.hid }} {{ dataset.title }} - - + + + + +
+ +
+ +
- - - + +
+ +
@@ -102,20 +108,16 @@ either through the props, and make updates through the events --> diff --git a/client/src/components/History/ContentItem/DatasetCollection/DatasetCollection.vue b/client/src/components/History/ContentItem/DatasetCollection/DatasetCollection.vue new file mode 100644 index 00000000000..86e54a534df --- /dev/null +++ b/client/src/components/History/ContentItem/DatasetCollection/DatasetCollection.vue @@ -0,0 +1,57 @@ + + + diff --git a/client/src/components/History/ContentItem/DatasetCollection/DscMenu.vue b/client/src/components/History/ContentItem/DatasetCollection/DscMenu.vue index 6a52cc1a6e1..fbe90484749 100644 --- a/client/src/components/History/ContentItem/DatasetCollection/DscMenu.vue +++ b/client/src/components/History/ContentItem/DatasetCollection/DscMenu.vue @@ -5,13 +5,13 @@ Delete Collection - Delete Collection Only + Delete Collection Only - + Delete Contained Datasets - + Purge Contained Datasets diff --git a/client/src/components/History/ContentItem/DatasetCollection/DscUI.vue b/client/src/components/History/ContentItem/DatasetCollection/DscUI.vue index 57a6cd1966b..25393e98d18 100644 --- a/client/src/components/History/ContentItem/DatasetCollection/DscUI.vue +++ b/client/src/components/History/ContentItem/DatasetCollection/DscUI.vue @@ -6,12 +6,9 @@ class="dataset dataset-collection collapsed" :class="{ selected }" :data-state="dsc.state" - @keydown.arrow-right.self.stop="$emit('select-collection', dsc)" + @keydown.arrow-right.self.stop="$emit('viewCollection')" @keydown.space.self.stop.prevent="$emit('update:selected', !selected)" - @click.stop=" - $emit('select-collection', dsc); - $emit('update:expanded', dsc); - " + @click.stop="$emit('viewCollection')" > + +
+ +
+ -
No summary
@@ -71,27 +75,20 @@ import { DatasetCollection } from "../../model/DatasetCollection"; import { StatusIcon, StateBtn } from "../../StatusIcon"; import JobStateProgress from "./JobStateProgress"; import DscMenu from "./DscMenu"; +import { Nametag } from "components/Nametags"; export default { - inject: ["listState", "STATES"], components: { StatusIcon, StateBtn, JobStateProgress, DscMenu, + Nametag, }, props: { dsc: { type: DatasetCollection, required: true }, selected: { type: Boolean, required: false, default: false }, - showHid: { type: Boolean, required: false, default: true }, - }, - computed: { - counter() { - return this.showHid ? this.dsc.hid : ""; - }, - showSelection() { - return this.listState.showSelection; - }, + showSelection: { type: Boolean, required: false, default: false }, }, methods: { onStatusClick() { diff --git a/client/src/components/History/ContentItem/DatasetCollection/index.js b/client/src/components/History/ContentItem/DatasetCollection/index.js new file mode 100644 index 00000000000..4d7f796c22b --- /dev/null +++ b/client/src/components/History/ContentItem/DatasetCollection/index.js @@ -0,0 +1,6 @@ +import DatasetCollection from "./DatasetCollection"; + +export { default as DscUI } from "./DscUI"; +export { default as DscMenu } from "./DscMenu"; +export { default as JobStateProgress } from "./JobStateProgress"; +export default DatasetCollection; diff --git a/client/src/components/History/ContentItem/DatasetCollection/index.vue b/client/src/components/History/ContentItem/DatasetCollection/index.vue deleted file mode 100644 index 49d4758c5d3..00000000000 --- a/client/src/components/History/ContentItem/DatasetCollection/index.vue +++ /dev/null @@ -1,67 +0,0 @@ - - - diff --git a/client/src/components/History/ContentItem/HistoryContentItem.js b/client/src/components/History/ContentItem/HistoryContentItem.js index fe65f664934..c6d8c6632dc 100644 --- a/client/src/components/History/ContentItem/HistoryContentItem.js +++ b/client/src/components/History/ContentItem/HistoryContentItem.js @@ -7,10 +7,6 @@ export default { contentItemComponent() { if (this.item.id === undefined) { return "Placeholder"; - // return "Loading"; - } - if (this.scrolling) { - return "Placeholder"; } const { history_content_type } = this.item; switch (history_content_type) { diff --git a/client/src/components/History/ContentItem/Subcollection.vue b/client/src/components/History/ContentItem/Subcollection.vue index fbef360ddfe..d17a8f6b5a0 100644 --- a/client/src/components/History/ContentItem/Subcollection.vue +++ b/client/src/components/History/ContentItem/Subcollection.vue @@ -7,12 +7,10 @@ v-on="$listeners" class="collapsed" :data-state="state" - @keydown.arrow-right.self.stop.prevent="$emit('select-collection', dsc)" + @keydown.arrow-right.self.stop.prevent="$emit('viewCollection')" + @click.stop="$emit('viewCollection')" > -