From 0d68d4cf389f5864c6fc5676012c9301fe850daa Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sun, 24 Sep 2017 21:20:01 +0200 Subject: [PATCH 01/51] WIP: skip job execution if equivalent job exists --- lib/galaxy/jobs/handler.py | 14 +++++- lib/galaxy/model/__init__.py | 7 +++ lib/galaxy/model/mapping.py | 1 + .../0136_add_copied_from_job_id_column.py | 40 ++++++++++++++++ lib/galaxy/tools/__init__.py | 11 ++++- lib/galaxy/tools/actions/__init__.py | 47 ++++++++++++++----- lib/galaxy/tools/execute.py | 13 +++-- lib/galaxy/workflow/modules.py | 9 ++++ 8 files changed, 120 insertions(+), 22 deletions(-) create mode 100644 lib/galaxy/model/migrate/versions/0136_add_copied_from_job_id_column.py diff --git a/lib/galaxy/jobs/handler.py b/lib/galaxy/jobs/handler.py index 80585c103d6..289e9bff222 100644 --- a/lib/galaxy/jobs/handler.py +++ b/lib/galaxy/jobs/handler.py @@ -313,7 +313,19 @@ class JobHandlerQueue(Monitors, object): elif job_state == JOB_INPUT_DELETED: log.info("(%d) Job unable to run: one or more inputs deleted" % job.id) elif job_state == JOB_READY: - self.dispatcher.put(self.job_wrappers.pop(job.id)) + if job.copied_from_job_id: + copied_from_job = self.sa_session.query(model.Job).get(job.copied_from_job_id) + job.state = copied_from_job.state + job.stderr = copied_from_job.stderr + job.stdout = copied_from_job.stdout + job.command_line = copied_from_job.command_line + job.traceback = copied_from_job.traceback + job.tool_version = copied_from_job.tool_version + job.exit_code = copied_from_job.exit_code + job.job_runner_name = copied_from_job.job_runner_name + job.job_runner_external_id = copied_from_job.job_runner_external_id + else: + self.dispatcher.put(self.job_wrappers.pop(job.id)) log.info("(%d) Job dispatched" % job.id) elif job_state == JOB_DELETED: log.info("(%d) Job deleted by user while still queued" % job.id) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 68610b8eabd..6360e5ca1a3 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -474,6 +474,7 @@ class Job(object, JobLike, UsesCreateAndUpdateTime, Dictifiable): self.user_id = None self.tool_id = None self.tool_version = None + self.copied_from_job_id = None self.command_line = None self.dependencies = [] self.param_filename = None @@ -542,6 +543,9 @@ class Job(object, JobLike, UsesCreateAndUpdateTime, Dictifiable): def get_parameters(self): return self.parameters + def get_copied_from_job_id(self): + return self.copied_from_job_id + def get_input_datasets(self): return self.input_datasets @@ -625,6 +629,9 @@ class Job(object, JobLike, UsesCreateAndUpdateTime, Dictifiable): def set_parameters(self, parameters): self.parameters = parameters + def set_copied_from_job_id(self, job_id): + self.copied_from_job_id = job_id + def set_input_datasets(self, input_datasets): self.input_datasets = input_datasets diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index 22c96af3964..f9667a327a4 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -464,6 +464,7 @@ model.Job.table = Table( Column("tool_version", TEXT, default="1.0.0"), 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("param_filename", String(1024)), diff --git a/lib/galaxy/model/migrate/versions/0136_add_copied_from_job_id_column.py b/lib/galaxy/model/migrate/versions/0136_add_copied_from_job_id_column.py new file mode 100644 index 00000000000..f6409d5ca91 --- /dev/null +++ b/lib/galaxy/model/migrate/versions/0136_add_copied_from_job_id_column.py @@ -0,0 +1,40 @@ +""" +Add copied_from_job_id column to jobs table +""" +from __future__ import print_function + +import logging + +from sqlalchemy import Column, Integer, MetaData, Table + +log = logging.getLogger(__name__) +copied_from_job_id_column = Column("copied_from_job_id", Integer, nullable=True) + + +def upgrade(migrate_engine): + print(__doc__) + metadata = MetaData() + metadata.bind = migrate_engine + metadata.reflect() + + # Add the dependencies column to the job table + try: + jobs_table = Table("job", metadata, autoload=True) + copied_from_job_id_column.create(jobs_table) + assert copied_from_job_id_column is jobs_table.c.copied_from_job_id + except Exception: + log.exception("Adding column 'copied_from_job_id_column' to job table failed.") + + +def downgrade(migrate_engine): + metadata = MetaData() + metadata.bind = migrate_engine + metadata.reflect() + + # Drop the job table's dependencies column. + try: + jobs_table = Table("job", metadata, autoload=True) + copied_from_job_id = jobs_table.c.copied_from_job_id_column + copied_from_job_id.drop() + except Exception: + log.exception("Dropping 'copied_from_job_id_column' column from job table failed.") diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index 52b42f94fdf..8749773b943 100755 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -26,6 +26,7 @@ from galaxy import ( ) from galaxy.datatypes.metadata import JobExternalOutputMetadataWrapper from galaxy.managers import histories +from galaxy.managers.jobs import JobSearch from galaxy.queue_worker import send_control_task from galaxy.tools.actions import DefaultToolAction from galaxy.tools.actions.data_manager import DataManagerToolAction @@ -438,6 +439,7 @@ class Tool(object, Dictifiable): raise e self.history_manager = histories.HistoryManager(app) self._view = views.DependencyResolversView(app) + self.job_search = JobSearch(app=self.app) @property def version_object(self): @@ -1294,7 +1296,14 @@ class Tool(object, Dictifiable): raise exceptions.MessageException(', '.join(msg for msg in err_data.values()), err_data=err_data) else: mapping_params = MappingParameters(incoming, all_params) - execution_tracker = execute_job(trans, self, mapping_params, history=request_context.history, rerun_remap_job_id=rerun_remap_job_id, collection_info=collection_info) + completed_jobs = {} + for i, param in enumerate(all_params): + completed_jobs[i] = self.job_search.by_tool_input( + trans=trans, + tool_id=self.id, + param_dump=self.params_to_strings(param, self.app, nested=True), + ) + execution_tracker = execute_job(trans, self, mapping_params, history=request_context.history, rerun_remap_job_id=rerun_remap_job_id, collection_info=collection_info, completed_jobs=completed_jobs) # Raise an exception if there were jobs to execute and none of them were submitted, # if at least one is submitted or there are no jobs to execute - return aggregate # information including per-job errors. Arguably we should just always return the diff --git a/lib/galaxy/tools/actions/__init__.py b/lib/galaxy/tools/actions/__init__.py index a0088e8344e..050e1c19374 100644 --- a/lib/galaxy/tools/actions/__init__.py +++ b/lib/galaxy/tools/actions/__init__.py @@ -195,7 +195,7 @@ class DefaultToolAction(object): return history, inp_data, inp_dataset_collections - def execute(self, tool, trans, incoming={}, return_job=False, set_output_hid=True, history=None, job_params=None, rerun_remap_job_id=None, execution_cache=None, dataset_collection_elements=None): + def execute(self, tool, trans, incoming={}, return_job=False, set_output_hid=True, history=None, job_params=None, rerun_remap_job_id=None, execution_cache=None, dataset_collection_elements=None, completed_job=None): """ Executes a tool, creating job and tool outputs, associating them, and submitting the job to the job queue. If history is not specified, use @@ -223,7 +223,7 @@ class DefaultToolAction(object): continue # Convert LDDA to an HDA. - if isinstance(data, LibraryDatasetDatasetAssociation): + if isinstance(data, LibraryDatasetDatasetAssociation) and not completed_job: data = data.to_history_dataset_association(None) inp_data[name] = data @@ -246,13 +246,14 @@ class DefaultToolAction(object): inp_data.update({"chromInfo": db_dataset}) incoming["chromInfo"] = chrom_info - # Determine output dataset permission/roles list - existing_datasets = [inp for inp in inp_data.values() if inp] - if existing_datasets: - output_permissions = app.security_agent.guess_derived_permissions_for_datasets(existing_datasets) - else: - # No valid inputs, we will use history defaults - output_permissions = app.security_agent.history_get_default_permissions(history) + if not completed_job: + # Determine output dataset permission/roles list + existing_datasets = [inp for inp in inp_data.values() if inp] + if existing_datasets: + output_permissions = app.security_agent.guess_derived_permissions_for_datasets(existing_datasets) + else: + # No valid inputs, we will use history defaults + output_permissions = app.security_agent.history_get_default_permissions(history) # Add the dbkey to the incoming parameters incoming["dbkey"] = input_dbkey @@ -301,7 +302,18 @@ class DefaultToolAction(object): inp_dataset_collections, input_ext ) - data = app.model.HistoryDatasetAssociation(extension=ext, create_dataset=True, flush=False) + create_datasets = True + dataset = None + + if completed_job: + for output_dataset in completed_job.output_datasets: + if output_dataset.name == name: + create_datasets = False + completed_data = output_dataset.dataset + dataset = output_dataset.dataset.dataset + break + + data = app.model.HistoryDatasetAssociation(extension=ext, dataset=dataset, create_dataset=create_datasets, flush=False) if hidden is None: hidden = output.hidden if not hidden and dataset_collection_elements is not None: # Mapping over a collection - hide datasets @@ -311,14 +323,17 @@ class DefaultToolAction(object): if dataset_collection_elements is not None and name in dataset_collection_elements: dataset_collection_elements[name].hda = data trans.sa_session.add(data) - trans.app.security_agent.set_all_dataset_permissions(data.dataset, output_permissions, new=True) + if not completed_job: + trans.app.security_agent.set_all_dataset_permissions(data.dataset, output_permissions, new=True) for _, tag in preserved_tags.items(): data.tags.append(tag.copy()) # Must flush before setting object store id currently. # TODO: optimize this. + trans.sa_session.flush() - object_store_populator.set_object_store_id(data) + if not completed_job: + object_store_populator.set_object_store_id(data) # This may not be neccesary with the new parent/child associations data.designation = name @@ -338,7 +353,11 @@ class DefaultToolAction(object): # Take dbkey from LAST input data.dbkey = str(input_dbkey) # Set state - data.blurb = "queued" + if completed_job: + data.blurb = completed_data.blurb + data.peek = completed_data.peek + else: + data.blurb = "queued" # Set output label data.name = self.get_output_name(output, data, tool, on_text, trans, incoming, history, wrapped_params.params, job_params) # Store output @@ -447,6 +466,8 @@ class DefaultToolAction(object): if job_params: job.params = dumps(job_params) job.set_handler(tool.get_job_handler(job_params)) + if completed_job: + job.set_copied_from_job_id(completed_job.id) trans.sa_session.add(job) # Now that we have a job id, we can remap any outputs if this is a rerun and the user chose to continue dependent jobs # This functionality requires tracking jobs in the database. diff --git a/lib/galaxy/tools/execute.py b/lib/galaxy/tools/execute.py index 1a83a0d245f..3b0baa5f90e 100644 --- a/lib/galaxy/tools/execute.py +++ b/lib/galaxy/tools/execute.py @@ -31,7 +31,7 @@ class PartialJobExecution(Exception): MappingParameters = collections.namedtuple("MappingParameters", ["param_template", "param_combinations"]) -def execute(trans, tool, mapping_params, history, rerun_remap_job_id=None, collection_info=None, workflow_invocation_uuid=None, invocation_step=None, max_num_jobs=None, job_callback=None): +def execute(trans, tool, mapping_params, history, rerun_remap_job_id=None, collection_info=None, workflow_invocation_uuid=None, invocation_step=None, max_num_jobs=None, job_callback=None, completed_jobs=None): """ Execute a tool and return object containing summary (output data, number of failures, etc...). @@ -49,7 +49,7 @@ def execute(trans, tool, mapping_params, history, rerun_remap_job_id=None, colle app = trans.app execution_cache = ToolExecutionCache(trans) - def execute_single_job(execution_slice): + def execute_single_job(execution_slice, completed_job): job_timer = ExecutionTimer() params = execution_slice.param_combination if workflow_invocation_uuid: @@ -59,7 +59,7 @@ def execute(trans, tool, mapping_params, history, rerun_remap_job_id=None, colle # values or rerun parameters. del params['__workflow_invocation_uuid__'] - job, result = tool.handle_single_execution(trans, rerun_remap_job_id, execution_slice, history, execution_cache) + job, result = tool.handle_single_execution(trans, rerun_remap_job_id, execution_slice, history, execution_cache, completed_job) if job: message = EXECUTION_SUCCESS_MESSAGE % (tool.id, job.id, job_timer) log.debug(message) @@ -89,13 +89,12 @@ def execute(trans, tool, mapping_params, history, rerun_remap_job_id=None, colle has_remaining_jobs = False if (job_count < burst_at or burst_threads < 2): - for execution_slice in execution_tracker.new_execution_slices(): + 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) - jobs_executed += 1 + execute_single_job(execution_slice, completed_jobs[i]) else: # TODO: re-record success... q = Queue() @@ -116,7 +115,7 @@ def execute(trans, tool, mapping_params, history, rerun_remap_job_id=None, colle has_remaining_jobs = True break else: - q.put(execution_slice) + q.put(execution_slice, completed_jobs[i]) jobs_executed += 1 q.join() diff --git a/lib/galaxy/workflow/modules.py b/lib/galaxy/workflow/modules.py index 6f4d92c5c17..f16b52848db 100644 --- a/lib/galaxy/workflow/modules.py +++ b/lib/galaxy/workflow/modules.py @@ -873,6 +873,14 @@ class ToolModule(WorkflowModule): param_combinations.append(execution_state.inputs) complete = False + completed_jobs = {} + for i, param in enumerate(param_combinations): + completed_jobs[i] = tool.job_search.by_tool_input( + trans=trans, + tool_id=tool.id, + param_dump=tool.params_to_strings(param, trans.app, nested=True), + is_workflow_step=True + ) try: mapping_params = MappingParameters(tool_state.inputs, param_combinations) max_num_jobs = progress.maximum_jobs_to_schedule_or_none @@ -886,6 +894,7 @@ class ToolModule(WorkflowModule): invocation_step=invocation_step, max_num_jobs=max_num_jobs, job_callback=lambda job: self._handle_post_job_actions(step, job, invocation.replacement_dict), + completed_jobs=completed_jobs ) complete = True except PartialJobExecution as pje: From 34b99bd8aaa0a04df5d6b81a2a9abb92d8244abb Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Thu, 28 Sep 2017 18:22:25 +0200 Subject: [PATCH 02/51] Improve detection of equivalent jobs in workflows and collection-context --- lib/galaxy/managers/jobs.py | 26 ++++++++++++++------------ lib/galaxy/workflow/modules.py | 1 - 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py index b17fa922f1b..72065823628 100644 --- a/lib/galaxy/managers/jobs.py +++ b/lib/galaxy/managers/jobs.py @@ -49,7 +49,7 @@ class JobSearch(object): self.ldda_manager = LDDAManager(app) self.decode_id = self.app.security.decode_id - def by_tool_input(self, trans, tool_id, param_dump=None, job_state='ok', is_workflow_step=False): + def by_tool_input(self, trans, tool_id, param_dump=None, job_state='ok'): """Search for jobs producing same results using the 'inputs' part of a tool POST.""" user = trans.user input_data = defaultdict(list) @@ -74,10 +74,9 @@ class JobSearch(object): input_data=input_data, job_state=job_state, param_dump=param_dump, - input_ids=input_ids, - is_workflow_step=is_workflow_step) + input_ids=input_ids) - def __search(self, tool_id, user, input_data, input_ids=None, job_state=None, param_dump=None, is_workflow_step=False): + def __search(self, tool_id, user, input_data, input_ids=None, job_state=None, param_dump=None): search_timer = ExecutionTimer() query = self.sa_session.query(model.Job).filter( model.Job.tool_id == tool_id, @@ -220,14 +219,17 @@ class JobSearch(object): )) if query.first() is None: continue - if is_workflow_step: - add_n_parameters = 3 - else: - add_n_parameters = 2 - if not len(job.parameters) == (len(new_param_dump) + add_n_parameters): - # Verify that equivalent jobs had the same number of job parameters - # We add 2 or 3 to new_param_dump because chrominfo and dbkey (and __workflow_invocation_uuid__) are not passed - # as input parameters + n_parameters = 0 + # Verify that equivalent jobs had the same number of job parameters + # We skip chrominfo, dbkey, __workflow_invocation_uuid__ and identifer + # parameter as these are not passed along when expanding tool parameters + # and they can differ without affecting the resulting dataset (when referencing identifier + # in the tool xml this may not be entirely true). + for parameter in job.parameters: + if parameter.name in {'__workflow_invocation_uuid__', 'chromInfo', 'dbkey'} or parameter.name.endswith('|__identifier__'): + continue + n_parameters += 1 + if not n_parameters == len(new_param_dump): continue # check to make sure none of the output datasets or collections have been deleted # TODO: refactors this into the initial job query diff --git a/lib/galaxy/workflow/modules.py b/lib/galaxy/workflow/modules.py index f16b52848db..c45a27b14a9 100644 --- a/lib/galaxy/workflow/modules.py +++ b/lib/galaxy/workflow/modules.py @@ -879,7 +879,6 @@ class ToolModule(WorkflowModule): trans=trans, tool_id=tool.id, param_dump=tool.params_to_strings(param, trans.app, nested=True), - is_workflow_step=True ) try: mapping_params = MappingParameters(tool_state.inputs, param_combinations) From cd32afe21e870898b7c8191628a2edcbee819527 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Thu, 28 Sep 2017 18:23:27 +0200 Subject: [PATCH 03/51] Skip creation of JobWrapper for copied jobs, copy metrics and dependencies --- lib/galaxy/jobs/handler.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/galaxy/jobs/handler.py b/lib/galaxy/jobs/handler.py index 289e9bff222..d03b930d6ba 100644 --- a/lib/galaxy/jobs/handler.py +++ b/lib/galaxy/jobs/handler.py @@ -305,6 +305,21 @@ class JobHandlerQueue(Monitors, object): try: # Check the job's dependencies, requeue if they're not done. # Some of these states will only happen when using the in-memory job queue + if job.copied_from_job_id: + copied_from_job = self.sa_session.query(model.Job).get(job.copied_from_job_id) + job.numeric_metrics = copied_from_job.numeric_metrics + 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.command_line = copied_from_job.command_line + job.traceback = copied_from_job.traceback + job.tool_version = copied_from_job.tool_version + job.exit_code = copied_from_job.exit_code + job.job_runner_name = copied_from_job.job_runner_name + job.job_runner_external_id = copied_from_job.job_runner_external_id + continue job_state = self.__check_job_state(job) if job_state == JOB_WAIT: new_waiting_jobs.append(job.id) @@ -313,19 +328,7 @@ class JobHandlerQueue(Monitors, object): elif job_state == JOB_INPUT_DELETED: log.info("(%d) Job unable to run: one or more inputs deleted" % job.id) elif job_state == JOB_READY: - if job.copied_from_job_id: - copied_from_job = self.sa_session.query(model.Job).get(job.copied_from_job_id) - job.state = copied_from_job.state - job.stderr = copied_from_job.stderr - job.stdout = copied_from_job.stdout - job.command_line = copied_from_job.command_line - job.traceback = copied_from_job.traceback - job.tool_version = copied_from_job.tool_version - job.exit_code = copied_from_job.exit_code - job.job_runner_name = copied_from_job.job_runner_name - job.job_runner_external_id = copied_from_job.job_runner_external_id - else: - self.dispatcher.put(self.job_wrappers.pop(job.id)) + self.dispatcher.put(self.job_wrappers.pop(job.id)) log.info("(%d) Job dispatched" % job.id) elif job_state == JOB_DELETED: log.info("(%d) Job deleted by user while still queued" % job.id) From 7defcddd19dfcc259ef6c0d83a7375f2363dc3f3 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Fri, 29 Sep 2017 10:36:00 +0200 Subject: [PATCH 04/51] Work around Exception("FileToolParameter cannot be persisted" --- lib/galaxy/tools/parameters/basic.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/galaxy/tools/parameters/basic.py b/lib/galaxy/tools/parameters/basic.py index 5ae79a7e303..e57989ae3c7 100644 --- a/lib/galaxy/tools/parameters/basic.py +++ b/lib/galaxy/tools/parameters/basic.py @@ -3,6 +3,7 @@ Basic tool parameters. """ from __future__ import print_function +import cgi import logging import os import os.path @@ -543,6 +544,8 @@ class FileToolParameter(ToolParameter): return value['local_filename'] except KeyError: return None + elif isinstance(value, cgi.FieldStorage): + return value.filename raise Exception("FileToolParameter cannot be persisted") def to_python(self, value, app): From e9a9b2a5c12921c4d9a80b02730f5bbe0f206598 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Fri, 29 Sep 2017 11:01:07 +0200 Subject: [PATCH 05/51] Also search jobs explicitly by version --- lib/galaxy/managers/jobs.py | 7 +++++-- lib/galaxy/tools/__init__.py | 1 + lib/galaxy/webapps/galaxy/api/jobs.py | 1 + lib/galaxy/workflow/modules.py | 1 + 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py index 72065823628..ba9c36c2f94 100644 --- a/lib/galaxy/managers/jobs.py +++ b/lib/galaxy/managers/jobs.py @@ -49,7 +49,7 @@ class JobSearch(object): self.ldda_manager = LDDAManager(app) self.decode_id = self.app.security.decode_id - def by_tool_input(self, trans, tool_id, param_dump=None, job_state='ok'): + def by_tool_input(self, trans, tool_id, tool_version, param_dump=None, job_state='ok'): """Search for jobs producing same results using the 'inputs' part of a tool POST.""" user = trans.user input_data = defaultdict(list) @@ -70,18 +70,21 @@ class JobSearch(object): remap(param_dump, visit=populate_input_data_input_id) return self.__search(tool_id=tool_id, + tool_version=tool_version, user=user, input_data=input_data, job_state=job_state, param_dump=param_dump, input_ids=input_ids) - def __search(self, tool_id, user, input_data, input_ids=None, job_state=None, param_dump=None): + def __search(self, tool_id, tool_version, user, input_data, input_ids=None, job_state=None, param_dump=None): search_timer = ExecutionTimer() query = self.sa_session.query(model.Job).filter( model.Job.tool_id == tool_id, model.Job.user == user ) + if tool_version: + query = query.filter(model.Job.tool_version == tool_version) if job_state is None: query = query.filter( diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index 8749773b943..09fe7d22562 100755 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -1301,6 +1301,7 @@ class Tool(object, Dictifiable): completed_jobs[i] = self.job_search.by_tool_input( trans=trans, tool_id=self.id, + tool_version=self.version, param_dump=self.params_to_strings(param, self.app, nested=True), ) execution_tracker = execute_job(trans, self, mapping_params, history=request_context.history, rerun_remap_job_id=rerun_remap_job_id, collection_info=collection_info, completed_jobs=completed_jobs) diff --git a/lib/galaxy/webapps/galaxy/api/jobs.py b/lib/galaxy/webapps/galaxy/api/jobs.py index e92dd35c402..39cfaa9cc5a 100644 --- a/lib/galaxy/webapps/galaxy/api/jobs.py +++ b/lib/galaxy/webapps/galaxy/api/jobs.py @@ -305,6 +305,7 @@ class JobController(BaseAPIController, UsesLibraryMixinItems): for param_dump in params_dump: job = self.job_search.by_tool_input(trans=trans, tool_id=tool_id, + tool_version=tool.version, param_dump=param_dump, job_state=payload.get('state')) if job: diff --git a/lib/galaxy/workflow/modules.py b/lib/galaxy/workflow/modules.py index c45a27b14a9..a408da498d5 100644 --- a/lib/galaxy/workflow/modules.py +++ b/lib/galaxy/workflow/modules.py @@ -878,6 +878,7 @@ class ToolModule(WorkflowModule): completed_jobs[i] = tool.job_search.by_tool_input( trans=trans, tool_id=tool.id, + tool_version=tool.version, param_dump=tool.params_to_strings(param, trans.app, nested=True), ) try: From d3a93af1221b4bb747f7c6e1bf54b894ac8a389e Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Fri, 29 Sep 2017 14:15:16 +0200 Subject: [PATCH 06/51] Cast tool_version to string and pre-filter jobs with wildcards --- lib/galaxy/managers/jobs.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py index ba9c36c2f94..f7a11dd82d8 100644 --- a/lib/galaxy/managers/jobs.py +++ b/lib/galaxy/managers/jobs.py @@ -64,7 +64,7 @@ class JobSearch(object): current_case = current_case[p] src = current_case['src'] input_data[path_key].append({'src': src, 'id': value}) - input_ids[src][value] = True + input_ids[src][value] = "__id_wildcard__" return key, value return key, value @@ -84,7 +84,7 @@ class JobSearch(object): model.Job.user == user ) if tool_version: - query = query.filter(model.Job.tool_version == tool_version) + query = query.filter(model.Job.tool_version == str(tool_version)) if job_state is None: query = query.filter( @@ -149,6 +149,14 @@ class JobSearch(object): else: return [] + for k, v in param_dump.items(): + a = aliased(model.JobParameter) + query = query.filter(and_( + model.Job.id == a.job_id, + a.name == k, + a.value.like(json.dumps(v).replace('id: "__id_wildcard__"', 'id: %')) + )) + for job in query.all(): # We found a job that is equal in terms of tool_id, user, state and input datasets, # but to be able to verify that the parameters match we need to modify all instances of From fc92adc1cc7db1fd6d94cb09d5c30be0e892a6ee Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Fri, 29 Sep 2017 17:58:18 +0200 Subject: [PATCH 07/51] Take into account HDA name, metadata, element_identifier and datatype --- lib/galaxy/managers/jobs.py | 38 +++++++++++++++++++++++---- lib/galaxy/tools/__init__.py | 1 + lib/galaxy/webapps/galaxy/api/jobs.py | 3 ++- lib/galaxy/workflow/modules.py | 1 + test/api/test_jobs.py | 15 +++++++++++ test/base/api.py | 6 +++++ test/base/interactor.py | 19 +++++++++++++- 7 files changed, 76 insertions(+), 7 deletions(-) diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py index f7a11dd82d8..ed1e3fceae0 100644 --- a/lib/galaxy/managers/jobs.py +++ b/lib/galaxy/managers/jobs.py @@ -49,7 +49,7 @@ class JobSearch(object): self.ldda_manager = LDDAManager(app) self.decode_id = self.app.security.decode_id - def by_tool_input(self, trans, tool_id, tool_version, param_dump=None, job_state='ok'): + def by_tool_input(self, trans, tool_id, tool_version, param=None, param_dump=None, job_state='ok'): """Search for jobs producing same results using the 'inputs' part of a tool POST.""" user = trans.user input_data = defaultdict(list) @@ -63,7 +63,19 @@ class JobSearch(object): for p in path: current_case = current_case[p] src = current_case['src'] - input_data[path_key].append({'src': src, 'id': value}) + current_case = param + for i, p in enumerate(path): + if p == 'values' and i == len(path) - 2: + continue + if isinstance(current_case, (list, dict)): + current_case = current_case[p] + identifier = getattr(current_case, "element_identifier", None) + name = current_case.name + input_data[path_key].append({'src': src, + 'id': value, + 'identifier': identifier, + 'name': name, + }) input_ids[src][value] = "__id_wildcard__" return key, value return key, value @@ -111,18 +123,32 @@ class JobSearch(object): for type_values in input_list: t = type_values['src'] v = type_values['id'] + identifier = type_values['identifier'] if t == 'hda': a = aliased(model.JobToInputDatasetAssociation) b = aliased(model.HistoryDatasetAssociation) c = aliased(model.HistoryDatasetAssociation) + d = aliased(model.JobParameter) query = query.filter(and_( model.Job.id == a.job_id, a.name == k, a.dataset_id == b.id, c.dataset_id == b.dataset_id, - c.id == v, + c.id == v, # c is the requested job input HDA + # We can only compare input dataset names and metadata for a job + # if we know that the input dataset hasn't changed since the job was run. + # This is relatively strict, we may be able to lift this requirement if we record the jobs' + # relevant parameters as JobParameters in the database + b.update_time < model.Job.create_time, + b.name == c.name, + b.extension == c.extension, + b._metadata == c._metadata, or_(b.deleted == false(), c.deleted == false()) )) + if identifier: + query = query.filter(model.Job.id == d.job_id, + d.name == "%s|__identifier__" % k, + d.value == json.dumps(identifier)) elif t == 'ldda': a = aliased(model.JobToInputLibraryDatasetAssociation) query = query.filter(and_( @@ -134,15 +160,17 @@ class JobSearch(object): a = aliased(model.JobToInputDatasetCollectionAssociation) b = aliased(model.HistoryDatasetCollectionAssociation) c = aliased(model.HistoryDatasetCollectionAssociation) + name = type_values['name'] query = query.filter(and_( model.Job.id == a.job_id, a.name == k, b.id == a.dataset_collection_id, c.id == v, - or_(and_(b.deleted == false(), b.id == v), + or_(and_(b.deleted == false(), b.id == v, b.name == name), and_(or_(c.copied_from_history_dataset_collection_association_id == b.id, b.copied_from_history_dataset_collection_association_id == c.id), - c.deleted == false() + c.deleted == false(), + c.name == name, ) ) )) diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index 09fe7d22562..e62410ccc94 100755 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -1302,6 +1302,7 @@ class Tool(object, Dictifiable): trans=trans, tool_id=self.id, tool_version=self.version, + param=param, param_dump=self.params_to_strings(param, self.app, nested=True), ) execution_tracker = execute_job(trans, self, mapping_params, history=request_context.history, rerun_remap_job_id=rerun_remap_job_id, collection_info=collection_info, completed_jobs=completed_jobs) diff --git a/lib/galaxy/webapps/galaxy/api/jobs.py b/lib/galaxy/webapps/galaxy/api/jobs.py index 39cfaa9cc5a..9330359ef55 100644 --- a/lib/galaxy/webapps/galaxy/api/jobs.py +++ b/lib/galaxy/webapps/galaxy/api/jobs.py @@ -302,10 +302,11 @@ class JobController(BaseAPIController, UsesLibraryMixinItems): return [] params_dump = [tool.params_to_strings(param, self.app, nested=True) for param in all_params] jobs = [] - for param_dump in params_dump: + for param_dump, param in zip(params_dump, all_params): job = self.job_search.by_tool_input(trans=trans, tool_id=tool_id, tool_version=tool.version, + param=param, param_dump=param_dump, job_state=payload.get('state')) if job: diff --git a/lib/galaxy/workflow/modules.py b/lib/galaxy/workflow/modules.py index a408da498d5..2e64319ed4b 100644 --- a/lib/galaxy/workflow/modules.py +++ b/lib/galaxy/workflow/modules.py @@ -879,6 +879,7 @@ class ToolModule(WorkflowModule): trans=trans, tool_id=tool.id, tool_version=tool.version, + param=param, param_dump=tool.params_to_strings(param, trans.app, nested=True), ) try: diff --git a/test/api/test_jobs.py b/test/api/test_jobs.py index 1220a5c198b..805a91f34d1 100644 --- a/test/api/test_jobs.py +++ b/test/api/test_jobs.py @@ -298,6 +298,21 @@ class JobsApiTestCase(api.ApiTestCase): self._assert_status_code_is(delete_respone, 200) self._search(search_payload, expected_search_count=0) + def test_search_handle_identifiers(self): + # Test that input name and element identifier of a jobs' output must match for a job to be returned. + history_id, dataset_id = self.__history_with_ok_dataset() + inputs = json.dumps({ + 'input1': {'src': 'hda', 'id': dataset_id} + }) + self._job_search(tool_id='identifier_single', history_id=history_id, inputs=inputs) + dataset_details = self._get("histories/%s/contents/%s" % (history_id, dataset_id)).json() + dataset_details['name'] = 'Renamed Test Dataset' + dataset_update_response = self._put("histories/%s/contents/%s" % (history_id, dataset_id), data=dict(name='Renamed Test Dataset')) + self._assert_status_code_is(dataset_update_response, 200) + assert dataset_update_response.json()['name'] == 'Renamed Test Dataset' + search_payload = self._search_payload(history_id=history_id, tool_id='identifier_single', inputs=inputs) + self._search(search_payload, expected_search_count=0) + def test_search_delete_outputs(self): history_id, dataset_id = self.__history_with_ok_dataset() inputs = json.dumps({ diff --git a/test/base/api.py b/test/base/api.py index abff78b4bb6..4cc36ac09d0 100644 --- a/test/base/api.py +++ b/test/base/api.py @@ -76,6 +76,9 @@ class UsesApiTestCaseMixin: def _delete(self, *args, **kwds): return self.galaxy_interactor.delete(*args, **kwds) + def _put(self, *args, **kwds): + return self.galaxy_interactor.put(*args, **kwds) + def _patch(self, *args, **kwds): return self.galaxy_interactor.patch(*args, **kwds) @@ -129,3 +132,6 @@ class ApiTestInteractor(BaseInteractor): def patch(self, *args, **kwds): return self._patch(*args, **kwds) + + def put(self, *args, **kwds): + return self._put(*args, **kwds) diff --git a/test/base/interactor.py b/test/base/interactor.py index 736527477f5..9ea5ed0318c 100644 --- a/test/base/interactor.py +++ b/test/base/interactor.py @@ -6,7 +6,13 @@ import time from json import dumps from logging import getLogger -from requests import delete, get, patch, post +from requests import ( + delete, + get, + patch, + post, + put, +) from six import StringIO, text_type from galaxy import util @@ -479,6 +485,17 @@ class GalaxyInteractorApi(object): params = {} return patch("%s/%s" % (self.api_url, path), params=params, data=data) + def _put(self, path, data={}, key=None, admin=False, anon=False): + if not anon: + if not key: + key = self.api_key if not admin else self.master_api_key + params = dict(key=key) + data = data.copy() + data['key'] = key + else: + params = {} + return put("%s/%s" % (self.api_url, path), params=params, data=data) + def _get(self, path, data={}, key=None, admin=False, anon=False): if not anon: if not key: From 009daeff81d60202ac6695304e40599dd1d4085c Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sat, 30 Sep 2017 19:28:25 +0200 Subject: [PATCH 08/51] Fix the wildcard search --- lib/galaxy/managers/jobs.py | 27 +++++++++++++-------------- test/api/test_jobs.py | 9 +++++---- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py index ed1e3fceae0..3a84a24ef54 100644 --- a/lib/galaxy/managers/jobs.py +++ b/lib/galaxy/managers/jobs.py @@ -53,7 +53,7 @@ class JobSearch(object): """Search for jobs producing same results using the 'inputs' part of a tool POST.""" user = trans.user input_data = defaultdict(list) - input_ids = defaultdict(dict) + input_ids = defaultdict(list) def populate_input_data_input_id(path, key, value): """Traverses expanded incoming using remap and collects input_ids and input_data.""" @@ -70,26 +70,25 @@ class JobSearch(object): if isinstance(current_case, (list, dict)): current_case = current_case[p] identifier = getattr(current_case, "element_identifier", None) - name = current_case.name input_data[path_key].append({'src': src, 'id': value, 'identifier': identifier, - 'name': name, }) - input_ids[src][value] = "__id_wildcard__" - return key, value + input_ids[src].append(value) + return key, "__id_wildcard__" return key, value - remap(param_dump, visit=populate_input_data_input_id) + wildcard_param_dump = remap(param_dump, visit=populate_input_data_input_id) return self.__search(tool_id=tool_id, tool_version=tool_version, user=user, input_data=input_data, job_state=job_state, param_dump=param_dump, + wildcard_param_dump=wildcard_param_dump, input_ids=input_ids) - def __search(self, tool_id, tool_version, user, input_data, input_ids=None, job_state=None, param_dump=None): + def __search(self, tool_id, tool_version, user, input_data, input_ids=None, job_state=None, param_dump=None, wildcard_param_dump=None): search_timer = ExecutionTimer() query = self.sa_session.query(model.Job).filter( model.Job.tool_id == tool_id, @@ -139,10 +138,10 @@ class JobSearch(object): # if we know that the input dataset hasn't changed since the job was run. # This is relatively strict, we may be able to lift this requirement if we record the jobs' # relevant parameters as JobParameters in the database - b.update_time < model.Job.create_time, + # b.update_time < model.Job.create_time, b.name == c.name, b.extension == c.extension, - b._metadata == c._metadata, + #b._metadata == c._metadata, or_(b.deleted == false(), c.deleted == false()) )) if identifier: @@ -160,29 +159,29 @@ class JobSearch(object): a = aliased(model.JobToInputDatasetCollectionAssociation) b = aliased(model.HistoryDatasetCollectionAssociation) c = aliased(model.HistoryDatasetCollectionAssociation) - name = type_values['name'] query = query.filter(and_( model.Job.id == a.job_id, a.name == k, b.id == a.dataset_collection_id, c.id == v, - or_(and_(b.deleted == false(), b.id == v, b.name == name), + b.name == c.name, + or_(and_(b.deleted == false(), b.id == v), and_(or_(c.copied_from_history_dataset_collection_association_id == b.id, b.copied_from_history_dataset_collection_association_id == c.id), c.deleted == false(), - c.name == name, ) ) )) else: return [] - for k, v in param_dump.items(): + for k, v in wildcard_param_dump.items(): + test = json.dumps(v).replace('"id": "__id_wildcard__"', '"id": %') a = aliased(model.JobParameter) query = query.filter(and_( model.Job.id == a.job_id, a.name == k, - a.value.like(json.dumps(v).replace('id: "__id_wildcard__"', 'id: %')) + a.value.like(test) )) for job in query.all(): diff --git a/test/api/test_jobs.py b/test/api/test_jobs.py index 805a91f34d1..eb90d01323c 100644 --- a/test/api/test_jobs.py +++ b/test/api/test_jobs.py @@ -274,15 +274,16 @@ class JobsApiTestCase(api.ApiTestCase): def test_search(self): history_id, dataset_id = self.__history_with_ok_dataset() + # We first copy the datasets, so that the update time is lower than the job creation time + new_history_id = self.dataset_populator.new_history() + copy_payload = {"content": dataset_id, "source": "hda", "type": "dataset"} + copy_response = self._post("histories/%s/contents" % new_history_id, data=copy_payload) + self._assert_status_code_is(copy_response, 200) inputs = json.dumps({ 'input1': {'src': 'hda', 'id': dataset_id} }) self._job_search(tool_id='cat1', history_id=history_id, inputs=inputs) # We test that a job can be found even if the dataset has been copied to another history - new_history_id = self.dataset_populator.new_history() - copy_payload = {"content": dataset_id, "source": "hda", "type": "dataset"} - copy_response = self._post("histories/%s/contents" % new_history_id, data=copy_payload) - self._assert_status_code_is(copy_response, 200) new_dataset_id = copy_response.json()['id'] copied_inputs = json.dumps({ 'input1': {'src': 'hda', 'id': new_dataset_id} From e75daa1ddd6a5662bf9d837ecc867655c485a8cb Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Mon, 2 Oct 2017 10:55:09 +0200 Subject: [PATCH 09/51] Skip test that deletes the jobs input dataset This does not work at the moment, since we need to verify a jobs' input name, dataset and metadata, for which we don't have an explicit trail. The only trace is via the update_time (i.e if a input dataset hasn't been updated since the job was created we know these were the actual parameters in use during the execution of this job). --- lib/galaxy/managers/jobs.py | 4 ++-- test/api/test_jobs.py | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py index 3a84a24ef54..1b971f28afc 100644 --- a/lib/galaxy/managers/jobs.py +++ b/lib/galaxy/managers/jobs.py @@ -138,10 +138,10 @@ class JobSearch(object): # if we know that the input dataset hasn't changed since the job was run. # This is relatively strict, we may be able to lift this requirement if we record the jobs' # relevant parameters as JobParameters in the database - # b.update_time < model.Job.create_time, + b.update_time < model.Job.create_time, b.name == c.name, b.extension == c.extension, - #b._metadata == c._metadata, + b.metadata == c.metadata, or_(b.deleted == false(), c.deleted == false()) )) if identifier: diff --git a/test/api/test_jobs.py b/test/api/test_jobs.py index eb90d01323c..83e0c36a6b5 100644 --- a/test/api/test_jobs.py +++ b/test/api/test_jobs.py @@ -290,14 +290,17 @@ class JobsApiTestCase(api.ApiTestCase): }) search_payload = self._search_payload(history_id=history_id, tool_id='cat1', inputs=copied_inputs) self._search(search_payload, expected_search_count=1) - # Now we delete the original input HDA that was used -- we should still be able to find the job - delete_respone = self._delete("histories/%s/contents/%s" % (history_id, dataset_id)) - self._assert_status_code_is(delete_respone, 200) - self._search(search_payload, expected_search_count=1) - # Now we also delete the copy -- we shouldn't find a job - delete_respone = self._delete("histories/%s/contents/%s" % (new_history_id, new_dataset_id)) - self._assert_status_code_is(delete_respone, 200) - self._search(search_payload, expected_search_count=0) + # TODO: the commented test does not work, since deleting the original input updates the + # update_time attribute, which we use to check for matching HDA metadata (name, datatype, metadata ...) + # We should either version HDAs, or record these metadata items with the job. + # # Now we delete the original input HDA that was used -- we should still be able to find the job + # delete_respone = self._delete("histories/%s/contents/%s" % (history_id, dataset_id)) + # self._assert_status_code_is(delete_respone, 200) + # self._search(search_payload, expected_search_count=1) + # # Now we also delete the copy -- we shouldn't find a job + # delete_respone = self._delete("histories/%s/contents/%s" % (new_history_id, new_dataset_id)) + # self._assert_status_code_is(delete_respone, 200) + # self._search(search_payload, expected_search_count=0) def test_search_handle_identifiers(self): # Test that input name and element identifier of a jobs' output must match for a job to be returned. From 0df2e18058ecf66322f3a15d42d8735ace37a17b Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Mon, 2 Oct 2017 12:40:09 +0200 Subject: [PATCH 10/51] Add mechanism for versioning database objects --- lib/galaxy/model/base.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/galaxy/model/base.py b/lib/galaxy/model/base.py index 01e834619da..016e201f24c 100644 --- a/lib/galaxy/model/base.py +++ b/lib/galaxy/model/base.py @@ -7,6 +7,7 @@ from inspect import ( isclass ) +from sqlalchemy import event from sqlalchemy.orm import ( scoped_session, sessionmaker @@ -20,7 +21,8 @@ class ModelMapping(Bunch): def __init__(self, model_modules, engine): self.engine = engine - context = scoped_session(sessionmaker(autoflush=False, autocommit=True)) + Session = sessionmaker(autoflush=False, autocommit=True) + context = scoped_session(Session) # For backward compatibility with "context.current" # deprecated? context.current = context @@ -44,3 +46,18 @@ class ModelMapping(Bunch): For backward compat., deprecated. """ return self.context + + +def versioned_objects(iter): + for obj in iter: + if hasattr(obj, '__create_version__'): + yield obj + + +def versioned_session(session): + @event.listens_for(session, 'before_flush') + def before_flush(session, flush_context, instances): + for obj in versioned_objects(session.dirty): + obj.__create_version__(session) + for obj in versioned_objects(session.deleted): + obj.__create_version__(session, deleted=True) From 409fa016b9651485d7648437b1f0b8766b4b0c90 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Tue, 3 Oct 2017 15:52:21 +0200 Subject: [PATCH 11/51] Add HistoryDatasetAssociationHistory to track mutations of HDAs --- lib/galaxy/managers/jobs.py | 17 +++++++--- lib/galaxy/model/__init__.py | 66 ++++++++++++++++++++++++++++++++++-- lib/galaxy/model/base.py | 1 + lib/galaxy/model/mapping.py | 18 ++++++++++ 4 files changed, 96 insertions(+), 6 deletions(-) diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py index 1b971f28afc..bdf467e4aa2 100644 --- a/lib/galaxy/managers/jobs.py +++ b/lib/galaxy/managers/jobs.py @@ -128,6 +128,7 @@ class JobSearch(object): b = aliased(model.HistoryDatasetAssociation) c = aliased(model.HistoryDatasetAssociation) d = aliased(model.JobParameter) + e = aliased(model.HistoryDatasetAssociationHistory) query = query.filter(and_( model.Job.id == a.job_id, a.name == k, @@ -138,10 +139,18 @@ class JobSearch(object): # if we know that the input dataset hasn't changed since the job was run. # This is relatively strict, we may be able to lift this requirement if we record the jobs' # relevant parameters as JobParameters in the database - b.update_time < model.Job.create_time, - b.name == c.name, - b.extension == c.extension, - b.metadata == c.metadata, + or_(and_( + b.update_time < model.Job.create_time, + b.name == c.name, + b.extension == c.extension, + b._metadata == c._metadata, + ), and_( + b.id == e.history_dataset_association_id, + a.dataset_version == e.version, + e.name == c.name, + e.extension == c.extension, + e.metadata == c._metadata, + )), or_(b.deleted == false(), c.deleted == false()) )) if identifier: diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 6360e5ca1a3..961c529c996 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -16,11 +16,21 @@ from string import Template from uuid import UUID, uuid4 from six import string_types -from sqlalchemy import (and_, func, join, not_, or_, select, true, type_coerce, - types) +from sqlalchemy import ( + and_, + func, + inspect, + join, + not_, + or_, + select, + true, + type_coerce, + types) from sqlalchemy.ext import hybrid from sqlalchemy.orm import aliased, joinedload, object_session + import galaxy.model.metadata import galaxy.model.orm.now import galaxy.security.passwords @@ -1000,6 +1010,7 @@ class JobToInputDatasetAssociation(object): def __init__(self, name, dataset): self.name = name self.dataset = dataset + self.dataset_version = dataset.version class JobToOutputDatasetAssociation(object): @@ -2401,6 +2412,36 @@ class HistoryDatasetAssociation(DatasetInstance, HasTags, Dictifiable, UsesAnnot self.copied_from_history_dataset_association = copied_from_history_dataset_association self.copied_from_library_dataset_dataset_association = copied_from_library_dataset_dataset_association + def __create_version__(self, session): + state = inspect(self) + changes = {} + + for attr in state.attrs: + hist = state.get_history(attr.key, True) + + if not hist.has_changes(): + continue + + # hist.deleted holds old value(s) + changes[attr.key] = hist.deleted + if self.update_time: + # We only record changes to HDAs that exist in the database and have a update_time + new_values = {} + new_values['name'] = changes.get('name', self.name) + new_values['dbkey'] = changes.get('dbkey', self.dbkey) + new_values['extension'] = changes.get('extension', self.extension) + new_values['extended_metadata_id'] = changes.get('extended_metadata_id', self.extended_metadata_id) + for k, v in new_values.items(): + if isinstance(v, list): + new_values[k] = v[0] + new_values['update_time'] = self.update_time + new_values['version'] = self.version or 1 + new_values['metadata'] = self._metadata + past_hda = HistoryDatasetAssociationHistory(history_dataset_association_id=self.id, + **new_values) + self.version = self.version + 1 if self.version else 1 + session.add(past_hda) + def copy(self, parent_id=None): """ Create a copy of this HDA. @@ -2616,6 +2657,27 @@ class HistoryDatasetAssociation(DatasetInstance, HasTags, Dictifiable, UsesAnnot self.tags.append(new_tag_assoc) +class HistoryDatasetAssociationHistory(object): + def __init__(self, + history_dataset_association_id, + name, + dbkey, + update_time, + version, + extension, + extended_metadata_id, + metadata, + ): + self.history_dataset_association_id = history_dataset_association_id + self.name = name + self.dbkey = dbkey + self.update_time = update_time + self.version = version + self.extension = extension + self.extended_metadata_id = extended_metadata_id + self.metadata = metadata + + class HistoryDatasetAssociationDisplayAtAuthorization(object): def __init__(self, hda=None, user=None, site=None): self.history_dataset_association = hda diff --git a/lib/galaxy/model/base.py b/lib/galaxy/model/base.py index 016e201f24c..016fdc80291 100644 --- a/lib/galaxy/model/base.py +++ b/lib/galaxy/model/base.py @@ -22,6 +22,7 @@ class ModelMapping(Bunch): def __init__(self, model_modules, engine): self.engine = engine Session = sessionmaker(autoflush=False, autocommit=True) + versioned_session(Session) context = scoped_session(Session) # For backward compatibility with "context.current" # deprecated? diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index f9667a327a4..15536718218 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -142,11 +142,26 @@ model.HistoryDatasetAssociation.table = Table( Column("deleted", Boolean, index=True, default=False), Column("visible", Boolean), Column("extended_metadata_id", Integer, ForeignKey("extended_metadata.id"), index=True), + Column("version", Integer, default=1, nullable=True, index=True), Column("hid", Integer), Column("purged", Boolean, index=True, default=False), Column("hidden_beneath_collection_instance_id", ForeignKey("history_dataset_collection_association.id"), nullable=True)) + +model.HistoryDatasetAssociationHistory.table = Table( + "history_dataset_association_history", metadata, + Column("id", Integer, primary_key=True), + Column("history_dataset_association_id", Integer, ForeignKey("history_dataset_association.id"), index=True), + Column("update_time", DateTime, default=now), + Column("version", Integer), + Column("name", TrimmedString(255)), + Column("extension", TrimmedString(64)), + Column("metadata", MetadataType()), + Column("extended_metadata_id", Integer, ForeignKey("extended_metadata.id"), index=True), +) + + model.Dataset.table = Table( "dataset", metadata, Column("id", Integer, primary_key=True), @@ -505,6 +520,7 @@ model.JobToInputDatasetAssociation.table = Table( Column("id", Integer, primary_key=True), Column("job_id", Integer, ForeignKey("job.id"), index=True), Column("dataset_id", Integer, ForeignKey("history_dataset_association.id"), index=True), + Column("dataset_version", Integer), Column("name", String(255))) model.JobToOutputDatasetAssociation.table = Table( @@ -1475,6 +1491,8 @@ simple_mapping(model.Dataset, backref='datasets') ) +mapper(model.HistoryDatasetAssociationHistory, model.HistoryDatasetAssociationHistory.table) + mapper(model.HistoryDatasetAssociationDisplayAtAuthorization, model.HistoryDatasetAssociationDisplayAtAuthorization.table, properties=dict( history_dataset_association=relation(model.HistoryDatasetAssociation), user=relation(model.User) From c88d8826d0960af5067830f121d3a54622386aba Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Fri, 6 Oct 2017 11:33:53 +0200 Subject: [PATCH 12/51] Compare metadata through _metadata for HDAH objects HistoryDatasetsAssoicationHistory objects don't have the full blown metadata objects, instead we compare the raw JSON dumped into the database. --- lib/galaxy/managers/jobs.py | 14 +++++++------- lib/galaxy/model/__init__.py | 7 ++++++- lib/galaxy/model/mapping.py | 2 +- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py index bdf467e4aa2..e8fb5b44a52 100644 --- a/lib/galaxy/managers/jobs.py +++ b/lib/galaxy/managers/jobs.py @@ -135,22 +135,22 @@ class JobSearch(object): a.dataset_id == b.id, c.dataset_id == b.dataset_id, c.id == v, # c is the requested job input HDA - # We can only compare input dataset names and metadata for a job - # if we know that the input dataset hasn't changed since the job was run. - # This is relatively strict, we may be able to lift this requirement if we record the jobs' - # relevant parameters as JobParameters in the database + # We can compare input dataset names and metadata for a job + # if we know that the input dataset hasn't changed since the job was run, + # or if the job recorded a dataset_version and name and metadata of the current + # job request matches those that were recorded for the job in question (introduced in release 18.01) or_(and_( b.update_time < model.Job.create_time, b.name == c.name, b.extension == c.extension, - b._metadata == c._metadata, + b.metadata == c.metadata, ), and_( b.id == e.history_dataset_association_id, a.dataset_version == e.version, e.name == c.name, e.extension == c.extension, - e.metadata == c._metadata, - )), + e._metadata == c._metadata, + )), or_(b.deleted == false(), c.deleted == false()) )) if identifier: diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 961c529c996..9c942befbd9 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -2675,7 +2675,12 @@ class HistoryDatasetAssociationHistory(object): self.version = version self.extension = extension self.extended_metadata_id = extended_metadata_id - self.metadata = metadata + if isinstance(metadata['dbkey'], list): + # The dbkey may or may not be stored in a list. + # To facilitate comparison we use the raw value + metadata = metadata.copy() + metadata['dbkey'] = metadata['dbkey'][0] + self._metadata = metadata class HistoryDatasetAssociationDisplayAtAuthorization(object): diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index 15536718218..bbb5b0cb764 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -157,7 +157,7 @@ model.HistoryDatasetAssociationHistory.table = Table( Column("version", Integer), Column("name", TrimmedString(255)), Column("extension", TrimmedString(64)), - Column("metadata", MetadataType()), + Column("metadata", MetadataType(), key="_metadata"), Column("extended_metadata_id", Integer, ForeignKey("extended_metadata.id"), index=True), ) From be09b35ff2da39c97836fe5e61fc0fbefd45cb06 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Fri, 6 Oct 2017 11:34:43 +0200 Subject: [PATCH 13/51] Avoid storing dbkey as list in metadata --- lib/galaxy/model/__init__.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 9c942befbd9..0dfce217f0f 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -1975,7 +1975,7 @@ class DatasetInstance(object): self.metadata = metadata or dict() self.extended_metadata = extended_metadata if dbkey: # dbkey is stored in metadata, only set if non-zero, or else we could clobber one supplied by input 'metadata' - self.dbkey = dbkey + self._metadata['dbkey'] = dbkey self.deleted = deleted self.visible = visible # Relationships @@ -2059,8 +2059,8 @@ class DatasetInstance(object): def set_dbkey(self, value): if "dbkey" in self.datatype.metadata_spec: - if not isinstance(value, list): - self.metadata.dbkey = [value] + if isinstance(value, list): + self.metadata.dbkey = value[0] else: self.metadata.dbkey = value dbkey = property(get_dbkey, set_dbkey) @@ -2675,7 +2675,8 @@ class HistoryDatasetAssociationHistory(object): self.version = version self.extension = extension self.extended_metadata_id = extended_metadata_id - if isinstance(metadata['dbkey'], list): + dbkey = metadata.get('dbkey') + if dbkey and isinstance(dbkey, list): # The dbkey may or may not be stored in a list. # To facilitate comparison we use the raw value metadata = metadata.copy() From 91696783b185ca119499b836623a8954ce3a5d46 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Fri, 6 Oct 2017 12:41:39 +0200 Subject: [PATCH 14/51] Uncomment test that relies on HDAHistory --- test/api/test_jobs.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/test/api/test_jobs.py b/test/api/test_jobs.py index 83e0c36a6b5..f876571725d 100644 --- a/test/api/test_jobs.py +++ b/test/api/test_jobs.py @@ -290,17 +290,14 @@ class JobsApiTestCase(api.ApiTestCase): }) search_payload = self._search_payload(history_id=history_id, tool_id='cat1', inputs=copied_inputs) self._search(search_payload, expected_search_count=1) - # TODO: the commented test does not work, since deleting the original input updates the - # update_time attribute, which we use to check for matching HDA metadata (name, datatype, metadata ...) - # We should either version HDAs, or record these metadata items with the job. - # # Now we delete the original input HDA that was used -- we should still be able to find the job - # delete_respone = self._delete("histories/%s/contents/%s" % (history_id, dataset_id)) - # self._assert_status_code_is(delete_respone, 200) - # self._search(search_payload, expected_search_count=1) - # # Now we also delete the copy -- we shouldn't find a job - # delete_respone = self._delete("histories/%s/contents/%s" % (new_history_id, new_dataset_id)) - # self._assert_status_code_is(delete_respone, 200) - # self._search(search_payload, expected_search_count=0) + # Now we delete the original input HDA that was used -- we should still be able to find the job + delete_respone = self._delete("histories/%s/contents/%s" % (history_id, dataset_id)) + self._assert_status_code_is(delete_respone, 200) + self._search(search_payload, expected_search_count=1) + # Now we also delete the copy -- we shouldn't find a job + delete_respone = self._delete("histories/%s/contents/%s" % (new_history_id, new_dataset_id)) + self._assert_status_code_is(delete_respone, 200) + self._search(search_payload, expected_search_count=0) def test_search_handle_identifiers(self): # Test that input name and element identifier of a jobs' output must match for a job to be returned. @@ -430,7 +427,7 @@ class JobsApiTestCase(api.ApiTestCase): def _search(self, payload, expected_search_count=1): # in case job and history aren't updated at exactly the same # time give time to wait - for i in range(15): + for i in range(5): search_count = self._search_count(payload) if search_count == expected_search_count: break From 255e3989b57dd90774d5219a97083cca92ed8678 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Fri, 6 Oct 2017 13:21:41 +0200 Subject: [PATCH 15/51] Add migration scripts --- .../migrate/versions/0137_add_hda_version.py | 40 ++++++++++++++++ ...story_dataset_association_history_table.py | 48 +++++++++++++++++++ ..._job_to_input_dataset_association_table.py | 40 ++++++++++++++++ 3 files changed, 128 insertions(+) create mode 100644 lib/galaxy/model/migrate/versions/0137_add_hda_version.py create mode 100644 lib/galaxy/model/migrate/versions/0138_add_history_dataset_association_history_table.py create mode 100644 lib/galaxy/model/migrate/versions/0139_add_dataset_version_to_job_to_input_dataset_association_table.py diff --git a/lib/galaxy/model/migrate/versions/0137_add_hda_version.py b/lib/galaxy/model/migrate/versions/0137_add_hda_version.py new file mode 100644 index 00000000000..9532e5ea25d --- /dev/null +++ b/lib/galaxy/model/migrate/versions/0137_add_hda_version.py @@ -0,0 +1,40 @@ +""" +Add copied_from_job_id column to jobs table +""" +from __future__ import print_function + +import logging + +from sqlalchemy import Column, Integer, MetaData, Table + +log = logging.getLogger(__name__) +version_column = Column("version", Integer, default=1) + + +def upgrade(migrate_engine): + print(__doc__) + metadata = MetaData() + metadata.bind = migrate_engine + metadata.reflect() + + # Add the version column to the history_dataset_association table + try: + hda_table = Table("history_dataset_association", metadata, autoload=True) + version_column.create(hda_table) + assert version_column is hda_table.c.version + except Exception: + log.exception("Adding column 'copied_from_job_id_column' to job table failed.") + + +def downgrade(migrate_engine): + metadata = MetaData() + metadata.bind = migrate_engine + metadata.reflect() + + # Drop the history_dataset_association table's version column. + try: + hda_table = Table("history_dataset_association", metadata, autoload=True) + version_column = hda_table.c.version + version_column.drop() + except Exception: + log.exception("Dropping 'copied_from_job_id_column' column from job table failed.") diff --git a/lib/galaxy/model/migrate/versions/0138_add_history_dataset_association_history_table.py b/lib/galaxy/model/migrate/versions/0138_add_history_dataset_association_history_table.py new file mode 100644 index 00000000000..fe5b5a7b4bc --- /dev/null +++ b/lib/galaxy/model/migrate/versions/0138_add_history_dataset_association_history_table.py @@ -0,0 +1,48 @@ +""" +Migration script to add the history_dataset_association_history table and data_manager_job_association. +""" +from __future__ import print_function + +import datetime +import logging + +from sqlalchemy import Column, DateTime, ForeignKey, Integer, MetaData, Table +from galaxy.model.custom_types import MetadataType, TrimmedString + +now = datetime.datetime.utcnow +log = logging.getLogger(__name__) +log.setLevel(logging.DEBUG) +metadata = MetaData() + +HistoryDatasetAssociationHistory_table = Table( + "history_dataset_association_history", metadata, + Column("id", Integer, primary_key=True), + Column("history_dataset_association_id", Integer, ForeignKey("history_dataset_association.id"), index=True), + Column("update_time", DateTime, default=now), + Column("version", Integer, index=True), + Column("name", TrimmedString(255)), + Column("extension", TrimmedString(64)), + Column("metadata", MetadataType(), key='_metadata'), + Column("extended_metadata_id", Integer, ForeignKey("extended_metadata.id"), index=True), +) + + +def upgrade(migrate_engine): + print(__doc__) + metadata.bind = migrate_engine + metadata.reflect() + try: + HistoryDatasetAssociationHistory_table.create() + log.debug("Created history_dataset_association_history table") + except Exception: + log.exception("Creating data_manager_history_association table failed.") + + +def downgrade(migrate_engine): + metadata.bind = migrate_engine + metadata.reflect() + try: + HistoryDatasetAssociationHistory_table.drop() + log.debug("Dropped history_dataset_association_history table") + except Exception: + log.exception("Dropping data_manager_history_association table failed.") diff --git a/lib/galaxy/model/migrate/versions/0139_add_dataset_version_to_job_to_input_dataset_association_table.py b/lib/galaxy/model/migrate/versions/0139_add_dataset_version_to_job_to_input_dataset_association_table.py new file mode 100644 index 00000000000..e4b8276ffa1 --- /dev/null +++ b/lib/galaxy/model/migrate/versions/0139_add_dataset_version_to_job_to_input_dataset_association_table.py @@ -0,0 +1,40 @@ +""" +Add dataset_version column to job_to_input_dataset table +""" +from __future__ import print_function + +import logging + +from sqlalchemy import Column, Integer, MetaData, Table + +log = logging.getLogger(__name__) +dataset_version_column = Column("dataset_version", Integer) + + +def upgrade(migrate_engine): + print(__doc__) + metadata = MetaData() + metadata.bind = migrate_engine + metadata.reflect() + + # Add the version column to the job_to_input_dataset table + try: + job_to_input_dataset_table = Table("job_to_input_dataset", metadata, autoload=True) + dataset_version_column.create(job_to_input_dataset_table) + assert dataset_version_column is job_to_input_dataset_table.c.dataset_version + except Exception: + log.exception("Adding column 'dataset_history_id' to job_to_input_dataset table failed.") + + +def downgrade(migrate_engine): + metadata = MetaData() + metadata.bind = migrate_engine + metadata.reflect() + + # Drop the job_to_input_dataset table's version column. + try: + job_to_input_dataset_table = Table("job_to_input_dataset", metadata, autoload=True) + dataset_version_column = job_to_input_dataset_table.c.dataset_version + dataset_version_column.drop() + except Exception: + log.exception("Dropping 'dataset_version' column from job_to_input_dataset table failed.") From 71da068e260ac6584ca9f4a77557a438e0c0f6fb Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sun, 8 Oct 2017 09:07:23 +0200 Subject: [PATCH 16/51] Require state to be OK in order to track version in database --- lib/galaxy/model/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 0dfce217f0f..95d88b810d0 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -2424,7 +2424,7 @@ class HistoryDatasetAssociation(DatasetInstance, HasTags, Dictifiable, UsesAnnot # hist.deleted holds old value(s) changes[attr.key] = hist.deleted - if self.update_time: + if self.update_time and self.state == self.states.OK: # We only record changes to HDAs that exist in the database and have a update_time new_values = {} new_values['name'] = changes.get('name', self.name) From e6c374a05a9114c4004de0c54a0f70b6f0b27405 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Mon, 9 Oct 2017 14:17:36 +0200 Subject: [PATCH 17/51] Fix JobToInputDatasetAssociation recording for tasks (dataset is None) --- lib/galaxy/model/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 95d88b810d0..6c2093ab0fb 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -1010,7 +1010,7 @@ class JobToInputDatasetAssociation(object): def __init__(self, name, dataset): self.name = name self.dataset = dataset - self.dataset_version = dataset.version + self.dataset_version = dataset.version if dataset else None class JobToOutputDatasetAssociation(object): From 6c79785f6f87632d07aafa275e2d3877ddc5895c Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Mon, 16 Oct 2017 10:33:36 +0200 Subject: [PATCH 18/51] Avoid recursion in chained sqlalchemy filter statements See https://stackoverflow.com/questions/41625847/maximum-recursion-depth-exceeded-with-sqlalchemy for details. --- lib/galaxy/managers/jobs.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py index e8fb5b44a52..4c1a30b919b 100644 --- a/lib/galaxy/managers/jobs.py +++ b/lib/galaxy/managers/jobs.py @@ -118,6 +118,7 @@ class JobSearch(object): or_(*o) ) + conditions = [] for k, input_list in input_data.items(): for type_values in input_list: t = type_values['src'] @@ -129,7 +130,7 @@ class JobSearch(object): c = aliased(model.HistoryDatasetAssociation) d = aliased(model.JobParameter) e = aliased(model.HistoryDatasetAssociationHistory) - query = query.filter(and_( + conditions.append(and_( model.Job.id == a.job_id, a.name == k, a.dataset_id == b.id, @@ -154,12 +155,12 @@ class JobSearch(object): or_(b.deleted == false(), c.deleted == false()) )) if identifier: - query = query.filter(model.Job.id == d.job_id, + conditions.append(and_(model.Job.id == d.job_id, d.name == "%s|__identifier__" % k, - d.value == json.dumps(identifier)) + d.value == json.dumps(identifier))) elif t == 'ldda': a = aliased(model.JobToInputLibraryDatasetAssociation) - query = query.filter(and_( + conditions.append(and_( model.Job.id == a.job_id, a.name == k, a.ldda_id == v @@ -168,7 +169,7 @@ class JobSearch(object): a = aliased(model.JobToInputDatasetCollectionAssociation) b = aliased(model.HistoryDatasetCollectionAssociation) c = aliased(model.HistoryDatasetCollectionAssociation) - query = query.filter(and_( + conditions.append(and_( model.Job.id == a.job_id, a.name == k, b.id == a.dataset_collection_id, @@ -187,12 +188,14 @@ class JobSearch(object): for k, v in wildcard_param_dump.items(): test = json.dumps(v).replace('"id": "__id_wildcard__"', '"id": %') a = aliased(model.JobParameter) - query = query.filter(and_( + conditions.append(and_( model.Job.id == a.job_id, a.name == k, a.value.like(test) )) + query = query.filter(and_(*conditions)) + for job in query.all(): # We found a job that is equal in terms of tool_id, user, state and input datasets, # but to be able to verify that the parameters match we need to modify all instances of From 40884fb94f41e6934483a4deee7d05b1e65c049c Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Mon, 16 Oct 2017 14:01:43 +0200 Subject: [PATCH 19/51] Reduce to single, non-chained query statement --- lib/galaxy/managers/jobs.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py index 4c1a30b919b..d4add8ee096 100644 --- a/lib/galaxy/managers/jobs.py +++ b/lib/galaxy/managers/jobs.py @@ -90,15 +90,14 @@ class JobSearch(object): def __search(self, tool_id, tool_version, user, input_data, input_ids=None, job_state=None, param_dump=None, wildcard_param_dump=None): search_timer = ExecutionTimer() - query = self.sa_session.query(model.Job).filter( - model.Job.tool_id == tool_id, - model.Job.user == user - ) + conditions = [and_(model.Job.tool_id == tool_id, + model.Job.user == user)] + if tool_version: - query = query.filter(model.Job.tool_version == str(tool_version)) + conditions.append(model.Job.tool_version == str(tool_version)) if job_state is None: - query = query.filter( + conditions.append( or_( model.Job.state == 'running', model.Job.state == 'queued', @@ -109,16 +108,16 @@ class JobSearch(object): ) else: if isinstance(job_state, string_types): - query = query.filter(model.Job.state == job_state) + conditions.append(model.Job.state == job_state) elif isinstance(job_state, list): o = [] for s in job_state: o.append(model.Job.state == s) - query = query.filter( + conditions.append( or_(*o) ) - conditions = [] + for k, input_list in input_data.items(): for type_values in input_list: t = type_values['src'] @@ -194,7 +193,7 @@ class JobSearch(object): a.value.like(test) )) - query = query.filter(and_(*conditions)) + query = self.sa_session.query(model.Job).filter(and_(*conditions)) for job in query.all(): # We found a job that is equal in terms of tool_id, user, state and input datasets, From fac13deb6b979327664647f0e0f10828ae4adc15 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Mon, 16 Oct 2017 14:43:35 +0200 Subject: [PATCH 20/51] Join for used data input ids This allows us to fetch all used job inputs with a single query as opposed to one query per input dataset. --- lib/galaxy/managers/jobs.py | 72 +++++++++++++------------------------ 1 file changed, 24 insertions(+), 48 deletions(-) diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py index d4add8ee096..2c8b264c1dd 100644 --- a/lib/galaxy/managers/jobs.py +++ b/lib/galaxy/managers/jobs.py @@ -117,11 +117,19 @@ class JobSearch(object): or_(*o) ) - + # We now build the query filters that relate to the input datasets + # that this job uses. We keep track of the requested dataset id in `requested_ids`, + # the type (hda, hdca or lda) in `data_types` + # and the ids that have been used in the job that has already been run in `used_ids`. + requested_ids = [] + data_types = [] + used_ids = [] for k, input_list in input_data.items(): for type_values in input_list: t = type_values['src'] v = type_values['id'] + requested_ids.append(v) + data_types.append(t) identifier = type_values['identifier'] if t == 'hda': a = aliased(model.JobToInputDatasetAssociation) @@ -157,6 +165,7 @@ class JobSearch(object): conditions.append(and_(model.Job.id == d.job_id, d.name == "%s|__identifier__" % k, d.value == json.dumps(identifier))) + used_ids.append(a.dataset_id) elif t == 'ldda': a = aliased(model.JobToInputLibraryDatasetAssociation) conditions.append(and_( @@ -164,6 +173,7 @@ class JobSearch(object): a.name == k, a.ldda_id == v )) + used_ids.append(a.ldda_id) elif t == 'hdca': a = aliased(model.JobToInputDatasetCollectionAssociation) b = aliased(model.HistoryDatasetCollectionAssociation) @@ -181,67 +191,33 @@ class JobSearch(object): ) ) )) + used_ids.append(a.dataset_collection_id) else: return [] for k, v in wildcard_param_dump.items(): - test = json.dumps(v).replace('"id": "__id_wildcard__"', '"id": %') + wildcard_value = json.dumps(v).replace('"id": "__id_wildcard__"', '"id": %') a = aliased(model.JobParameter) conditions.append(and_( model.Job.id == a.job_id, a.name == k, - a.value.like(test) + a.value.like(wildcard_value) )) - query = self.sa_session.query(model.Job).filter(and_(*conditions)) + query = self.sa_session.query(model.Job, *used_ids).filter(and_(*conditions)) for job in query.all(): # We found a job that is equal in terms of tool_id, user, state and input datasets, # but to be able to verify that the parameters match we need to modify all instances of # dataset_ids (HDA, LDDA, HDCA) in the incoming param_dump to point to those used by the # possibly equivalent job, which may have been run on copies of the original input data. - replacement_timer = ExecutionTimer() + job, current_jobs_data_ids = job[0], job[1:] job_input_ids = {} - for src, items in input_ids.items(): - for dataset_id in items: - if src in job_input_ids and dataset_id in job_input_ids[src]: - continue - if src == 'hda': - a = aliased(model.JobToInputDatasetAssociation) - b = aliased(model.HistoryDatasetAssociation) - c = aliased(model.HistoryDatasetAssociation) - - (job_dataset_id,) = self.sa_session.query(b.id).filter( - and_( - a.job_id == job.id, - b.id == a.dataset_id, - c.dataset_id == b.dataset_id, - c.id == dataset_id - ) - ).first() - elif src == 'hdca': - a = aliased(model.JobToInputDatasetCollectionAssociation) - b = aliased(model.HistoryDatasetCollectionAssociation) - c = aliased(model.HistoryDatasetCollectionAssociation) - - (job_dataset_id,) = self.sa_session.query(b.id).filter( - and_( - a.job_id == job.id, - b.id == a.dataset_collection_id, - c.id == dataset_id, - or_(b.id == c.id, or_(c.copied_from_history_dataset_collection_association_id == b.id, - b.copied_from_history_dataset_collection_association_id == c.id) - ) - ) - ).first() - elif src == 'ldda': - job_dataset_id = dataset_id - else: - return [] - if src not in job_input_ids: - job_input_ids[src] = {dataset_id: job_dataset_id} - else: - job_input_ids[src][dataset_id] = job_dataset_id + for src, requested_id, used_id in zip(data_types, requested_ids, current_jobs_data_ids): + if src not in job_input_ids: + job_input_ids[src] = {requested_id: used_id} + else: + job_input_ids[src][requested_id] = used_id def replace_dataset_ids(path, key, value): """Exchanges dataset_ids (HDA, LDA, HDCA, not Dataset) in param_dump with dataset ids used in job.""" @@ -255,17 +231,17 @@ class JobSearch(object): return key, value new_param_dump = remap(param_dump, visit=replace_dataset_ids) - log.info("Parameter replacement finished %s", replacement_timer) # new_param_dump has its dataset ids remapped to those used by the job. # We now ask if the remapped job parameters match the current job. - query = self.sa_session.query(model.Job).filter(model.Job.id == job.id) + job_parameter_conditions = [model.Job.id == job.id] for k, v in new_param_dump.items(): a = aliased(model.JobParameter) - query = query.filter(and_( + job_parameter_conditions.append(and_( a.job_id == job.id, a.name == k, a.value == json.dumps(v) )) + query = self.sa_session.query(model.Job).filter(*job_parameter_conditions) if query.first() is None: continue n_parameters = 0 From 70f0ec7930e256f0e93f91db07e03cea5e64f03c Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Mon, 16 Oct 2017 15:51:00 +0200 Subject: [PATCH 21/51] Fix search for tools without data inputs --- lib/galaxy/managers/jobs.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py index 2c8b264c1dd..565a146fa26 100644 --- a/lib/galaxy/managers/jobs.py +++ b/lib/galaxy/managers/jobs.py @@ -211,8 +211,10 @@ class JobSearch(object): # but to be able to verify that the parameters match we need to modify all instances of # dataset_ids (HDA, LDDA, HDCA) in the incoming param_dump to point to those used by the # possibly equivalent job, which may have been run on copies of the original input data. - job, current_jobs_data_ids = job[0], job[1:] job_input_ids = {} + current_jobs_data_ids = [] + if isinstance(job, tuple): + job, current_jobs_data_ids = job[0], job[1:] for src, requested_id, used_id in zip(data_types, requested_ids, current_jobs_data_ids): if src not in job_input_ids: job_input_ids[src] = {requested_id: used_id} From de75540182833732ddcd7e3a8981eb2c6beb80bb Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Mon, 16 Oct 2017 17:29:02 +0200 Subject: [PATCH 22/51] Skip second JobParameter filter if no input datasets have been used --- lib/galaxy/managers/jobs.py | 71 +++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py index 565a146fa26..d7215aad4ce 100644 --- a/lib/galaxy/managers/jobs.py +++ b/lib/galaxy/managers/jobs.py @@ -90,6 +90,18 @@ class JobSearch(object): def __search(self, tool_id, tool_version, user, input_data, input_ids=None, job_state=None, param_dump=None, wildcard_param_dump=None): search_timer = ExecutionTimer() + + def replace_dataset_ids(path, key, value): + """Exchanges dataset_ids (HDA, LDA, HDCA, not Dataset) in param_dump with dataset ids used in job.""" + if key == 'id': + current_case = param_dump + for p in path: + current_case = current_case[p] + src = current_case['src'] + value = job_input_ids[src][value] + return key, value + return key, value + conditions = [and_(model.Job.tool_id == tool_id, model.Job.user == user)] @@ -205,7 +217,6 @@ class JobSearch(object): )) query = self.sa_session.query(model.Job, *used_ids).filter(and_(*conditions)) - for job in query.all(): # We found a job that is equal in terms of tool_id, user, state and input datasets, # but to be able to verify that the parameters match we need to modify all instances of @@ -214,38 +225,27 @@ class JobSearch(object): job_input_ids = {} current_jobs_data_ids = [] if isinstance(job, tuple): + # If there are any input datasets job will be a tuple job, current_jobs_data_ids = job[0], job[1:] - for src, requested_id, used_id in zip(data_types, requested_ids, current_jobs_data_ids): - if src not in job_input_ids: - job_input_ids[src] = {requested_id: used_id} - else: - job_input_ids[src][requested_id] = used_id - - def replace_dataset_ids(path, key, value): - """Exchanges dataset_ids (HDA, LDA, HDCA, not Dataset) in param_dump with dataset ids used in job.""" - if key == 'id': - current_case = param_dump - for p in path: - current_case = current_case[p] - src = current_case['src'] - value = job_input_ids[src][value] - return key, value - return key, value - - new_param_dump = remap(param_dump, visit=replace_dataset_ids) - # new_param_dump has its dataset ids remapped to those used by the job. - # We now ask if the remapped job parameters match the current job. - job_parameter_conditions = [model.Job.id == job.id] - for k, v in new_param_dump.items(): - a = aliased(model.JobParameter) - job_parameter_conditions.append(and_( - a.job_id == job.id, - a.name == k, - a.value == json.dumps(v) - )) - query = self.sa_session.query(model.Job).filter(*job_parameter_conditions) - if query.first() is None: - continue + for src, requested_id, used_id in zip(data_types, requested_ids, current_jobs_data_ids): + if src not in job_input_ids: + job_input_ids[src] = {requested_id: used_id} + else: + job_input_ids[src][requested_id] = used_id + new_param_dump = remap(param_dump, visit=replace_dataset_ids) + # new_param_dump has its dataset ids remapped to those used by the job. + # We now ask if the remapped job parameters match the current job. + job_parameter_conditions = [model.Job.id == job.id] + for k, v in new_param_dump.items(): + a = aliased(model.JobParameter) + job_parameter_conditions.append(and_( + a.job_id == job.id, + a.name == k, + a.value == json.dumps(v) + )) + query = self.sa_session.query(model.Job).filter(*job_parameter_conditions) + if query.first() is None: + continue n_parameters = 0 # Verify that equivalent jobs had the same number of job parameters # We skip chrominfo, dbkey, __workflow_invocation_uuid__ and identifer @@ -256,13 +256,13 @@ class JobSearch(object): if parameter.name in {'__workflow_invocation_uuid__', 'chromInfo', 'dbkey'} or parameter.name.endswith('|__identifier__'): continue n_parameters += 1 - if not n_parameters == len(new_param_dump): + if not n_parameters == len(param_dump): continue # check to make sure none of the output datasets or collections have been deleted # TODO: refactors this into the initial job query outputs_deleted = False - for hda in job.output_datasets: - if hda.dataset.deleted: + for job_to_output_dataset_association in job.output_datasets: + if job_to_output_dataset_association.dataset.deleted: outputs_deleted = True break if not outputs_deleted: @@ -273,6 +273,7 @@ class JobSearch(object): if not outputs_deleted: log.info("Searching jobs finished %s", search_timer) return job + log.info("Searching jobs finished %s", search_timer) return None From 732543ea3bbf49ef0a904d63b7fa61ae4efad0ea Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Tue, 17 Oct 2017 12:16:53 +0200 Subject: [PATCH 23/51] Move check for deleted output datasets to Job relationship properties --- lib/galaxy/managers/jobs.py | 22 +++++++--------------- lib/galaxy/model/mapping.py | 17 ++++++++++++++++- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py index d7215aad4ce..9089d0c852b 100644 --- a/lib/galaxy/managers/jobs.py +++ b/lib/galaxy/managers/jobs.py @@ -216,6 +216,11 @@ class JobSearch(object): a.value.like(wildcard_value) )) + conditions.append(and_( + model.Job.any_output_dataset_collection_instances_deleted == false(), + model.Job.any_output_dataset_deleted == false() + )) + query = self.sa_session.query(model.Job, *used_ids).filter(and_(*conditions)) for job in query.all(): # We found a job that is equal in terms of tool_id, user, state and input datasets, @@ -258,21 +263,8 @@ class JobSearch(object): n_parameters += 1 if not n_parameters == len(param_dump): continue - # check to make sure none of the output datasets or collections have been deleted - # TODO: refactors this into the initial job query - outputs_deleted = False - for job_to_output_dataset_association in job.output_datasets: - if job_to_output_dataset_association.dataset.deleted: - outputs_deleted = True - break - if not outputs_deleted: - for collection_instance in job.output_dataset_collection_instances: - if collection_instance.dataset_collection_instance.deleted: - outputs_deleted = True - break - if not outputs_deleted: - log.info("Searching jobs finished %s", search_timer) - return job + log.info("Searching jobs finished %s", search_timer) + return job log.info("Searching jobs finished %s", search_timer) return None diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index bbb5b0cb764..1ff335bf089 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -29,8 +29,9 @@ from sqlalchemy import ( ) from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.ext.orderinglist import ordering_list -from sqlalchemy.orm import backref, class_mapper, deferred, mapper, object_session, relation +from sqlalchemy.orm import backref, class_mapper, deferred, mapper, object_session, relation, column_property from sqlalchemy.orm.collections import attribute_mapped_collection +from sqlalchemy.sql import exists from sqlalchemy.types import BigInteger from galaxy import model @@ -1984,6 +1985,20 @@ mapper(model.Job, model.Job.table, properties=dict( input_datasets=relation(model.JobToInputDatasetAssociation), input_dataset_collections=relation(model.JobToInputDatasetCollectionAssociation, lazy=True), output_datasets=relation(model.JobToOutputDatasetAssociation, lazy=True), + any_output_dataset_deleted=column_property( + exists([model.HistoryDatasetAssociation], + and_(model.Job.table.c.id == model.JobToOutputDatasetAssociation.table.c.job_id, + model.HistoryDatasetAssociation.table.c.id == model.JobToOutputDatasetAssociation.table.c.dataset_id, + model.HistoryDatasetAssociation.table.c.deleted == true()) + ) + ), + any_output_dataset_collection_instances_deleted=column_property( + exists([model.HistoryDatasetCollectionAssociation.table.c.id], + and_(model.Job.table.c.id == model.JobToOutputDatasetCollectionAssociation.table.c.job_id, + model.HistoryDatasetCollectionAssociation.table.c.id == model.JobToOutputDatasetCollectionAssociation.table.c.dataset_collection_id, + model.HistoryDatasetCollectionAssociation.table.c.deleted == true()) + ) + ), output_dataset_collection_instances=relation(model.JobToOutputDatasetCollectionAssociation, lazy=True), output_dataset_collections=relation(model.JobToImplicitOutputDatasetCollectionAssociation, lazy=True), post_job_actions=relation(model.PostJobActionAssociation, lazy=False), From 0e02cf3aac9636d97f6ce0086da048b909215617 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Tue, 17 Oct 2017 12:20:37 +0200 Subject: [PATCH 24/51] Remove unused current_jobs_data_ids --- lib/galaxy/managers/jobs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py index 9089d0c852b..e5af711a98b 100644 --- a/lib/galaxy/managers/jobs.py +++ b/lib/galaxy/managers/jobs.py @@ -228,7 +228,6 @@ class JobSearch(object): # dataset_ids (HDA, LDDA, HDCA) in the incoming param_dump to point to those used by the # possibly equivalent job, which may have been run on copies of the original input data. job_input_ids = {} - current_jobs_data_ids = [] if isinstance(job, tuple): # If there are any input datasets job will be a tuple job, current_jobs_data_ids = job[0], job[1:] From d53918eb2d6aad1865416432710b157d967badad Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Tue, 17 Oct 2017 12:31:27 +0200 Subject: [PATCH 25/51] Use joinedload for job parameters which we iterate over --- lib/galaxy/managers/jobs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py index e5af711a98b..c01597b0e0f 100644 --- a/lib/galaxy/managers/jobs.py +++ b/lib/galaxy/managers/jobs.py @@ -4,7 +4,7 @@ import logging from boltons.iterutils import remap from six import string_types from sqlalchemy import and_, false, func, or_ -from sqlalchemy.orm import aliased +from sqlalchemy.orm import aliased, joinedload from sqlalchemy.sql import select from galaxy import model @@ -221,7 +221,7 @@ class JobSearch(object): model.Job.any_output_dataset_deleted == false() )) - query = self.sa_session.query(model.Job, *used_ids).filter(and_(*conditions)) + query = self.sa_session.query(model.Job, *used_ids).filter(and_(*conditions)).options(joinedload("parameters")) for job in query.all(): # We found a job that is equal in terms of tool_id, user, state and input datasets, # but to be able to verify that the parameters match we need to modify all instances of From 75ddb8ef62ca857f3a7f0707a6d7cd749ec32fa7 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Tue, 17 Oct 2017 13:58:22 +0200 Subject: [PATCH 26/51] Revert "Use joinedload for job parameters which we iterate over" This reverts commit a72dcca6ca3d27bf5ada4d98fde784408314e331. --- lib/galaxy/managers/jobs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py index c01597b0e0f..e5af711a98b 100644 --- a/lib/galaxy/managers/jobs.py +++ b/lib/galaxy/managers/jobs.py @@ -4,7 +4,7 @@ import logging from boltons.iterutils import remap from six import string_types from sqlalchemy import and_, false, func, or_ -from sqlalchemy.orm import aliased, joinedload +from sqlalchemy.orm import aliased from sqlalchemy.sql import select from galaxy import model @@ -221,7 +221,7 @@ class JobSearch(object): model.Job.any_output_dataset_deleted == false() )) - query = self.sa_session.query(model.Job, *used_ids).filter(and_(*conditions)).options(joinedload("parameters")) + query = self.sa_session.query(model.Job, *used_ids).filter(and_(*conditions)) for job in query.all(): # We found a job that is equal in terms of tool_id, user, state and input datasets, # but to be able to verify that the parameters match we need to modify all instances of From 528a05e00218ecba0c8180268f7f551cf09768e5 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Tue, 17 Oct 2017 16:45:09 +0200 Subject: [PATCH 27/51] Debug: add profiling to figure out why search is slow sometimes --- lib/galaxy/dependencies/dev-requirements.txt | 18 +++++++++++++++++- lib/galaxy/managers/jobs.py | 7 ++++--- 2 files changed, 21 insertions(+), 4 deletions(-) mode change 120000 => 100644 lib/galaxy/dependencies/dev-requirements.txt diff --git a/lib/galaxy/dependencies/dev-requirements.txt b/lib/galaxy/dependencies/dev-requirements.txt deleted file mode 120000 index 8051686e764..00000000000 --- a/lib/galaxy/dependencies/dev-requirements.txt +++ /dev/null @@ -1 +0,0 @@ -pipfiles/develop/pinned-requirements.txt \ No newline at end of file diff --git a/lib/galaxy/dependencies/dev-requirements.txt b/lib/galaxy/dependencies/dev-requirements.txt new file mode 100644 index 00000000000..b9cf54391d1 --- /dev/null +++ b/lib/galaxy/dependencies/dev-requirements.txt @@ -0,0 +1,17 @@ +# For tests +nose +NoseHTML +twill==0.9.1 +mock +selenium +watchdog +profilehooks + +# For relase process +pygithub3 + +# For documentation building +sphinx +sphinx_rtd_theme +lxml +recommonmark diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py index e5af711a98b..4d8d92ab824 100644 --- a/lib/galaxy/managers/jobs.py +++ b/lib/galaxy/managers/jobs.py @@ -16,6 +16,7 @@ from galaxy.util import ( defaultdict, ExecutionTimer ) +from profilehooks import profile log = logging.getLogger(__name__) @@ -88,6 +89,7 @@ class JobSearch(object): wildcard_param_dump=wildcard_param_dump, input_ids=input_ids) + @profile def __search(self, tool_id, tool_version, user, input_data, input_ids=None, job_state=None, param_dump=None, wildcard_param_dump=None): search_timer = ExecutionTimer() @@ -229,7 +231,7 @@ class JobSearch(object): # possibly equivalent job, which may have been run on copies of the original input data. job_input_ids = {} if isinstance(job, tuple): - # If there are any input datasets job will be a tuple + # If there are any input datasets, job will be a tuple job, current_jobs_data_ids = job[0], job[1:] for src, requested_id, used_id in zip(data_types, requested_ids, current_jobs_data_ids): if src not in job_input_ids: @@ -254,8 +256,7 @@ class JobSearch(object): # Verify that equivalent jobs had the same number of job parameters # We skip chrominfo, dbkey, __workflow_invocation_uuid__ and identifer # parameter as these are not passed along when expanding tool parameters - # and they can differ without affecting the resulting dataset (when referencing identifier - # in the tool xml this may not be entirely true). + # and they can differ without affecting the resulting dataset. for parameter in job.parameters: if parameter.name in {'__workflow_invocation_uuid__', 'chromInfo', 'dbkey'} or parameter.name.endswith('|__identifier__'): continue From b5a4822edbf67eb02213a4f2a5ab762feef1a391 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Tue, 17 Oct 2017 19:50:26 +0200 Subject: [PATCH 28/51] Optimize query --- lib/galaxy/managers/jobs.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py index 4d8d92ab824..73bf821e517 100644 --- a/lib/galaxy/managers/jobs.py +++ b/lib/galaxy/managers/jobs.py @@ -16,7 +16,6 @@ from galaxy.util import ( defaultdict, ExecutionTimer ) -from profilehooks import profile log = logging.getLogger(__name__) @@ -89,7 +88,6 @@ class JobSearch(object): wildcard_param_dump=wildcard_param_dump, input_ids=input_ids) - @profile def __search(self, tool_id, tool_version, user, input_data, input_ids=None, job_state=None, param_dump=None, wildcard_param_dump=None): search_timer = ExecutionTimer() @@ -223,16 +221,17 @@ class JobSearch(object): model.Job.any_output_dataset_deleted == false() )) - query = self.sa_session.query(model.Job, *used_ids).filter(and_(*conditions)) + query = self.sa_session.query(model.Job.id, *used_ids).filter(and_(*conditions)) for job in query.all(): # We found a job that is equal in terms of tool_id, user, state and input datasets, # but to be able to verify that the parameters match we need to modify all instances of # dataset_ids (HDA, LDDA, HDCA) in the incoming param_dump to point to those used by the # possibly equivalent job, which may have been run on copies of the original input data. job_input_ids = {} - if isinstance(job, tuple): - # If there are any input datasets, job will be a tuple - job, current_jobs_data_ids = job[0], job[1:] + if len(job) > 1: + # We do have datasets to check + job_id, current_jobs_data_ids = job[0], job[1:] + job_parameter_conditions = [model.Job.id == job_id] for src, requested_id, used_id in zip(data_types, requested_ids, current_jobs_data_ids): if src not in job_input_ids: job_input_ids[src] = {requested_id: used_id} @@ -241,17 +240,18 @@ class JobSearch(object): new_param_dump = remap(param_dump, visit=replace_dataset_ids) # new_param_dump has its dataset ids remapped to those used by the job. # We now ask if the remapped job parameters match the current job. - job_parameter_conditions = [model.Job.id == job.id] for k, v in new_param_dump.items(): a = aliased(model.JobParameter) job_parameter_conditions.append(and_( - a.job_id == job.id, a.name == k, a.value == json.dumps(v) )) - query = self.sa_session.query(model.Job).filter(*job_parameter_conditions) - if query.first() is None: - continue + else: + job_parameter_conditions = [model.Job.id == job] + query = self.sa_session.query(model.Job).filter(*job_parameter_conditions) + job = query.first() + if job is None: + continue n_parameters = 0 # Verify that equivalent jobs had the same number of job parameters # We skip chrominfo, dbkey, __workflow_invocation_uuid__ and identifer From 77b9610b59eaa294cfc0a5521e0db50b69caf07e Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sun, 10 Dec 2017 17:07:07 +0100 Subject: [PATCH 29/51] Fix passing correct completed job during threaded job submission --- lib/galaxy/tools/execute.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/tools/execute.py b/lib/galaxy/tools/execute.py index 3b0baa5f90e..880bc4a38c3 100644 --- a/lib/galaxy/tools/execute.py +++ b/lib/galaxy/tools/execute.py @@ -110,7 +110,7 @@ def execute(trans, tool, mapping_params, history, rerun_remap_job_id=None, colle t.daemon = True t.start() - for execution_slice in execution_tracker.new_execution_slices(): + 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 From c8a5ea2996a6af85d61e2db054c16a3826efa89b Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sun, 10 Dec 2017 17:12:33 +0100 Subject: [PATCH 30/51] Fix bad rebase --- lib/galaxy/dependencies/dev-requirements.txt | 18 +----------------- lib/galaxy/tools/__init__.py | 3 ++- 2 files changed, 3 insertions(+), 18 deletions(-) mode change 100644 => 120000 lib/galaxy/dependencies/dev-requirements.txt diff --git a/lib/galaxy/dependencies/dev-requirements.txt b/lib/galaxy/dependencies/dev-requirements.txt deleted file mode 100644 index b9cf54391d1..00000000000 --- a/lib/galaxy/dependencies/dev-requirements.txt +++ /dev/null @@ -1,17 +0,0 @@ -# For tests -nose -NoseHTML -twill==0.9.1 -mock -selenium -watchdog -profilehooks - -# For relase process -pygithub3 - -# For documentation building -sphinx -sphinx_rtd_theme -lxml -recommonmark diff --git a/lib/galaxy/dependencies/dev-requirements.txt b/lib/galaxy/dependencies/dev-requirements.txt new file mode 120000 index 00000000000..8051686e764 --- /dev/null +++ b/lib/galaxy/dependencies/dev-requirements.txt @@ -0,0 +1 @@ +pipfiles/develop/pinned-requirements.txt \ No newline at end of file diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index e62410ccc94..370271d1fba 100755 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -1322,7 +1322,7 @@ class Tool(object, Dictifiable): output_collections=execution_tracker.output_collections, implicit_collections=execution_tracker.implicit_collections) - def handle_single_execution(self, trans, rerun_remap_job_id, execution_slice, history, execution_cache=None): + def handle_single_execution(self, trans, rerun_remap_job_id, execution_slice, history, execution_cache=None, completed_job=None): """ Return a pair with whether execution is successful as well as either resulting output data or an error message indicating the problem. @@ -1335,6 +1335,7 @@ class Tool(object, Dictifiable): rerun_remap_job_id=rerun_remap_job_id, execution_cache=execution_cache, dataset_collection_elements=execution_slice.dataset_collection_elements, + completed_job=completed_job, ) except httpexceptions.HTTPFound as e: # if it's a paste redirect exception, pass it up the stack From 3178cf13a1789ac18c238d7f180110dc9fabb0f3 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sun, 10 Dec 2017 17:20:23 +0100 Subject: [PATCH 31/51] Fix import order --- lib/galaxy/model/mapping.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index 1ff335bf089..8ab7ca9c82f 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -29,7 +29,7 @@ from sqlalchemy import ( ) from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.ext.orderinglist import ordering_list -from sqlalchemy.orm import backref, class_mapper, deferred, mapper, object_session, relation, column_property +from sqlalchemy.orm import backref, class_mapper, column_property, deferred, mapper, object_session, relation from sqlalchemy.orm.collections import attribute_mapped_collection from sqlalchemy.sql import exists from sqlalchemy.types import BigInteger From fd0728abb9276c6e89b9e097cd10ce2d621ce8fa Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sun, 10 Dec 2017 18:03:53 +0100 Subject: [PATCH 32/51] Fix migrations --- ...rom_job_id_column.py => 0137_add_copied_from_job_id_column.py} | 0 .../versions/{0137_add_hda_version.py => 0138_add_hda_version.py} | 0 ...e.py => 0139_add_history_dataset_association_history_table.py} | 0 ..._dataset_version_to_job_to_input_dataset_association_table.py} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename lib/galaxy/model/migrate/versions/{0136_add_copied_from_job_id_column.py => 0137_add_copied_from_job_id_column.py} (100%) rename lib/galaxy/model/migrate/versions/{0137_add_hda_version.py => 0138_add_hda_version.py} (100%) rename lib/galaxy/model/migrate/versions/{0138_add_history_dataset_association_history_table.py => 0139_add_history_dataset_association_history_table.py} (100%) rename lib/galaxy/model/migrate/versions/{0139_add_dataset_version_to_job_to_input_dataset_association_table.py => 0140_add_dataset_version_to_job_to_input_dataset_association_table.py} (100%) diff --git a/lib/galaxy/model/migrate/versions/0136_add_copied_from_job_id_column.py b/lib/galaxy/model/migrate/versions/0137_add_copied_from_job_id_column.py similarity index 100% rename from lib/galaxy/model/migrate/versions/0136_add_copied_from_job_id_column.py rename to lib/galaxy/model/migrate/versions/0137_add_copied_from_job_id_column.py diff --git a/lib/galaxy/model/migrate/versions/0137_add_hda_version.py b/lib/galaxy/model/migrate/versions/0138_add_hda_version.py similarity index 100% rename from lib/galaxy/model/migrate/versions/0137_add_hda_version.py rename to lib/galaxy/model/migrate/versions/0138_add_hda_version.py diff --git a/lib/galaxy/model/migrate/versions/0138_add_history_dataset_association_history_table.py b/lib/galaxy/model/migrate/versions/0139_add_history_dataset_association_history_table.py similarity index 100% rename from lib/galaxy/model/migrate/versions/0138_add_history_dataset_association_history_table.py rename to lib/galaxy/model/migrate/versions/0139_add_history_dataset_association_history_table.py diff --git a/lib/galaxy/model/migrate/versions/0139_add_dataset_version_to_job_to_input_dataset_association_table.py b/lib/galaxy/model/migrate/versions/0140_add_dataset_version_to_job_to_input_dataset_association_table.py similarity index 100% rename from lib/galaxy/model/migrate/versions/0139_add_dataset_version_to_job_to_input_dataset_association_table.py rename to lib/galaxy/model/migrate/versions/0140_add_dataset_version_to_job_to_input_dataset_association_table.py From f6413bfaa5e3d2d1a48355f79380cde0dec1be42 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sun, 10 Dec 2017 18:16:58 +0100 Subject: [PATCH 33/51] Linting fix (I201) --- .../0139_add_history_dataset_association_history_table.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/galaxy/model/migrate/versions/0139_add_history_dataset_association_history_table.py b/lib/galaxy/model/migrate/versions/0139_add_history_dataset_association_history_table.py index fe5b5a7b4bc..fed8a032d6e 100644 --- a/lib/galaxy/model/migrate/versions/0139_add_history_dataset_association_history_table.py +++ b/lib/galaxy/model/migrate/versions/0139_add_history_dataset_association_history_table.py @@ -7,6 +7,7 @@ import datetime import logging from sqlalchemy import Column, DateTime, ForeignKey, Integer, MetaData, Table + from galaxy.model.custom_types import MetadataType, TrimmedString now = datetime.datetime.utcnow From 74dd664712909adf5857f34fc6c0b1fe8c866059 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sun, 10 Dec 2017 19:56:30 +0100 Subject: [PATCH 34/51] Make skipping job configurable --- lib/galaxy/tools/__init__.py | 19 ++++++++++------- lib/galaxy/webapps/galaxy/api/tools.py | 3 ++- lib/galaxy/workflow/modules.py | 29 ++++++++++++++------------ lib/galaxy/workflow/run.py | 6 +++++- lib/galaxy/workflow/run_request.py | 13 ++++++++++-- 5 files changed, 45 insertions(+), 25 deletions(-) diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index 370271d1fba..40779c7ec19 100755 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -1281,7 +1281,7 @@ class Tool(object, Dictifiable): log.debug('Validated and populated state for tool request %s' % validation_timer) return all_params, all_errors, rerun_remap_job_id, collection_info - def handle_input(self, trans, incoming, history=None): + def handle_input(self, trans, incoming, history=None, use_cached_job=False): """ Process incoming parameters for this tool from the dict `incoming`, update the tool state (or create if none existed), and either return @@ -1298,13 +1298,16 @@ class Tool(object, Dictifiable): mapping_params = MappingParameters(incoming, all_params) completed_jobs = {} for i, param in enumerate(all_params): - completed_jobs[i] = self.job_search.by_tool_input( - trans=trans, - tool_id=self.id, - tool_version=self.version, - param=param, - param_dump=self.params_to_strings(param, self.app, nested=True), - ) + if use_cached_job: + completed_jobs[i] = self.job_search.by_tool_input( + trans=trans, + tool_id=self.id, + tool_version=self.version, + param=param, + param_dump=self.params_to_strings(param, self.app, nested=True), + ) + else: + completed_jobs[i] = None execution_tracker = execute_job(trans, self, mapping_params, history=request_context.history, rerun_remap_job_id=rerun_remap_job_id, collection_info=collection_info, completed_jobs=completed_jobs) # Raise an exception if there were jobs to execute and none of them were submitted, # if at least one is submitted or there are no jobs to execute - return aggregate diff --git a/lib/galaxy/webapps/galaxy/api/tools.py b/lib/galaxy/webapps/galaxy/api/tools.py index 41578548888..0d4262414e7 100644 --- a/lib/galaxy/webapps/galaxy/api/tools.py +++ b/lib/galaxy/webapps/galaxy/api/tools.py @@ -346,7 +346,8 @@ class ToolsController(BaseAPIController, UsesVisualizationMixin): # TODO: handle dbkeys params = util.Params(inputs, sanitize=False) incoming = params.__dict__ - vars = tool.handle_input(trans, incoming, history=target_history) + + vars = tool.handle_input(trans, incoming, history=target_history, use_cached_job=kwd.get('use_cached_job', False)) # TODO: check for errors and ensure that output dataset(s) are available. output_datasets = vars.get('out_data', []) diff --git a/lib/galaxy/workflow/modules.py b/lib/galaxy/workflow/modules.py index 2e64319ed4b..d99d77673cc 100644 --- a/lib/galaxy/workflow/modules.py +++ b/lib/galaxy/workflow/modules.py @@ -214,7 +214,7 @@ class WorkflowModule(object): state.decode(runtime_state, Bunch(inputs=self.get_runtime_inputs()), self.trans.app) return state - def execute(self, trans, progress, invocation_step): + def execute(self, trans, progress, invocation_step, use_cached_job=False): """ Execute the given workflow invocation step. Use the supplied workflow progress object to track outputs, find @@ -333,7 +333,7 @@ class SubWorkflowModule(WorkflowModule): def get_content_id(self): return self.trans.security.encode_id(self.subworkflow.id) - def execute(self, trans, progress, invocation_step): + def execute(self, trans, progress, invocation_step, use_cached_job=False): """ Execute the given workflow step in the given workflow invocation. Use the supplied workflow progress object to track outputs, find inputs, etc... @@ -367,7 +367,7 @@ class InputModule(WorkflowModule): def get_data_inputs(self): return [] - def execute(self, trans, progress, invocation_step): + def execute(self, trans, progress, invocation_step, use_cached_job=False): invocation = invocation_step.workflow_invocation step = invocation_step.workflow_step step_outputs = dict(output=step.state.inputs['input']) @@ -504,7 +504,7 @@ class InputParameterModule(WorkflowModule): def get_data_inputs(self): return [] - def execute(self, trans, progress, invocation_step): + def execute(self, trans, progress, invocation_step, use_cached_job=False): step = invocation_step.workflow_step step_outputs = dict(output=step.state.inputs['input']) progress.set_outputs_for_input(invocation_step, step_outputs) @@ -535,7 +535,7 @@ class PauseModule(WorkflowModule): state.inputs = dict() return state - def execute(self, trans, progress, invocation_step): + def execute(self, trans, progress, invocation_step, use_cached_job=False): step = invocation_step.workflow_step progress.mark_step_outputs_delayed(step, why="executing pause step") @@ -805,7 +805,7 @@ class ToolModule(WorkflowModule): else: raise ToolMissingException("Tool %s missing. Cannot recover runtime state." % self.tool_id) - def execute(self, trans, progress, invocation_step): + 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) @@ -875,13 +875,16 @@ class ToolModule(WorkflowModule): complete = False completed_jobs = {} for i, param in enumerate(param_combinations): - completed_jobs[i] = tool.job_search.by_tool_input( - trans=trans, - tool_id=tool.id, - tool_version=tool.version, - param=param, - param_dump=tool.params_to_strings(param, trans.app, nested=True), - ) + if use_cached_job: + completed_jobs[i] = tool.job_search.by_tool_input( + trans=trans, + tool_id=tool.id, + tool_version=tool.version, + param=param, + param_dump=tool.params_to_strings(param, trans.app, nested=True), + ) + else: + completed_jobs[i] = None try: mapping_params = MappingParameters(tool_state.inputs, param_combinations) max_num_jobs = progress.maximum_jobs_to_schedule_or_none diff --git a/lib/galaxy/workflow/run.py b/lib/galaxy/workflow/run.py index a97bf4bf067..5a8371a0523 100644 --- a/lib/galaxy/workflow/run.py +++ b/lib/galaxy/workflow/run.py @@ -136,6 +136,7 @@ class WorkflowInvoker(object): self.workflow_invocation = workflow_invocation self.workflow_invocation.copy_inputs_to_history = workflow_run_config.copy_inputs_to_history + self.workflow_invocation.use_cached_job = workflow_run_config.use_cached_jobs self.workflow_invocation.replacement_dict = workflow_run_config.replacement_dict module_injector = modules.WorkflowModuleInjector(trans) @@ -255,7 +256,10 @@ class WorkflowInvoker(object): pass def _invoke_step(self, invocation_step): - incomplete_or_none = invocation_step.workflow_step.module.execute(self.trans, self.progress, invocation_step) + incomplete_or_none = invocation_step.workflow_step.module.execute(self.trans, + self.progress, + invocation_step, + use_cached_job=self.workflow_invocation.use_cached_job) return incomplete_or_none diff --git a/lib/galaxy/workflow/run_request.py b/lib/galaxy/workflow/run_request.py index 6cee1620673..199584537c2 100644 --- a/lib/galaxy/workflow/run_request.py +++ b/lib/galaxy/workflow/run_request.py @@ -41,13 +41,20 @@ class WorkflowRunConfig(object): :type param_map: dict """ - def __init__(self, target_history, replacement_dict, copy_inputs_to_history=False, inputs={}, param_map={}, allow_tool_state_corrections=False): + def __init__(self, target_history, + replacement_dict, + copy_inputs_to_history=False, + inputs={}, + param_map={}, + allow_tool_state_corrections=False, + use_cached_jobs=False): self.target_history = target_history self.replacement_dict = replacement_dict self.copy_inputs_to_history = copy_inputs_to_history self.inputs = inputs self.param_map = param_map self.allow_tool_state_corrections = allow_tool_state_corrections + self.use_cached_jobs = use_cached_jobs def _normalize_inputs(steps, inputs, inputs_by): @@ -197,6 +204,7 @@ def _get_target_history(trans, workflow, payload, param_keys=[], index=0): def build_workflow_run_configs(trans, workflow, payload): app = trans.app allow_tool_state_corrections = payload.get('allow_tool_state_corrections', False) + use_cached_jobs = payload.get('use_cached_jobs', False) # Sanity checks. if len(workflow.steps) == 0: @@ -301,7 +309,8 @@ def build_workflow_run_configs(trans, workflow, payload): replacement_dict=payload.get('replacement_params', {}), inputs=normalized_inputs, param_map=param_map, - allow_tool_state_corrections=allow_tool_state_corrections + allow_tool_state_corrections=allow_tool_state_corrections, + use_cached_jobs=use_cached_jobs, )) return run_configs From 4b6e403622f1a50a7b31d5d1821c5e61032b111b Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sun, 10 Dec 2017 22:26:04 +0100 Subject: [PATCH 35/51] Add use_cached_job param to tools and workflow API Also includes a simple test that runs cat1 three times, the second time with `use_cached_job=False` (the default), and the third time with `use_cached_job=True` and verifies that the second output is different from the third, and that the first and third output are identical. --- lib/galaxy/managers/jobs.py | 19 ++++++-------- lib/galaxy/tools/__init__.py | 1 + lib/galaxy/webapps/galaxy/api/tools.py | 2 +- lib/galaxy/webapps/galaxy/api/workflows.py | 3 +++ lib/galaxy/workflow/modules.py | 1 + test/api/test_tools.py | 29 +++++++++++++++++++--- 6 files changed, 39 insertions(+), 16 deletions(-) diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py index 73bf821e517..68b240ba809 100644 --- a/lib/galaxy/managers/jobs.py +++ b/lib/galaxy/managers/jobs.py @@ -53,7 +53,6 @@ class JobSearch(object): """Search for jobs producing same results using the 'inputs' part of a tool POST.""" user = trans.user input_data = defaultdict(list) - input_ids = defaultdict(list) def populate_input_data_input_id(path, key, value): """Traverses expanded incoming using remap and collects input_ids and input_data.""" @@ -74,7 +73,6 @@ class JobSearch(object): 'id': value, 'identifier': identifier, }) - input_ids[src].append(value) return key, "__id_wildcard__" return key, value @@ -85,10 +83,9 @@ class JobSearch(object): input_data=input_data, job_state=job_state, param_dump=param_dump, - wildcard_param_dump=wildcard_param_dump, - input_ids=input_ids) + wildcard_param_dump=wildcard_param_dump) - def __search(self, tool_id, tool_version, user, input_data, input_ids=None, job_state=None, param_dump=None, wildcard_param_dump=None): + def __search(self, tool_id, tool_version, user, input_data, job_state=None, param_dump=None, wildcard_param_dump=None): search_timer = ExecutionTimer() def replace_dataset_ids(path, key, value): @@ -110,13 +107,11 @@ class JobSearch(object): if job_state is None: conditions.append( - or_( - model.Job.state == 'running', - model.Job.state == 'queued', - model.Job.state == 'waiting', - model.Job.state == 'running', - model.Job.state == 'ok', - ) + model.Job.state.in_([model.Job.states.NEW, + model.Job.states.QUEUED, + model.Job.states.WAITING, + model.Job.states.RUNNING, + model.Job.states.OK]) ) else: if isinstance(job_state, string_types): diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index 40779c7ec19..d0d1fcc1d81 100755 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -1305,6 +1305,7 @@ class Tool(object, Dictifiable): tool_version=self.version, param=param, param_dump=self.params_to_strings(param, self.app, nested=True), + job_state=None, ) else: completed_jobs[i] = None diff --git a/lib/galaxy/webapps/galaxy/api/tools.py b/lib/galaxy/webapps/galaxy/api/tools.py index 0d4262414e7..66367f8d73e 100644 --- a/lib/galaxy/webapps/galaxy/api/tools.py +++ b/lib/galaxy/webapps/galaxy/api/tools.py @@ -347,7 +347,7 @@ class ToolsController(BaseAPIController, UsesVisualizationMixin): params = util.Params(inputs, sanitize=False) incoming = params.__dict__ - vars = tool.handle_input(trans, incoming, history=target_history, use_cached_job=kwd.get('use_cached_job', False)) + vars = tool.handle_input(trans, incoming, history=target_history, use_cached_job=payload.get('use_cached_job', False)) # TODO: check for errors and ensure that output dataset(s) are available. output_datasets = vars.get('out_data', []) diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index 1db5d3df3b4..1f08541b7c7 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -255,6 +255,9 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn :param allow_tool_state_corrections: If set to True, any Tool parameter changes will not prevent running workflow, defaults to False :type allow_tool_state_corrections: bool + + :param use_cached_job: If set to True galaxy will attempt to find previously executed steps for all workflow steps with the exact same parameter combinations + and will copy the outputs of the previously executed step. """ ways_to_create = set([ 'workflow_id', diff --git a/lib/galaxy/workflow/modules.py b/lib/galaxy/workflow/modules.py index d99d77673cc..9f261fe76b9 100644 --- a/lib/galaxy/workflow/modules.py +++ b/lib/galaxy/workflow/modules.py @@ -882,6 +882,7 @@ class ToolModule(WorkflowModule): tool_version=tool.version, param=param, param_dump=tool.params_to_strings(param, trans.app, nested=True), + job_state=None, ) else: completed_jobs[i] = None diff --git a/test/api/test_tools.py b/test/api/test_tools.py index f42bb4a2f45..0702c7e01e6 100644 --- a/test/api/test_tools.py +++ b/test/api/test_tools.py @@ -303,6 +303,25 @@ class ToolsTestCase(api.ApiTestCase): output1_content = self.dataset_populator.get_history_dataset_content(history_id, dataset=output1) self.assertEqual(output1_content.strip(), "Cat1Test") + @skip_without_tool("cat1") + def test_run_cat1_use_cached_job(self): + with self.dataset_populator.test_history() as history_id: + # Run simple non-upload tool with an input data parameter. + history_id = self.dataset_populator.new_history() + new_dataset = self.dataset_populator.new_dataset(history_id, content='Cat1Test') + inputs = dict( + input1=dataset_to_param(new_dataset), + ) + outputs_one = self._run_cat1(history_id, inputs=inputs, assert_ok=True, wait_for_job=True) + outputs_two = self._run_cat1(history_id, inputs=inputs, use_cached_job=False, assert_ok=True, wait_for_job=True) + outputs_three = self._run_cat1(history_id, inputs=inputs, use_cached_job=True, assert_ok=True, wait_for_job=True) + dataset_details = [] + for output in [outputs_one, outputs_two, outputs_three]: + output_id = output['outputs'][0]['id'] + dataset_details.append(self._get("datasets/%s" % output_id).json()) + self.assertNotEqual(dataset_details[0]['file_name'], dataset_details[1]['file_name']) + self.assertEqual(dataset_details[0]['file_name'], dataset_details[2]['file_name']) + @skip_without_tool("cat1") def test_run_cat1_listified_param(self): # Run simple non-upload tool with an input data parameter. @@ -1384,10 +1403,10 @@ class ToolsTestCase(api.ApiTestCase): self._assert_status_code_is(create_response, 200) return create_response.json()['outputs'] - def _run_cat1(self, history_id, inputs, assert_ok=False): - return self._run('cat1', history_id, inputs, assert_ok=assert_ok) + 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): + def _run(self, tool_id, history_id, inputs, assert_ok=False, tool_version=None, use_cached_job=False, wait_for_job=False): payload = self.dataset_populator.run_tool_payload( tool_id=tool_id, inputs=inputs, @@ -1395,7 +1414,11 @@ class ToolsTestCase(api.ApiTestCase): ) if tool_version is not None: payload["tool_version"] = tool_version + if use_cached_job: + payload['use_cached_job'] = True create_response = self._post("tools", data=payload) + if wait_for_job: + self.dataset_populator.wait_for_job(job_id=create_response.json()['jobs'][0]['id']) if assert_ok: self._assert_status_code_is(create_response, 200) create = create_response.json() From 0dcf9d7fd6504e449b7b222ab78f3b9e8a143043 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Mon, 11 Dec 2017 09:43:08 +0100 Subject: [PATCH 36/51] Refine cached job tool test to make less asumptions --- test/api/test_tools.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/api/test_tools.py b/test/api/test_tools.py index 0702c7e01e6..23eae8cd71f 100644 --- a/test/api/test_tools.py +++ b/test/api/test_tools.py @@ -319,8 +319,9 @@ class ToolsTestCase(api.ApiTestCase): for output in [outputs_one, outputs_two, outputs_three]: output_id = output['outputs'][0]['id'] dataset_details.append(self._get("datasets/%s" % output_id).json()) - self.assertNotEqual(dataset_details[0]['file_name'], dataset_details[1]['file_name']) - self.assertEqual(dataset_details[0]['file_name'], dataset_details[2]['file_name']) + filenames = [dd['file_name'] for dd in dataset_details] + assert len(filenames) == 3, filenames + assert len(set(filenames)) <= 2, filenames @skip_without_tool("cat1") def test_run_cat1_listified_param(self): From 1c4476a6af6f6042ef8daf7ca3c193bd58f8c96c Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Mon, 11 Dec 2017 18:10:36 +0100 Subject: [PATCH 37/51] Add a test for skipping jobs in workflow context This is using the simplest of workflows. I had to remove the chromInfo part from the workflow, because the path referenced would be changed dynamically and so the job search wouldn't recognize this as being equivalent. --- lib/galaxy/managers/jobs.py | 4 ++-- lib/galaxy/workflow/run.py | 2 +- lib/galaxy/workflow/run_request.py | 8 ++++---- test/api/test_tools.py | 1 - test/api/test_workflows.py | 25 +++++++++++++++++++++++++ test/base/data/test_workflow_1.ga | 4 ++-- 6 files changed, 34 insertions(+), 10 deletions(-) diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py index 68b240ba809..d845ae1296e 100644 --- a/lib/galaxy/managers/jobs.py +++ b/lib/galaxy/managers/jobs.py @@ -258,9 +258,9 @@ class JobSearch(object): n_parameters += 1 if not n_parameters == len(param_dump): continue - log.info("Searching jobs finished %s", search_timer) + log.info("Found equivalent job %s", search_timer) return job - log.info("Searching jobs finished %s", search_timer) + log.info("No equivalent jobs found %s", search_timer) return None diff --git a/lib/galaxy/workflow/run.py b/lib/galaxy/workflow/run.py index 5a8371a0523..2eb7d2f933b 100644 --- a/lib/galaxy/workflow/run.py +++ b/lib/galaxy/workflow/run.py @@ -136,7 +136,7 @@ class WorkflowInvoker(object): self.workflow_invocation = workflow_invocation self.workflow_invocation.copy_inputs_to_history = workflow_run_config.copy_inputs_to_history - self.workflow_invocation.use_cached_job = workflow_run_config.use_cached_jobs + self.workflow_invocation.use_cached_job = workflow_run_config.use_cached_job self.workflow_invocation.replacement_dict = workflow_run_config.replacement_dict module_injector = modules.WorkflowModuleInjector(trans) diff --git a/lib/galaxy/workflow/run_request.py b/lib/galaxy/workflow/run_request.py index 199584537c2..47611f4c86a 100644 --- a/lib/galaxy/workflow/run_request.py +++ b/lib/galaxy/workflow/run_request.py @@ -47,14 +47,14 @@ class WorkflowRunConfig(object): inputs={}, param_map={}, allow_tool_state_corrections=False, - use_cached_jobs=False): + use_cached_job=False): self.target_history = target_history self.replacement_dict = replacement_dict self.copy_inputs_to_history = copy_inputs_to_history self.inputs = inputs self.param_map = param_map self.allow_tool_state_corrections = allow_tool_state_corrections - self.use_cached_jobs = use_cached_jobs + self.use_cached_job = use_cached_job def _normalize_inputs(steps, inputs, inputs_by): @@ -204,7 +204,7 @@ def _get_target_history(trans, workflow, payload, param_keys=[], index=0): def build_workflow_run_configs(trans, workflow, payload): app = trans.app allow_tool_state_corrections = payload.get('allow_tool_state_corrections', False) - use_cached_jobs = payload.get('use_cached_jobs', False) + use_cached_job = payload.get('use_cached_job', False) # Sanity checks. if len(workflow.steps) == 0: @@ -310,7 +310,7 @@ def build_workflow_run_configs(trans, workflow, payload): inputs=normalized_inputs, param_map=param_map, allow_tool_state_corrections=allow_tool_state_corrections, - use_cached_jobs=use_cached_jobs, + use_cached_job=use_cached_job, )) return run_configs diff --git a/test/api/test_tools.py b/test/api/test_tools.py index 23eae8cd71f..4ce2ffc893f 100644 --- a/test/api/test_tools.py +++ b/test/api/test_tools.py @@ -307,7 +307,6 @@ class ToolsTestCase(api.ApiTestCase): def test_run_cat1_use_cached_job(self): with self.dataset_populator.test_history() as history_id: # Run simple non-upload tool with an input data parameter. - history_id = self.dataset_populator.new_history() new_dataset = self.dataset_populator.new_dataset(history_id, content='Cat1Test') inputs = dict( input1=dataset_to_param(new_dataset), diff --git a/test/api/test_workflows.py b/test/api/test_workflows.py index edf397cedf2..304b80f88f1 100644 --- a/test/api/test_workflows.py +++ b/test/api/test_workflows.py @@ -1,5 +1,6 @@ from __future__ import print_function +import json import time from collections import namedtuple from json import dumps @@ -1817,6 +1818,30 @@ test_data: self.dataset_populator.wait_for_history_jobs(history_id, assert_ok=assert_ok) time.sleep(.5) + @skip_without_tool('cat1') + def test_workflow_rerun_with_use_cached_job(self): + # We launch a workflow + workflow = self.workflow_populator.load_workflow(name="test_for_run") + with self.dataset_populator.test_history() as history_id: + workflow_request, _ = self._setup_workflow_run(workflow, history_id=history_id) + run_workflow_response = self._post("workflows", data=workflow_request).json() + # We copy the workflow inputs to a new history + new_workflow_request = workflow_request.copy() + new_ds_map = json.loads(new_workflow_request['ds_map']) + with self.dataset_populator.test_history() as new_history_id: + for key, input_values in run_workflow_response['inputs'].items(): + copy_payload = {"content": input_values['id'], "source": "hda", "type": "dataset"} + copy_response = self._post("histories/%s/contents" % new_history_id, data=copy_payload).json() + new_ds_map[key]['id'] = copy_response['id'] + new_workflow_request['ds_map'] = json.dumps(new_ds_map) + new_workflow_request['history'] = "hist_id=%s" % new_history_id + new_workflow_request['use_cached_job'] = True + # We run the workflow again, it should not produce any new outputs + new_workflow_response = self._post("workflows", data=new_workflow_request).json() + first_wf_output_filename = self._get("datasets/%s" % run_workflow_response['outputs'][0]).json()['file_name'] + second_wf_output_filename = self._get("datasets/%s" % new_workflow_response['outputs'][0]).json()['file_name'] + self.assertEquals(first_wf_output_filename, second_wf_output_filename) + def test_cannot_run_inaccessible_workflow(self): workflow = self.workflow_populator.load_workflow(name="test_for_run_cannot_access") workflow_request, history_id = self._setup_workflow_run(workflow) diff --git a/test/base/data/test_workflow_1.ga b/test/base/data/test_workflow_1.ga index 1f069696d8c..a39ee30747d 100644 --- a/test/base/data/test_workflow_1.ga +++ b/test/base/data/test_workflow_1.ga @@ -78,10 +78,10 @@ "post_job_actions": {}, "tool_errors": null, "tool_id": "cat1", - "tool_state": "{\"__page__\": 0, \"__rerun_remap_job_id__\": null, \"input1\": \"null\", \"chromInfo\": \"\\\"/home/john/workspace/galaxy-central/tool-data/shared/ucsc/chrom/?.len\\\"\", \"queries\": \"[{\\\"input2\\\": null, \\\"__index__\\\": 0}]\"}", + "tool_state": "{\"__page__\": 0, \"__rerun_remap_job_id__\": null, \"input1\": \"null\", \"queries\": \"[{\\\"input2\\\": null, \\\"__index__\\\": 0}]\"}", "tool_version": "1.0.0", "type": "tool", "user_outputs": [] } } -} \ No newline at end of file +} From c6468f0677002dad4875b2b7cbeb67f018ef5be3 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Mon, 11 Dec 2017 21:42:40 +0100 Subject: [PATCH 38/51] Add conditional for use_cached_job to workflow run form Surprisingly the API endpoint that is used by the UI is a little different from the `create` endpoint the tests are testing, so this also adds a change in how the `use_cached_job` is passed along. --- .../scripts/mvc/tool/tool-form-composite.js | 25 +++++++++++++++++++ lib/galaxy/workflow/run_request.py | 6 +++++ 2 files changed, 31 insertions(+) diff --git a/client/galaxy/scripts/mvc/tool/tool-form-composite.js b/client/galaxy/scripts/mvc/tool/tool-form-composite.js index 7a143f9555b..4db7cda1d8c 100644 --- a/client/galaxy/scripts/mvc/tool/tool-form-composite.js +++ b/client/galaxy/scripts/mvc/tool/tool-form-composite.js @@ -200,6 +200,7 @@ var View = Backbone.View.extend({ this._renderMessage(); this._renderParameters(); this._renderHistory(); + this._renderUseCachedJob(); _.each(this.steps, step => { self._renderStep(step); }); @@ -311,6 +312,28 @@ var View = Backbone.View.extend({ this._append(this.$steps, this.history_form.$el); }, + /** Render job caching option */ + _renderUseCachedJob: function() { + this.job_options_form = new Form({ + cls: "ui-portlet-narrow", + title: "Job re-use Options", + inputs: [ + { + type: "conditional", + name: "use_cached_job", + test_param: { + name: "check", + label: "BETA: Attempt to reuse jobs with identical parameters?", + type: "boolean", + value: "false", + help: "This may skip executing jobs that you have already run." + }, + } + ] + }); + this._append(this.$steps, this.job_options_form.$el); + }, + /** Render step */ _renderStep: function(step) { var self = this; @@ -497,8 +520,10 @@ var View = Backbone.View.extend({ _submit: function() { var self = this; var history_form_data = this.history_form.data.create(); + var job_options_form_data = this.job_options_form.data.create(); var job_def = { new_history_name: history_form_data["new_history|name"] ? history_form_data["new_history|name"] : null, + use_cached_job: true ? job_options_form_data["use_cached_job|check"] === 'true' : false, history_id: !history_form_data["new_history|name"] ? this.model.get("history_id") : null, replacement_params: this.wp_form ? this.wp_form.data.create() : {}, parameters: {}, diff --git a/lib/galaxy/workflow/run_request.py b/lib/galaxy/workflow/run_request.py index 47611f4c86a..fca6a3e199a 100644 --- a/lib/galaxy/workflow/run_request.py +++ b/lib/galaxy/workflow/run_request.py @@ -347,6 +347,7 @@ def workflow_run_config_to_request(trans, run_config, workflow): target_history=run_config.target_history, replacement_dict=run_config.replacement_dict, copy_inputs_to_history=False, + use_cached_job=run_config.use_cached_job, inputs={}, param_map={}, allow_tool_state_corrections=run_config.allow_tool_state_corrections @@ -372,6 +373,7 @@ def workflow_run_config_to_request(trans, run_config, workflow): workflow_invocation.add_input(content, step_id) add_parameter("copy_inputs_to_history", "true" if run_config.copy_inputs_to_history else "false", param_types.META_PARAMETERS) + add_parameter("use_cached_job", "true" if run_config.use_cached_job else "false", param_types.META_PARAMETERS) return workflow_invocation @@ -382,6 +384,7 @@ def workflow_request_to_run_config(work_request_context, workflow_invocation): inputs = {} param_map = {} copy_inputs_to_history = None + use_cached_job = False for parameter in workflow_invocation.input_parameters: parameter_type = parameter.type @@ -390,6 +393,8 @@ def workflow_request_to_run_config(work_request_context, workflow_invocation): elif parameter_type == param_types.META_PARAMETERS: if parameter.name == "copy_inputs_to_history": copy_inputs_to_history = (parameter.value == "true") + if parameter.name == 'use_cached_job': + use_cached_job = (parameter.value == 'true') for input_association in workflow_invocation.input_datasets: inputs[input_association.workflow_step_id] = input_association.dataset for input_association in workflow_invocation.input_dataset_collections: @@ -404,6 +409,7 @@ def workflow_request_to_run_config(work_request_context, workflow_invocation): inputs=inputs, param_map=param_map, copy_inputs_to_history=copy_inputs_to_history, + use_cached_job=use_cached_job, ) return workflow_run_config From 509b0ca7a1e6a5f53b76b2995551d7705d1376d1 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Mon, 11 Dec 2017 22:18:57 +0100 Subject: [PATCH 39/51] Add the possibility to send reuse jobs via the tool form --- client/galaxy/scripts/mvc/tool/tool-form.js | 12 ++++++++++++ lib/galaxy/webapps/galaxy/api/tools.py | 6 +++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/client/galaxy/scripts/mvc/tool/tool-form.js b/client/galaxy/scripts/mvc/tool/tool-form.js index 6e0d5132d3d..c035bebf270 100644 --- a/client/galaxy/scripts/mvc/tool/tool-form.js +++ b/client/galaxy/scripts/mvc/tool/tool-form.js @@ -148,6 +148,18 @@ var View = Backbone.View.extend({ "The previous run of this tool failed and other tools were waiting for it to finish successfully. Use this option to resume those tools using the new output(s) of this tool run." }); } + + // Job Re-use Options + options.inputs.push({ + label: "BETA: Attempt to re-use jobs with identical parameters ?", + help: "This may skip executing jobs that you have already run", + name: "use_cached_job", + type: "select", + display: "radio", + ignore: "__ignore__", + value: "__ignore__", + options: [["No", false], ["Yes", true]], + }); }, /** Submit a regular job. diff --git a/lib/galaxy/webapps/galaxy/api/tools.py b/lib/galaxy/webapps/galaxy/api/tools.py index 66367f8d73e..412cc4082d8 100644 --- a/lib/galaxy/webapps/galaxy/api/tools.py +++ b/lib/galaxy/webapps/galaxy/api/tools.py @@ -347,7 +347,11 @@ class ToolsController(BaseAPIController, UsesVisualizationMixin): params = util.Params(inputs, sanitize=False) incoming = params.__dict__ - vars = tool.handle_input(trans, incoming, history=target_history, use_cached_job=payload.get('use_cached_job', False)) + # use_cached_job can be passed in via the top-level payload or among the tool inputs. + # I think it should be a top-level parameter, but because the selector is implemented + # as a regular tool parameter we accept both. + use_cached_job = payload.get('use_cached_job', False) or util.string_as_bool(inputs.get('use_cached_job', 'false')) + vars = tool.handle_input(trans, incoming, history=target_history, use_cached_job=use_cached_job) # TODO: check for errors and ensure that output dataset(s) are available. output_datasets = vars.get('out_data', []) From 9d38219d6531a4d956145a02d657c6c0ce22412e Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Fri, 15 Dec 2017 15:47:10 +0100 Subject: [PATCH 40/51] Pass use_cached_jobs on to subworkflow invoker --- lib/galaxy/webapps/galaxy/api/workflows.py | 10 +++-- lib/galaxy/workflow/modules.py | 2 +- lib/galaxy/workflow/run.py | 3 +- test/api/test_workflows.py | 46 ++++++++++++++++++++-- 4 files changed, 51 insertions(+), 10 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index 1f08541b7c7..6e6233173ce 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -339,10 +339,12 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn rval = {} rval['history'] = trans.security.encode_id(history.id) rval['outputs'] = [] - for step in workflow.steps: - if step.type == 'tool' or step.type is None: - for v in outputs[step.id].values(): - rval['outputs'].append(trans.security.encode_id(v.id)) + if outputs: + # Newer outputs don't necessarily fill outputs (?) + for step in workflow.steps: + if step.type == 'tool' or step.type is None: + for v in outputs[step.id].values(): + rval['outputs'].append(trans.security.encode_id(v.id)) # Newer version of this API just returns the invocation as a dict, to # facilitate migration - produce the newer style response and blend in diff --git a/lib/galaxy/workflow/modules.py b/lib/galaxy/workflow/modules.py index 9f261fe76b9..28fdba1c6b8 100644 --- a/lib/galaxy/workflow/modules.py +++ b/lib/galaxy/workflow/modules.py @@ -339,7 +339,7 @@ class SubWorkflowModule(WorkflowModule): inputs, etc... """ step = invocation_step.workflow_step - subworkflow_invoker = progress.subworkflow_invoker(trans, step) + subworkflow_invoker = progress.subworkflow_invoker(trans, step, use_cached_job=use_cached_job) subworkflow_invoker.invoke() subworkflow = subworkflow_invoker.workflow subworkflow_progress = subworkflow_invoker.progress diff --git a/lib/galaxy/workflow/run.py b/lib/galaxy/workflow/run.py index 2eb7d2f933b..8271a4c4458 100644 --- a/lib/galaxy/workflow/run.py +++ b/lib/galaxy/workflow/run.py @@ -441,7 +441,7 @@ class WorkflowProgress(object): raise Exception("Failed to find persisted workflow invocation for step [%s]" % step.id) return subworkflow_invocation - def subworkflow_invoker(self, trans, step): + def subworkflow_invoker(self, trans, step, use_cached_job=False): subworkflow_progress = self.subworkflow_progress(step) subworkflow_invocation = subworkflow_progress.workflow_invocation workflow_run_config = WorkflowRunConfig( @@ -450,6 +450,7 @@ class WorkflowProgress(object): inputs={}, param_map={}, copy_inputs_to_history=False, + use_cached_job=use_cached_job ) return WorkflowInvoker( trans, diff --git a/test/api/test_workflows.py b/test/api/test_workflows.py index 304b80f88f1..45da2706e59 100644 --- a/test/api/test_workflows.py +++ b/test/api/test_workflows.py @@ -255,6 +255,8 @@ class BaseWorkflowsApiTestCase(api.ApiTestCase): invocation_id=invocation_id, inputs=inputs, jobs=jobs, + invocation=invocation, + workflow_request=workflow_request ) def _history_jobs(self, history_id): @@ -986,7 +988,7 @@ test_data: value: 1.bed type: File """ % SIMPLE_NESTED_WORKFLOW_YAML - self._run_jobs(workflow_run_description, history_id=history_id) + run_jobs_summary = self._run_jobs(workflow_run_description, history_id=history_id) content = self.dataset_populator.get_history_dataset_content(history_id) self.assertEqual("chr5\t131424298\t131424460\tCCDS4149.1_cds_0_0_chr5_131424299_f\t0\t+\nchr5\t131424298\t131424460\tCCDS4149.1_cds_0_0_chr5_131424299_f\t0\t+\n", content) @@ -1820,10 +1822,46 @@ test_data: @skip_without_tool('cat1') def test_workflow_rerun_with_use_cached_job(self): - # We launch a workflow workflow = self.workflow_populator.load_workflow(name="test_for_run") + self._workflow_rerun_with_use_cached_job(workflow=workflow) + + @skip_without_tool('cat1') + def test_nested_workflow_rerun_with_use_cached_job(self): + history_id = self.dataset_populator.new_history() + workflow_run_description = """%s + +test_data: + outer_input: + value: 1.bed + type: File +""" % SIMPLE_NESTED_WORKFLOW_YAML + run_jobs_summary = self._run_jobs(workflow_run_description, history_id=history_id) + self.dataset_populator.wait_for_history(history_id, assert_ok=True) + workflow_request = run_jobs_summary.workflow_request + # We copy the inputs to a new history and re-reun the workflow + new_history_id = self.dataset_populator.new_history() + inputs = json.loads(workflow_request['inputs']) + dataset_type = inputs['outer_input']['src'] + dataset_id = inputs['outer_input']['id'] + copy_payload = {"content": dataset_id, "source": dataset_type, "type": "dataset"} + copy_response = self._post("histories/%s/contents" % new_history_id, data=copy_payload) + self._assert_status_code_is(copy_response, 200) + new_dataset_id = copy_response.json()['id'] + inputs['outer_input']['id'] = new_dataset_id + workflow_request['use_cached_job'] = True + workflow_request['history'] = "hist_id={new_history_id}".format(new_history_id=new_history_id) + workflow_request['inputs'] = json.dumps(inputs) + run_workflow_response = self._post("workflows", data=run_jobs_summary.workflow_request).json() + self.wait_for_invocation_and_jobs(new_history_id, run_jobs_summary.workflow_id, run_workflow_response['id']) + pass + + def _workflow_rerun_with_use_cached_job(self, workflow=None, workflow_id=None): + # We launch a workflow with self.dataset_populator.test_history() as history_id: - workflow_request, _ = self._setup_workflow_run(workflow, history_id=history_id) + if workflow: + workflow_request, _ = self._setup_workflow_run(workflow, history_id=history_id) + else: + workflow_request, _ = self._setup_workflow_run(workflow_id=workflow_id, history_id=history_id) run_workflow_response = self._post("workflows", data=workflow_request).json() # We copy the workflow inputs to a new history new_workflow_request = workflow_request.copy() @@ -2727,4 +2765,4 @@ steps: ) -RunJobsSummary = namedtuple('RunJobsSummary', ['history_id', 'workflow_id', 'invocation_id', 'inputs', 'jobs']) +RunJobsSummary = namedtuple('RunJobsSummary', ['history_id', 'workflow_id', 'invocation_id', 'inputs', 'jobs', 'invocation', 'workflow_request']) From 81c733d940eb716d48057446faf21c5f99065019 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sun, 17 Dec 2017 12:38:51 +0100 Subject: [PATCH 41/51] Revert the dbkey metadata hack; the real fix is to copy the metadata correctly --- lib/galaxy/model/__init__.py | 12 ++---------- lib/galaxy/tools/actions/__init__.py | 1 + 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 6c2093ab0fb..9f9b9924612 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -2059,10 +2059,8 @@ class DatasetInstance(object): def set_dbkey(self, value): if "dbkey" in self.datatype.metadata_spec: - if isinstance(value, list): - self.metadata.dbkey = value[0] - else: - self.metadata.dbkey = value + if not isinstance(value, list): + self.metadata.dbkey = [value] dbkey = property(get_dbkey, set_dbkey) def change_datatype(self, new_ext): @@ -2675,12 +2673,6 @@ class HistoryDatasetAssociationHistory(object): self.version = version self.extension = extension self.extended_metadata_id = extended_metadata_id - dbkey = metadata.get('dbkey') - if dbkey and isinstance(dbkey, list): - # The dbkey may or may not be stored in a list. - # To facilitate comparison we use the raw value - metadata = metadata.copy() - metadata['dbkey'] = metadata['dbkey'][0] self._metadata = metadata diff --git a/lib/galaxy/tools/actions/__init__.py b/lib/galaxy/tools/actions/__init__.py index 050e1c19374..5ff1a25b0e6 100644 --- a/lib/galaxy/tools/actions/__init__.py +++ b/lib/galaxy/tools/actions/__init__.py @@ -356,6 +356,7 @@ class DefaultToolAction(object): if completed_job: data.blurb = completed_data.blurb data.peek = completed_data.peek + data._metadata = completed_data._metadata else: data.blurb = "queued" # Set output label From 78c37d32d9c0cdbb65b75960c820412185bd129f Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sun, 17 Dec 2017 14:29:54 +0100 Subject: [PATCH 42/51] Assert that histories contains same dataset ids --- test/api/test_workflows.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/test/api/test_workflows.py b/test/api/test_workflows.py index 45da2706e59..cfa8c434af8 100644 --- a/test/api/test_workflows.py +++ b/test/api/test_workflows.py @@ -988,7 +988,7 @@ test_data: value: 1.bed type: File """ % SIMPLE_NESTED_WORKFLOW_YAML - run_jobs_summary = self._run_jobs(workflow_run_description, history_id=history_id) + self._run_jobs(workflow_run_description, history_id=history_id) content = self.dataset_populator.get_history_dataset_content(history_id) self.assertEqual("chr5\t131424298\t131424460\tCCDS4149.1_cds_0_0_chr5_131424299_f\t0\t+\nchr5\t131424298\t131424460\tCCDS4149.1_cds_0_0_chr5_131424299_f\t0\t+\n", content) @@ -1852,8 +1852,16 @@ test_data: workflow_request['history'] = "hist_id={new_history_id}".format(new_history_id=new_history_id) workflow_request['inputs'] = json.dumps(inputs) run_workflow_response = self._post("workflows", data=run_jobs_summary.workflow_request).json() - self.wait_for_invocation_and_jobs(new_history_id, run_jobs_summary.workflow_id, run_workflow_response['id']) - pass + self.workflow_populator.wait_for_workflow(workflow_request['workflow_id'], run_workflow_response['id'], history_id, assert_ok=True) + # Now make sure that the HDAs in each history point to the same dataset instances + history_one_contents = self.__history_contents(history_id) + history_two_contents = self.__history_contents(new_history_id) + assert len(history_one_contents) == len(history_two_contents) + for i, (item_one, item_two) in enumerate(zip(history_one_contents, history_two_contents)): + assert item_one['dataset_id'] == item_two['dataset_id'], \ + 'Dataset ids should match, but "%s" and "%s" are not the same for History item %i' % (item_one['dataset_id'], + item_two['dataset_id'], + i + 1) def _workflow_rerun_with_use_cached_job(self, workflow=None, workflow_id=None): # We launch a workflow From 43224af880f2a825d5fd2f655a3f9af11f04f689 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sun, 17 Dec 2017 16:23:02 +0100 Subject: [PATCH 43/51] Fix migration comments --- .../migrate/versions/0137_add_copied_from_job_id_column.py | 4 ++-- lib/galaxy/model/migrate/versions/0138_add_hda_version.py | 2 +- .../0139_add_history_dataset_association_history_table.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/galaxy/model/migrate/versions/0137_add_copied_from_job_id_column.py b/lib/galaxy/model/migrate/versions/0137_add_copied_from_job_id_column.py index f6409d5ca91..bb2c2030dd7 100644 --- a/lib/galaxy/model/migrate/versions/0137_add_copied_from_job_id_column.py +++ b/lib/galaxy/model/migrate/versions/0137_add_copied_from_job_id_column.py @@ -17,7 +17,7 @@ def upgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - # Add the dependencies column to the job table + # Add the copied_from_job_id column to the job table try: jobs_table = Table("job", metadata, autoload=True) copied_from_job_id_column.create(jobs_table) @@ -31,7 +31,7 @@ def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - # Drop the job table's dependencies column. + # Drop the job table's copied_from_job_id column. try: jobs_table = Table("job", metadata, autoload=True) copied_from_job_id = jobs_table.c.copied_from_job_id_column diff --git a/lib/galaxy/model/migrate/versions/0138_add_hda_version.py b/lib/galaxy/model/migrate/versions/0138_add_hda_version.py index 9532e5ea25d..55db96d4fc3 100644 --- a/lib/galaxy/model/migrate/versions/0138_add_hda_version.py +++ b/lib/galaxy/model/migrate/versions/0138_add_hda_version.py @@ -1,5 +1,5 @@ """ -Add copied_from_job_id column to jobs table +Add version column to history_dataset_association table """ from __future__ import print_function diff --git a/lib/galaxy/model/migrate/versions/0139_add_history_dataset_association_history_table.py b/lib/galaxy/model/migrate/versions/0139_add_history_dataset_association_history_table.py index fed8a032d6e..8c8175a8c73 100644 --- a/lib/galaxy/model/migrate/versions/0139_add_history_dataset_association_history_table.py +++ b/lib/galaxy/model/migrate/versions/0139_add_history_dataset_association_history_table.py @@ -1,5 +1,5 @@ """ -Migration script to add the history_dataset_association_history table and data_manager_job_association. +Migration script to add the history_dataset_association_history table. """ from __future__ import print_function @@ -36,7 +36,7 @@ def upgrade(migrate_engine): HistoryDatasetAssociationHistory_table.create() log.debug("Created history_dataset_association_history table") except Exception: - log.exception("Creating data_manager_history_association table failed.") + log.exception("Creating history_dataset_association_history table failed.") def downgrade(migrate_engine): @@ -46,4 +46,4 @@ def downgrade(migrate_engine): HistoryDatasetAssociationHistory_table.drop() log.debug("Dropped history_dataset_association_history table") except Exception: - log.exception("Dropping data_manager_history_association table failed.") + log.exception("Dropping history_dataset_association_history table failed.") From c327594ed76fab4d235e547053ebc3ed5572b28a Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sun, 17 Dec 2017 17:07:25 +0100 Subject: [PATCH 44/51] Simplify use_cached_jobs workflow tests --- test/api/test_workflows.py | 86 ++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 45 deletions(-) diff --git a/test/api/test_workflows.py b/test/api/test_workflows.py index cfa8c434af8..09f9ba8da60 100644 --- a/test/api/test_workflows.py +++ b/test/api/test_workflows.py @@ -1823,53 +1823,9 @@ test_data: @skip_without_tool('cat1') def test_workflow_rerun_with_use_cached_job(self): workflow = self.workflow_populator.load_workflow(name="test_for_run") - self._workflow_rerun_with_use_cached_job(workflow=workflow) - - @skip_without_tool('cat1') - def test_nested_workflow_rerun_with_use_cached_job(self): - history_id = self.dataset_populator.new_history() - workflow_run_description = """%s - -test_data: - outer_input: - value: 1.bed - type: File -""" % SIMPLE_NESTED_WORKFLOW_YAML - run_jobs_summary = self._run_jobs(workflow_run_description, history_id=history_id) - self.dataset_populator.wait_for_history(history_id, assert_ok=True) - workflow_request = run_jobs_summary.workflow_request - # We copy the inputs to a new history and re-reun the workflow - new_history_id = self.dataset_populator.new_history() - inputs = json.loads(workflow_request['inputs']) - dataset_type = inputs['outer_input']['src'] - dataset_id = inputs['outer_input']['id'] - copy_payload = {"content": dataset_id, "source": dataset_type, "type": "dataset"} - copy_response = self._post("histories/%s/contents" % new_history_id, data=copy_payload) - self._assert_status_code_is(copy_response, 200) - new_dataset_id = copy_response.json()['id'] - inputs['outer_input']['id'] = new_dataset_id - workflow_request['use_cached_job'] = True - workflow_request['history'] = "hist_id={new_history_id}".format(new_history_id=new_history_id) - workflow_request['inputs'] = json.dumps(inputs) - run_workflow_response = self._post("workflows", data=run_jobs_summary.workflow_request).json() - self.workflow_populator.wait_for_workflow(workflow_request['workflow_id'], run_workflow_response['id'], history_id, assert_ok=True) - # Now make sure that the HDAs in each history point to the same dataset instances - history_one_contents = self.__history_contents(history_id) - history_two_contents = self.__history_contents(new_history_id) - assert len(history_one_contents) == len(history_two_contents) - for i, (item_one, item_two) in enumerate(zip(history_one_contents, history_two_contents)): - assert item_one['dataset_id'] == item_two['dataset_id'], \ - 'Dataset ids should match, but "%s" and "%s" are not the same for History item %i' % (item_one['dataset_id'], - item_two['dataset_id'], - i + 1) - - def _workflow_rerun_with_use_cached_job(self, workflow=None, workflow_id=None): # We launch a workflow with self.dataset_populator.test_history() as history_id: - if workflow: - workflow_request, _ = self._setup_workflow_run(workflow, history_id=history_id) - else: - workflow_request, _ = self._setup_workflow_run(workflow_id=workflow_id, history_id=history_id) + workflow_request, _ = self._setup_workflow_run(workflow, history_id=history_id) run_workflow_response = self._post("workflows", data=workflow_request).json() # We copy the workflow inputs to a new history new_workflow_request = workflow_request.copy() @@ -1888,6 +1844,46 @@ test_data: second_wf_output_filename = self._get("datasets/%s" % new_workflow_response['outputs'][0]).json()['file_name'] self.assertEquals(first_wf_output_filename, second_wf_output_filename) + @skip_without_tool('cat1') + def test_nested_workflow_rerun_with_use_cached_job(self): + with self.dataset_populator.test_history() as history_id_one, self.dataset_populator.test_history() as history_id_two: + workflow_run_description = """%s + +test_data: + outer_input: + value: 1.bed + type: File +""" % SIMPLE_NESTED_WORKFLOW_YAML + run_jobs_summary = self._run_jobs(workflow_run_description, history_id=history_id_one) + self.dataset_populator.wait_for_history(history_id_one, assert_ok=True) + workflow_request = run_jobs_summary.workflow_request + # We copy the inputs to a new history and re-reun the workflow + inputs = json.loads(workflow_request['inputs']) + dataset_type = inputs['outer_input']['src'] + dataset_id = inputs['outer_input']['id'] + copy_payload = {"content": dataset_id, "source": dataset_type, "type": "dataset"} + copy_response = self._post("histories/%s/contents" % history_id_two, data=copy_payload) + self._assert_status_code_is(copy_response, 200) + new_dataset_id = copy_response.json()['id'] + inputs['outer_input']['id'] = new_dataset_id + workflow_request['use_cached_job'] = True + workflow_request['history'] = "hist_id={history_id_two}".format(history_id_two=history_id_two) + workflow_request['inputs'] = json.dumps(inputs) + run_workflow_response = self._post("workflows", data=run_jobs_summary.workflow_request).json() + self.workflow_populator.wait_for_workflow(workflow_request['workflow_id'], + run_workflow_response['id'], + history_id_two, + assert_ok=True) + # Now make sure that the HDAs in each history point to the same dataset instances + history_one_contents = self.__history_contents(history_id_one) + history_two_contents = self.__history_contents(history_id_two) + assert len(history_one_contents) == len(history_two_contents) + for i, (item_one, item_two) in enumerate(zip(history_one_contents, history_two_contents)): + assert item_one['dataset_id'] == item_two['dataset_id'], \ + 'Dataset ids should match, but "%s" and "%s" are not the same for History item %i.' % (item_one['dataset_id'], + item_two['dataset_id'], + i + 1) + def test_cannot_run_inaccessible_workflow(self): workflow = self.workflow_populator.load_workflow(name="test_for_run_cannot_access") workflow_request, history_id = self._setup_workflow_run(workflow) From 81c3b8cc4d6439aa7d44b1d4cece284c0bb6b813 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sun, 17 Dec 2017 18:26:22 +0100 Subject: [PATCH 45/51] Include more information in assertion --- test/api/test_workflows.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/api/test_workflows.py b/test/api/test_workflows.py index 09f9ba8da60..77559b7468e 100644 --- a/test/api/test_workflows.py +++ b/test/api/test_workflows.py @@ -1840,9 +1840,10 @@ test_data: new_workflow_request['use_cached_job'] = True # We run the workflow again, it should not produce any new outputs new_workflow_response = self._post("workflows", data=new_workflow_request).json() - first_wf_output_filename = self._get("datasets/%s" % run_workflow_response['outputs'][0]).json()['file_name'] - second_wf_output_filename = self._get("datasets/%s" % new_workflow_response['outputs'][0]).json()['file_name'] - self.assertEquals(first_wf_output_filename, second_wf_output_filename) + first_wf_output = self._get("datasets/%s" % run_workflow_response['outputs'][0]).json() + second_wf_output = self._get("datasets/%s" % new_workflow_response['outputs'][0]).json() + assert first_wf_output['file_name'] == second_wf_output['file_name'], \ + "first output :\n%s\nsecond output: %s" % (first_wf_output, second_wf_output) @skip_without_tool('cat1') def test_nested_workflow_rerun_with_use_cached_job(self): From e973403ae58154de1c65f24dc822eff401e9febb Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Tue, 19 Dec 2017 16:31:45 +0100 Subject: [PATCH 46/51] Simplify retrieval of extra_user_preferences --- lib/galaxy/model/__init__.py | 12 ++++++++++++ lib/galaxy/webapps/galaxy/api/users.py | 24 ++++++++++++------------ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 9f9b9924612..984dfb883b1 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -5,6 +5,7 @@ Naming: try to use class names that have a distinct plural form so that the relationship cardinalities are obvious (e.g. prefer Dataset to Data) """ import errno +import json import logging import numbers import operator @@ -211,6 +212,17 @@ class User(object, Dictifiable): self.credentials = [] # ? self.roles = [] + @property + def extra_preferences(self): + data = {} + extra_user_preferences = self.preferences.get('extra_user_preferences') + if extra_user_preferences: + try: + data = json.loads(extra_user_preferences) + except Exception: + pass + return data + def set_password_cleartext(self, cleartext): """ Set user password to the digest of `cleartext`. diff --git a/lib/galaxy/webapps/galaxy/api/users.py b/lib/galaxy/webapps/galaxy/api/users.py index faced8ccf5b..c3adcc95b26 100644 --- a/lib/galaxy/webapps/galaxy/api/users.py +++ b/lib/galaxy/webapps/galaxy/api/users.py @@ -296,21 +296,21 @@ class UserAPIController(BaseAPIController, UsesTagsMixin, CreatesUsersMixin, Cre """ if not preferences: return [] - data = [] - # Get data if present - data_key = "extra_user_preferences" - if data_key in user.preferences: - data = json.loads(user.preferences[data_key]) extra_pref_inputs = list() # Build sections for different categories of inputs for item, value in preferences.items(): if value is not None: for input in value["inputs"]: - input['help'] = 'Required' if input['required'] else '' + help = input.get('help', '') + required = 'Required' if util.string_as_bool(input.get('required')) else '' + if help: + input['help'] = "%s %s" % (help, required) + else: + input['help'] = required field = item + '|' + input['name'] - for data_item in data: + for data_item in user.extra_preferences: if field in data_item: - input['value'] = data[data_item] + input['value'] = user.extra_preferences[data_item] extra_pref_inputs.append({'type': 'section', 'title': value['description'], 'name': item, 'expanded': True, 'inputs': value['inputs']}) return extra_pref_inputs @@ -455,9 +455,9 @@ class UserAPIController(BaseAPIController, UsesTagsMixin, CreatesUsersMixin, Cre # Update values for extra user preference items extra_user_pref_data = dict() - get_extra_pref_keys = self._get_extra_user_preferences(trans) - if get_extra_pref_keys is not None: - for key in get_extra_pref_keys: + extra_pref_keys = self._get_extra_user_preferences(trans) + if extra_pref_keys is not None: + for key in extra_pref_keys: key_prefix = key + '|' for item in payload: if item.startswith(key_prefix): @@ -465,7 +465,7 @@ class UserAPIController(BaseAPIController, UsesTagsMixin, CreatesUsersMixin, Cre if payload[item] == "": # Raise an exception when a required field is empty while saving the form keys = item.split("|") - section = get_extra_pref_keys[keys[0]] + 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") From 3f3476495815e6fceaa555c5ca0bbb997515447d Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Tue, 19 Dec 2017 16:33:52 +0100 Subject: [PATCH 47/51] Only show use_cached_job options if user has activated this feature in the user preferences --- .../scripts/mvc/tool/tool-form-composite.js | 40 ++++++++++--------- client/galaxy/scripts/mvc/tool/tool-form.js | 26 +++++++----- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/client/galaxy/scripts/mvc/tool/tool-form-composite.js b/client/galaxy/scripts/mvc/tool/tool-form-composite.js index 4db7cda1d8c..35207943334 100644 --- a/client/galaxy/scripts/mvc/tool/tool-form-composite.js +++ b/client/galaxy/scripts/mvc/tool/tool-form-composite.js @@ -314,24 +314,28 @@ var View = Backbone.View.extend({ /** Render job caching option */ _renderUseCachedJob: function() { - this.job_options_form = new Form({ - cls: "ui-portlet-narrow", - title: "Job re-use Options", - inputs: [ - { - type: "conditional", - name: "use_cached_job", - test_param: { - name: "check", - label: "BETA: Attempt to reuse jobs with identical parameters?", - type: "boolean", - value: "false", - help: "This may skip executing jobs that you have already run." - }, - } - ] - }); - this._append(this.$steps, this.job_options_form.$el); + var extra_user_preferences = JSON.parse(Galaxy.user.attributes.preferences.extra_user_preferences); + var use_cached_job = 'use_cached_job|use_cached_job_checkbox' in extra_user_preferences ? extra_user_preferences['use_cached_job|use_cached_job_checkbox'] : false ; + if (use_cached_job === 'true'){ + this.job_options_form = new Form({ + cls: "ui-portlet-narrow", + title: "Job re-use Options", + inputs: [ + { + type: "conditional", + name: "use_cached_job", + test_param: { + name: "check", + label: "BETA: Attempt to reuse jobs with identical parameters?", + type: "boolean", + value: "false", + help: "This may skip executing jobs that you have already run." + }, + } + ] + }); + this._append(this.$steps, this.job_options_form.$el); + } }, /** Render step */ diff --git a/client/galaxy/scripts/mvc/tool/tool-form.js b/client/galaxy/scripts/mvc/tool/tool-form.js index c035bebf270..ed42d5b2d7b 100644 --- a/client/galaxy/scripts/mvc/tool/tool-form.js +++ b/client/galaxy/scripts/mvc/tool/tool-form.js @@ -149,17 +149,21 @@ var View = Backbone.View.extend({ }); } - // Job Re-use Options - options.inputs.push({ - label: "BETA: Attempt to re-use jobs with identical parameters ?", - help: "This may skip executing jobs that you have already run", - name: "use_cached_job", - type: "select", - display: "radio", - ignore: "__ignore__", - value: "__ignore__", - options: [["No", false], ["Yes", true]], - }); + // Job Re-use Options + var extra_user_preferences = JSON.parse(Galaxy.user.attributes.preferences.extra_user_preferences); + var use_cached_job = 'use_cached_job|use_cached_job_checkbox' in extra_user_preferences ? extra_user_preferences['use_cached_job|use_cached_job_checkbox'] : false ; + if (use_cached_job === 'true'){ + options.inputs.push({ + label: "BETA: Attempt to re-use jobs with identical parameters ?", + help: "This may skip executing jobs that you have already run", + name: "use_cached_job", + type: "select", + display: "radio", + ignore: "__ignore__", + value: "__ignore__", + options: [["No", false], ["Yes", true]], + }); + } }, /** Submit a regular job. From 58b8aa986bf3af32de71d6cf17f1f2c60adff17f Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Tue, 19 Dec 2017 16:37:07 +0100 Subject: [PATCH 48/51] Add template to user_preferences_extra_conf.yml.sample for activating job reuse feature --- config/user_preferences_extra_conf.yml.sample | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/config/user_preferences_extra_conf.yml.sample b/config/user_preferences_extra_conf.yml.sample index 8f8ef07670a..0718773e478 100644 --- a/config/user_preferences_extra_conf.yml.sample +++ b/config/user_preferences_extra_conf.yml.sample @@ -39,3 +39,13 @@ preferences: label: Maximum number of search results type: text required: False + + use_cached_job: + description: Do you want to be able to re-use previously run jobs ? + inputs: + - name: use_cached_job_checkbox + label: Do you want to be able to re-use equivalent jobs ? + type: boolean + checked: false + value: false + help: If you select yes, you will be able to select for each tool and workflow run if you would like to use this feature. From 47c2f3c0d4e42c27c46ae7f7016900f60ea5c8eb Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Tue, 19 Dec 2017 17:06:11 +0100 Subject: [PATCH 49/51] Don't assume extra_preferences exists --- client/galaxy/scripts/mvc/tool/tool-form-composite.js | 5 ++++- client/galaxy/scripts/mvc/tool/tool-form.js | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/client/galaxy/scripts/mvc/tool/tool-form-composite.js b/client/galaxy/scripts/mvc/tool/tool-form-composite.js index 35207943334..878bebbbb48 100644 --- a/client/galaxy/scripts/mvc/tool/tool-form-composite.js +++ b/client/galaxy/scripts/mvc/tool/tool-form-composite.js @@ -314,7 +314,10 @@ var View = Backbone.View.extend({ /** Render job caching option */ _renderUseCachedJob: function() { - var extra_user_preferences = JSON.parse(Galaxy.user.attributes.preferences.extra_user_preferences); + var extra_user_preferences = {}; + if (Galaxy.user.attributes.preferences && 'extra_user_preferences' in Galaxy.user.attributes.preferences){ + extra_user_preferences = JSON.parse(Galaxy.user.attributes.preferences.extra_user_preferences); + } var use_cached_job = 'use_cached_job|use_cached_job_checkbox' in extra_user_preferences ? extra_user_preferences['use_cached_job|use_cached_job_checkbox'] : false ; if (use_cached_job === 'true'){ this.job_options_form = new Form({ diff --git a/client/galaxy/scripts/mvc/tool/tool-form.js b/client/galaxy/scripts/mvc/tool/tool-form.js index ed42d5b2d7b..e0038c3c97c 100644 --- a/client/galaxy/scripts/mvc/tool/tool-form.js +++ b/client/galaxy/scripts/mvc/tool/tool-form.js @@ -150,7 +150,10 @@ var View = Backbone.View.extend({ } // Job Re-use Options - var extra_user_preferences = JSON.parse(Galaxy.user.attributes.preferences.extra_user_preferences); + var extra_user_preferences = {}; + if (Galaxy.user.attributes.preferences && 'extra_user_preferences' in Galaxy.user.attributes.preferences) { + extra_user_preferences = JSON.parse(Galaxy.user.attributes.preferences.extra_user_preferences); + } var use_cached_job = 'use_cached_job|use_cached_job_checkbox' in extra_user_preferences ? extra_user_preferences['use_cached_job|use_cached_job_checkbox'] : false ; if (use_cached_job === 'true'){ options.inputs.push({ From 3d45cce423de3e4b323b54fb3fd22e354a52dad7 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Wed, 20 Dec 2017 16:57:45 +0100 Subject: [PATCH 50/51] Fix undefined variable --- client/galaxy/scripts/mvc/tool/tool-form-composite.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/client/galaxy/scripts/mvc/tool/tool-form-composite.js b/client/galaxy/scripts/mvc/tool/tool-form-composite.js index 878bebbbb48..9a6cd1ab24e 100644 --- a/client/galaxy/scripts/mvc/tool/tool-form-composite.js +++ b/client/galaxy/scripts/mvc/tool/tool-form-composite.js @@ -318,8 +318,9 @@ var View = Backbone.View.extend({ if (Galaxy.user.attributes.preferences && 'extra_user_preferences' in Galaxy.user.attributes.preferences){ extra_user_preferences = JSON.parse(Galaxy.user.attributes.preferences.extra_user_preferences); } - var use_cached_job = 'use_cached_job|use_cached_job_checkbox' in extra_user_preferences ? extra_user_preferences['use_cached_job|use_cached_job_checkbox'] : false ; - if (use_cached_job === 'true'){ + var display_use_cached_job_checkbox = 'use_cached_job|use_cached_job_checkbox' in extra_user_preferences ? extra_user_preferences['use_cached_job|use_cached_job_checkbox'] : false ; + this.display_use_cached_job_checkbox = display_use_cached_job_checkbox === 'true'; + if (this.display_use_cached_job_checkbox){ this.job_options_form = new Form({ cls: "ui-portlet-narrow", title: "Job re-use Options", @@ -527,10 +528,8 @@ var View = Backbone.View.extend({ _submit: function() { var self = this; var history_form_data = this.history_form.data.create(); - var job_options_form_data = this.job_options_form.data.create(); var job_def = { new_history_name: history_form_data["new_history|name"] ? history_form_data["new_history|name"] : null, - use_cached_job: true ? job_options_form_data["use_cached_job|check"] === 'true' : false, history_id: !history_form_data["new_history|name"] ? this.model.get("history_id") : null, replacement_params: this.wp_form ? this.wp_form.data.create() : {}, parameters: {}, @@ -542,6 +541,9 @@ var View = Backbone.View.extend({ // so that inputs can be batched. batch: true }; + if (this.display_use_cached_job_checkbox) { + job_def['use_cached_job'] = this.job_options_form.data.create()["use_cached_job|check"] === 'true'; + } var validated = true; for (var i in this.forms) { var form = this.forms[i]; From 655af3026b154e02fb6b25f320b9828b64451deb Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sun, 31 Dec 2017 18:31:27 +0200 Subject: [PATCH 51/51] Build client --- static/scripts/bundled/analysis.bundled.js | 2 +- static/scripts/bundled/libs.bundled.js | 2 +- static/scripts/libs/vue.js | 1 + static/scripts/mvc/tool/tool-form-composite.js | 2 +- static/scripts/mvc/tool/tool-form.js | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 static/scripts/libs/vue.js diff --git a/static/scripts/bundled/analysis.bundled.js b/static/scripts/bundled/analysis.bundled.js index e8977269dd4..310f3be5be7 100644 --- a/static/scripts/bundled/analysis.bundled.js +++ b/static/scripts/bundled/analysis.bundled.js @@ -1,3 +1,3 @@ webpackJsonp([1],[,,,,,,,,function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n={UPLOAD:"upload",QUEUED:"queued",RUNNING:"running",SETTING_METADATA:"setting_metadata",NEW:"new",EMPTY:"empty",OK:"ok",PAUSED:"paused",FAILED_METADATA:"failed_metadata",NOT_VIEWABLE:"noPermission",DISCARDED:"discarded",ERROR:"error"};n.READY_STATES=[n.OK,n.EMPTY,n.PAUSED,n.FAILED_METADATA,n.NOT_VIEWABLE,n.DISCARDED,n.ERROR],n.NOT_READY_STATES=[n.UPLOAD,n.QUEUED,n.RUNNING,n.SETTING_METADATA,n.NEW],t.default=n},,function(e,t,i){"use strict";(function(e){Object.defineProperty(t,"__esModule",{value:!0});var n=i(0),s=function(e){return e&&e.__esModule?e:{default:e}}(n),a=s.default,o=function(t){t=t||{},t.tooltipConfig=t.tooltipConfig||{placement:"bottom"},t.classes=["icon-btn"].concat(t.classes||[]),t.disabled&&t.classes.push("disabled");var i=['','',""].join(""),n=a(i).tooltip(t.tooltipConfig);return e.isFunction(t.onclick)&&n.click(t.onclick),n};t.default=o}).call(t,i(1))},,function(e,t,i){"use strict";(function(e,n){Object.defineProperty(t,"__esModule",{value:!0});var s=i(5),a=function(e){return e&&e.__esModule?e:{default:e}}(s),o=e.View.extend({optionsDefault:{css:"",placeholder:"No data available",data:[],value:null,multiple:!1,minimumInputLength:0,initialData:""},initialize:function(e){if(this.options=a.default.merge(e,this.optionsDefault),this.setElement(this._template(this.options)),!this.options.container)return void console.log("ui-select::initialize() : container not specified.");if(this.options.container.append(this.$el),this.select_data=this.options.data,this._refresh(),!this.options.multiple){this.options.value&&this._setValue(this.options.value);var t=this;this.options.onchange&&this.$el.on("change",function(){t.options.onchange(t.value())})}},value:function(e){var t=this._getValue();void 0!==e&&this._setValue(e);var i=this._getValue();return i!=t&&this.options.onchange&&this.options.onchange(i),i},text:function(){return this.$el.select2("data").text},disabled:function(){return!this.$el.select2("enable")},enable:function(){this.$el.select2("enable",!0)},disable:function(){this.$el.select2("enable",!1)},add:function(e){this.select_data.push({id:e.id,text:e.text}),this._refresh()},del:function(e){var t=this._getIndex(e);-1!=t&&(this.select_data.splice(t,1),this._refresh())},remove:function(){this.$el.select2("destroy")},update:function(e){this.select_data=[];for(var t in e.data)this.select_data.push(e.data[t]);this._refresh()},_refresh:function(){if(this.options.multiple){var e={multiple:this.options.multiple,containerCssClass:this.options.css,placeholder:this.options.placeholder,minimumInputLength:this.options.minimumInputLength,ajax:this.options.ajax,dropdownCssClass:this.options.dropdownCssClass,escapeMarkup:this.options.escapeMarkup,formatResult:this.options.formatResult,formatSelection:this.options.formatSelection,initSelection:this.options.initSelection,initialData:this.options.initialData};this.$el.select2(e)}else{var t=this._getValue(),e={data:this.select_data,containerCssClass:this.options.css,placeholder:this.options.placeholder,dropdownAutoWidth:!0};this.$el.select2(e),this._setValue(t)}},_getIndex:function(e){n.findIndex(this.select_data,{id:e})},_getValue:function(){return this.$el.select2("val")},_setValue:function(e){-1==this._getIndex(e)&&this.select_data.length>0&&(e=this.select_data[0].id),this.$el.select2("val",e)},_template:function(e){return''}});t.default={View:o}}).call(t,i(2),i(1))},,,,function(e,t,i){"use strict";(function(e,n,s){function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(56),l=a(o),r=i(37),d=a(r),c=i(20),u=a(c),h=i(31),f=a(h),p=i(70),m=a(p),g=i(4),_=a(g),v=i(32),w=a(v),y=500;try{y=localStorage.getItem("historyContentsLimitPerPageDefault")||y}catch(e){}var b=l.default.PaginatedCollection,x=b.extend(_.default.LoggableMixin).extend({_logNamespace:"history",limitPerPage:y,limitPerProgressiveFetch:y,order:"hid",urlRoot:Galaxy.root+"api/histories",url:function(){return this.urlRoot+"/"+this.historyId+"/contents"},initialize:function(e,t){this.on({"sync add":this.trackJobStates}),t=t||{},b.prototype.initialize.call(this,e,t),this.history=t.history||null,this.setHistoryId(t.historyId||null),this.includeDeleted=t.includeDeleted||this.includeDeleted,this.includeHidden=t.includeHidden||this.includeHidden,this.model.prototype.idAttribute="type_id"},trackJobStates:function(){var e=this;this.each(function(t){if(!t.has("job_states_summary")&&"dataset_collection"===t.attributes.history_content_type){var i=t.attributes.job_source_type,n=t.attributes.job_source_id;if(i){e.jobStateSummariesCollection.add({id:n,model:i,history_id:e.history_id,collection_id:t.attributes.id});var s=e.jobStateSummariesCollection.get(n);t.jobStatesSummary=s}}})},model:function(e,t){return"dataset"===e.history_content_type?new d.default.HistoryDatasetAssociation(e,t):"dataset_collection"===e.history_content_type?new u.default.HistoryDatasetCollection(e,t):{validationError:"Unknown history_content_type: "+e.history_content_type}},stopPolling:function(){this.jobStateSummariesCollection&&(this.jobStateSummariesCollection.active=!1,this.jobStateSummariesCollection.clearUpdateTimeout())},setHistoryId:function(e){this.stopPolling(),this.historyId=e,e&&(this._setUpWebStorage(),this.jobStateSummariesCollection=new m.default.JobStatesSummaryCollection,this.jobStateSummariesCollection.historyId=e,this.jobStateSummariesCollection.monitor())},_setUpWebStorage:function(e){return this.storage=new f.default.HistoryPrefs({id:f.default.HistoryPrefs.historyStorageKey(this.historyId)}),this.trigger("new-storage",this.storage,this),this.on({"include-deleted":function(e){this.storage.includeDeleted(e)},"include-hidden":function(e){this.storage.includeHidden(e)}}),this.includeDeleted=this.storage.includeDeleted()||!1,this.includeHidden=this.storage.includeHidden()||!1,this},comparators:e.extend(e.clone(b.prototype.comparators),{name:_.default.buildComparator("name",{ascending:!0}),"name-dsc":_.default.buildComparator("name",{ascending:!1}),hid:_.default.buildComparator("hid",{ascending:!1}),"hid-asc":_.default.buildComparator("hid",{ascending:!0})}),running:function(){return this.filter(function(e){return!e.inReadyState()})},runningAndActive:function(){return this.filter(function(e){return!e.inReadyState()&&e.get("visible")&&!e.get("deleted")})},getByHid:function(e){return this.findWhere({hid:e})},haveDetails:function(){return this.all(function(e){return e.hasDetails()})},hidden:function(){return this.filter(function(e){return e.hidden()})},deleted:function(){return this.filter(function(e){return e.get("deleted")})},visibleAndUndeleted:function(){return this.filter(function(e){return e.get("visible")&&!e.get("deleted")})},setIncludeDeleted:function(t,i){if(e.isBoolean(t)&&t!==this.includeDeleted){if(this.includeDeleted=t,e.result(i,"silent"))return;this.trigger("include-deleted",t,this)}},setIncludeHidden:function(t,i){if(e.isBoolean(t)&&t!==this.includeHidden){if(this.includeHidden=t,i=i||{},e.result(i,"silent"))return;this.trigger("include-hidden",t,this)}},fetch:function(t){if(t=t||{},this.historyId&&!t.details){var i=f.default.HistoryPrefs.get(this.historyId).toJSON();e.isEmpty(i.expandedIds)||(t.details=e.values(i.expandedIds).join(","))}return b.prototype.fetch.call(this,t)},_buildFetchData:function(t){return e.extend(b.prototype._buildFetchData.call(this,t),{v:"dev"})},_fetchParams:b.prototype._fetchParams.concat(["v","details"]),_buildFetchFilters:function(t){var i=b.prototype._buildFetchFilters.call(this,t)||{},n={};return this.includeDeleted||(n.deleted=!1,n.purged=!1),this.includeHidden||(n.visible=!0),e.defaults(i,n)},getTotalItemCount:function(){return this.history.contentsShown()},fetchUpdated:function(e,t){return e&&(t=t||{filters:{}},t.remove=!1,t.filters={"update_time-ge":e.toISOString(),visible:""}),this.fetch(t)},fetchDeleted:function(t){var i=this;return t=t||{},t.filters=e.extend(t.filters,{deleted:!0,purged:void 0}),t.remove=!1,this.trigger("fetching-deleted",this),this.fetch(t).always(function(){i.trigger("fetching-deleted-done",i)})},fetchHidden:function(t){t=t||{};var i=this;return t.filters=e.extend(t.filters,{visible:!1}),t.remove=!1,i.trigger("fetching-hidden",i),i.fetch(t).always(function(){i.trigger("fetching-hidden-done",i)})},fetchAllDetails:function(t){t=t||{};var i={details:"all"};return t.data=e.extend(t.data||{},i),this.fetch(t)},_filterAndUpdate:function(e,t){var i=this,n=i.model.prototype.idAttribute,s=[t];return i.fetch({filters:e,remove:!1}).then(function(e){return e=e.reduce(function(e,t,s){var a=i.get(t[n]);return a?e.concat(a):e},[]),i.ajaxQueue("save",s,e)})},ajaxQueue:function(t,i,n){return n=n||this.models,new w.default.AjaxQueue(n.slice().reverse().map(function(n,s){var a=e.isString(t)?n[t]:t;return function(){return a.apply(n,i)}})).deferred},progressivelyFetchDetails:function(t){function i(n){n=n||0;var l=e.extend(e.clone(t),{view:"summary",keys:r,limit:o,offset:n,reset:0===n,remove:!1});e.defer(function(){a.fetch.call(a,l).fail(s.reject).done(function(e){s.notify(e,o,n),e.length!==o?(a.allFetched=!0,s.resolve(e,o,n)):i(n+o)})})}t=t||{};var s=n.Deferred(),a=this,o=t.limitPerCall||a.limitPerProgressiveFetch,l=d.default.HistoryDatasetAssociation.prototype.searchAttributes,r=l.join(",");return i(),s},isCopyable:function(t){var i=["HistoryDatasetAssociation","HistoryDatasetCollectionAssociation"];return e.isObject(t)&&t.id&&e.contains(i,t.model_class)},copy:function(t){var i,s,a;e.isString(t)?(i=t,a="hda",s="dataset"):(i=t.id,a={HistoryDatasetAssociation:"hda",LibraryDatasetDatasetAssociation:"ldda",HistoryDatasetCollectionAssociation:"hdca"}[t.model_class]||"hda",s="hdca"===a?"dataset_collection":"dataset");var o=this,l=n.ajax(this.url(),{method:"POST",contentType:"application/json",data:JSON.stringify({content:i,source:a,type:s})}).done(function(e){o.add([e],{parse:!0})}).fail(function(e,t,n){o.trigger("error",o,l,{},"Error copying contents",{type:s,id:i,source:a})});return l},createHDCA:function(e,t,i,n,s){return this.model({history_content_type:"dataset_collection",collection_type:t,history_id:this.historyId,name:i,hide_source_items:n||!1,element_identifiers:e}).save(s)},haveSearchDetails:function(){return this.allFetched&&this.all(function(t){return e.has(t.attributes,"annotation")})},matches:function(e){return this.filter(function(t){return t.matches(e)})},clone:function(){var e=s.Collection.prototype.clone.call(this);return e.historyId=this.historyId,e},toString:function(){return["HistoryContents(",[this.historyId,this.length].join(),")"].join("")}});t.default={HistoryContents:x}}).call(t,i(1),i(0),i(2))},function(e,t,i){"use strict";(function(e,n,s,a){function o(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var l=i(21),r=o(l),d=i(8),c=o(d),u=i(10),h=o(u),f=i(4),p=o(f),m=i(3),g=o(m),_=r.default.ListItemView,v=_.extend({_logNamespace:"dataset",className:_.prototype.className+" dataset",id:function(){return["dataset",this.model.get("id")].join("-")},initialize:function(e){e.logger&&(this.logger=this.model.logger=e.logger),this.log(this+".initialize:",e),_.prototype.initialize.call(this,e),this.linkTarget=e.linkTarget||"_blank"},_setUpListeners:function(){_.prototype._setUpListeners.call(this);var t=this;return t.listenTo(t.model,{change:function(i){t.model.changedAttributes().state&&t.model.inReadyState()&&t.expanded&&!t.model.hasDetails()?t.model.fetch({silent:!0}).done(function(){t.render()}):e.has(i.changed,"tags")&&1===e.keys(i.changed).length?t.$(".nametags").html(t._renderNametags()):t.render()}})},_fetchModelDetails:function(){var e=this;return e.model.inReadyState()&&!e.model.hasDetails()?e.model.fetch({silent:!0}):n.when()},remove:function(e,t){var i=this;e=e||this.fxSpeed,this.$el.fadeOut(e,function(){s.View.prototype.remove.call(i),t&&t.call(i)})},_swapNewRender:function(e){return _.prototype._swapNewRender.call(this,e),this.model.has("state")&&this.$el.addClass("state-"+this.model.get("state")),this.$el},_renderPrimaryActions:function(){return[this._renderDisplayButton()]},_renderDisplayButton:function(){var e=this.model.get("state");if(e===c.default.NOT_VIEWABLE||e===c.default.DISCARDED||!this.model.get("accessible"))return null;var t={target:this.linkTarget,classes:"display-btn"};if(this.model.get("purged"))t.disabled=!0,t.title=(0,g.default)("Cannot display datasets removed from disk");else if(e===c.default.UPLOAD)t.disabled=!0,t.title=(0,g.default)("This dataset must finish uploading before it can be viewed");else if(e===c.default.NEW)t.disabled=!0,t.title=(0,g.default)("This dataset is not yet viewable");else{t.title=(0,g.default)("View data"),t.href=this.model.urls.display;var i=this;t.onclick=function(e){Galaxy.frame&&Galaxy.frame.active&&(Galaxy.frame.addDataset(i.model.get("id")),e.preventDefault())}}return t.faIcon="fa-eye",(0,h.default)(t)},_renderDetails:function(){if(this.model.get("state")===c.default.NOT_VIEWABLE)return a(this.templates.noAccess(this.model.toJSON(),this));var e=_.prototype._renderDetails.call(this);return e.find(".actions .left").empty().append(this._renderSecondaryActions()),e.find(".summary").html(this._renderSummary()).prepend(this._renderDetailMessages()),e.find(".display-applications").html(this._renderDisplayApplications()),this._setUpBehaviors(e),e},_renderSummary:function(){var e=this.model.toJSON(),t=this.templates.summaries[e.state];return(t=t||this.templates.summaries.unknown)(e,this)},_renderDetailMessages:function(){var t=this,i=a('
'),n=t.model.toJSON();return e.each(t.templates.detailMessages,function(e){i.append(a(e(n,t)))}),i},_renderDisplayApplications:function(){return this.model.isDeletedOrPurged()?"":[this.templates.displayApplications(this.model.get("display_apps"),this),this.templates.displayApplications(this.model.get("display_types"),this)].join("")},_renderSecondaryActions:function(){switch(this.debug("_renderSecondaryActions"),this.model.get("state")){case c.default.NOT_VIEWABLE:return[];case c.default.OK:case c.default.FAILED_METADATA:case c.default.ERROR:return[this._renderDownloadButton(),this._renderShowParamsButton()]}return[this._renderShowParamsButton()]},_renderShowParamsButton:function(){return(0,h.default)({title:(0,g.default)("View details"),classes:"params-btn",href:this.model.urls.show_params,target:this.linkTarget,faIcon:"fa-info-circle",onclick:function(e){Galaxy.frame&&Galaxy.frame.active&&(Galaxy.frame.add({title:(0,g.default)("Dataset details"),url:this.href}),e.preventDefault(),e.stopPropagation())}})},_renderDownloadButton:function(){return this.model.get("purged")||!this.model.hasData()?null:e.isEmpty(this.model.get("meta_files"))?a('\n \n \n '):this._renderMetaFileDownloadButton()},_renderMetaFileDownloadButton:function(){var t=this.model.urls;return a('\n ")},_renderNametags:function(){return e.template(["<% _.each(_.sortBy(_.uniq(tags), function(x) { return x }), function(tag){ %>",'<% if (tag.indexOf("name:") == 0){ %>','<%- tag.slice(5) %>',"<% } %>","<% }); %>"].join(""))({tags:this.model.get("tags")})},events:e.extend(e.clone(_.prototype.events),{"click .display-btn":function(e){this.trigger("display",this,e)},"click .params-btn":function(e){this.trigger("params",this,e)},"click .download-btn":function(e){this.trigger("download",this,e)}}),toString:function(){return"DatasetListItemView("+(this.model?""+this.model:"(no model)")+")"}});v.prototype.templates=function(){var t=e.extend({},_.prototype.templates.warnings,{failed_metadata:p.default.wrapTemplate(['<% if( model.state === "failed_metadata" ){ %>','
',(0,g.default)("An error occurred setting the metadata for this dataset"),"
","<% } %>"]),error:p.default.wrapTemplate(["<% if( model.error ){ %>",'
',(0,g.default)("There was an error getting the data for this dataset"),": <%- model.error %>","
","<% } %>"]),purged:p.default.wrapTemplate(["<% if( model.purged ){ %>",'
',(0,g.default)("This dataset has been deleted and removed from disk"),"
","<% } %>"]),deleted:p.default.wrapTemplate(["<% if( model.deleted && !model.purged ){ %>",'
',(0,g.default)("This dataset has been deleted"),"
","<% } %>"])}),i=p.default.wrapTemplate(['
','
','
','
','
',"
","<% if( !dataset.deleted && !dataset.purged ){ %>",'
','
','
',"<% if( dataset.peek ){ %>",'
<%= dataset.peek %>
',"<% } %>","<% } %>","
"],"dataset"),n=p.default.wrapTemplate(['
','
',(0,g.default)("You do not have permission to view this dataset"),"
","
"],"dataset"),s={};s[c.default.OK]=s[c.default.FAILED_METADATA]=p.default.wrapTemplate(["<% if( dataset.misc_blurb ){ %>",'
','<%- dataset.misc_blurb %>',"
","<% } %>","<% if( dataset.file_ext ){ %>",'
','",'<%- dataset.file_ext %>',"
","<% } %>","<% if( dataset.metadata_dbkey ){ %>",'
','",'',"<%- dataset.metadata_dbkey %>","","
","<% } %>","<% if( dataset.misc_info ){ %>",'
','<%- dataset.misc_info %>',"
","<% } %>"],"dataset"),s[c.default.NEW]=p.default.wrapTemplate(["
",(0,g.default)("This is a new dataset and not all of its data are available yet"),"
"],"dataset"),s[c.default.NOT_VIEWABLE]=p.default.wrapTemplate(["
",(0,g.default)("You do not have permission to view this dataset"),"
"],"dataset"),s[c.default.DISCARDED]=p.default.wrapTemplate(["
",(0,g.default)("The job creating this dataset was cancelled before completion"),"
"],"dataset"),s[c.default.QUEUED]=p.default.wrapTemplate(["
",(0,g.default)("This job is waiting to run"),"
"],"dataset"),s[c.default.RUNNING]=p.default.wrapTemplate(["
",(0,g.default)("This job is currently running"),"
"],"dataset"),s[c.default.UPLOAD]=p.default.wrapTemplate(["
",(0,g.default)("This dataset is currently uploading"),"
"],"dataset"),s[c.default.SETTING_METADATA]=p.default.wrapTemplate(["
",(0,g.default)("Metadata is being auto-detected"),"
"],"dataset"),s[c.default.PAUSED]=p.default.wrapTemplate(["
",(0,g.default)('This job is paused. Use the "Resume Paused Jobs" in the history menu to resume'),"
"],"dataset"),s[c.default.ERROR]=p.default.wrapTemplate(["<% if( !dataset.purged ){ %>","
<%- dataset.misc_blurb %>
","<% } %>",'',(0,g.default)("An error occurred with this dataset"),":",'
<%- dataset.misc_info %>
'],"dataset"),s[c.default.EMPTY]=p.default.wrapTemplate(["
",(0,g.default)("No data"),": <%- dataset.misc_blurb %>
"],"dataset"),s.unknown=p.default.wrapTemplate(['
Error: unknown dataset state: "<%- dataset.state %>"
'],"dataset");var a={resubmitted:p.default.wrapTemplate(["<% if( model.resubmitted ){ %>",'
',(0,g.default)("The job creating this dataset has been resubmitted"),"
","<% } %>"])},o=p.default.wrapTemplate(["<% _.each( apps, function( app ){ %>",'
','<%- app.label %> ','',"<% _.each( app.links, function( link ){ %>",'',"<% print( _l( link.text ) ); %>"," ","<% }); %>","","
","<% }); %>"],"apps");return e.extend({},_.prototype.templates,{warnings:t,details:i,noAccess:n,summaries:s,detailMessages:a,displayApplications:o})}(),t.default={DatasetListItemView:v}}).call(t,i(1),i(0),i(2),i(0))},,,function(e,t,i){"use strict";(function(e){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var s=i(29),a=n(s),o=i(51),l=n(o),r=i(3),d=(n(r),l.default.HistoryContentMixin),c=a.default.DatasetCollection,u=c.extend(d).extend({defaults:e.extend(e.clone(c.prototype.defaults),{history_content_type:"dataset_collection",model_class:"HistoryDatasetCollectionAssociation"}),save:function(e,t){return this.isNew()&&(t=t||{},t.url=this.urlRoot+this.get("history_id")+"/contents",e=e||{},e.type="dataset_collection"),c.prototype.save.call(this,e,t)},toString:function(){return"History"+c.prototype.toString.call(this)}});t.default={HistoryDatasetCollection:u}}).call(t,i(1))},function(e,t,i){"use strict";(function(e,n,s,a){function o(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var l=i(4),r=o(l),d=i(3),c=(o(d),e.View.extend(r.default.LoggableMixin).extend({_logNamespace:"list",initialize:function(e){this.expanded=e.expanded||!1,this.log("\t expanded:",this.expanded),this.fxSpeed=void 0!==e.fxSpeed?e.fxSpeed:this.fxSpeed},fxSpeed:"fast",render:function(e){var t=this._buildNewRender();return this._setUpBehaviors(t),this._queueNewRender(t,e),this},_buildNewRender:function(){var e=n(this.templates.el(this.model.toJSON(),this));return this.expanded&&this.$details(e).replaceWith(this._renderDetails().show()),e},_queueNewRender:function(e,t){t=void 0===t?this.fxSpeed:t;var i=this;0===t?(i._swapNewRender(e),i.trigger("rendered",i)):n(i).queue("fx",[function(e){i.$el.fadeOut(t,e)},function(t){i._swapNewRender(e),t()},function(e){i.$el.fadeIn(t,e)},function(e){i.trigger("rendered",i),e()}])},_swapNewRender:function(e){return this.$el.empty().attr("class",s.isFunction(this.className)?this.className():this.className).append(e.children())},_setUpBehaviors:function(e){e=e||this.$el,e.find("[title]").tooltip({placement:"bottom"})},$details:function(e){return e=e||this.$el,e.find("> .details")},_renderDetails:function(){var e=n(this.templates.details(this.model.toJSON(),this));return this._setUpBehaviors(e),e},toggleExpanded:function(e){return e=void 0===e?!this.expanded:e,e?this.expand():this.collapse(),this},expand:function(){var e=this;return e._fetchModelDetails().always(function(){e._expand()})},_fetchModelDetails:function(){return this.model.hasDetails()?a.when():this.model.fetch()},_expand:function(){var e=this,t=e._renderDetails();e.$details().replaceWith(t),e.expanded=!0,e.$details().slideDown(e.fxSpeed,function(){e.trigger("expanded",e)})},collapse:function(){this.debug(this+"(ExpandableView).collapse");var e=this;e.expanded=!1,this.$details().slideUp(e.fxSpeed,function(){e.trigger("collapsed",e)})}})),u=c.extend(r.default.mixin(r.default.SelectableViewMixin,r.default.DraggableViewMixin,{tagName:"div",className:"list-item",initialize:function(e){c.prototype.initialize.call(this,e),r.default.SelectableViewMixin.initialize.call(this,e),r.default.DraggableViewMixin.initialize.call(this,e),this._setUpListeners()},_setUpListeners:function(){return this.on("selectable",function(e){e?this.$(".primary-actions").hide():this.$(".primary-actions").show()},this),this},_buildNewRender:function(){var e=c.prototype._buildNewRender.call(this);return e.children(".warnings").replaceWith(this._renderWarnings()),e.children(".title-bar").replaceWith(this._renderTitleBar()),e.children(".primary-actions").append(this._renderPrimaryActions()),e.find("> .title-bar .subtitle").replaceWith(this._renderSubtitle()),e},_swapNewRender:function(e){return c.prototype._swapNewRender.call(this,e),this.selectable&&this.showSelector(0),this.draggable&&this.draggableOn(),this.$el},_renderWarnings:function(){var e=this,t=n('
'),i=e.model.toJSON();return s.each(e.templates.warnings,function(s){t.append(n(s(i,e)))}),t},_renderTitleBar:function(){return n(this.templates.titleBar(this.model.toJSON(),this))},_renderPrimaryActions:function(){return[]},_renderSubtitle:function(){return n(this.templates.subtitle(this.model.toJSON(),this))},events:{"click .title-bar":"_clickTitleBar","keydown .title-bar":"_keyDownTitleBar","click .selector":"toggleSelect"},_clickTitleBar:function(e){e.stopPropagation(),e.altKey?(this.toggleSelect(e),this.selectable||this.showSelector()):this.toggleExpanded()},_keyDownTitleBar:function(e){return!e||"keydown"!==e.type||32!==e.keyCode&&13!==e.keyCode||(this.toggleExpanded(),e.stopPropagation(),!1)},toString:function(){return"ListItemView("+(this.model?""+this.model:"(no model)")+")"}}));u.prototype.templates=function(){return{el:r.default.wrapTemplate(['
','
','
','',"
",'
','
','
',"
"]),warnings:{},titleBar:r.default.wrapTemplate(['
','','
','<%- element.name %>',"
",'
',"
"],"element"),subtitle:r.default.wrapTemplate(['
']),details:r.default.wrapTemplate(['
'])}}();var h=u.extend({foldoutStyle:"foldout",foldoutPanelClass:null,initialize:function(e){"drilldown"===this.foldoutStyle&&(this.expanded=!1),this.foldoutStyle=e.foldoutStyle||this.foldoutStyle,this.foldoutPanelClass=e.foldoutPanelClass||this.foldoutPanelClass,u.prototype.initialize.call(this,e),this.foldout=this._createFoldoutPanel()},_renderDetails:function(){if("drilldown"===this.foldoutStyle)return n();var e=u.prototype._renderDetails.call(this);return this._attachFoldout(this.foldout,e)},_createFoldoutPanel:function(){var e=this.model,t=this._getFoldoutPanelClass(e),i=this._getFoldoutPanelOptions(e);return new t(s.extend(i,{model:e}))},_getFoldoutPanelClass:function(){return this.foldoutPanelClass},_getFoldoutPanelOptions:function(){return{foldoutStyle:this.foldoutStyle,fxSpeed:this.fxSpeed}},_attachFoldout:function(e,t){return t=t||this.$("> .details"),this.foldout=e.render(0),e.$("> .controls").hide(),t.append(e.$el)},expand:function(){var e=this;return e._fetchModelDetails().always(function(){"foldout"===e.foldoutStyle?e._expand():"drilldown"===e.foldoutStyle&&e._expandByDrilldown()})},_expandByDrilldown:function(){var e=this;e.listenTo(e.foldout,"close",function(){e.trigger("collapsed:drilldown",e,e.foldout)}),e.trigger("expanded:drilldown",e,e.foldout)}});h.prototype.templates=function(){var e=r.default.wrapTemplate(['
',"
"],"collection");return s.extend({},u.prototype.templates,{details:e})}(),t.default={ExpandableView:c,ListItemView:u,FoldoutListItemView:h}}).call(t,i(2),i(0),i(1),i(0))},function(e,t,i){"use strict";(function(e,n){function s(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=i(4),o=s(a),l=i(3),r=s(l),d=e.View.extend(o.default.LoggableMixin).extend(o.default.HiddenUntilActivatedViewMixin).extend({tagName:"div",className:"tags-display",select_width:"100%",events:{},initialize:function(e){this.show_editor=!1,!1===e.usePrompt?this.label="":this.label='",this.workflow_mode=e.workflow_mode||!1,this.workflow_mode&&(this.events.click="showEditor",this.events.keydown="keydownHandler"),this.hiddenUntilActivated(e.$activator,e)},render:function(){var e=this;return this.workflow_mode?this.$el.html(this._workflowTemplate()):this.$el.html(this._defaultTemplate()),this.$input().select2({placeholder:"Add tags",width:this.workflow_mode?this.width:this.select_width,tags:function(){return e._getTagsUsed()}}),this._setUpBehaviors(),this},_hashToName:function(e){return e.startsWith("#")?"name:"+e.slice(1):e},_nameToHash:function(e){return e.startsWith("name:")&&(e="#"+e.slice(5)),e},_defaultTemplate:function(){return[this.label,this._renderEditor()].join("")},_workflowTemplate:function(){return[this.show_editor?this._renderEditor():this._renderTags()].join(" ")},keydownHandler:function(e){switch(e.which){case 27:this.hideEditor()}},showEditor:function(){this.show_editor=!0,this.render()},hideEditor:function(){this.show_editor=!1,this.render()},_renderEditor:function(){return''},_renderTags:function(){var e=this.model.get("tags"),t=Galaxy.root+"static/images/fugue/tag--plus.png",i=[];return n.each(e,function(e){e=0==e.indexOf("name:")?e.slice(5):e;var t=''+e+"";i.push(t)}),0===i.length&&i.push("'),i.join(" ")},tagsToCSV:function(){var e=this,t=this.model.get("tags");return!n.isArray(t)||n.isEmpty(t)?"":t.map(function(t){return n.escape(e._nameToHash(t))}).sort().join(",")},$input:function(){return this.$el.find("input.tags-input")},_getTagsUsed:function(){var e=this;return n.map(Galaxy.user.get("tags_used"),e._nameToHash)},_setUpBehaviors:function(){var e=this;this.$input().on("change",function(t){t.val=n.map(t.val,e._hashToName),e.model.save({tags:t.val}),t.added&&e._addNewTagToTagsUsed(""+t.added.text)})},_addNewTagToTagsUsed:function(e){var t=Galaxy.user.get("tags_used");n.contains(t,e)||(t.push(e),t.sort(),Galaxy.user.set("tags_used",t))},remove:function(){this.$input.off(),this.stopListening(this.model),e.View.prototype.remove.call(this)},toString:function(){return["TagsEditor(",""+this.model,")"].join("")}});t.default={TagsEditor:d}}).call(t,i(2),i(1))},,,,,function(e,t,i){"use strict";(function(e,n,s){Object.defineProperty(t,"__esModule",{value:!0});var a=i(5),o=function(e){return e&&e.__esModule?e:{default:e}}(a),l=e.View.extend({optionsDefault:{with_close:!0,title:null,placement:"top",container:"body",body:null},initialize:function(e){this.setElement(this._template()),this.uid=o.default.uid(),this.options=n.defaults(e||{},this.optionsDefault),this.options.container.parent().append(this.el),this.$title=this.$(".popover-title-label"),this.$close=this.$(".popover-close"),this.$body=this.$(".popover-content"),this.options.body&&this.append(this.options.body);var t=this;s("body").on("mousedown."+this.uid,function(e){t.visible&&!s(t.options.container).is(e.target)&&!s(t.el).is(e.target)&&0===s(t.el).has(e.target).length&&t.hide()})},render:function(){this.$title.html(this.options.title),this.$el.removeClass().addClass("ui-popover popover fade in").addClass(this.options.placement),this.$el.css(this._get_placement(this.options.placement));var e=this;this.options.with_close?this.$close.on("click",function(){e.hide()}).show():this.$close.off().hide()},title:function(e){void 0!==e&&(this.options.title=e,this.$title.html(e))},show:function(){this.render(),this.$el.show(),this.visible=!0},hide:function(){this.$el.hide(),this.visible=!1},append:function(e){this.$body.append(e)},empty:function(){this.$body.empty()},remove:function(){s("body").off("mousedown."+this.uid),this.$el.remove()},_get_placement:function(e){var t,i,n=this._get_width(this.$el),s=this.$el.height(),a=this.options.container,o=this._get_width(a),l=this._get_height(a),r=a.position();if(t=i=0,-1!=["top","bottom"].indexOf(e))switch(i=r.left-n+(o+n)/2,e){case"top":t=r.top-s-5;break;case"bottom":t=r.top+l+5}else switch(t=r.top-s+(l+s)/2,e){case"right":i=r.left+o}return{top:t,left:i}},_get_width:function(e){return e.width()+parseInt(e.css("padding-left"))+parseInt(e.css("margin-left"))+parseInt(e.css("padding-right"))+parseInt(e.css("margin-right"))},_get_height:function(e){return e.height()+parseInt(e.css("padding-top"))+parseInt(e.css("padding-bottom"))},_template:function(e){return'
'}});t.default={View:l}}).call(t,i(2),i(1),i(0))},function(e,t,i){"use strict";(function(e,n,s,a){function o(e){return e&&e.__esModule?e:{default:e}}function l(e,t){var i=e.toJSON();return C(i,{defaultHideSourceItems:t,creationFn:function(t,i,n){return t=t.map(function(e){return{id:e.id,name:e.name,src:"dataset"===e.history_content_type?"hda":"hdca"}}),e.createHDCA(t,"list",i,n)}})}Object.defineProperty(t,"__esModule",{value:!0});var r=i(20),d=o(r),c=i(8),u=o(c),h=i(4),f=o(h),p=i(52),m=o(p),g=i(9),_=o(g),v=i(53),w=(o(v),i(3)),y=o(w);i(54);var b=e.View.extend(f.default.LoggableMixin).extend({_logNamespace:"collections",tagName:"li",className:"collection-element",initialize:function(e){this.element=e.element||{},this.selected=e.selected||!1},render:function(){this.dragStartHandler=n.bind(this._dragstart,this),this.dragEndHandler=n.bind(this._dragend,this);var e=this.$el.attr("data-element-id",this.element.id).attr("draggable",!0).html(this.template({element:this.element})).get(0);return e.addEventListener("dragstart",this.dragStartHandler,!1),e.addEventListener("dragend",this.dragEndHandler,!1),this.selected&&this.$el.addClass("selected"),this},template:n.template(['',"<%- element.name %>","",'"].join("")),select:function(e){this.$el.toggleClass("selected",e),this.trigger("select",{source:this,selected:this.$el.hasClass("selected")})},discard:function(){var e=this,t=this.$el.parent().width();this.$el.animate({"margin-right":t},"fast",function(){e.trigger("discard",{source:e}),e.destroy()})},destroy:function(){this.off(),this.$el.remove()},events:{click:"_click","click .name":"_clickName","click .discard":"_clickDiscard",dragover:"_sendToParent",drop:"_sendToParent"},_click:function(e){e.stopPropagation(),this.select(e)},_clickName:function(e){e.stopPropagation(),e.preventDefault();var t=([(0,y.default)("Enter a new name for the element"),":\n(",(0,y.default)("Note that changing the name here will not rename the dataset"),")"].join(""),prompt((0,y.default)("Enter a new name for the element")+":",this.element.name));t&&(this.element.name=t,this.render())},_clickDiscard:function(e){e.stopPropagation(),this.discard()},_dragstart:function(e){e.originalEvent&&(e=e.originalEvent),e.dataTransfer.effectAllowed="move",e.dataTransfer.setData("text/plain",JSON.stringify(this.element)),this.$el.addClass("dragging"),this.$el.parent().trigger("collection-element.dragstart",[this])},_dragend:function(e){this.$el.removeClass("dragging"),this.$el.parent().trigger("collection-element.dragend",[this])},_sendToParent:function(e){this.$el.parent().trigger(e)},toString:function(){return"DatasetCollectionElementView()"}}),x=e.View.extend(f.default.LoggableMixin).extend(m.default.CollectionCreatorMixin).extend({_logNamespace:"collections",elementViewClass:b,collectionClass:d.default.HistoryDatasetCollection,className:"list-collection-creator collection-creator flex-row-container",minElements:1,defaultAttributes:{creationFn:function(){throw new TypeError("no creation fn for creator")},oncreate:function(){},oncancel:function(){},autoscrollDist:24,highlightClr:"rgba( 64, 255, 255, 1.0 )"},footerSettings:{".hide-originals":"hideOriginals"},initialize:function(e){this.metric("ListCollectionCreator.initialize",e);var t=this;n.each(this.defaultAttributes,function(i,n){i=e[n]||i,t[n]=i}),t.initialElements=e.elements||[],this._setUpCommonSettings(e),this._instanceSetUp(),this._elementsSetUp(),this._setUpBehaviors()},_instanceSetUp:function(){this.selectedIds={},this.$dragging=null,this.blocking=!1},_elementsSetUp:function(){this.invalidElements=[],this.workingElements=[],this.elementViews=[],this.workingElements=this.initialElements.slice(0),this._ensureElementIds(),this._validateElements(),this._mangleDuplicateNames(),this._sortElements()},_ensureElementIds:function(){return this.workingElements.forEach(function(e){e.hasOwnProperty("id")||(e.id=n.uniqueId())}),this.workingElements},_validateElements:function(){var e=this;return e.invalidElements=[],this.workingElements=this.workingElements.filter(function(t){var i=e._isElementInvalid(t);return i&&e.invalidElements.push({element:t,text:i}),!i}),this.workingElements},_isElementInvalid:function(e){return"dataset_collection"===e.history_content_type?(0,y.default)("is a collection, this is not allowed"):e.state===u.default.OK||n.contains(u.default.NOT_READY_STATES,e.state)?e.deleted||e.purged?(0,y.default)("has been deleted or purged"):null:(0,y.default)("has errored, is paused, or is not accessible")},_mangleDuplicateNames:function(){var e=1,t={};this.workingElements.forEach(function(i){for(var n=i.name;t.hasOwnProperty(n);)if(n=i.name+" ("+e+")",(e+=1)>=900)throw new Error("Safety hit in while loop - thats impressive");i.name=n,t[i.name]=!0})},_sortElements:function(e){},render:function(e,t){return this.workingElements.length .clear-selected").show():this.$(".collection-elements-controls > .clear-selected").hide()},_renderList:function(e,t){var i=this,s=a("
"),o=i.$list();n.each(this.elementViews,function(e){e.destroy(),i.removeElementView(e)}),i.workingElements.forEach(function(e){var t=i._createElementView(e);s.append(t.$el)}),i._renderClearSelected(),o.empty().append(s.children()),n.invoke(i.elementViews,"render"),o.height()>o.css("max-height")?o.css("border-width","1px 0px 1px 0px"):o.css("border-width","0px")},_createElementView:function(e){var t=new this.elementViewClass({element:e,selected:n.has(this.selectedIds,e.id)});return this.elementViews.push(t),this._listenToElementView(t),t},_listenToElementView:function(e){var t=this;t.listenTo(e,{select:function(e){var i=e.source.element;e.selected?t.selectedIds[i.id]=!0:delete t.selectedIds[i.id],t.trigger("elements:select",e)},discard:function(e){t.trigger("elements:discard",e)}})},addElementView:function(e){},removeElementView:function(e){delete this.selectedIds[e.element.id],this._renderClearSelected(),this.elementViews=n.without(this.elementViews,e),this.stopListening(e)},_renderNoElementsLeft:function(){this._disableNameAndCreate(!0),this.$(".collection-elements").append(this.templates.noElementsLeft())},_elementToJSON:function(e){return e},createList:function(e){if(!this.workingElements.length){var t=(0,y.default)("No valid elements for final list")+". ";return t+=''+(0,y.default)("Cancel")+" ",t+=(0,y.default)("or"),t+=' '+(0,y.default)("start over")+".",void this._showAlert(t)}var i=this,n=this.workingElements.map(function(e){return i._elementToJSON(e)});return i.blocking=!0,i.creationFn(n,e,i.hideOriginals).always(function(){i.blocking=!1}).fail(function(e,t,n){i.trigger("error",{xhr:e,status:t,message:(0,y.default)("An error occurred while creating this collection")})}).done(function(e,t,n){i.trigger("collection:created",e,t,n),i.metric("collection:created",e),"function"==typeof i.oncreate&&i.oncreate.call(this,e,t,n)})},_setUpBehaviors:function(){return this.on("error",this._errorHandler),this.once("rendered",function(){this.trigger("rendered:initial",this)}),this.on("elements:select",function(e){this._renderClearSelected()}),this.on("elements:discard",function(e){var t=e.source.element;this.removeElementView(e.source),this.workingElements=n.without(this.workingElements,t),this.workingElements.length||this._renderNoElementsLeft()}),this},_errorHandler:function(e){this.error(e);var t=this,i=e.message||(0,y.default)("An error occurred");if(e.xhr){var n=e.xhr,s=e.message;0===n.readyState&&0===n.status?i+=": "+(0,y.default)("Galaxy could not be reached and may be updating.")+(0,y.default)(" Try again in a few minutes."):n.responseJSON?i+=":
"+JSON.stringify(n.responseJSON)+"
":i+=": "+s}t._showAlert(i,"alert-danger")},events:{"click .more-help":"_clickMoreHelp","click .less-help":"_clickLessHelp","click .main-help":"_toggleHelp","click .header .alert button":"_hideAlert","click .reset":"reset","click .clear-selected":"clearSelectedElements","click .collection-elements":"clearSelectedElements","dragover .collection-elements":"_dragoverElements","drop .collection-elements":"_dropElements","collection-element.dragstart .collection-elements":"_elementDragstart","collection-element.dragend .collection-elements":"_elementDragend","change .collection-name":"_changeName","keydown .collection-name":"_nameCheckForEnter","change .hide-originals":"_changeHideOriginals","click .cancel-create":"_cancelCreate","click .create-collection":"_clickCreate"},reset:function(){this._instanceSetUp(),this._elementsSetUp(),this.render()},clearSelectedElements:function(e){this.$(".collection-elements .collection-element").removeClass("selected"),this.$(".collection-elements-controls > .clear-selected").hide()},_dragoverElements:function(e){e.preventDefault();var t=this.$list();this._checkForAutoscroll(t,e.originalEvent.clientY);var i=this._getNearestElement(e.originalEvent.clientY);this.$(".element-drop-placeholder").remove();var n=s('
');i.length?i.before(n):t.append(n)},_checkForAutoscroll:function(e,t){var i=e.offset(),n=e.scrollTop(),s=t-i.top,a=i.top+e.outerHeight()-t;s>=0&&s=0&&ae&&a-o','',(0,y.default)("More help"),"",'","
",'
','','',"
"].join("")),middle:n.template(['",'
',"
"].join("")),footer:n.template(['
','
','","
",'
','','
',(0,y.default)("Name"),":
","
","
",'
','
','",'
','",'","
","
",'
','","
","
"].join("")),helpContent:n.template(["

",(0,y.default)(["Collections of datasets are permanent, ordered lists of datasets that can be passed to tools and ","workflows in order to have analyses done on each member of the entire group. This interface allows ","you to create a collection and re-order the final collection."].join("")),"

","
    ","
  • ",(0,y.default)(["Rename elements in the list by clicking on ",'the existing name.'].join("")),"
  • ","
  • ",(0,y.default)(["Discard elements from the final created list by clicking on the ",'"Discard" button.'].join("")),"
  • ","
  • ",(0,y.default)(["Reorder the list by clicking and dragging elements. Select multiple elements by clicking on ",'them and you can then move those selected by dragging the ',"entire group. Deselect them by clicking them again or by clicking the ",'the "Clear selected" link.'].join("")),"
  • ","
  • ",(0,y.default)(['Click the "Start over" link to begin again as if you had just opened ',"the interface."].join("")),"
  • ","
  • ",(0,y.default)(['Click the "Cancel" button to exit the interface.'].join("")),"
  • ","

","

",(0,y.default)(['Once your collection is complete, enter a name and ','click "Create list".'].join("")),"

"].join("")),invalidElements:n.template([(0,y.default)("The following selections could not be included due to problems:"),"
    <% _.each( problems, function( problem ){ %>","
  • <%- problem.element.name %>: <%- problem.text %>
  • ","<% }); %>
"].join("")),noElementsLeft:n.template(['
  • ',(0,y.default)("No elements left! "),(0,y.default)("Would you like to "),'',(0,y.default)("start over"),"?","
  • "].join("")),invalidInitial:n.template(['
    ','
    ','',"<% if( _.size( problems ) ){ %>",(0,y.default)("The following selections could not be included due to problems"),":","
      <% _.each( problems, function( problem ){ %>","
    • <%- problem.element.name %>: <%- problem.text %>
    • ","<% }); %>
    ","<% } else if( _.size( elements ) < 1 ){ %>",(0,y.default)("No datasets were selected"),".","<% } %>","
    ",(0,y.default)("At least one element is needed for the collection"),". ",(0,y.default)("You may need to "),'',(0,y.default)("cancel")," ",(0,y.default)("and reselect new elements"),".","
    ","
    ","
    ",'"].join(""))}),toString:function(){return"ListCollectionCreator"}}),$=function(e,t,i){var s,o=a.Deferred(),l=Galaxy.modal||new _.default.View;return t=n.defaults(t||{},{elements:e,oncancel:function(){l.hide(),o.reject("cancelled")},oncreate:function(e,t){l.hide(),o.resolve(t)}}),s=new i(t),l.show({title:t.title||(0,y.default)("Create a collection"),body:s.$el,width:"80%",height:"100%",closing_events:!0}),s.render(),window._collectionCreator=s,o},C=function(e,t){return t=t||{},t.title=(0,y.default)("Create a collection from a list of datasets"),$(e,t,x)};t.default={DatasetCollectionElementView:b,ListCollectionCreator:x,collectionCreatorModal:$,listCollectionCreatorModal:C,createListCollection:l}}).call(t,i(2),i(1),i(0),i(0))},function(e,t,i){"use strict";(function(e,n,s){function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(50),l=a(o),r=i(4),d=a(r),c=i(3),u=(a(c),{defaults:{model_class:"DatasetCollectionElement",element_identifier:null,element_index:null,element_type:null},_mergeObject:function(t){return e.extend(t,t.object,{element_id:t.id}),delete t.object,t},constructor:function(e,t){e=this._mergeObject(e),this.idAttribute="element_id",n.Model.apply(this,arguments)},parse:function(e,t){var i=e;return i=this._mergeObject(i)}}),h=n.Model.extend(d.default.LoggableMixin).extend(u).extend({_logNamespace:"collections"}),f=n.Collection.extend(d.default.LoggableMixin).extend({_logNamespace:"collections",model:h,toString:function(){return["DatasetCollectionElementCollection(",this.length,")"].join("")}}),p=l.default.DatasetAssociation.extend(d.default.mixin(u,{url:function(){return this.has("history_id")?Galaxy.root+"api/histories/"+this.get("history_id")+"/contents/"+this.get("id"):(console.warn("no endpoint for non-hdas within a collection yet"),Galaxy.root+"api/datasets")},defaults:e.extend({},l.default.DatasetAssociation.prototype.defaults,u.defaults),_downloadQueryParameters:function(){var e=this.get("file_ext"),t=this.get("element_identifier");return"?to_ext="+e+"&hdca_id="+this.get("parent_hdca_id")+"&element_identifier="+t},constructor:function(e,t){this.debug("\t DatasetDCE.constructor:",e,t),u.constructor.call(this,e,t)},hasDetails:function(){return this.elements&&this.elements.length},toString:function(){this.get("element_identifier");return"DatasetDCE({objStr})"}})),m=f.extend({model:p,toString:function(){return["DatasetDCECollection(",this.length,")"].join("")}}),g=n.Model.extend(d.default.LoggableMixin).extend(d.default.SearchableModelMixin).extend({_logNamespace:"collections",defaults:{collection_type:null,deleted:!1},collectionClass:function(){return this.attributes.collection_type.indexOf(":")>0?v:m},initialize:function(e,t){this.debug(this+"(DatasetCollection).initialize:",e,t,this),this.elements=this._createElementsModel(),this.on("change:elements",function(){this.log("change:elements"),this.elements=this._createElementsModel()})},_createElementsModel:function(){var t=this.collectionClass();this.debug(this+"._createElementsModel",t,this.get("elements"),this.elements);var i=this.get("elements")||[];this.unset("elements",{silent:!0});var n=this;return e.each(i,function(t,i){e.extend(t,{parent_hdca_id:n.get("id")})}),this.elements=new t(i),this.elements},toJSON:function(){var e=n.Model.prototype.toJSON.call(this);return this.elements&&(e.elements=this.elements.toJSON()),e},inReadyState:function(){var e=this.get("populated");return this.isDeletedOrPurged()||e},hasDetails:function(){return 0!==this.elements.length},getVisibleContents:function(e){return this.elements},parse:function(e,t){var i=n.Model.prototype.parse.call(this,e,t);return i.create_time&&(i.create_time=new Date(i.create_time)),i.update_time&&(i.update_time=new Date(i.update_time)),i},delete:function(e){return this.get("deleted")?s.when():this.save({deleted:!0},e)},undelete:function(e){return!this.get("deleted")||this.get("purged")?s.when():this.save({deleted:!1},e)},isDeletedOrPurged:function(){return this.get("deleted")||this.get("purged")},searchAttributes:["name","tags"],toString:function(){return"DatasetCollection("+[this.get("id"),this.get("name")||this.get("element_identifier")].join(",")+")"}}),_=g.extend(d.default.mixin(u,{constructor:function(e,t){this.debug("\t NestedDCDCE.constructor:",e,t),u.constructor.call(this,e,t)},toString:function(){return["NestedDCDCE(",this.object?""+this.object:this.get("element_identifier"),")"].join("")}})),v=f.extend({model:_,toString:function(){return["NestedDCDCECollection(",this.length,")"].join("")}});t.default={DatasetCollection:g}}).call(t,i(1),i(2),i(0))},function(e,t,i){"use strict";(function(e,n,s,a){function o(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var l=i(16),r=o(l),d=i(31),c=(o(d),i(56)),u=o(c),h=i(5),f=o(h),p=i(4),m=o(p),g=i(3),_=o(g),v=e.Model.extend(m.default.LoggableMixin).extend(m.default.mixin(m.default.SearchableModelMixin,{_logNamespace:"history",UPDATE_DELAY:4e3,defaults:{model_class:"History",id:null,name:"Unnamed History",state:"new",deleted:!1,contents_active:{},contents_states:{}},urlRoot:Galaxy.root+"api/histories",contentsClass:r.default.HistoryContents,searchAttributes:["name","annotation","tags"],searchAliases:{title:"name",tag:"tags"},initialize:function(e,t){t=t||{},this.logger=t.logger||null,this.log(this+".initialize:",e,t),this.contents=new this.contentsClass([],{history:this,historyId:this.get("id"),order:t.order}),this._setUpListeners(),this._setUpCollectionListeners(),this.updateTimeoutId=null},_setUpListeners:function(){return this.on({error:function(e,t,i,n,s){this.clearUpdateTimeout()},"change:id":function(e,t){this.contents&&(this.contents.historyId=t)}})},_setUpCollectionListeners:function(){return this.contents?this.listenTo(this.contents,{error:function(){this.trigger.apply(this,n.makeArray(arguments))}}):this},contentsShown:function(){var e=this.get("contents_active"),t=e.active||0;return t+=this.contents.includeDeleted?e.deleted:0,t+=this.contents.includeHidden?e.hidden:0},nice_size:function(){var e=this.get("size");return e?f.default.bytesToString(e,!0,2):(0,_.default)("(empty)")},toJSON:function(){return s.extend(e.Model.prototype.toJSON.call(this),{nice_size:this.nice_size()})},get:function(t){return"nice_size"===t?this.nice_size():e.Model.prototype.get.apply(this,arguments)},ownedByCurrUser:function(){return!(!Galaxy||!Galaxy.user)&&(!Galaxy.user.isAnonymous()&&Galaxy.user.id===this.get("user_id"))},numOfUnfinishedJobs:function(){var e=this.get("non_ready_jobs");return e?e.length:0},numOfUnfinishedShownContents:function(){return this.contents.runningAndActive().length||0},_fetchContentRelatedAttributes:function(){var e=["size","non_ready_jobs","contents_active","hid_counter"];return this.fetch({data:a.param({keys:e.join(",")})})},refresh:function(e){var t=this;e=e||{};var i=this.lastUpdateTime;return this.contents.allFetched=!1,(0!==this.contents.currentPage?function(){return t.contents.fetchPage(t.contents.currentPage)}:function(){return t.contents.fetchUpdated(i)})().done(function(i,n,s){var a;try{a=new Date(s.getResponseHeader("Date"))}catch(e){}t.lastUpdateTime=a||new Date,t.checkForUpdates(e)})},checkForUpdates:function(e){var t=this;e=e||{};var i=this.UPDATE_DELAY;if(this.id){var n=function(){t.clearUpdateTimeout(),t.updateTimeoutId=setTimeout(function(){t.refresh(e)},i)};this.numOfUnfinishedShownContents()>0?n():this._fetchContentRelatedAttributes().done(function(e){t.numOfUnfinishedJobs()>0?n():t.trigger("ready")})}},clearUpdateTimeout:function(){this.updateTimeoutId&&(clearTimeout(this.updateTimeoutId),this.updateTimeoutId=null)},stopPolling:function(){this.clearUpdateTimeout(),this.contents&&this.contents.stopPolling()},parse:function(t,i){var n=e.Model.prototype.parse.call(this,t,i);return n.create_time&&(n.create_time=new Date(n.create_time)),n.update_time&&(n.update_time=new Date(n.update_time)),n},fetchWithContents:function(e,t){e=e||{};var i=this;return e.view="dev-detailed",this.fetch(e).then(function(e){return i.contents.history=i,i.contents.setHistoryId(e.id),i.fetchContents(t)})},fetchContents:function(e){e=e||{};var t=this;return t.lastUpdateTime=new Date,t.contents.fetchCurrentPage(e)},_delete:function(e){return this.get("deleted")?n.when():this.save({deleted:!0},e)},purge:function(e){return this.get("purged")?n.when():this.save({deleted:!0,purged:!0},e)},undelete:function(e){return this.get("deleted")?this.save({deleted:!1},e):n.when()},copy:function(e,t,i){if(e=void 0===e||e,!this.id)throw new Error("You must set the history ID before copying it.");var s={history_id:this.id};e&&(s.current=!0),t&&(s.name=t),i||(s.all_datasets=!1),s.view="dev-detailed";var a=this,o=n.post(this.urlRoot,s);return e?o.then(function(e){return new v(e).setAsCurrent().done(function(){a.trigger("copied",a,e)})}):o.done(function(e){a.trigger("copied",a,e)})},setAsCurrent:function(){var e=this,t=n.getJSON(Galaxy.root+"history/set_as_current?id="+this.id);return t.done(function(){e.trigger("set-as-current",e)}),t},toString:function(){return"History("+this.get("id")+","+this.get("name")+")"}})),w=u.default.InfinitelyScrollingCollection,y=w.extend(m.default.LoggableMixin).extend({_logNamespace:"history",model:v,order:"update_time",limitOnFirstFetch:10,limitPerFetch:10,initialize:function(e,t){t=t||{},this.log("HistoryCollection.initialize",e,t),w.prototype.initialize.call(this,e,t),this.includeDeleted=t.includeDeleted||!1,this.currentHistoryId=t.currentHistoryId,this.setUpListeners()},urlRoot:Galaxy.root+"api/histories",url:function(){return this.urlRoot},setUpListeners:function(){return this.on({"change:deleted":function(e){this.debug("change:deleted",this.includeDeleted,e.get("deleted")),!this.includeDeleted&&e.get("deleted")&&this.remove(e)},copied:function(e,t){this.setCurrent(new v(t,[]))},"set-as-current":function(e){var t=this.currentHistoryId;this.trigger("no-longer-current",t),this.currentHistoryId=e.id}})},_buildFetchData:function(e){return s.extend(w.prototype._buildFetchData.call(this,e),{view:"dev-detailed"})},_buildFetchFilters:function(e){var t=w.prototype._buildFetchFilters.call(this,e)||{},i={};return this.includeDeleted?i.deleted=null:(i.deleted=!1,i.purged=!1),s.defaults(t,i)},fetchFirst:function(e){var t=this,i=a.when();return this.currentHistoryId&&(i=w.prototype.fetchFirst.call(t,{silent:!0,limit:1,filters:{purged:"",deleted:"","encoded_id-in":this.currentHistoryId}})),i.then(function(){return e=e||{},e.offset=0,t.fetchMore(e)})},comparators:s.extend(s.clone(w.prototype.comparators),{name:m.default.buildComparator("name",{ascending:!0}),"name-dsc":m.default.buildComparator("name",{ascending:!1}),size:m.default.buildComparator("size",{ascending:!1}),"size-asc":m.default.buildComparator("size",{ascending:!0})}),sort:function(e){e=e||{};var t=e.silent,i=this.remove(this.get(this.currentHistoryId));return w.prototype.sort.call(this,s.defaults({silent:!0},e)),this.unshift(i,{silent:!0}),t||this.trigger("sort",this,e),this},create:function(e,t,i,s){var a=this;return n.getJSON(Galaxy.root+"history/create_new_current").done(function(e){a.setCurrent(new v(e,[],i||{}))})},setCurrent:function(e,t){return t=t||{},this.unshift(e,t),this.currentHistoryId=e.get("id"),t.silent||this.trigger("new-current",e,this),this},toString:function(){return"HistoryCollection("+this.length+",current:"+this.currentHistoryId+")"}});t.default={History:v,HistoryCollection:y}}).call(t,i(2),i(0),i(1),i(0))},function(e,t,i){"use strict";(function(e){Object.defineProperty(t,"__esModule",{value:!0});var n=i(4),s=function(e){return e&&e.__esModule?e:{default:e}}(n),a=s.default.SessionStorageModel.extend({defaults:{expandedIds:{},show_deleted:!1,show_hidden:!1},addExpanded:function(e){var t=this.get("expandedIds");t[e.id]=e.get("id"),this.save("expandedIds",t)},removeExpanded:function(e){var t=this.get("expandedIds");delete t[e.id],this.save("expandedIds",t)},isExpanded:function(t){return e.result(this.get("expandedIds"),t,!1)},allExpanded:function(){return e.values(this.get("expandedIds"))},clearExpanded:function(){this.set("expandedIds",{})},includeDeleted:function(t){return e.isUndefined(t)||this.set("show_deleted",t),this.get("show_deleted")},includeHidden:function(t){return e.isUndefined(t)||this.set("show_hidden",t),this.get("show_hidden")},toString:function(){return"HistoryPrefs("+this.id+")"}},{storageKeyPrefix:"history:",historyStorageKey:function(e){if(!e)throw new Error("HistoryPrefs.historyStorageKey needs valid id: "+e);return a.storageKeyPrefix+e},get:function(e){return new a({id:a.historyStorageKey(e)})},clearAll:function(e){for(var t in sessionStorage)0===t.indexOf(a.storageKeyPrefix)&&sessionStorage.removeItem(t)}});t.default={HistoryPrefs:a}}).call(t,i(1))},function(e,t,i){"use strict";(function(e){function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function n(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function s(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var a=function e(t,i,n){null===t&&(t=Function.prototype);var s=Object.getOwnPropertyDescriptor(t,i);if(void 0===s){var a=Object.getPrototypeOf(t);return null===a?void 0:e(a,i,n)}if("value"in s)return s.value;var o=s.get;if(void 0!==o)return o.call(n)},o=function(){function e(e,t){for(var i=0;i .title-bar .subtitle").replaceWith(this._renderSubtitle())})},_renderSubtitle:function(){return n(this.templates.subtitle(this.model.toJSON(),this))},_getFoldoutPanelOptions:function(){var t=p.prototype._getFoldoutPanelOptions.call(this);return e.extend(t,{linkTarget:this.linkTarget,hasUser:this.hasUser})},$selector:function(){return this.$("> .selector")},toString:function(){return"DCListItemView("+(this.model?""+this.model:"(no model)")+")"}});g.prototype.templates=function(){var t=e.extend({},p.prototype.templates.warnings,{error:u.default.wrapTemplate(["<% if( model.error ){ %>",'
    ',(0,f.default)("There was an error getting the data for this collection"),": <%- model.error %>","
    ","<% } %>"]),purged:u.default.wrapTemplate(["<% if( model.purged ){ %>",'
    ',(0,f.default)("This collection has been deleted and removed from disk"),"
    ","<% } %>"]),deleted:u.default.wrapTemplate(["<% if( model.deleted && !model.purged ){ %>",'
    ',(0,f.default)("This collection has been deleted"),"
    ","<% } %>"])}),i=u.default.wrapTemplate(['
    ','
    ','<%- collection.element_identifier || collection.name %>',"
    ",'
    ',"
    "],"collection"),n=u.default.wrapTemplate(['
    ','<% var countText = collection.element_count? ( collection.element_count + " " ) : ""; %>','<% if( collection.collection_type === "list" ){ %>',(0,f.default)("a list of <%- countText %>datasets"),'<% } else if( collection.collection_type === "paired" ){ %>',(0,f.default)("a pair of datasets"),'<% } else if( collection.collection_type === "list:paired" ){ %>',(0,f.default)("a list of <%- countText %>dataset pairs"),'<% } else if( collection.collection_type === "list:list" ){ %>',(0,f.default)("a list of <%- countText %>dataset lists"),"<% } %>","
    "],"collection");return e.extend({},p.prototype.templates,{warnings:t,titleBar:i,subtitle:n})}();var _=m.extend({className:m.prototype.className+" dataset-collection-element",initialize:function(e){e.logger&&(this.logger=this.model.logger=e.logger),this.log("DCEListItemView.initialize:",e),m.prototype.initialize.call(this,e)},toString:function(){return"DCEListItemView("+(this.model?""+this.model:"(no model)")+")"}});_.prototype.templates=function(){var t=u.default.wrapTemplate(['
    ','
    ','<%- element.element_identifier %>',"
    ",'
    ',"
    "],"element");return e.extend({},m.prototype.templates,{titleBar:t})}();var v=d.default.DatasetListItemView.extend({className:d.default.DatasetListItemView.prototype.className+" dataset-collection-element",initialize:function(e){e.logger&&(this.logger=this.model.logger=e.logger),this.log("DatasetDCEListItemView.initialize:",e),d.default.DatasetListItemView.prototype.initialize.call(this,e)},_fetchModelDetails:function(){var e=this;return e.model.inReadyState()&&!e.model.hasDetails()?e.model.fetch({silent:!0}):s.when()},toString:function(){return"DatasetDCEListItemView("+(this.model?""+this.model:"(no model)")+")"}});v.prototype.templates=function(){var t=u.default.wrapTemplate(['
    ','','
    ','<%- element.element_identifier %>',"
    ","
    "],"element");return e.extend({},d.default.DatasetListItemView.prototype.templates,{titleBar:t})}();var w=g.extend({className:g.prototype.className+" dataset-collection-element",_swapNewRender:function(e){g.prototype._swapNewRender.call(this,e);var t=this.model.get("state")||"ok";return this.$el.addClass("state-"+t),this.$el},toString:function(){return"NestedDCDCEListItemView("+(this.model?""+this.model:"(no model)")+")"}});t.default={DCListItemView:g,DCEListItemView:_,DatasetDCEListItemView:v,NestedDCDCEListItemView:w}}).call(t,i(1),i(0),i(0))},,,function(e,t,i){"use strict";(function(e,n){function s(t,i,n){return Galaxy.modal.show({title:i,body:t,closing_events:!0,buttons:{Ok:function(){Galaxy.modal.hide()}}}),Galaxy.modal.$el.addClass("error-modal"),n&&(Galaxy.modal.$(".error-details").add(Galaxy.modal.$('button:contains("Details")')).remove(),e("
    ").addClass("error-details").hide().appendTo(Galaxy.modal.$(".modal-content")).append([e("

    ").text(p),e("

    ").text(JSON.stringify(n,null,"  "))]),e('").appendTo(Galaxy.modal.$(".buttons")).click(function(){Galaxy.modal.$(".error-details").toggle()})),Galaxy.modal}function a(e,t,i){if(e){if(e=(0,u.default)(e),t=(0,u.default)(t)||(0,u.default)("Error:"),window.Galaxy&&Galaxy.modal)return s(e,t,i);alert(t+"\n\n"+e),console.log("error details:",JSON.stringify(i))}}function o(){return a((0,u.default)("You appear to be offline. Please check your connection and try again."),(0,u.default)("Offline?"))}function l(){return a((0,u.default)("Galaxy is currently unreachable. Please try again in a few minutes.")+" "+h,(0,u.default)("Cannot connect to Galaxy"))}function r(e,t,i,n,s){return n=n||f,n+=" "+h,s=s||(0,u.default)("An error occurred"),a(n,s,d(e,t,i))}function d(e,t,i){return{raven:n.result(window.Raven,"lastEventId"),userAgent:navigator.userAgent,onLine:navigator.onLine,version:n.result(Galaxy.config,"version_major"),xhr:n.omit(t,n.functions(t)),options:n.omit(i,"xhr"),url:n.result(Galaxy.lastAjax,"url"),data:n.result(Galaxy.lastAjax,"data"),model:n.result(e,"toJSON",""+e),user:n.omit(n.result(Galaxy.user,"toJSON"),"email")}}Object.defineProperty(t,"__esModule",{value:!0});var c=i(3),u=function(e){return e&&e.__esModule?e:{default:e}}(c),h=(0,u.default)("Please contact a Galaxy administrator if the problem persists."),f=(0,u.default)("An error occurred while updating information with the server."),p=(0,u.default)("The following information can assist the developers in finding the source of the error:");t.default={errorModal:a,offlineErrorModal:o,badGatewayErrorModal:l,ajaxErrorModal:r}}).call(t,i(0),i(1))},function(e,t,i){"use strict";(function(e){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var s=i(50),a=n(s),o=i(51),l=n(o),r=i(4),d=n(r),c=i(3),u=(n(c),a.default.DatasetAssociation),h=l.default.HistoryContentMixin,f=u.extend(d.default.mixin(h,{defaults:e.extend({},u.prototype.defaults,h.defaults,{history_content_type:"dataset",model_class:"HistoryDatasetAssociation"})}));t.default={HistoryDatasetAssociation:f}}).call(t,i(1))},function(e,t,i){"use strict";(function(e,n,s){function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(21),l=a(o),r=i(95),d=a(r),c=i(4),u=a(c),h=i(3),f=a(h);i(39);var p=e.View.extend(u.default.LoggableMixin).extend({_logNamespace:"list",viewClass:l.default.ListItemView,collectionClass:e.Collection,tagName:"div",className:"list-panel",fxSpeed:"fast",emptyMsg:(0,f.default)("This list is empty"),noneFoundMsg:(0,f.default)("No matching items found"),searchPlaceholder:(0,f.default)("search"),initialize:function(e,t){e=e||{},e.logger&&(this.logger=e.logger),this.log(this+".initialize:",e),this.fxSpeed=n.has(e,"fxSpeed")?e.fxSpeed:this.fxSpeed,this.filters=[],this.searchFor=e.searchFor||"",this.selecting=void 0===e.selecting||e.selecting,this.selected=e.selected||[],this.lastSelected=null,this.dragItems=e.dragItems||!1,this.viewClass=e.viewClass||this.viewClass,this.views=[],this.collection=e.collection||this._createDefaultCollection(),this.filters=e.filters||[],this.$scrollContainer=e.$scrollContainer||this.$scrollContainer,this.title=e.title||"",this.subtitle=e.subtitle||"",this._setUpListeners()},_setUpListeners:function(){return this.off(),this.on({error:function(e,t,i,n,s){console.error(e,t,i,n,s)},loading:function(){this._showLoadingIndicator("loading...",40)},"loading-done":function(){this._hideLoadingIndicator(40)}}),this.once("rendered",function(){this.trigger("rendered:initial",this)}),this._setUpCollectionListeners(),this._setUpViewListeners(),this},_createDefaultCollection:function(){return new this.collectionClass([])},_setUpCollectionListeners:function(){return this.log(this+"._setUpCollectionListeners",this.collection),this.stopListening(this.collection),this.listenTo(this.collection,{error:function(e,t,i,n,s){this.trigger("error",e,t,i,n,s)},update:function(e,t){var i=t.changes;return t.renderAll||i.added.length+i.removed.length>1?this.renderItems():1===i.added.length?this.addItemView(n.first(i.added),e,t):1===i.removed.length?this.removeItemView(n.first(i.removed),e,t):void 0}}),this},_setUpViewListeners:function(){this.log(this+"._setUpViewListeners"),this.on({"view:selected":function(e,t){if(t&&t.shiftKey&&this.lastSelected){var i=this.viewFromModelId(this.lastSelected);i&&this.selectRange(e,i)}else t&&t.altKey&&!this.selecting&&this.showSelectors();this.selected.push(e.model.id),this.lastSelected=e.model.id},"view:de-selected":function(e,t){this.selected=n.without(this.selected,e.model.id)}})},render:function(e){this.log(this+".render",e);var t=this._buildNewRender();return this._setUpBehaviors(t),this._queueNewRender(t,e),this},_buildNewRender:function(){this.debug(this+"(ListPanel)._buildNewRender");var e=s(this.templates.el({},this));return this._renderControls(e),this._renderTitle(e),this._renderSubtitle(e),this._renderSearch(e),this.renderItems(e),e},_renderControls:function(e){this.debug(this+"(ListPanel)._renderControls");var t=s(this.templates.controls({},this));return e.find(".controls").replaceWith(t),t},_renderTitle:function(e){},_renderSubtitle:function(e){},_queueNewRender:function(e,t){t=void 0===t?this.fxSpeed:t;var i=this;i.log("_queueNewRender:",e,t),s(i).queue("fx",[function(e){i.$el.fadeOut(t,e)},function(t){i._swapNewRender(e),t()},function(e){i.$el.fadeIn(t,e)},function(e){i.trigger("rendered",i),e()}])},_swapNewRender:function(e){return this.$el.empty().attr("class",this.className).append(e.children()),this.selecting&&this.showSelectors(0),this},_setUpBehaviors:function(e){return e=e||this.$el,this.$controls(e).find("[title]").tooltip(),this._renderMultiselectActionMenu(e),this},_renderMultiselectActionMenu:function(e){e=e||this.$el;var t=e.find(".list-action-menu"),i=this.multiselectActions();if(!i.length)return t.empty();var n=s(['
    ','",'","
    "].join("")),a=i.map(function(e){var t=['
  • ',e.html,"
  • "].join("");return s(t).click(function(t){return t.preventDefault(),e.func(t)})});return n.find("ul").append(a),t.replaceWith(n),n},multiselectActions:function(){return[]},$scrollContainer:function(e){return(e||this.$el).parent().parent()},$controls:function(e){return(e||this.$el).find("> .controls")},$list:function(e){return(e||this.$el).find("> .list-items")},$messages:function(e){return(e||this.$el).find("> .controls .messages")},$emptyMessage:function(e){return(e||this.$el).find("> .empty-message")},renderItems:function(e){e=e||this.$el;var t=this;t.log(this+".renderItems",e);var i=t.$list(e);t.freeViews();var n=t._filterCollection();return t.views=n.map(function(e){return t._createItemView(e)}),i.empty(),t.views.length&&t._attachItems(e),t._renderEmptyMessage(e).toggle(!t.views.length),t.trigger("views:ready",t.views),t.views},_filterCollection:function(){var e=this;return e.collection.filter(n.bind(e._filterItem,e))},_filterItem:function(e){var t=this;return n.every(t.filters.map(function(t){return t.call(e)}))&&(!t.searchFor||e.matchesAll(t.searchFor))},_createItemView:function(e){var t=this._getItemViewClass(e),i=n.extend(this._getItemViewOptions(e),{model:e}),s=new t(i);return this._setUpItemViewListeners(s),s},_destroyItemView:function(e){this.stopListening(e),this.views=n.without(this.views,e)},_destroyItemViews:function(e){var t=this;return t.views.forEach(function(e){t.stopListening(e)}),t.views=[],t},freeViews:function(){return this._destroyItemViews()},_getItemViewClass:function(e){return this.viewClass},_getItemViewOptions:function(e){return{fxSpeed:this.fxSpeed,expanded:!1,selectable:this.selecting,selected:n.contains(this.selected,e.id),draggable:this.dragItems}},_setUpItemViewListeners:function(e){var t=this;return this.listenTo(e,"all",function(){var e=Array.prototype.slice.call(arguments,0);e[0]="view:"+e[0],t.trigger.apply(t,e)}),this.listenTo(e,"draggable:dragstart",function(e,t){var i={},n=this.getSelectedModels();i=n.length?n.toJSON():[t.model.toJSON()],e.dataTransfer.setData("text",JSON.stringify(i))},this),t},_attachItems:function(e){var t=this;return this.$list(e).append(this.views.map(function(e){return t._renderItemView$el(e)})),this},_renderItemView$el:function(e){return e.render(0).$el},_renderEmptyMessage:function(e){this.debug("_renderEmptyMessage",e,this.searchFor);var t=this.searchFor?this.noneFoundMsg:this.emptyMsg;return this.$emptyMessage(e).text(t)},expandAll:function(){n.each(this.views,function(e){e.expand()})},collapseAll:function(){n.each(this.views,function(e){e.collapse()})},addItemView:function(e,t,i){var n=this,a=n._filterCollection().indexOf(e);if(-1!==a){var o=n._createItemView(e);return s(o).queue("fx",[function(e){n.$emptyMessage().is(":visible")?n.$emptyMessage().fadeOut(n.fxSpeed,e):e()},function(e){n._attachView(o,a),e()}]),o}},_attachView:function(e,t,i){i=!!n.isUndefined(i)||i,t=t||0;var s=this;return s.views.splice(t,0,e),s._insertIntoListAt(t,s._renderItemView$el(e).hide()),s.trigger("view:attached",e),i?e.$el.slideDown(s.fxSpeed,function(){s.trigger("view:attached:rendered")}):(e.$el.show(),s.trigger("view:attached:rendered")),e},_insertIntoListAt:function(e,t){var i=this.$list();return 0===e?i.prepend(t):i.children().eq(e-1).after(t),t},removeItemView:function(e,t,i){var a=this,o=n.find(a.views,function(t){return t.model===e});if(o)return a.views=n.without(a.views,o),a.trigger("view:removed",o),s({}).queue("fx",[function(e){o.$el.fadeOut(a.fxSpeed,e)},function(e){o.remove(),a.trigger("view:removed:rendered"),a.views.length?e():a._renderEmptyMessage().fadeIn(a.fxSpeed,e)}]),o},viewFromModelId:function(e){return n.find(this.views,function(t){return t.model.id===e})},viewFromModel:function(e){return e?this.viewFromModelId(e.id):void 0},viewsWhereModel:function(e){return this.views.filter(function(t){return n.isMatch(t.model.attributes,e)})},viewRange:function(e,t){if(e===t)return e?[e]:[];var i=this.views.indexOf(e),n=this.views.indexOf(t);return-1===i||-1===n?i===n?[]:-1===i?[t]:[e]:i .controls .search-query");return i.val()!==e&&i.val(e),this},clearSearch:function(e){return this.searchFor="",this.trigger("search:clear",this),this.$("> .controls .search-query").val(""),this.renderItems(),this},THROTTLE_SELECTOR_FX_AT:20,showSelectors:function(e){e=void 0!==e?e:this.fxSpeed,this.selecting=!0,this.$(".list-actions").slideDown(e),e=this.views.length>=this.THROTTLE_SELECTOR_FX_AT?0:e,n.each(this.views,function(t){t.showSelector(e)})},hideSelectors:function(e){e=void 0!==e?e:this.fxSpeed,this.selecting=!1,this.$(".list-actions").slideUp(e),e=this.views.length>=this.THROTTLE_SELECTOR_FX_AT?0:e,n.each(this.views,function(t){t.hideSelector(e)}),this.selected=[],this.lastSelected=null},toggleSelectors:function(){this.selecting?this.hideSelectors():this.showSelectors()},selectAll:function(e){n.each(this.views,function(t){t.select(e)})},deselectAll:function(e){this.lastSelected=null,n.each(this.views,function(t){t.deselect(e)})},selectRange:function(e,t){var i=this.viewRange(e,t);return n.each(i,function(e){e.select()}),i},getSelectedViews:function(){return n.filter(this.views,function(e){return e.selected})},getSelectedModels:function(){return new this.collection.constructor(n.map(this.getSelectedViews(),function(e){return e.model}))},_showLoadingIndicator:function(e,t,i){this.debug("_showLoadingIndicator",this.indicator,e,t,i),t=void 0!==t?t:this.fxSpeed,this.indicator||(this.indicator=new d.default(this.$el),this.debug("\t created",this.indicator)),this.$el.is(":visible")?(this.$el.fadeOut(t),this.indicator.show(e,t,i)):this.indicator.show(0,i)},_hideLoadingIndicator:function(e,t){this.debug("_hideLoadingIndicator",this.indicator,e,t),e=void 0!==e?e:this.fxSpeed,this.indicator&&this.indicator.hide(e,t)},scrollPosition:function(){return this.$scrollContainer().scrollTop()},scrollTo:function(e,t){return t=t||0,this.$scrollContainer().animate({scrollTop:e},t),this},scrollToTop:function(e){return this.scrollTo(0,e)},scrollToItem:function(e,t){return this},scrollToId:function(e,t){return this.scrollToItem(this.viewFromModelId(e),t)},events:{"click .select-all":"selectAll","click .deselect-all":"deselectAll"},toString:function(){return"ListPanel("+this.collection+")"}});p.prototype.templates=function(){return{el:u.default.wrapTemplate(["
    ",'
    ','
    ','
    ',"
    "]),controls:u.default.wrapTemplate(['
    ','
    ','
    <%- view.title %>
    ',"
    ",'
    <%- view.subtitle %>
    ','
    ','
    ','",'
    ','
    ','",'","
    ",'
    ',"
    ","
    ","
    "])}}();var m=p.extend({modelCollectionKey:"contents",initialize:function(e){p.prototype.initialize.call(this,e),this.selecting=void 0!==e.selecting&&e.selecting,this.setModel(this.model,e)},setModel:function(e,t){if(t=t||{},this.debug(this+".setModel:",e,t),this.freeModel(),this.freeViews(),e){var i=this.model?this.model.get("id"):null;this.model=e,this.logger&&(this.model.logger=this.logger),this._setUpModelListeners(),this.stopListening(this.collection),this.collection=this.model[this.modelCollectionKey]||t.collection||this._createDefaultCollection(),this._setUpCollectionListeners(),i&&e.get("id")!==i&&this.trigger("new-model",this)}return this},freeModel:function(){return this.model&&this.stopListening(this.model),this},_setUpModelListeners:function(){return this.log(this+"._setUpModelListeners",this.model),this.listenTo(this.model,"error",function(){var e=Array.prototype.slice.call(arguments,0);e.unshift("error"),this.trigger.apply(this,e)},this),this.logger&&this.listenTo(this.model,"all",function(e){this.info(this+"(model)",e,arguments)}),this},_renderControls:function(e){this.debug(this+"(ModelListPanel)._renderControls");var t=this.model?this.model.toJSON():{},i=s(this.templates.controls(t,this));return e.find(".controls").replaceWith(i),i},toString:function(){return"ModelListPanel("+this.model+")"}});m.prototype.templates=function(){var e=u.default.wrapTemplate(['
    ','
    ','
    <%- model.name %>
    ',"
    ",'
    <%- view.subtitle %>
    ','
    ','
    ','",'
    ','
    ','",'","
    ",'
    ',"
    ","
    ","
    "]);return n.extend(n.clone(p.prototype.templates),{controls:e})}(),t.default={ListPanel:p,ModelListPanel:m}}).call(t,i(2),i(1),i(0))},function(e,t,i){"use strict";function n(e,t){function i(e){o(this).parent().children("input").val("").trigger("searchInput.clear").blur(),t.onclear()}function n(e,n){if(!n)return i();o(this).trigger("search.search",n),"function"==typeof t.onfirstsearch&&u?(u=!1,t.onfirstsearch(n)):t.onsearch(n)}function s(){return['"].join("")}var r=27,d=13,c=o(e),u=!0,h={initialVal:"",name:"search",placeholder:"search",classes:"",onclear:function(){},onfirstsearch:null,onsearch:function(e){},minSearchLen:0,escWillClear:!0,oninit:function(){}};return"string"===a.default.type(t)?("toggle-loading"===t&&function(){c.find(".search-loading").toggle(),c.find(".search-clear").toggle()}(),c):("object"===a.default.type(t)&&(t=a.default.extend(!0,{},h,t)),c.addClass("search-input").prepend([function(){return o(s()).focus(function(e){o(this).select()}).keyup(function(e){if(e.preventDefault(),e.stopPropagation(),e.which===r&&t.escWillClear)i.call(this,e);else{var s=o(this).val();(e.which===d||t.minSearchLen&&s.length>=t.minSearchLen)&&n.call(this,e,s)}}).val(t.initialVal)}(),function(){return o([''].join("")).tooltip({placement:"bottom"}).click(function(e){i.call(this,e)})}(),function(){return o([''].join("")).hide().tooltip({placement:"bottom"})}()]))}var s=i(0),a=function(e){return e&&e.__esModule?e:{default:e}}(s),o=a.default,l=window._l||function(e){return e};a.default.fn.extend({searchInput:function(e){return this.each(function(){return n(this,e)})}})},function(e,t,i){"use strict";(function(e){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var s=i(17),a=n(s),o=i(4),l=n(o),r=i(58),d=n(r),c=i(3),u=n(c),h=a.default.DatasetListItemView,f=h.extend({className:h.prototype.className+" history-content",initialize:function(e,t){h.prototype.initialize.call(this,e,t)},toString:function(){return"HDAListItemView("+(this.model?""+this.model:"(no model)")+")"}});f.prototype.templates=function(){var t=function(t){return'\n
    \n \n
    \n '+t.hid+'\n '+e.escape(t.name)+"\n
    \n
    \n "+d.default.nametagTemplate(t)+"\n
    \n "},i=e.extend({},h.prototype.templates.warnings,{hidden:l.default.wrapTemplate(["<% if( !dataset.visible ){ %>",'
    ',(0,u.default)("This dataset has been hidden"),"
    ","<% } %>"],"dataset")});return e.extend({},h.prototype.templates,{titleBar:t,warnings:i})}(),t.default={HDAListItemView:f}}).call(t,i(1))},function(e,t,i){"use strict";(function(e){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var s=i(8),a=n(s),o=i(33),l=n(o),r=i(59),d=n(r),c=i(4),u=(n(c),i(58)),h=n(u),f=i(3),p=n(f),m=l.default.DCListItemView,g=m.extend({className:m.prototype.className+" history-content",_setUpListeners:function(){var e=this;m.prototype._setUpListeners.call(this);var t=function(t,i){e.render()};this.model.jobStatesSummary&&this.listenTo(this.model.jobStatesSummary,"change",t),this.listenTo(this.model,{"change:tags change:visible change:state":t})},_getFoldoutPanelClass:function(){return d.default.CollectionView},_swapNewRender:function(e){m.prototype._swapNewRender.call(this,e);var t,i=this.model.jobStatesSummary;t=i?i.new()?"loading":i.errored()?"error":i.terminal()?"ok":i.running()?"running":"queued":this.model.get("job_source_id")?"loading":this.model.get("populated_state")?a.default.OK:a.default.RUNNING,this.$el.addClass("state-"+t);var n=this.stateDescription();return this.$(".state-description").html(n),this.$el},stateDescription:function(){var e=this.model,t=(e.get("element_count"),e.get("job_source_type")),i=(this.model.get("collection_type"),d.default.collectionTypeDescription(e)),n=d.default.collectionDescription(e),s=e.jobStatesSummary;if(t&&"Job"!=t){if(s&&s.hasDetails()){var a=s.new(),o=a?null:s.jobCount();if(a)return'\n
    \n Creating jobs...\n
    \n
    ';if(s.errored()){return"a "+i+" with "+s.numInError()+" / "+o+" jobs in error"}if(s.terminal())return n;var l=s.states().running||0,r=s.states().ok||0,c=r/(1*o),u=l/(1*o),h=1-c-u;return'\n
    \n '+(o&&o>1?o+" jobs":"a job")+" generating a "+i+'\n
    \n
    \n
    \n
    '}return'\n
    \n Loading job data for '+i+'...\n
    \n
    '}return n},toString:function(){return"HDCAListItemView("+(this.model?""+this.model:"(no model)")+")"}});g.prototype.templates=function(){var t=e.extend({},m.prototype.templates.warnings,{hidden:function(e){e.visible||(0,p.default)("This collection has been hidden")}}),i=function(t){return'\n
    \n \n
    \n '+t.hid+'\n '+e.escape(t.name)+'\n
    \n
    \n
    \n '+h.default.nametagTemplate(t)+"\n
    \n "};return e.extend({},m.prototype.templates,{warnings:t,titleBar:i})}(),t.default={HDCAListItemView:g}}).call(t,i(1))},,function(e,t,i){"use strict";(function(e,n,s){function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(67),l=a(o),r=i(42),d=a(r),c=i(9),u=a(c),h=i(5),f=a(h),p=e.View.extend({el:"body",className:"full-content",_panelids:["left","right"],initialize:function(t){var i=this;this.config=n.defaults(t.config||{},{message_box_visible:!1,message_box_content:"",message_box_class:"info",show_inactivity_warning:!1,inactivity_box_content:""}),Galaxy.modal=this.modal=new u.default.View,Galaxy.display=this.display=function(e){e.title?(f.default.setWindowTitle(e.title),e.allow_title_display=!1):(f.default.setWindowTitle(),e.allow_title_display=!0),i.center.display(e)},Galaxy.router=this.router=t.Router&&new t.Router(i,t),this.masthead=new l.default.View(this.config),this.center=new d.default.CenterPanel,this.$el.attr("scroll","no"),this.$el.html(this._template()),this.$("#masthead").replaceWith(this.masthead.$el),this.$("#center").append(this.center.$el),this.$el.append(this.masthead.frame.$el),this.$el.append(this.modal.$el),this.$messagebox=this.$("#messagebox"),this.$inactivebox=this.$("#inactivebox"),this.panels={},n.each(this._panelids,function(e){var n=e.charAt(0).toUpperCase()+e.slice(1),s=t[n];if(s){var a=new s(i,t);i[a.toString()]=a,i.panels[e]=new d.default.SidePanel({id:e,el:i.$("#"+e),view:a})}}),this.render(),this.router&&e.history.start({root:Galaxy.root,pushState:!0})},render:function(){return s(".select2-hidden-accessible").remove(),this.masthead.render(),this.renderMessageBox(),this.renderInactivityBox(),this.renderPanels(),this._checkCommunicationServerOnline(),this},renderMessageBox:function(){if(this.config.message_box_visible){var e=this.config.message_box_content||"",t=this.config.message_box_class||"info";this.$el.addClass("has-message-box"),this.$messagebox.attr("class","panel-"+t+"-message").html(e).toggle(!!e).show()}else this.$el.removeClass("has-message-box"),this.$messagebox.hide();return this},renderInactivityBox:function(){if(this.config.show_inactivity_warning){var e=this.config.inactivity_box_content||"",t=s("").attr("href",Galaxy.root+"user/resend_verification").text("Resend verification");this.$el.addClass("has-inactivity-box"),this.$inactivebox.html(e+" ").append(t).toggle(!!e).show()}else this.$el.removeClass("has-inactivity-box"),this.$inactivebox.hide();return this},renderPanels:function(){var e=this;return n.each(this._panelids,function(t){var i=e.panels[t];i?i.render():(e.$("#center").css(t,0),e.$("#"+t).hide())}),this},_template:function(){return['
    ','
    ','
    ','
    ','
    ','
    ','
    ','",'
    '].join("")},toString:function(){return"PageLayoutView"},_checkCommunicationServerOnline:function(){var e=window.Galaxy.config.communication_server_host,t=window.Galaxy.config.communication_server_port,i=window.Galaxy.user.attributes.preferences,n=s("#show-chat-online");i&&-1!=["1","true"].indexOf(i.communication_server)?s.ajax({url:e+":"+t}).success(function(e){null!==window.Galaxy.user.id&&"hidden"===n.css("visibility")&&n.css("visibility","visible")}).error(function(e){n.css("visibility","hidden")}):n.css("visibility","hidden")}});t.default={View:p}}).call(t,i(2),i(1),i(0))},,,,,,function(e,t,i){"use strict";(function(e){Object.defineProperty(t,"__esModule",{value:!0});var i=e.Model.extend({defaults:{extension:"auto",genome:"?",url_paste:"",status:"init",info:null,file_name:"",file_mode:"",file_size:0,file_type:null,file_path:"",file_data:null,percentage:0,space_to_tab:!1,to_posix_lines:!0,enabled:!0},reset:function(e){this.clear().set(this.defaults).set(e)}}),n=e.Collection.extend({model:i});t.default={Model:i,Collection:n}}).call(t,i(2))},function(e,t,i){"use strict";(function(e,n,s){function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(8),l=a(o),r=i(4),d=a(r),c=i(3),u=a(c),h=d.default.SearchableModelMixin,f=e.Model.extend(d.default.LoggableMixin).extend(d.default.mixin(h,{_logNamespace:"dataset",defaults:{state:l.default.NEW,deleted:!1,purged:!1,name:"(unnamed dataset)",accessible:!0,data_type:"",file_ext:"",file_size:0,meta_files:[],misc_blurb:"",misc_info:"",tags:[]},initialize:function(e,t){this.debug(this+"(Dataset).initialize",e,t), //!! this state is not in trans.app.model.Dataset.states - set it here - -this.get("accessible")||this.set("state",l.default.NOT_VIEWABLE),this.urls=this._generateUrls(),this._setUpListeners()},_generateUrls:function(){var e=this.get("id");if(!e)return{};var t={purge:"datasets/"+e+"/purge_async",display:"datasets/"+e+"/display/?preview=True",edit:"datasets/edit?dataset_id="+e,download:"datasets/"+e+"/display"+this._downloadQueryParameters(),report_error:"dataset/errors?id="+e,rerun:"tool_runner/rerun?id="+e,show_params:"datasets/"+e+"/show_params",visualization:"visualization",meta_download:"dataset/get_metadata_file?hda_id="+e+"&metadata_name="};return n.each(t,function(e,i){t[i]=Galaxy.root+e}),this.urls=t,t},_downloadQueryParameters:function(){return"?to_ext="+this.get("file_ext")},_setUpListeners:function(){this.on("change:state",function(e,t){this.log(this+" has changed state:",e,t),this.inReadyState()&&this.trigger("state:ready",e,t,this.previous("state"))}),this.on("change:id change:file_ext",function(e){this._generateUrls()})},toJSON:function(){var t=e.Model.prototype.toJSON.call(this);return n.extend(t,{urls:this.urls})},isDeletedOrPurged:function(){return this.get("deleted")||this.get("purged")},inReadyState:function(){var e=n.contains(l.default.READY_STATES,this.get("state"));return this.isDeletedOrPurged()||e},hasDetails:function(){return!this.get("accessible")||this.has("annotation")},hasData:function(){return this.get("file_size")>0},fetch:function(t){var i=this;return e.Model.prototype.fetch.call(this,t).always(function(){i._generateUrls()})},parse:function(t,i){var n=e.Model.prototype.parse.call(this,t,i);return n.create_time&&(n.create_time=new Date(n.create_time)),n.update_time&&(n.update_time=new Date(n.update_time)),n},save:function(t,i){return i=i||{},i.wait=!!n.isUndefined(i.wait)||i.wait,e.Model.prototype.save.call(this,t,i)},delete:function(e){return this.get("deleted")?s.when():this.save({deleted:!0},e)},undelete:function(e){return!this.get("deleted")||this.get("purged")?s.when():this.save({deleted:!1},e)},purge:function(e){if(this.get("purged"))return s.when();e=e||{},e.url=this.urls.purge;var t=this,i=s.ajax(e);return i.done(function(e,i,n){t.set({deleted:!0,purged:!0})}),i.fail(function(i,n,s){var a=(0,u.default)("Unable to purge dataset");i.responseJSON&&i.responseJSON.error?a=i.responseJSON.error:-1!==i.responseText.indexOf("Removal of datasets by users is not allowed in this Galaxy instance")&&(a="Removal of datasets by users is not allowed in this Galaxy instance"),i.responseText=a,t.trigger("error",t,i,e,(0,u.default)(a),{error:a})}),i},searchAttributes:["name","file_ext","genome_build","misc_blurb","misc_info","annotation","tags"],searchAliases:{title:"name",format:"file_ext",database:"genome_build",blurb:"misc_blurb",description:"misc_blurb",info:"misc_info",tag:"tags"},toString:function(){var e=this.get("id")||"";return this.get("name")&&(e='"'+this.get("name")+'",'+e),"Dataset("+e+")"}})),p=e.Collection.extend(d.default.LoggableMixin).extend({_logNamespace:"dataset",model:f,urlRoot:Galaxy.root+"api/datasets",url:function(){return this.urlRoot},ids:function(){return this.map(function(e){return e.get("id")})},notReady:function(){return this.filter(function(e){return!e.inReadyState()})},haveDetails:function(){return this.all(function(e){return e.hasDetails()})},ajaxQueue:function(e,t){var i=s.Deferred(),n=this.length,a=[];if(!n)return i.resolve([]),i;var o=this.chain().reverse().map(function(s,l){return function(){var r=e.call(s,t);r.done(function(e){i.notify({curr:l,total:n,response:e,model:s})}),r.always(function(e){a.push(e),o.length?o.shift()():i.resolve(a)})}}).value();return o.shift()(),i},matches:function(e){return this.filter(function(t){return t.matches(e)})},toString:function(){return["DatasetAssociationCollection(",this.length,")"].join("")}});t.default={DatasetAssociation:f,DatasetAssociationCollection:p}}).call(t,i(2),i(1),i(0))},function(e,t,i){"use strict";(function(e){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var s=i(8),a=(n(s),i(4)),o=(n(a),i(3)),l=(n(o),1e3);try{l=localStorage.getItem("collectionFuzzyCountDefault")||l}catch(e){}var r={defaults:{history_id:null,history_content_type:null,hid:null,visible:!0},idAttribute:"type_id",hidden:function(){return!this.get("visible")},isVisible:function(e,t){var i=!0;return e||!this.get("deleted")&&!this.get("purged")||(i=!1),t||this.get("visible")||(i=!1),i},urlRoot:Galaxy.root+"api/histories/",url:function(){var e=this.get("history_content_type"),t=this.get("history_id"),i=this.get("id"),n=""+this.urlRoot+t+"/contents/"+e+"s/"+i;return"dataset_collection"==e&&(n=n+"?view=element-reference&fuzzy_count="+l),n},hide:function(t){return this.get("visible")?this.save({visible:!1},t):e.when()},unhide:function(t){return this.get("visible")?e.when():this.save({visible:!0},t)},toString:function(){return[this.get("type_id"),this.get("hid"),this.get("name")].join(":")}};t.default={HistoryContentMixin:r}}).call(t,i(0))},function(e,t,i){"use strict";(function(e){Object.defineProperty(t,"__esModule",{value:!0});var i={_validationWarning:function(e,t){"name"===e&&(e=this.$(".collection-name").add(this.$(".collection-name-prompt")),this.$(".collection-name").focus().select()),t?(e=e||this.$(".validation-warning"),e.removeClass("validation-warning")):e.addClass("validation-warning")},_changeHideOriginals:function(e){this.hideOriginals=this.$(".hide-originals").prop("checked")},_changeName:function(e){this._validationWarning("name",!!this._getName())},_nameCheckForEnter:function(e){13!==e.keyCode||this.blocking||this._clickCreate()},_getName:function(){return e.escape(this.$(".collection-name").val())},_clickMoreHelp:function(e){e.stopPropagation(),this.$(".main-help").addClass("expanded"),this.$(".more-help").hide()},_clickLessHelp:function(e){e.stopPropagation(),this.$(".main-help").removeClass("expanded"),this.$(".more-help").show()},_toggleHelp:function(e){e.stopPropagation(),this.$(".main-help").toggleClass("expanded"),this.$(".more-help").toggle()},_showAlert:function(e,t){t=t||"alert-danger",this.$(".main-help").hide(),this.$(".header .alert").attr("class","alert alert-dismissable").addClass(t).show().find(".alert-message").html(e)},_hideAlert:function(e){this.$(".main-help").show(),this.$(".header .alert").hide()},_cancelCreate:function(e){"function"==typeof this.oncancel&&this.oncancel.call(this)},_clickCreate:function(e){var t=this._getName();t?this.blocking||this.createList(t):this._validationWarning("name")},_setUpCommonSettings:function(e){this.hideOriginals=e.defaultHideSourceItems||!1},_renderFooter:function(t,i){var n=this,s=this.$(".footer").empty().html(this.templates.footer());return e.each(this.footerSettings,function(e,t){n.$(t).prop("checked",n[e])}),"function"==typeof this.oncancel&&this.$(".cancel-create.btn").show(),s},_creatorTemplates:{main:e.template(['
    ','
    ',''].join(""))}};t.default={CollectionCreatorMixin:i}}).call(t,i(1))},function(e,t,i){"use strict";function n(e,t){var i=/(-?[0-9\.]+)/g,n=e.toString().toLowerCase()||"",s=t.toString().toLowerCase()||"",a=String.fromCharCode(0),o=n.replace(i,a+"$1"+a).split(a),l=s.replace(i,a+"$1"+a).split(a),r=new Date(n).getTime(),d=r?new Date(s).getTime():null;if(d){if(rd)return 1}for(var c,u,h=0,f=Math.max(o.length,l.length);hu)return 1}return 0}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,i){"use strict";var n=i(0),s=function(e){return e&&e.__esModule?e:{default:e}}(n),a=s.default;s.default.fn.extend({hoverhighlight:function(e,t){return e=e||"body",this.length?(a(this).each(function(){var i=a(this),n=i.data("target");n&&i.mouseover(function(i){a(n,e).css({background:t})}).mouseout(function(e){a(n).css({background:""})})}),this):this}})},function(e,t,i){"use strict";(function(e,n,s){function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(9),l=a(o),r=i(36),d=a(r),c=i(3),u=a(c),h={defaultName:e.template("Copy of '<%- name %>'"),title:e.template((0,u.default)("Copying history")+' "<%- name %>"'),submitLabel:(0,u.default)("Copy"),errorMessage:(0,u.default)("History could not be copied."),progressive:(0,u.default)("Copying history"),activeLabel:(0,u.default)("Copy only the active, non-deleted datasets"),allLabel:(0,u.default)("Copy all datasets including deleted ones"),anonWarning:(0,u.default)("As an anonymous user, unless you login or register, you will lose your current history ")+(0,u.default)("after copying this history. "),_template:e.template(["<% if( isAnon ){ %>",'
    ","<% } %>","
    ",'
    ",'','","<% if( allowAll ){ %>","
    ","

    ",(0,u.default)("Choose which datasets from the original history to include:"),"

    ",'/>','',"
    ",'/>','',"<% } %>","
    "].join("")),_showAjaxIndicator:function(){var e='

    '+this.progressive+"...

    ";this.modal.$(".modal-body").empty().append(e).css({"margin-top":"8px"})},dialog:function(t,i,a){function o(){var e=t.$("#copy-modal-title").val();if(!e)return void t.$(".invalid-title").show();var n="copy-all"===t.$('input[name="copy-what"]:checked').val();t.$("button").prop("disabled",!0),l._showAjaxIndicator(),i.copy(!0,e,n).done(function(e){r.resolve(e)}).fail(function(t,s,a){var o={name:e,copyAllDatasets:n};d.default.ajaxErrorModal(i,t,o,l.errorMessage),r.rejectWith(r,arguments)}).done(function(){m&&t.hide()})}a=a||{};var l=this,r=n.Deferred(),c=a.nameFn||this.defaultName,h=c({name:i.get("name")}),f=a.allDatasets?"copy-all":"copy-non-deleted",p=!!e.isUndefined(a.allowAll)||a.allowAll,m=!!e.isUndefined(a.autoClose)||a.autoClose;this.modal=t;var g=a.closing_callback;return t.show(e.extend(a,{title:this.title({name:i.get("name")}),body:s(l._template({name:h,isAnon:Galaxy.user.isAnonymous(),allowAll:p,copyWhat:f,activeLabel:this.activeLabel,allLabel:this.allLabel,anonWarning:this.anonWarning})),buttons:e.object([[(0,u.default)("Cancel"),function(){t.hide()}],[this.submitLabel,o]]),height:"auto",closing_events:!0,closing_callback:function(e){e&&r.reject({cancelled:!0}),g&&g(e)}})),t.$("#copy-modal-title").focus().select(),t.$("#copy-modal-title").on("keydown",function(e){13===e.keyCode&&(e.preventDefault(),o())}),r}},f=e.extend({},h,{defaultName:e.template("imported: <%- name %>"),title:e.template((0,u.default)("Importing history")+' "<%- name %>"'),submitLabel:(0,u.default)("Import"),errorMessage:(0,u.default)("History could not be imported."),progressive:(0,u.default)("Importing history"),activeLabel:(0,u.default)("Import only the active, non-deleted datasets"),allLabel:(0,u.default)("Import all datasets including deleted ones"),anonWarning:(0,u.default)("As an anonymous user, unless you login or register, you will lose your current history ")+(0,u.default)("after importing this history. ")}),p=function(e,t){t=t||{};var i=window.parent.Galaxy.modal||new l.default.View({});return t.useImport?f.dialog(i,e,t):h.dialog(i,e,t)};t.default=p}).call(t,i(1),i(0),i(0))},function(e,t,i){"use strict";(function(e){function n(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[i]=e[i]);return t.default=e,t}Object.defineProperty(t,"__esModule",{value:!0});var s=i(1),a=n(s),o=i(2),l=n(o),r=i(4),d=function(e){return e&&e.__esModule?e:{default:e}}(r),c=l.Collection.extend({initialize:function(e,t){l.Collection.prototype.initialize.call(this,e,t),this.setOrder(t.order||this.order,{silent:!0})},_setUpListeners:function(){return this.on({"changed-order":this.sort})},fetch:function(e){return e=this._buildFetchOptions(e),Galaxy.debug("fetch options:",e),l.Collection.prototype.fetch.call(this,e)},_buildFetchOptions:function(e){e=a.clone(e)||{};var t=this;e.traditional=!0,e.data=e.data||t._buildFetchData(e),Galaxy.debug("data:",e.data);var i=this._buildFetchFilters(e);return Galaxy.debug("filters:",i),a.isEmpty(i)||a.extend(e.data,this._fetchFiltersToAjaxData(i)),Galaxy.debug("data:",e.data),e},_buildFetchData:function(e){var t={};return this.order&&(t.order=this.order),a.defaults(a.pick(e,this._fetchParams),t)},_fetchParams:["order","limit","offset","view","keys"],_buildFetchFilters:function(e){return a.clone(e.filters||{})},_fetchFiltersToAjaxData:function(e){var t={q:[],qv:[]};return a.each(e,function(e,i){void 0!==e&&""!==e&&(!0===e&&(e="True"),!1===e&&(e="False"),null===e&&(e="None"),t.q.push(i),t.qv.push(e))}),t},reset:function(e,t){return this.allFetched=!1,l.Collection.prototype.reset.call(this,e,t)},order:null,comparators:{update_time:d.default.buildComparator("update_time",{ascending:!1}),"update_time-asc":d.default.buildComparator("update_time",{ascending:!0}),create_time:d.default.buildComparator("create_time",{ascending:!1}),"create_time-asc":d.default.buildComparator("create_time",{ascending:!0})},setOrder:function(e,t){t=t||{};var i=this,n=i.comparators[e];if(a.isUndefined(n))throw new Error("unknown order: "+e);if(n!==i.comparator)return i.order=e,i.comparator=n,t.silent||i.trigger("changed-order",t),i}}),u=c.extend({limitPerPage:500,initialize:function(e,t){c.prototype.initialize.call(this,e,t),this.currentPage=t.currentPage||0},getTotalItemCount:function(){return this.length},shouldPaginate:function(){return this.getTotalItemCount()>=this.limitPerPage},getLastPage:function(){return Math.floor(this.getTotalItemCount()/this.limitPerPage)},getPageCount:function(){return this.getLastPage()+1},getPageLimitOffset:function(e){return e=this.constrainPageNum(e),{limit:this.limitPerPage,offset:e*this.limitPerPage}},constrainPageNum:function(e){return Math.max(0,Math.min(e,this.getLastPage()))},fetchPage:function(e,t){var i=this;return e=i.constrainPageNum(e),i.currentPage=e,t=a.defaults(t||{},i.getPageLimitOffset(e)),i.trigger("fetching-more"),i.fetch(t).always(function(){i.trigger("fetching-more-done")})},fetchCurrentPage:function(e){return this.fetchPage(this.currentPage,e)},fetchPrevPage:function(e){return this.fetchPage(this.currentPage-1,e)},fetchNextPage:function(e){return this.fetchPage(this.currentPage+1,e)}}),h=c.extend({limitOnFirstFetch:null,limitPerFetch:100,initialize:function(e,t){c.prototype.initialize.call(this,e,t),this.limitOnFirstFetch=t.limitOnFirstFetch||this.limitOnFirstFetch,this.limitPerFetch=t.limitPerFetch||this.limitPerFetch,this.allFetched=!1,this.lastFetched=t.lastFetched||0},_buildFetchOptions:function(e){return e.remove=e.remove||!1,c.prototype._buildFetchOptions.call(this,e)},fetchFirst:function(e){return Galaxy.debug("ControlledFetchCollection.fetchFirst:",e),e=e?a.clone(e):{},this.allFetched=!1,this.lastFetched=0,this.fetchMore(a.defaults(e,{reset:!0,limit:this.limitOnFirstFetch}))},fetchMore:function(t){Galaxy.debug("ControlledFetchCollection.fetchMore:",t),t=a.clone(t||{});var i=this;if(Galaxy.debug("fetchMore, options.reset:",t.reset),!t.reset&&i.allFetched)return e.when();t.reset?t.offset=0:void 0===t.offset&&(t.offset=i.lastFetched);var n=t.limit=t.limit||i.limitPerFetch||null;return Galaxy.debug("fetchMore, limit:",n,"offset:",t.offset),i.trigger("fetching-more"),i.fetch(t).always(function(){i.trigger("fetching-more-done")}).done(function(e){var t=a.isArray(e)?e.length:0;i.lastFetched+=t,Galaxy.debug("fetchMore, lastFetched:",i.lastFetched),(!n||t');this.$el.html(a.text(t).slideDown(e.isUndefined(i)?this.fxSpeed:i))},_hideLoadingIndicator:function(t){this.$(".loading-indicator").slideUp(e.isUndefined(t)?this.fxSpeed+200:t,function(){n(this).remove()})},_buildNewRender:function(){var e=k.prototype._buildNewRender.call(this);return this._renderSelectButton(e),e},_renderSelectButton:function(e){if(e=e||this.$el,!this.multiselectActions().length)return null;if(!this.views.length)return this.hideSelectors(),e.find(".controls .actions .show-selectors-btn").remove(),null;var t=e.find(".controls .actions .show-selectors-btn");return t.length?t:(0,y.default)({title:(0,C.default)("Operations on multiple datasets"),classes:"show-selectors-btn",faIcon:"fa-check-square-o"}).prependTo(e.find(".controls .actions"))},_renderEmptyMessage:function(e){var t=this.$emptyMessage(e);return this.model.get("contents_active").active<=0?t.empty().append(this.emptyMsg).show():this.searchFor&&this.model.contents.haveSearchDetails()&&!this.views.length?t.empty().append(this.noneFoundMsg).show():(t.hide(),n())},$scrollContainer:function(e){return this.$list(e)},_toggleContentsLoadingIndicator:function(e){e?this.$list().html('
    '):this.$list().find(".contents-loading-indicator").remove()},renderItems:function(e){e=e||this.$el;var t=this.$list(e);n(".tooltip").remove(),t.empty(),this.views=[];var i=this._filterCollection();return i.length?(this._renderPagination(e),this.views=this._renderSomeItems(i,t)):e.find("> .controls .list-pagination").empty(),this._renderEmptyMessage(e).toggle(!i.length),this.trigger("views:ready",this.views),this.views},_renderPagination:function(e){var t=e.find("> .controls .list-pagination");return this.searchFor||!this.model.contents.shouldPaginate()?t.empty():(t.html(this.templates.pagination({current:this.model.contents.currentPage+1,last:this.model.contents.getLastPage()+1},this)),t.find("select.pages").tooltip(),t)},_renderSomeItems:function(e,t){var i=this,n=[];return t.append(e.map(function(e){var t=i._createItemView(e);return n.push(t),i._renderItemView$el(t)})),n},_filterItem:function(e){var t=this.model.contents;return(t.includeHidden||!e.hidden())&&(t.includeDeleted||!e.isDeletedOrPurged())&&k.prototype._filterItem.call(this,e)},_getItemViewClass:function(e){var t=e.get("history_content_type");switch(t){case"dataset":return this.HDAViewClass;case"dataset_collection":return this.HDCAViewClass}throw new TypeError("Unknown history_content_type: "+t)},_getItemViewOptions:function(t){var i=k.prototype._getItemViewOptions.call(this,t);return e.extend(i,{linkTarget:this.linkTarget,expanded:this.model.contents.storage.isExpanded(t.id),hasUser:this.model.ownedByCurrUser()})},_setUpItemViewListeners:function(e){var t=this;return k.prototype._setUpItemViewListeners.call(t,e),t.listenTo(e,{expanded:function(e){t.model.contents.storage.addExpanded(e.model)},collapsed:function(e){t.model.contents.storage.removeExpanded(e.model)}})},collapseAll:function(){this.model.contents.storage.clearExpanded(),k.prototype.collapseAll.call(this)},getSelectedModels:function(){var e=k.prototype.getSelectedModels.call(this);return e.historyId=this.collection.historyId,e},events:e.extend(e.clone(k.prototype.events),{"click .show-selectors-btn":"toggleSelectors","click > .controls .prev":"_clickPrevPage","click > .controls .next":"_clickNextPage","change > .controls .pages":"_changePageSelect","click .messages [class$=message]":"clearMessages"}),_clickPrevPage:function(e){this.model.stopPolling(),this.model.contents.fetchPrevPage()},_clickNextPage:function(e){this.model.stopPolling(),this.model.contents.fetchNextPage()},_changePageSelect:function(e){this.model.stopPolling();var t=n(e.currentTarget).val();this.model.contents.fetchPage(t)},toggleShowDeleted:function(e,t){e=void 0!==e?e:!this.model.contents.includeDeleted;var i=this.model.contents;return i.setIncludeDeleted(e,t),this.trigger("show-deleted",e),i.fetchCurrentPage({renderAll:!0}),e},toggleShowHidden:function(e,t,i){e=void 0!==e?e:!this.model.contents.includeHidden;var n=this.model.contents;return n.setIncludeHidden(e,i),this.trigger("show-hidden",e),n.fetchCurrentPage({renderAll:!0}),e},_firstSearch:function(e){var t=this;if(this.log("onFirstSearch",e),this.model.contents.haveSearchDetails())return void this.searchItems(e);this.$("> .controls .search-input").searchInput("toggle-loading"),this.searchFor=e;this.model.contents.progressivelyFetchDetails({silent:!0}).progress(function(e,i,n){t.renderItems(),t.trigger("search:loading-progress",i,n)}).always(function(){t.$el.find("> .controls .search-input").searchInput("toggle-loading")}).done(function(){t.searchItems(e,"force")})},clearSearch:function(e){var t=this;return this.searchFor?(this.searchFor="",this.trigger("search:clear",this),this.$("> .controls .search-query").val(""),this.model.contents.fetchCurrentPage({silent:!0}).done(function(){t.renderItems()}),this):this},errorHandler:function(t,i,n){if(!i||0!==i.status||0!==i.readyState){if(this.error(t,i,n),e.isString(t)&&e.isString(i)){var s=t,a=i;return v.default.errorModal(s,a,n)}return i&&502===i.status?v.default.badGatewayErrorModal():v.default.ajaxErrorModal(t,i,n)}},clearMessages:function(t){return(e.isUndefined(t)?this.$messages().children('[class$="message"]'):n(t.currentTarget)).fadeOut(this.fxSpeed,function(){n(this).remove()}),this},scrollToHid:function(t){return this.scrollToItem(e.first(this.viewsWhereModel({hid:t})))},ordinalIndicator:function(e){var t=""+e;switch(t.charAt(t.length-1)){case"1":return t+"st";case"2":return t+"nd";case"3":return t+"rd";default:return t+"th"}},toString:function(){return"HistoryView("+(this.model?this.model.get("name"):"")+")"}});S.prototype.templates=function(){var t=function(){return'
    \n
    \n
      \n
      \',\n
      '},i=x.default.wrapTemplate(['
      ','
      ','
      <%- history.name %>
      ',"
      ",'
      ','
      <%- history.nice_size %>
      ','
      ','
      ',"<% if( history.deleted && history.purged ){ %>",'
      ',(0,C.default)("This history has been purged and deleted"),"
      ","<% } else if( history.deleted ){ %>",'
      ',(0,C.default)("This history has been deleted"),"
      ","<% } else if( history.purged ){ %>",'
      ',(0,C.default)("This history has been purged"),"
      ","<% } %>","<% if( history.message ){ %>",'
      messagesmall">',"<%= history.message.text %>","
      ","<% } %>","
      ",'
      ','
      ','",'
      ','
      ','",'","
      ",'
      ',"
      ","
      ",'
      ',"
      "],"history"),n=x.default.wrapTemplate(['','",''],"pages");return e.extend(e.clone(k.prototype.templates),{el:t,controls:i,pagination:n})}(),t.default={HistoryView:S}}).call(t,i(1),i(0))},function(e,t,i){"use strict";(function(e){function i(t){return''+e.escape(t.slice(5))+""}function n(t){var n=e.filter(e.uniq(t.tags),function(e){return 0===e.indexOf("name:")}),s=e.sortBy(n).map(i);return'\n
      \n '+s.join("")+"\n
      "}Object.defineProperty(t,"__esModule",{value:!0}),t.default={nametagTemplate:n}}).call(t,i(1))},function(e,t,i){"use strict";(function(e){function n(e){return e&&e.__esModule?e:{default:e}}function s(e){var t=e.get("collection_type");return"list"==t?(0,f.default)("list"):"paired"==t?(0,f.default)("dataset pair"):"list:paired"==t?(0,f.default)("list of pairs"):(0,f.default)("nested list")}function a(e){var t=e.get("element_count"),i="a "+s(e);if(t){var n;1==t?n="with 1 item":t&&(n="with "+t+" items"),i=i+" "+(0,f.default)(n)}return i}Object.defineProperty(t,"__esModule",{value:!0});var o=i(38),l=n(o),r=i(29),d=(n(r),i(33)),c=n(d),u=i(4),h=(n(u),i(3)),f=n(h),p=l.default.ModelListPanel,m=p.extend({_logNamespace:"collections",className:p.prototype.className+" dataset-collection-panel",DatasetDCEViewClass:c.default.DatasetDCEListItemView,modelCollectionKey:"elements",initialize:function(e){p.prototype.initialize.call(this,e),this.linkTarget=e.linkTarget||"_blank",this.hasUser=e.hasUser,this.panelStack=[],this.parentName=e.parentName,this.foldoutStyle=e.foldoutStyle||"foldout",this.downloadUrl=Galaxy.root+"api/dataset_collections/"+this.model.attributes.id+"/download"},getNestedDCDCEViewClass:function(){return c.default.NestedDCDCEListItemView.extend({foldoutPanelClass:m})},_queueNewRender:function(e,t){t=void 0===t?this.fxSpeed:t;var i=this;this.handleWarning(e),i.log("_queueNewRender:",e,t),i._swapNewRender(e),i.trigger("rendered",i)},handleWarning:function(e){var t=this.views.length,i=this.model.get("element_count");if(i&&i!==t){var n=(0,f.default)("displaying only "+t+" of "+i+" items");e.find(".elements-warning").html('
      '+n+"
      ")}},_filterCollection:function(){return this.model.getVisibleContents()},_getItemViewClass:function(e){switch(e.get("element_type")){case"hda":return this.DatasetDCEViewClass;case"dataset_collection":return this.getNestedDCDCEViewClass()}throw new TypeError("Unknown element type:",e.get("element_type"))},_getItemViewOptions:function(t){var i=p.prototype._getItemViewOptions.call(this,t);return e.extend(i,{linkTarget:this.linkTarget,hasUser:this.hasUser,foldoutStyle:this.foldoutStyle})},_setUpItemViewListeners:function(e){var t=this;return p.prototype._setUpItemViewListeners.call(t,e),t.listenTo(e,{"expanded:drilldown":function(e,t){this._expandDrilldownPanel(t)},"collapsed:drilldown":function(e,t){this._collapseDrilldownPanel(t)}}),this},_expandDrilldownPanel:function(e){this.panelStack.push(e),this.$("> .controls").add(this.$list()).hide(),e.parentName=this.model.get("name"),this.$el.append(e.render().$el)},_collapseDrilldownPanel:function(e){this.panelStack.pop(),this.render()},events:{"click .navigation .back":"close"},close:function(e){this.remove(),this.trigger("close")},toString:function(){return"CollectionView("+(this.model?this.model.get("name"):"")+")"}});m.prototype.templates=function(){var t=function(t,i){var n=a(i.model);return'\n
      \n \n
      \n
      '+(e.escape(t.name)||e.escape(t.element_identifier))+'
      \n
      \n '+n+'\n
      \n
      \n
      \n
      \n
      \n
      \n \n \n \n
      \n
      '};return e.extend(e.clone(p.prototype.templates),{controls:t})}(),t.default={collectionTypeDescription:s,collectionDescription:a,CollectionView:m}}).call(t,i(1))},function(e,t,i){"use strict";(function(e,n){function s(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=i(8),o=s(a),l=i(17),r=s(l),d=i(22),c=s(d),u=i(61),h=s(u),f=i(10),p=s(f),m=i(4),g=s(m),_=i(3),v=s(_),w=r.default.DatasetListItemView,y=w.extend({initialize:function(e){w.prototype.initialize.call(this,e),this.hasUser=e.hasUser,this.purgeAllowed=e.purgeAllowed||!1,this.tagsEditorShown=e.tagsEditorShown||!1,this.annotationEditorShown=e.annotationEditorShown||!1},_renderPrimaryActions:function(){var e=w.prototype._renderPrimaryActions.call(this);return this.model.get("state")===o.default.NOT_VIEWABLE?e:w.prototype._renderPrimaryActions.call(this).concat([this._renderEditButton(),this._renderDeleteButton()])},_renderEditButton:function(){var t=this;if(this.model.get("state")===o.default.DISCARDED||!this.model.get("accessible"))return null;var i=this.model.get("purged"),n=this.model.get("deleted"),s={title:(0,v.default)("Edit attributes"),href:Galaxy.root+"datasets/edit?dataset_id="+this.model.attributes.id,faIcon:"fa-pencil",classes:"edit-btn",onclick:function(e){Galaxy.router&&(e.preventDefault(),Galaxy.router.push("datasets/edit",{dataset_id:t.model.attributes.id}))}};return n||i?(s.disabled=!0,i?s.title=(0,v.default)("Cannot edit attributes of datasets removed from disk"):n&&(s.title=(0,v.default)("Undelete dataset to edit attributes"))):e.contains([o.default.UPLOAD,o.default.NEW],this.model.get("state"))&&(s.disabled=!0,s.title=(0,v.default)("This dataset is not yet editable")),(0,p.default)(s)},_renderDeleteButton:function(){if(!this.model.get("accessible"))return null;var e=this,t=this.model.isDeletedOrPurged();return(0,p.default)({title:t?(0,v.default)("Dataset is already deleted"):(0,v.default)("Delete"),disabled:t,faIcon:"fa-times",classes:"delete-btn",onclick:function(){e.$el.find(".icon-btn.delete-btn").trigger("mouseout"),e.model.delete()}})},_renderDetails:function(){var t=w.prototype._renderDetails.call(this),i=this.model.get("state");return!this.model.isDeletedOrPurged()&&e.contains([o.default.OK,o.default.FAILED_METADATA],i)&&(this._renderTags(t),this._renderAnnotation(t),this._makeDbkeyEditLink(t)),this._setUpBehaviors(t),t},_renderToolHelpButton:function(){var e=this.model.attributes.dataset_id,t=this.model.attributes.creating_job,i=this,s=function(t){var s='
      ';t.name&&t.help?(s+="Tool help for "+t.name+"
      ",s+=t.help):s+="Tool help is unavailable for this dataset.
      ",s+="
      ",i.$el.find(".details").append(n.parseHTML(s))},a=function(e){n.ajax({url:Galaxy.root+"api/tools/"+e.tool_id+"/build"}).done(function(e){s(e)}).fail(function(){s({})})};return null===Galaxy.user.id?null:(0,p.default)({title:(0,v.default)("Tool Help"),classes:"icon-btn",href:"#",faIcon:"fa-question",onclick:function(){i.$el.find(".toolhelp").length>0?i.$el.find(".toolhelp").toggle():n.ajax({url:Galaxy.root+"api/jobs/"+t}).done(function(e){a(e)}).fail(function(){console.log('Failed at recovering job information from the Galaxy API for job id "'+t+'".')})}})},_renderSecondaryActions:function(){var e=w.prototype._renderSecondaryActions.call(this);switch(this.model.get("state")){case o.default.UPLOAD:case o.default.NOT_VIEWABLE:return e;case o.default.ERROR:return e.unshift(this._renderErrButton()),e.concat([this._renderRerunButton(),this._renderToolHelpButton()]);case o.default.OK:case o.default.FAILED_METADATA:return e.concat([this._renderRerunButton(),this._renderVisualizationsButton(),this._renderToolHelpButton()])}return e.concat([this._renderRerunButton(),this._renderToolHelpButton()])},_renderErrButton:function(){var e=this;return(0,p.default)({title:(0,v.default)("View or report this error"),href:Galaxy.root+"datasets/error?dataset_id="+this.model.attributes.id,classes:"report-error-btn",faIcon:"fa-bug",onclick:function(t){Galaxy.router&&(t.preventDefault(),Galaxy.router.push("datasets/error",{dataset_id:e.model.attributes.id}))}})},_renderRerunButton:function(){var e=this.model.get("creating_job");if(this.model.get("rerunnable"))return(0,p.default)({title:(0,v.default)("Run this job again"),href:this.model.urls.rerun,classes:"rerun-btn",target:this.linkTarget,faIcon:"fa-refresh",onclick:function(t){Galaxy.router&&(t.preventDefault(),Galaxy.router.push("/",{job_id:e}))}})},_renderVisualizationsButton:function(){var t=this.model.get("visualizations");if(this.model.isDeletedOrPurged()||!this.hasUser||!this.model.hasData()||e.isEmpty(t))return null;if(!e.isObject(t[0]))return this.warn("Visualizations have been switched off"),null;var i=n(this.templates.visualizations(t,this));return i.find('[target="galaxy_main"]').attr("target",this.linkTarget),this._addScratchBookFn(i.find(".visualization-link").addBack(".visualization-link")),i},_addScratchBookFn:function(e){e.click(function(e){Galaxy.frame&&Galaxy.frame.active&&(Galaxy.frame.add({title:(0,v.default)("Visualization"),url:n(this).attr("href")}),e.preventDefault(),e.stopPropagation())})},_renderTags:function(e){if(this.hasUser){var t=this;this.tagsEditor=new c.default.TagsEditor({model:this.model,el:e.find(".tags-display"),onshowFirstTime:function(){this.render()},onshow:function(){t.tagsEditorShown=!0},onhide:function(){t.tagsEditorShown=!1},$activator:(0,p.default)({title:(0,v.default)("Edit dataset tags"),classes:"tag-btn",faIcon:"fa-tags"}).appendTo(e.find(".actions .right"))}),this.tagsEditorShown&&this.tagsEditor.toggle(!0)}},_renderAnnotation:function(e){if(this.hasUser){var t=this;this.annotationEditor=new h.default.AnnotationEditor({model:this.model,el:e.find(".annotation-display"),onshowFirstTime:function(){this.render()},onshow:function(){t.annotationEditorShown=!0},onhide:function(){t.annotationEditorShown=!1},$activator:(0,p.default)({title:(0,v.default)("Edit dataset annotation"),classes:"annotate-btn",faIcon:"fa-comment"}).appendTo(e.find(".actions .right"))}),this.annotationEditorShown&&this.annotationEditor.toggle(!0)}},_makeDbkeyEditLink:function(e){if("?"===this.model.get("metadata_dbkey")&&!this.model.isDeletedOrPurged()){var t=n('?').attr("href",this.model.urls.edit).attr("target","_top");e.find(".dbkey .value").replaceWith(t)}},events:e.extend(e.clone(w.prototype.events),{"click .undelete-link":"_clickUndeleteLink","click .purge-link":"_clickPurgeLink","click .edit-btn":function(e){this.trigger("edit",this,e)},"click .delete-btn":function(e){this.trigger("delete",this,e)},"click .rerun-btn":function(e){this.trigger("rerun",this,e)},"click .report-err-btn":function(e){this.trigger("report-err",this,e)},"click .visualization-btn":function(e){this.trigger("visualize",this,e)},"click .dbkey a":function(e){this.trigger("edit",this,e)}}),_clickUndeleteLink:function(e){return this.model.undelete(),!1},_clickPurgeLink:function(e){return confirm((0,v.default)("This will permanently remove the data in your dataset. Are you sure?"))&&this.model.purge(),!1},toString:function(){return"HDAEditView("+(this.model?""+this.model:"(no model)")+")"}});y.prototype.templates=function(){var t=e.extend({},w.prototype.templates.warnings,{failed_metadata:g.default.wrapTemplate(['<% if( dataset.state === "failed_metadata" ){ %>','","<% } %>"],"dataset"),deleted:g.default.wrapTemplate(["<% if( dataset.deleted && !dataset.purged ){ %>",'
      ',(0,v.default)("This dataset has been deleted"),'
      ',(0,v.default)("Undelete it"),"","<% if( view.purgeAllowed ){ %>",'
      ',(0,v.default)("Permanently remove it from disk"),"","<% } %>","
      ","<% } %>"],"dataset")}),i=g.default.wrapTemplate(["<% if( visualizations.length === 1 ){ %>",'">','',"","<% } else { %>",'","<% } %>"],"visualizations");return e.extend({},w.prototype.templates,{warnings:t,visualizations:i})}(),t.default={DatasetListItemEdit:y}}).call(t,i(1),i(0))},function(e,t,i){"use strict";(function(e,n){function s(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=i(4),o=s(a),l=i(3),r=s(l);i(15);var d=e.View.extend(o.default.LoggableMixin).extend(o.default.HiddenUntilActivatedViewMixin).extend({tagName:"div",className:"annotation-display",initialize:function(e){e=e||{},this.tooltipConfig=e.tooltipConfig||{placement:"bottom"},this.listenTo(this.model,"change:annotation",function(){this.render()}),this.hiddenUntilActivated(e.$activator,e)},render:function(){var e=this;return this.$el.html(this._template()),this.$annotation().make_text_editable({use_textarea:!0,on_finish:function(t){e.$annotation().text(t),e.model.save({annotation:t},{silent:!0}).fail(function(){e.$annotation().text(e.model.previous("annotation"))})}}),this},_template:function(){var e=this.model.get("annotation");return['",'
      ',n.escape(e),"
      "].join("")},$annotation:function(){return this.$el.find(".annotation")},remove:function(){this.$annotation.off(),this.stopListening(this.model),e.View.prototype.remove.call(this)},toString:function(){return["AnnotationEditor(",""+this.model,")"].join("")}});t.default={AnnotationEditor:d}}).call(t,i(2),i(1))},,,,,,,,,function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=i(2),s=function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[i]=e[i]);return t.default=e,t}(n),a=i(32),o=function(e){return e&&e.__esModule?e:{default:e}}(a),l=["new","queued","running"],r=["error","deleted"],d=s.Model.extend({url:function(){return Galaxy.root+"api/histories/"+this.attributes.history_id+"/contents/dataset_collections/"+this.attributes.collection_id+"/jobs_summary"},hasDetails:function(){return this.has("populated_state")},new:function(){return!this.hasDetails()||"new"==this.get("populated_state")},errored:function(){return"error"===this.get("populated_state")||this.anyWithStates(r)},states:function(){return this.get("states")||{}},anyWithState:function(e){return(this.states()[e]||0)>0},anyWithStates:function(e){var t=this.states();for(var i in e)if((t[e[i]]||0)>0)return!0;return!1},numWithStates:function(e){var t=this.states(),i=0;for(var n in e)i+=t[e[n]]||0;return i},numInError:function(){return this.numWithStates(r)},running:function(){return this.anyWithState("running")},terminal:function(){return!this.new()&&!this.anyWithStates(l)},jobCount:function(){var e=this.states(),t=0;for(var i in e)t+=e[i];return t},toString:function(){return"JobStatesSummary(id="+this.get("id")+")"}}),c=s.Collection.extend({model:d,initialize:function(){this.updateTimeoutId=null,this.active=!0},url:function(){var e=this.models.filter(function(e){return!e.terminal()}),t=e.map(function(e){return e.get("id")}).join(","),i=e.map(function(e){return e.get("model")}).join(",");return Galaxy.root+"api/histories/"+this.historyId+"/jobs_summary?ids="+t+"&types="+i},monitor:function(){var e=this;if(this.clearUpdateTimeout(),this.active){var t=function(){e.updateTimeoutId=setTimeout(function(){e.monitor()},2e3)},i=this.models.filter(function(e){return!e.terminal()});if(i.length,!1){var n=i.map(function(e){return function(){return e.fetch()}});return new o.default.AjaxQueue(n).done(t)}i.length>0?this.fetch({remove:!1}).done(t):t()}},clearUpdateTimeout:function(){this.updateTimeoutId&&(clearTimeout(this.updateTimeoutId),this.updateTimeoutId=null)},toString:function(){return"JobStatesSummaryCollection()"}});t.default={JobStatesSummary:d,JobStatesSummaryCollection:c,FETCH_STATE_ON_ADD:!1}},function(e,t,i){"use strict";(function(e,n,s){function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(57),l=a(o),r=i(16),d=(a(r),i(8)),c=(a(d),i(37)),u=a(c),h=i(72),f=a(h),p=i(96),m=a(p),g=i(22),_=a(g),v=i(61),w=a(v),y=i(28),b=a(y),x=i(73),$=a(x),C=i(74),k=a(C),S=i(10),T=a(S),P=i(47),D=(a(P),i(4)),E=a(D),A=i(3),M=a(A);i(15);var I=l.default.HistoryView,O=I.extend({HDAViewClass:f.default.HDAListItemEdit,HDCAViewClass:m.default.HDCAListItemEdit,initialize:function(e){e=e||{},I.prototype.initialize.call(this,e),this.tagsEditor=null,this.dragItems=!0,this.annotationEditor=null,this.purgeAllowed=e.purgeAllowed||!1,this.annotationEditorShown=e.annotationEditorShown||!1,this.tagsEditorShown=e.tagsEditorShown||!1},_setUpListeners:function(){return I.prototype._setUpListeners.call(this),this.on({"droptarget:drop":function(e,t){this.dataDropped(t),this.dropTargetOff()},"view:attached view:removed":function(){this._renderCounts()},"search:loading-progress":this._renderSearchProgress,"search:searching":this._renderSearchFindings})},_setUpModelListeners:function(){return I.prototype._setUpModelListeners.call(this),this.listenTo(this.model,"change:size",this.updateHistoryDiskSize),this},_setUpCollectionListeners:function(){return I.prototype._setUpCollectionListeners.call(this),this.listenTo(this.collection,{"change:deleted":this._handleItemDeletedChange,"change:visible":this._handleItemVisibleChange,"change:purged":function(e){this.model.fetch()},"fetching-deleted":function(e){this.$("> .controls .deleted-count").html(""+(0,M.default)("loading...")+"")},"fetching-hidden":function(e){this.$("> .controls .hidden-count").html(""+(0,M.default)("loading...")+"")},"fetching-deleted-done fetching-hidden-done":this._renderCounts}),this},_buildNewRender:function(){var e=I.prototype._buildNewRender.call(this);return this.model?(Galaxy&&Galaxy.user&&Galaxy.user.id&&Galaxy.user.id===this.model.get("user_id")&&(this._renderTags(e),this._renderAnnotation(e)),e):e},updateHistoryDiskSize:function(){this.$(".history-size").text(this.model.get("nice_size"))},renderItems:function(e){var t=I.prototype.renderItems.call(this,e);return this.searchFor?this._renderSearchFindings(e):this._renderCounts(e),t},_renderCounts:function(t){t=t instanceof e?t:this.$el;var i=this.templates.counts(this.model.toJSON(),this);return t.find("> .controls .subtitle").html(i)},_renderTags:function(e){var t=this;this.tagsEditor=new _.default.TagsEditor({model:this.model,el:e.find(".controls .tags-display"),onshowFirstTime:function(){this.render()},onshow:function(){t.toggleHDATagEditors(!0,t.fxSpeed)},onhide:function(){t.toggleHDATagEditors(!1,t.fxSpeed)},$activator:(0,T.default)({title:(0,M.default)("Edit history tags"),classes:"history-tag-btn",faIcon:"fa-tags"}).appendTo(e.find(".controls .actions"))})},_renderAnnotation:function(e){var t=this;this.annotationEditor=new w.default.AnnotationEditor({model:this.model,el:e.find(".controls .annotation-display"),onshowFirstTime:function(){this.render()},onshow:function(){t.toggleHDAAnnotationEditors(!0,t.fxSpeed)},onhide:function(){t.toggleHDAAnnotationEditors(!1,t.fxSpeed)},$activator:(0,T.default)({title:(0,M.default)("Edit history annotation"),classes:"history-annotate-btn",faIcon:"fa-comment"}).appendTo(e.find(".controls .actions"))})},_setUpBehaviors:function(e){if(e=e||this.$el,I.prototype._setUpBehaviors.call(this,e),this.model&&Galaxy.user&&!Galaxy.user.isAnonymous()&&Galaxy.user.id===this.model.get("user_id")){var t=this;e.find("> .controls .name").attr("title",(0,M.default)("Click to rename history")).tooltip({placement:"bottom"}).make_text_editable({on_finish:function(e){var i=t.model.get("name");e&&e!==i?(t.$el.find("> .controls .name").text(e),t.model.save({name:e}).fail(function(){t.$el.find("> .controls .name").text(t.model.previous("name"))})):t.$el.find("> .controls .name").text(i)}})}},multiselectActions:function(){var e=this,t=[{html:(0,M.default)("Hide datasets"),func:function(){var t=u.default.HistoryDatasetAssociation.prototype.hide;e.getSelectedModels().ajaxQueue(t)}},{html:(0,M.default)("Unhide datasets"),func:function(){var t=u.default.HistoryDatasetAssociation.prototype.unhide;e.getSelectedModels().ajaxQueue(t)}},{html:(0,M.default)("Delete datasets"),func:function(){var t=u.default.HistoryDatasetAssociation.prototype.delete;e.getSelectedModels().ajaxQueue(t)}},{html:(0,M.default)("Undelete datasets"),func:function(){var t=u.default.HistoryDatasetAssociation.prototype.undelete;e.getSelectedModels().ajaxQueue(t)}}];return e.purgeAllowed&&t.push({html:(0,M.default)("Permanently delete datasets"),func:function(){if(confirm((0,M.default)("This will permanently remove the data in your datasets. Are you sure?"))){var t=u.default.HistoryDatasetAssociation.prototype.purge;e.getSelectedModels().ajaxQueue(t)}}}),t=t.concat(e._collectionActions())},_collectionActions:function(){var e=this;return[{html:(0,M.default)("Build Dataset List"),func:function(){e.buildCollection("list")}},{html:(0,M.default)("Build Dataset Pair"),func:function(){e.buildCollection("paired")}},{html:(0,M.default)("Build List of Dataset Pairs"),func:function(){e.buildCollection("list:paired")}}]},buildCollection:function(e,t,i){var n,s=this,t=t||s.getSelectedModels(),i=i||!1;"list"==e?n=b.default.createListCollection:"paired"==e?n=$.default.createPairCollection:"list:paired"==e?n=k.default.createListOfPairsCollection:console.warn("Unknown collectionType encountered "+e),n(t,i).done(function(){s.model.refresh()})},_getItemViewOptions:function(e){var t=I.prototype._getItemViewOptions.call(this,e);return n.extend(t,{purgeAllowed:this.purgeAllowed,tagsEditorShown:this.tagsEditor&&!this.tagsEditor.hidden,annotationEditorShown:this.annotationEditor&&!this.annotationEditor.hidden}),t},_handleItemDeletedChange:function(e){e.get("deleted")?this._handleItemDeletion(e):this._handleItemUndeletion(e),this._renderCounts()},_handleItemDeletion:function(e){var t=this.model.get("contents_active");t.deleted+=1,t.active-=1,this.model.contents.includeDeleted||this.removeItemView(e),this.model.set("contents_active",t)},_handleItemUndeletion:function(e){var t=this.model.get("contents_active");t.deleted-=1,this.model.contents.includeDeleted||(t.active-=1),this.model.set("contents_active",t)},_handleItemVisibleChange:function(e){e.hidden()?this._handleItemHidden(e):this._handleItemUnhidden(e),this._renderCounts()},_handleItemHidden:function(e){var t=this.model.get("contents_active");t.hidden+=1,t.active-=1,this.model.contents.includeHidden||this.removeItemView(e),this.model.set("contents_active",t)},_handleItemUnhidden:function(e){var t=this.model.get("contents_active");t.hidden-=1,this.model.contents.includeHidden||(t.active-=1),this.model.set("contents_active",t)},toggleHDATagEditors:function(e,t){n.each(this.views,function(i){i.tagsEditor&&i.tagsEditor.toggle(e,t)})},toggleHDAAnnotationEditors:function(e,t){n.each(this.views,function(i){i.annotationEditor&&i.annotationEditor.toggle(e,t)})},events:n.extend(n.clone(I.prototype.events),{"click .show-selectors-btn":"toggleSelectors","click .toggle-deleted-link":function(e){this.toggleShowDeleted()},"click .toggle-hidden-link":function(e){this.toggleShowHidden()}}),_renderSearchProgress:function(e,t){var i=e+t;return this.$("> .controls .subtitle").html(["",(0,M.default)("Searching "),i,"/",this.model.contentsShown(),""].join(""))},_renderSearchFindings:function(t){t=t instanceof e?t:this.$el;var i=this.templates.found(this.model.toJSON(),this);return t.find("> .controls .subtitle").html(i),this},dropTargetOn:function(){if(this.dropTarget)return this;this.dropTarget=!0;var e={dragenter:n.bind(this.dragenter,this),dragover:n.bind(this.dragover,this),dragleave:n.bind(this.dragleave,this),drop:n.bind(this.drop,this)},t=this._renderDropTarget();this.$list().before([this._renderDropTargetHelp(),t]);for(var i in e)e.hasOwnProperty(i)&&t.on(i,e[i]);return this},_renderDropTarget:function(){return this.$(".history-drop-target").remove(),s("
      ").addClass("history-drop-target")},_renderDropTargetHelp:function(){return this.$(".history-drop-target-help").remove(),s("
      ").addClass("history-drop-target-help").text((0,M.default)("Drag datasets here to copy them to the current history"))},dropTargetOff:function(){if(!this.dropTarget)return this;this.dropTarget=!1;var e=this.$(".history-drop-target").get(0);for(var t in this._dropHandlers)this._dropHandlers.hasOwnProperty(t)&&e.off(t,this._dropHandlers[t]);return this.$(".history-drop-target").remove(),this.$(".history-drop-target-help").remove(),this},dropTargetToggle:function(){return this.dropTarget?this.dropTargetOff():this.dropTargetOn(),this},dragenter:function(e){e.preventDefault(),e.stopPropagation(),this.$(".history-drop-target").css("border","2px solid black")},dragover:function(e){e.preventDefault(),e.stopPropagation()},dragleave:function(e){e.preventDefault(),e.stopPropagation(),this.$(".history-drop-target").css("border","1px dashed black")},drop:function(e){e.preventDefault();var t=this,i=e.originalEvent.dataTransfer,n=i.getData("text");i.dropEffect="move";try{n=JSON.parse(n)}catch(e){t.warn("error parsing JSON from drop:",n)}return t.trigger("droptarget:drop",e,n,t),!1},dataDropped:function(t){var i=this;return n.isObject(t)&&"HistoryDatasetAssociation"===t.model_class&&t.id?0!==i.contents.currentPage?i.contents.fetchPage(0).then(function(){return i.model.contents.copy(t.id)}):i.model.contents.copy(t.id):e.when()},toString:function(){return"HistoryViewEdit("+(this.model?this.model.get("name"):"")+")"}});O.prototype.templates=function(){var e=E.default.wrapTemplate(["<% var shown = Math.max( view.views.length, history.contents_active.active ) %>","<% if( shown ){ %>",'',"<%- shown %> ",(0,M.default)("shown"),"","<% } %>","<% if( history.contents_active.deleted ){ %>",'',"<% if( view.model.contents.includeDeleted ){ %>",'',(0,M.default)("hide deleted"),"","<% } else { %>","<%- history.contents_active.deleted %> ",'',(0,M.default)("deleted"),"","<% } %>","","<% } %>","<% if( history.contents_active.hidden ){ %>",'',"<% if( view.model.contents.includeHidden ){ %>",'',(0,M.default)("hide hidden"),"","<% } else { %>","<%- history.contents_active.hidden %> ",'',(0,M.default)("hidden"),"","<% } %>","","<% } %>"],"history"),t=E.default.wrapTemplate([(0,M.default)("Found")," <%- view.views.length %>, ","<% if( history.contents_active.deleted ){ %>","<% if( view.model.contents.includeDeleted ){ %>",'',(0,M.default)("hide deleted"),", ","<% } else { %>",'',(0,M.default)("show deleted"),", ","<% } %>","<% } %>","<% if( history.contents_active.hidden ){ %>","<% if( view.model.contents.includeHidden ){ %>",'',(0,M.default)("hide hidden"),"","<% } else { %>",'',(0,M.default)("show hidden"),"","<% } %>","<% } %>"],"history");return n.extend(n.clone(I.prototype.templates),{counts:e,found:t})}(),t.default={HistoryViewEdit:O}}).call(t,i(0),i(1),i(0))},function(e,t,i){"use strict";(function(e,n){function s(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=i(60),o=s(a),l=i(40),r=s(l),d=i(4),c=s(d),u=i(3),h=s(u),f=o.default.DatasetListItemEdit,p=f.extend({className:f.prototype.className+" history-content",_fetchModelDetails:function(){var t=this;return t.model.inReadyState()&&!t.model.hasDetails()?t.model.fetch({silent:!0}):t.model.has("rerunnable")?e.when():t.model.fetch({silent:!0,data:{keys:["rerunnable","creating_job"].join(",")}})},events:n.extend(n.clone(f.prototype.events),{"click .unhide-link":function(e){return this.model.unhide(),!1}}),toString:function(){return"HDAListItemEdit("+(this.model?""+this.model:"(no model)")+")"}});p.prototype.templates=function(){var e=n.extend({},f.prototype.templates.warnings,{hidden:c.default.wrapTemplate(["<% if( !dataset.visible ){ %>",'
      ',(0,h.default)("This dataset has been hidden"),'
      ',(0,h.default)("Unhide it"),"","
      ","<% } %>"],"dataset")});return n.extend({},f.prototype.templates,{titleBar:r.default.HDAListItemView.prototype.templates.titleBar,warnings:e})}(),t.default={HDAListItemEdit:p}}).call(t,i(0),i(1))},function(e,t,i){"use strict";(function(e,n,s){function a(e){return e&&e.__esModule?e:{default:e}}function o(e,t){var i=e.toJSON();return v(i,{defaultHideSourceItems:t,creationFn:function(t,i,n){return t=[{name:"forward",src:"hda",id:t[0].id},{name:"reverse",src:"hda",id:t[1].id}],e.createHDCA(t,"paired",i,n)}})}Object.defineProperty(t,"__esModule",{value:!0});var l=i(28),r=a(l),d=i(20),c=a(d),u=i(4),h=a(u),f=i(3),p=a(f),m=e.View.extend(h.default.LoggableMixin).extend({_logNamespace:"collections",tagName:"li",className:"collection-element",initialize:function(e){this.element=e.element||{},this.identifier=e.identifier},render:function(){return this.$el.attr("data-element-id",this.element.id).html(this.template({identifier:this.identifier,element:this.element})),this},template:n.template(['<%- identifier %>','<%- element.name %>'].join("")),destroy:function(){this.off(),this.$el.remove()},toString:function(){return"DatasetCollectionElementView()"}}),g=r.default.ListCollectionCreator,_=g.extend({elementViewClass:m,collectionClass:c.default.HistoryDatasetCollection,className:"pair-collection-creator collection-creator flex-row-container",_mangleDuplicateNames:function(){},render:function(e,t){return 2===this.workingElements.length?g.prototype.render.call(this,e,t):this._renderInvalid(e,t)},_renderList:function(e,t){var i=this,a=s("
      "),o=i.$list();n.each(this.elementViews,function(e){e.destroy(),i.removeElementView(e)}),a.append(i._createForwardElementView().$el),a.append(i._createReverseElementView().$el),o.empty().append(a.children()),n.invoke(i.elementViews,"render")},_createForwardElementView:function(){return this._createElementView(this.workingElements[0],{identifier:"forward"})},_createReverseElementView:function(){return this._createElementView(this.workingElements[1],{identifier:"reverse"})},_createElementView:function(e,t){var i=new this.elementViewClass(n.extend(t,{element:e}));return this.elementViews.push(i),i},swap:function(){this.workingElements=[this.workingElements[1],this.workingElements[0]],this._renderList()},events:n.extend(n.clone(g.prototype.events),{"click .swap":"swap"}),templates:n.extend(n.clone(g.prototype.templates),{middle:n.template(['",'
      ',"
      "].join("")),helpContent:n.template(["

      ",(0,p.default)(["Pair collections are permanent collections containing two datasets: one forward and one reverse. ","Often these are forward and reverse reads. The pair collections can be passed to tools and ","workflows in order to have analyses done on both datasets. This interface allows ","you to create a pair, name it, and swap which is forward and which reverse."].join("")),"

      ","
        ","
      • ",(0,p.default)(['Click the "Swap" link to make your forward dataset the reverse ',"and the reverse dataset forward."].join("")),"
      • ","
      • ",(0,p.default)(['Click the "Cancel" button to exit the interface.'].join("")),"
      • ","

      ","

      ",(0,p.default)(['Once your collection is complete, enter a name and ','click "Create list".'].join("")),"

      "].join("")),invalidInitial:n.template(['
      ','
      ','',"<% if( _.size( problems ) ){ %>",(0,p.default)("The following selections could not be included due to problems"),"
        <% _.each( problems, function( problem ){ %>","
      • <%- problem.element.name %>: <%- problem.text %>
      • ","<% }); %>
      ","<% } else if( _.size( elements ) === 0 ){ %>",(0,p.default)("No datasets were selected"),".","<% } else if( _.size( elements ) === 1 ){ %>",(0,p.default)("Only one dataset was selected"),": <%- elements[0].name %>","<% } else if( _.size( elements ) > 2 ){ %>",(0,p.default)("Too many datasets were selected"),': <%- _.pluck( elements, "name" ).join( ", ") %>',"<% } %>","
      ",(0,p.default)("Two (and only two) elements are needed for the pair"),". ",(0,p.default)("You may need to "),'',(0,p.default)("cancel")," ",(0,p.default)("and reselect new elements"),".","
      ","
      ","
      ",'"].join(""))}),toString:function(){return"PairCollectionCreator"}}),v=function(e,t){return t=t||{},t.title=(0,p.default)("Create a collection from a pair of datasets"),r.default.collectionCreatorModal(e,t,_)};t.default={PairCollectionCreator:_,pairCollectionCreatorModal:v,createPairCollection:o}}).call(t,i(2),i(1),i(0))},function(e,t,i){"use strict";(function(e,n,s,a){function o(e){return e&&e.__esModule?e:{default:e}}function l(e){function t(){return i.length||(i=[new RegExp(this.filters[0]),new RegExp(this.filters[1])]),i}e=e||{},e.createPair=e.createPair||function(e){e=e||{};var t=e.listA.splice(e.indexA,1)[0],i=e.listB.splice(e.indexB,1)[0],n=e.listB.indexOf(t),s=e.listA.indexOf(i);return-1!==n&&e.listB.splice(n,1),-1!==s&&e.listA.splice(s,1),this._pair(t,i,{silent:!0})};var i=[];return e.preprocessMatch=e.preprocessMatch||function(e){var i=t.call(this);return n.extend(e,{matchTo:e.matchTo.name.replace(i[0],""),possible:e.possible.name.replace(i[1],"")})},function(t){this.debug("autopair _strategy ---------------------------"),t=t||{};var i,n=t.listA,s=t.listB,a=0,o={score:0,index:null},l=[];for(this.debug("starting list lens:",n.length,s.length),this.debug("bestMatch (starting):",JSON.stringify(o,null," "));a=c?l.push(e.createPair.call(this,{listA:n,indexA:a,listB:s,indexB:o.index})):a+=1,!n.length||!s.length)return l}return this.debug("paired:",JSON.stringify(l,null," ")),this.debug("autopair _strategy ---------------------------"),l}}function r(e,t){var i=e.toJSON();return b(i,{historyId:e.historyId,defaultHideSourceItems:t})}Object.defineProperty(t,"__esModule",{value:!0});var d=i(99),c=o(d),u=i(53),h=o(u),f=i(52),p=o(f),m=i(4),g=o(m),_=i(3),v=o(_);i(54);var w=e.View.extend(g.default.LoggableMixin).extend({_logNamespace:"collections",tagName:"li",className:"dataset paired",initialize:function(e){this.pair=e.pair||{}},template:n.template(['<%- pair.forward.name %>','','<%- pair.name %>',"",'<%- pair.reverse.name %>'].join("")),render:function(){this.dragStartHandler=n.bind(this._dragstart,this),this.dragEndHandler=n.bind(this._dragend,this);var e=this.$el.attr("draggable",!0).data("pair",this.pair).html(this.template({pair:this.pair})).addClass("flex-column-container").get(0);return e.addEventListener("dragstart",this.dragStartHandler,!1),e.addEventListener("dragend",this.dragEndHandler,!1),this},events:{dragover:"_sendToParent",drop:"_sendToParent"},_dragstart:function(e){e.currentTarget.style.opacity="0.4",e.originalEvent&&(e=e.originalEvent),e.dataTransfer.effectAllowed="move",e.dataTransfer.setData("text/plain",JSON.stringify(this.pair)),this.$el.parent().trigger("pair.dragstart",[this])},_dragend:function(e){e.currentTarget.style.opacity="1.0",this.$el.parent().trigger("pair.dragend",[this])},_sendToParent:function(e){this.$el.parent().trigger(e)},toString:function(){return"PairView("+this.pair.name+")"}}),y=e.View.extend(g.default.LoggableMixin).extend(p.default.CollectionCreatorMixin).extend({_logNamespace:"collections",className:"list-of-pairs-collection-creator collection-creator flex-row-container",initialize:function(e){this.metric("PairedCollectionCreator.initialize",e),e=n.defaults(e,{datasets:[],filters:this.DEFAULT_FILTERS,automaticallyPair:!0,strategy:"lcs",matchPercentage:.9,twoPassAutopairing:!0}),this.initialList=e.datasets,this.historyId=e.historyId,this.filters=this.commonFilters[e.filters]||this.commonFilters[this.DEFAULT_FILTERS],n.isArray(e.filters)&&(this.filters=e.filters),this.automaticallyPair=e.automaticallyPair,this.strategy=this.strategies[e.strategy]||this.strategies[this.DEFAULT_STRATEGY],n.isFunction(e.strategy)&&(this.strategy=e.strategy),this.matchPercentage=e.matchPercentage,this.twoPassAutopairing=e.twoPassAutopairing,this.removeExtensions=!0,this.oncancel=e.oncancel,this.oncreate=e.oncreate,this.autoscrollDist=e.autoscrollDist||24,this.unpairedPanelHidden=!1,this.pairedPanelHidden=!1,this.$dragging=null,this.blocking=!1,this._setUpCommonSettings(e),this._setUpBehaviors(),this._dataSetUp()},commonFilters:{illumina:["_1","_2"],Rs:["_R1","_R2"]},DEFAULT_FILTERS:"illumina",strategies:{simple:"autopairSimple",lcs:"autopairLCS",levenshtein:"autopairLevenshtein"},DEFAULT_STRATEGY:"lcs",_dataSetUp:function(){this.paired=[],this.unpaired=[],this.selectedIds=[],this._sortInitialList(),this._ensureIds(),this.unpaired=this.initialList.slice(0),this.automaticallyPair&&(this.autoPair(),this.once("rendered:initial",function(){this.trigger("autopair")}))},_sortInitialList:function(){this._sortDatasetList(this.initialList)},_sortDatasetList:function(e){return e.sort(function(e,t){return(0,h.default)(e.name,t.name)}),e},_ensureIds:function(){return this.initialList.forEach(function(e){e.hasOwnProperty("id")||(e.id=n.uniqueId())}),this.initialList},_splitByFilters:function(){function e(e,t){return t.test(e.name)}var t=this.filters.map(function(e){return new RegExp(e)}),i=[[],[]];return this.unpaired.forEach(function(n){t.forEach(function(t,s){e(n,t)&&i[s].push(n)})}),i},_addToUnpaired:function(e){var t=this;this.unpaired.splice(function i(n,s){if(n===s)return n;var a=Math.floor((s-n)/2)+n,o=(0,h.default)(e.name,t.unpaired[a].name);if(o<0)return i(n,a);if(o>0)return i(a+1,s);for(;t.unpaired[a]&&t.unpaired[a].name===e.name;)a++;return a}(0,this.unpaired.length),0,e)},autoPair:function(e){var t=this._splitByFilters(),i=[];return this.twoPassAutopairing&&(i=this.autopairSimple({listA:t[0],listB:t[1]}),t=this._splitByFilters()),e=e||this.strategy,t=this._splitByFilters(),i=i.concat(this[e].call(this,{listA:t[0],listB:t[1]}))},autopairSimple:l({scoreThreshold:function(){return.6},match:function(e){return e=e||{},e.matchTo===e.possible?{index:e.index,score:1}:e.bestMatch}}),autopairLevenshtein:l({scoreThreshold:function(){return this.matchPercentage},match:function(e){e=e||{};var t=(0,c.default)(e.matchTo,e.possible),i=1-t/Math.max(e.matchTo.length,e.possible.length);return i>e.bestMatch.score?{index:e.index,score:i}:e.bestMatch}}),autopairLCS:l({scoreThreshold:function(){return this.matchPercentage},match:function(e){e=e||{};var t=this._naiveStartingAndEndingLCS(e.matchTo,e.possible).length,i=t/Math.max(e.matchTo.length,e.possible.length);return i>e.bestMatch.score?{index:e.index,score:i}:e.bestMatch}}),_naiveStartingAndEndingLCS:function(e,t){for(var i="",n="",s=0,a=0;s=0&&a>=0&&e[s]===t[a];)n=[e[s],n].join(""),s-=1,a-=1;return i+n},_pair:function(e,t,i){i=i||{},this.debug("_pair:",e,t);var s=this._createPair(e,t,i.name);return this.paired.push(s),this.unpaired=n.without(this.unpaired,e,t),i.silent||this.trigger("pair:new",s),s},_createPair:function(e,t,i){if(!e||!t||e===t)throw new Error("Bad pairing: "+[JSON.stringify(e),JSON.stringify(t)]);return i=i||this._guessNameForPair(e,t),{forward:e,name:i,reverse:t}},_guessNameForPair:function(e,t,i){i=void 0!==i?i:this.removeExtensions;var n=e.name,s=t.name,a=this._naiveStartingAndEndingLCS(n.replace(new RegExp(this.filters[0]),""),s.replace(new RegExp(this.filters[1]),""));if(i){var o=a.lastIndexOf(".");if(o>0){var l=a.slice(o,a.length);a=a.replace(l,""),n=n.replace(l,""),s=s.replace(l,"")}}return a||n+" & "+s},_unpair:function(e,t){if(t=t||{},!e)throw new Error("Bad pair: "+JSON.stringify(e));return this.paired=n.without(this.paired,e),this._addToUnpaired(e.forward),this._addToUnpaired(e.reverse),t.silent||this.trigger("pair:unpair",[e]),e},unpairAll:function(){for(var e=[];this.paired.length;)e.push(this._unpair(this.paired[0],{silent:!0}));this.trigger("pair:unpair",e)},_pairToJSON:function(e,t){return t=t||"hda",{collection_type:"paired",src:"new_collection",name:e.name,element_identifiers:[{name:"forward",id:e.forward.id,src:t},{name:"reverse",id:e.reverse.id,src:t}]}},createList:function(e){var t=this,i=Galaxy.root+"api/histories/"+this.historyId+"/contents/dataset_collections",a={type:"dataset_collection",collection_type:"list:paired",hide_source_items:t.hideOriginals||!1,name:n.escape(e||t.$(".collection-name").val()),element_identifiers:t.paired.map(function(e){return t._pairToJSON(e)})};return t.blocking=!0,s.ajax(i,{type:"POST",contentType:"application/json",dataType:"json",data:JSON.stringify(a)}).always(function(){t.blocking=!1}).fail(function(e,i,n){t._ajaxErrHandler(e,i,n)}).done(function(e,i,n){t.trigger("collection:created",e,i,n),t.metric("collection:created",e),"function"==typeof t.oncreate&&t.oncreate.call(this,e,i,n)})},_ajaxErrHandler:function(e,t,i){this.error(e,t,i);var n=this,s=(0,v.default)("An error occurred while creating this collection");e&&(0===e.readyState&&0===e.status?s+=": "+(0,v.default)("Galaxy could not be reached and may be updating.")+(0,v.default)(" Try again in a few minutes."):e.responseJSON?s+="
      "+JSON.stringify(e.responseJSON)+"
      ":s+=": "+i),n._showAlert(s,"alert-danger")},render:function(e,t){return this.$el.empty().html(this.templates.main()),this._renderHeader(e),this._renderMiddle(e),this._renderFooter(e),this._addPluginComponents(),this.trigger("rendered",this),this},_renderHeader:function(e,t){var i=this.$(".header").empty().html(this.templates.header()).find(".help-content").prepend(a(this.templates.helpContent()));return this._renderFilters(),i},_renderFilters:function(){return this.$(".forward-column .column-header input").val(this.filters[0]).add(this.$(".reverse-column .column-header input").val(this.filters[1]))},_renderMiddle:function(e,t){var i=this.$(".middle").empty().html(this.templates.middle());return this.unpairedPanelHidden?this.$(".unpaired-columns").hide():this.pairedPanelHidden&&this.$(".paired-columns").hide(),this._renderUnpaired(),this._renderPaired(),i},_renderUnpaired:function(e,t){var i,n,s=this,a=[],o=this._splitByFilters();return this.$(".forward-column .title").text([o[0].length,(0,v.default)("unpaired forward")].join(" ")),this.$(".forward-column .unpaired-info").text(this._renderUnpairedDisplayStr(this.unpaired.length-o[0].length)),this.$(".reverse-column .title").text([o[1].length,(0,v.default)("unpaired reverse")].join(" ")),this.$(".reverse-column .unpaired-info").text(this._renderUnpairedDisplayStr(this.unpaired.length-o[1].length)),this.$(".unpaired-columns .column-datasets").empty(),this.$(".autopair-link").toggle(0!==this.unpaired.length),0===this.unpaired.length?void this._renderUnpairedEmpty():(n=o[1].map(function(e,t){return void 0!==o[0][t]&&o[0][t]!==e&&a.push(s._renderPairButton()),s._renderUnpairedDataset(e)}),i=o[0].map(function(e){return s._renderUnpairedDataset(e)}),i.length||n.length?(this.$(".unpaired-columns .forward-column .column-datasets").append(i).add(this.$(".unpaired-columns .paired-column .column-datasets").append(a)).add(this.$(".unpaired-columns .reverse-column .column-datasets").append(n)),void this._adjUnpairedOnScrollbar()):void this._renderUnpairedNotShown())},_renderUnpairedDisplayStr:function(e){return["(",e," ",(0,v.default)("filtered out"),")"].join("")},_renderUnpairedDataset:function(e){return a("
    • ").attr("id","dataset-"+e.id).addClass("dataset unpaired").attr("draggable",!0).addClass(e.selected?"selected":"").append(a("").addClass("dataset-name").text(e.name)).data("dataset",e)},_renderPairButton:function(){return a("
    • ").addClass("dataset unpaired").append(a("").addClass("dataset-name").text((0,v.default)("Pair these datasets")))},_renderUnpairedEmpty:function(){var e=a('
      ').text("("+(0,v.default)("no remaining unpaired datasets")+")");return this.$(".unpaired-columns .paired-column .column-datasets").empty().prepend(e),e},_renderUnpairedNotShown:function(){var e=a('
      ').text("("+(0,v.default)("no datasets were found matching the current filters")+")");return this.$(".unpaired-columns .paired-column .column-datasets").empty().prepend(e),e},_adjUnpairedOnScrollbar:function(){var e=this.$(".unpaired-columns").last(),t=this.$(".unpaired-columns .reverse-column .dataset").first();if(t.length){var i=e.offset().left+e.outerWidth(),n=t.offset().left+t.outerWidth(),s=Math.floor(i)-Math.floor(n);this.$(".unpaired-columns .forward-column").css("margin-left",s>0?s:0)}},_renderPaired:function(e,t){if(this.$(".paired-column-title .title").text([this.paired.length,(0,v.default)("paired")].join(" ")),this.$(".unpair-all-link").toggle(0!==this.paired.length),0===this.paired.length)return void this._renderPairedEmpty();this.$(".remove-extensions-link").show(),this.$(".paired-columns .column-datasets").empty();var i=this;this.paired.forEach(function(e,t){var n=new w({pair:e});i.$(".paired-columns .column-datasets").append(n.render().$el).append(['"].join(""))})},_renderPairedEmpty:function(){var e=a('
      ').text("("+(0,v.default)("no paired datasets yet")+")");return this.$(".paired-columns .column-datasets").empty().prepend(e),e},footerSettings:{".hide-originals":"hideOriginals",".remove-extensions":"removeExtensions"},_addPluginComponents:function(){this._chooseFiltersPopover(".choose-filters-link"),this.$(".help-content i").hoverhighlight(".collection-creator","rgba( 64, 255, 255, 1.0 )")},_chooseFiltersPopover:function(e){function t(e,t){return['"].join("")}var i=a(n.template(['
      ','
      ',(0,v.default)("Choose from the following filters to change which unpaired reads are shown in the display"),":
      ",n.values(this.commonFilters).map(function(e){return t(e[0],e[1])}).join(""),"
      "].join(""))({}));return this.$(e).popover({container:".collection-creator",placement:"bottom",html:!0,content:i})},_validationWarning:function(e,t){"name"===e&&(e=this.$(".collection-name").add(this.$(".collection-name-prompt")),this.$(".collection-name").focus().select()),t?(e=e||this.$(".validation-warning"),e.removeClass("validation-warning")):e.addClass("validation-warning")},_setUpBehaviors:function(){return this.once("rendered",function(){this.trigger("rendered:initial",this)}),this.on("pair:new",function(){this._renderUnpaired(),this._renderPaired(),this.$(".paired-columns").scrollTop(8e6)}),this.on("pair:unpair",function(e){this._renderUnpaired(),this._renderPaired(),this.splitView()}),this.on("filter-change",function(){this.filters=[this.$(".forward-unpaired-filter input").val(),this.$(".reverse-unpaired-filter input").val()],this.metric("filter-change",this.filters),this._renderFilters(),this._renderUnpaired()}),this.on("autopair",function(){this._renderUnpaired(),this._renderPaired();var e,t=null;this.paired.length?(t="alert-success",e=this.paired.length+" "+(0,v.default)("pairs created"),this.unpaired.length||(e+=": "+(0,v.default)("all datasets have been successfully paired"),this.hideUnpaired(),this.$(".collection-name").focus())):e=(0,v.default)(["Could not automatically create any pairs from the given dataset names.","You may want to choose or enter different filters and try auto-pairing again.","Close this message using the X on the right to view more help."].join(" ")),this._showAlert(e,t)}),this},events:{"click .more-help":"_clickMoreHelp","click .less-help":"_clickLessHelp","click .main-help":"_toggleHelp","click .header .alert button":"_hideAlert","click .forward-column .column-title":"_clickShowOnlyUnpaired","click .reverse-column .column-title":"_clickShowOnlyUnpaired","click .unpair-all-link":"_clickUnpairAll","change .forward-unpaired-filter input":function(e){this.trigger("filter-change")},"focus .forward-unpaired-filter input":function(e){a(e.currentTarget).select()},"click .autopair-link":"_clickAutopair","click .choose-filters .filter-choice":"_clickFilterChoice","click .clear-filters-link":"_clearFilters","change .reverse-unpaired-filter input":function(e){this.trigger("filter-change")},"focus .reverse-unpaired-filter input":function(e){a(e.currentTarget).select()},"click .forward-column .dataset.unpaired":"_clickUnpairedDataset","click .reverse-column .dataset.unpaired":"_clickUnpairedDataset","click .paired-column .dataset.unpaired":"_clickPairRow","click .unpaired-columns":"clearSelectedUnpaired","mousedown .unpaired-columns .dataset":"_mousedownUnpaired","click .paired-column-title":"_clickShowOnlyPaired","mousedown .flexible-partition-drag":"_startPartitionDrag","click .paired-columns .dataset.paired":"selectPair","click .paired-columns":"clearSelectedPaired","click .paired-columns .pair-name":"_clickPairName","click .unpair-btn":"_clickUnpair","dragover .paired-columns .column-datasets":"_dragoverPairedColumns","drop .paired-columns .column-datasets":"_dropPairedColumns","pair.dragstart .paired-columns .column-datasets":"_pairDragstart","pair.dragend .paired-columns .column-datasets":"_pairDragend","change .remove-extensions":function(e){this.toggleExtensions()},"change .collection-name":"_changeName","keydown .collection-name":"_nameCheckForEnter","change .hide-originals":"_changeHideOriginals","click .cancel-create":"_cancelCreate","click .create-collection":"_clickCreate"},_clickShowOnlyUnpaired:function(e){this.$(".paired-columns").is(":visible")?this.hidePaired():this.splitView()},_clickShowOnlyPaired:function(e){this.$(".unpaired-columns").is(":visible")?this.hideUnpaired():this.splitView()},hideUnpaired:function(e,t){this.unpairedPanelHidden=!0,this.pairedPanelHidden=!1,this._renderMiddle(e,t)},hidePaired:function(e,t){this.unpairedPanelHidden=!1,this.pairedPanelHidden=!0,this._renderMiddle(e,t)},splitView:function(e,t){return this.unpairedPanelHidden=this.pairedPanelHidden=!1,this._renderMiddle(e,t),this},_clickUnpairAll:function(e){this.metric("unpairAll"),this.unpairAll()},_clickAutopair:function(e){var t=this.autoPair();this.metric("autopair",t.length,this.unpaired.length),this.trigger("autopair")},_clickFilterChoice:function(e){var t=a(e.currentTarget);this.$(".forward-unpaired-filter input").val(t.data("forward")),this.$(".reverse-unpaired-filter input").val(t.data("reverse")),this._hideChooseFilters(),this.trigger("filter-change")},_hideChooseFilters:function(){this.$(".choose-filters-link").popover("hide"),this.$(".popover").css("display","none")},_clearFilters:function(e){this.$(".forward-unpaired-filter input").val(""),this.$(".reverse-unpaired-filter input").val(""),this.trigger("filter-change")},_clickUnpairedDataset:function(e){return e.stopPropagation(),this.toggleSelectUnpaired(a(e.currentTarget))},toggleSelectUnpaired:function(e,t){t=t||{};var i=e.data("dataset"),n=void 0!==t.force?t.force:!e.hasClass("selected");return e.length&&void 0!==i?(n?(e.addClass("selected"),t.waitToPair||this.pairAllSelected()):e.removeClass("selected"),e):e},pairAllSelected:function(e){e=e||{};var t=this,i=[],n=[],s=[];return t.$(".unpaired-columns .forward-column .dataset.selected").each(function(){i.push(a(this).data("dataset"))}),t.$(".unpaired-columns .reverse-column .dataset.selected").each(function(){n.push(a(this).data("dataset"))}),i.length=n.length=Math.min(i.length,n.length),i.forEach(function(e,i){try{s.push(t._pair(e,n[i],{silent:!0}))}catch(e){t.error(e)}}),s.length&&!e.silent&&this.trigger("pair:new",s),s},clearSelectedUnpaired:function(){this.$(".unpaired-columns .dataset.selected").removeClass("selected")},_mousedownUnpaired:function(e){if(e.shiftKey){var t=this,i=a(e.target).addClass("selected"),n=function(e){t.$(e.target).filter(".dataset").addClass("selected")};i.parent().on("mousemove",n),a(document).one("mouseup",function(e){i.parent().off("mousemove",n),t.pairAllSelected()})}},_clickPairRow:function(e){var t=a(e.currentTarget).index(),i=a(".unpaired-columns .forward-column .dataset").eq(t).data("dataset"),n=a(".unpaired-columns .reverse-column .dataset").eq(t).data("dataset");this._pair(i,n)},_startPartitionDrag:function(e){function t(e){n.$(".flexible-partition-drag").css("color",""),a("body").css("cursor","").unbind("mousemove",i)}function i(e){var t=e.pageY-s;n.adjPartition(t)||a("body").trigger("mouseup"),n._adjUnpairedOnScrollbar(),s+=t}var n=this,s=e.pageY;a("body").css("cursor","ns-resize"),n.$(".flexible-partition-drag").css("color","black"),a("body").mousemove(i),a("body").one("mouseup",t)},adjPartition:function(e){var t=this.$(".unpaired-columns"),i=this.$(".paired-columns"),n=parseInt(t.css("height"),10),s=parseInt(i.css("height"),10);n=Math.max(10,n+e),s-=e;var a=e<0;if(a){if(this.unpairedPanelHidden)return!1;if(n<=10)return this.hideUnpaired(),!1}else this.unpairedPanelHidden&&(t.show(),this.unpairedPanelHidden=!1);if(a)this.pairedPanelHidden&&(i.show(),this.pairedPanelHidden=!1);else{if(this.pairedPanelHidden)return!1;if(s<=15)return this.hidePaired(),!1}return t.css({height:n+"px",flex:"0 0 auto"}),!0},selectPair:function(e){e.stopPropagation(),a(e.currentTarget).toggleClass("selected")},clearSelectedPaired:function(e){this.$(".paired-columns .dataset.selected").removeClass("selected")},_clickPairName:function(e){e.stopPropagation();var t=a(e.currentTarget),i=t.parent().parent(),n=i.index(".dataset.paired"),s=this.paired[n],o=prompt("Enter a new name for the pair:",s.name);o&&(s.name=o,s.customizedName=!0,t.text(s.name))},_clickUnpair:function(e){var t=Math.floor(a(e.currentTarget).index(".unpair-btn"));this._unpair(this.paired[t])},_dragoverPairedColumns:function(e){e.preventDefault();var t=this.$(".paired-columns .column-datasets");this._checkForAutoscroll(t,e.originalEvent.clientY);var i=this._getNearestPairedDatasetLi(e.originalEvent.clientY);a(".element-drop-placeholder").remove();var n=a('
      ');i.length?i.before(n):t.append(n)},_checkForAutoscroll:function(e,t){var i=e.offset(),n=e.scrollTop(),s=t-i.top,a=i.top+e.outerHeight()-t;s>=0&&s=0&&ae&&s-o",e.name)},toString:function(){return"PairedCollectionCreator"},templates:n.extend({},p.default.CollectionCreatorMixin._creatorTemplates,{header:n.template(['",'
      ','','',"
      ",'
      ','
      ','
      ','
      ','',(0,v.default)("Unpaired forward"),"",'',"
      ",'
      ','',"
      ","
      ","
      ",'",'
      ','
      ','
      ','',(0,v.default)("Unpaired reverse"),"",'',"
      ",'
      ','',"
      ","
      ","
      ","
      "].join("")),middle:n.template(['
      ','
      ','
        ',"
        ",'
        ','
          ',"
          ",'
          ','
            ',"
            ","
            ",'
            ','
            ','","
            ",'
            ','
              ',"
              "].join("")),footer:n.template(['
              ','
              ','",'","
              ",'
              ','','
              ',(0,v.default)("Name"),":
              ","
              ","
              ",'
              ','
              ','",'
              ','",'","
              ","
              ",'
              ','","
              ","
              "].join("")),helpContent:n.template(["

              ",(0,v.default)(["Collections of paired datasets are ordered lists of dataset pairs (often forward and reverse reads). ","These collections can be passed to tools and workflows in order to have analyses done on each member of ","the entire group. This interface allows you to create a collection, choose which datasets are paired, ","and re-order the final collection."].join("")),"

              ","

              ",(0,v.default)(['Unpaired datasets are shown in the unpaired section ',"(hover over the underlined words to highlight below). ",'Paired datasets are shown in the paired section.',"

                To pair datasets, you can:","
              • Click a dataset in the ",'forward column ',"to select it then click a dataset in the ",'reverse column.',"
              • ",'
              • Click one of the "Pair these datasets" buttons in the ','middle column ',"to pair the datasets in a particular row.","
              • ",'
              • Click "Auto-pair" ',"to have your datasets automatically paired based on name.","
              • ","
              "].join("")),"

              ","

              ",(0,v.default)(["

                You can filter what is shown in the unpaired sections by:","
              • Entering partial dataset names in either the ",'forward filter or ','reverse filter.',"
              • ","
              • Choosing from a list of preset filters by clicking the ",'"Choose filters" link.',"
              • ","
              • Entering regular expressions to match dataset names. See: ",'MDN\'s JavaScript Regular Expression Tutorial. ',"Note: forward slashes (\\) are not needed.","
              • ","
              • Clearing the filters by clicking the ",'"Clear filters" link.',"
              • ","
              "].join("")),"

              ","

              ",(0,v.default)(["To unpair individual dataset pairs, click the ",'unpair buttons ( ). ','Click the "Unpair all" link to unpair all pairs.'].join("")),"

              ","

              ",(0,v.default)(['You can include or remove the file extensions (e.g. ".fastq") from your pair names by toggling the ','"Remove file extensions from pair names?" control.'].join("")),"

              ","

              ",(0,v.default)(['Once your collection is complete, enter a name and ','click "Create list". ',"(Note: you do not have to pair all unpaired datasets to finish.)"].join("")),"

              "].join(""))})}),b=function(e,t){var i,a=s.Deferred();if(t=n.defaults(t||{},{datasets:e,oncancel:function(){Galaxy.modal.hide(),a.reject("cancelled")},oncreate:function(e,t){Galaxy.modal.hide(),a.resolve(t)},title:(0,v.default)("Create a collection of paired datasets")}),!window.Galaxy||!Galaxy.modal)throw new Error("Galaxy or Galaxy.modal not found");return i=new y(t),Galaxy.modal.show({title:t.title,body:i.$el,width:"80%",height:"800px",closing_events:!0}),i.render(),window.creator=i,a};t.default={PairedCollectionCreator:y,pairedCollectionCreatorModal:b,createListOfPairsCollection:r}}).call(t,i(2),i(1),i(0),i(0))},,,,,,,,,,,,,,,function(e,t,i){"use strict";(function(e){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var s=i(0),a=n(s),o=i(26),l=n(o),r=i(6),d=n(r),c=a.default,u=e.Router.extend({initialize:function(e,t){this.page=e,this.options=t},push:function(e,t){t=t||{},t.__identifer=Math.random().toString(36).substr(2),e+=-1==e.indexOf("?")?"?":"&",e+=c.param(t,!0),Galaxy.params=t,this.navigate(e,{trigger:!0})},execute:function(e,t,i){Galaxy.debug("router execute:",e,t,i);var n=l.default.parse(t.pop());t.push(n),e&&(this.authenticate(t,i)?e.apply(this,t):this.access_denied())},authenticate:function(e,t){return!0},access_denied:function(){this.page.display(new d.default.Message({status:"danger",message:"You must be logged in with proper credentials to make this request.",persistent:!0}))}});t.default=u}).call(t,i(2))},function(e,t,i){"use strict";(function(e,n,s){Object.defineProperty(t,"__esModule",{value:!0});var a=i(5);!function(e){e&&e.__esModule}(a);t.default=e.View.extend({options:{class_check:"fa-check-square-o",class_uncheck:"fa-square-o",parameters:[{id:"space_to_tab",title:"Convert spaces to tabs"},{id:"to_posix_lines",title:"Use POSIX standard"}]},initialize:function(e){this.model=e.model,this.setElement(n("
              ").addClass("upload-settings")),this.$el.append(n("
              ").addClass("upload-settings-cover")),this.$el.append(n("").addClass("upload-settings-table ui-table-striped").append("")),this.$cover=this.$(".upload-settings-cover"),this.$table=this.$(".upload-settings-table > tbody"),this.listenTo(this.model,"change",this.render,this),this.model.trigger("change")},render:function(){var e=this;this.$table.empty(),s.each(this.options.parameters,function(t){var i=n("
              ").addClass("upload-"+t.id+" upload-icon-button fa").addClass(e.model.get(t.id)&&e.options.class_check||e.options.class_uncheck).on("click",function(){e.model.get("enabled")&&e.model.set(t.id,!e.model.get(t.id))});e.$table.append(n("
              ").append(n(""},_template:function(){return'
              '+this.model.get("help_text")+'
              Available files:   
              ").append(i)).append(n("").append(t.title)))}),this.$cover[this.model.get("enabled")&&"hide"||"show"]()}})}).call(t,i(2),i(0),i(1))},function(e,t,i){"use strict";(function(e,n,s){Object.defineProperty(t,"__esModule",{value:!0});var a=i(5),o=function(e){return e&&e.__esModule?e:{default:e}}(a);t.default=e.View.extend({initialize:function(t){this.model=new e.Model({cls:"upload-ftp",class_add:"upload-icon-button fa fa-square-o",class_remove:"upload-icon-button fa fa-check-square-o",class_partial:"upload-icon-button fa fa-minus-square-o",help_enabled:!0,help_text:"This Galaxy server allows you to upload files via FTP. To upload some files, log in to the FTP server at "+t.ftp_upload_site+" using your Galaxy credentials.",collection:null,onchange:function(){},onadd:function(){},onremove:function(){}}).set(t),this.collection=this.model.get("collection"),this.setElement(this._template()),this.$content=this.$(".upload-ftp-content"),this.$wait=this.$(".upload-ftp-wait"),this.$help=this.$(".upload-ftp-help"),this.$number=this.$(".upload-ftp-number"),this.$disk=this.$(".upload-ftp-disk"),this.$body=this.$(".upload-ftp-body"),this.$warning=this.$(".upload-ftp-warning"),this.$select=this.$(".upload-ftp-select-all"),this.render()},render:function(){var e=this;this.$wait.show(),this.$content.hide(),this.$warning.hide(),this.$help.hide(),n.ajax({url:Galaxy.root+"api/remote_files",method:"GET",success:function(t){e.model.set("ftp_files",t),e._index(),e._renderTable()},error:function(){e._renderTable()}})},_renderTable:function(){var e=this,t=this.model.get("ftp_files");if(this.rows=[],t&&t.length>0){this.$body.empty();var i=0;s.each(t,function(t){e.rows.push(e._renderRow(t)),i+=t.size}),this.$number.html(t.length+" files"),this.$disk.html(o.default.bytesToString(i,!0)),this.collection&&(this.$("._has_collection").show(),this.$select.addClass(this.model.get("class_add")).off().on("click",function(){e._all()}),this._refresh()),this.$content.show()}else this.$warning.show();this.model.get("help_enabled")&&this.$help.show(),this.$wait.hide()},_renderRow:function(e){var t=this,i=this.model.attributes,s=n(this._templateRow(e)),a=s.find(".icon");if(this.$body.append(s),this.collection){var o=this.ftp_index[e.path];a.addClass(void 0===o?i.class_add:i.class_remove),s.on("click",function(){t._switch(a,e),t._refresh()})}else s.on("click",function(){i.onchange(e)});return a},_index:function(){var e=this;this.ftp_index={},this.collection&&this.collection.each(function(t){"ftp"==t.get("file_mode")&&(e.ftp_index[t.get("file_path")]=t.id)})},_all:function(){var e=this.model.attributes,t=this.model.get("ftp_files"),i=this.$select.hasClass(e.class_add);for(var n in t){var s=t[n],a=this.ftp_index[s.path];(void 0===a&&i||void 0!==a&&!i)&&this._switch(this.rows[n],s)}this._refresh()},_switch:function(e,t){e.removeClass();var i=this.model.attributes,n=this.ftp_index[t.path];if(void 0===n){var s=i.onadd(t);e.addClass(i.class_remove),this.ftp_index[t.path]=s}else i.onremove(n),e.addClass(i.class_add),this.ftp_index[t.path]=void 0},_refresh:function(){var e=s.reduce(this.ftp_index,function(e,t){return void 0!==t&&e++,e},0);this.$select.removeClass(),0==e?this.$select.addClass(this.model.get("class_add")):this.$select.addClass(e==this.rows.length?this.model.get("class_remove"):this.model.get("class_partial"))},_templateRow:function(e){return'
              '+s.escape(e.path)+''+o.default.bytesToString(e.size)+''+e.ctime+"
              NameSizeCreated
              Your FTP directory does not contain any files.
              '}})}).call(t,i(2),i(0),i(1))},function(e,t,i){"use strict";(function(e,n){function s(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=i(5),o=(s(a),i(27)),l=s(o);t.default=e.View.extend({initialize:function(t){this.model=new e.Model(t),this.setElement("
              "),this.render()},render:function(){var e=this.model.attributes,t=n.findWhere(e.list,{id:e.extension});this.extension_popup&&this.extension_popup.remove(),this.extension_popup=new l.default.View({placement:e.placement||"bottom",container:e.$el}),this.extension_popup.title(e.title),this.extension_popup.empty(),this.extension_popup.append(this._templateDescription(t)),this.extension_popup.show()},_templateDescription:function(e){if(e.description){var t=e.description;return e.description_url&&(t+=' (read more)'),t}return"There is no description available for this file extension."}})}).call(t,i(2),i(1))},function(e,t,i){"use strict";(function(e,t){!function(i){e.event.props.push("dataTransfer"),i.uploadpost=function(t){var n=i.extend({},{data:{},success:function(){},error:function(){},progress:function(){},url:null,maxfilesize:2048,error_filesize:"File exceeds 2GB. Please use a FTP client.",error_default:"Please make sure the file is available.",error_server:"Upload request failed.",error_login:"Uploads require you to log in."},t),s=n.data;if(s.error_message)return void n.error(s.error_message);var a=new FormData;for(var o in s.payload)a.append(o,s.payload[o]);var l=0;for(var o in s.files){var r=s.files[o];a.append(r.name,r.file,r.file.name),l+=r.file.size}if(l>1048576*n.maxfilesize)return void n.error(n.error_filesize);var d=new XMLHttpRequest;d.open("POST",n.url,!0),d.setRequestHeader("Accept","application/json"),d.setRequestHeader("Cache-Control","no-cache"),d.setRequestHeader("X-Requested-With","XMLHttpRequest"),d.onreadystatechange=function(){if(d.readyState==d.DONE){var t=null,i="";if(d.responseText)try{t=e.parseJSON(d.responseText),i=t.err_msg}catch(e){t=d.responseText,i=t}if(d.status<200||d.status>299){var s=d.statusText;403==d.status?s=n.error_login:0==d.status?s=n.error_server:s||(s=n.error_default),n.error(s+" ("+d.status+"). "+i)}else n.success(t)}},d.upload.addEventListener("progress",function(e){e.lengthComputable&&n.progress(Math.round(100*e.loaded/e.total))},!1),Galaxy.emit.debug("uploadbox::uploadpost()","Posting following data.",n),d.send(a)},i.fn.uploadinput=function(e){var t=this,n=i.extend({},{ondragover:function(){},ondragleave:function(){},onchange:function(){},multiple:!1},e),s=i('");return t.append(s.change(function(e){n.onchange(e.target.files),i(this).val("")})),t.on("drop",function(e){n.ondragleave(e),e.dataTransfer&&(n.onchange(e.dataTransfer.files),e.preventDefault())}),t.on("dragover",function(e){e.preventDefault(),n.ondragover(e)}),t.on("dragleave",function(e){e.stopPropagation(),n.ondragleave(e)}),{dialog:function(){s.trigger("click")}}},i.fn.uploadbox=function(e){function n(e){if(e&&e.length&&!g){var i=void 0;return t.each(e,function(e,i){"new"!==e.mode&&t.filter(f,function(t){return t.name===e.name&&t.size===e.size}).length&&(e.duplicate=!0)}),t.each(e,function(e){e.duplicate||(i=String(p++),f[i]=e,h.announce(i,f[i]),m++)}),i}}function s(e){f[e]&&(delete f[e],m--)}function a(){if(0==m||_)return _=!1,g=!1,void h.complete();g=!0;var e=-1;for(var t in f){e=t;break}f[e];s(e),i.uploadpost({url:h.url,data:h.initialize(e),success:function(t){h.success(e,t),a()},error:function(t){h.error(e,t),a()},progress:function(t){h.progress(e,t)}})}function o(){v.dialog()}function l(e){for(e in f)s(e)}function r(){g||(g=!0,a())}function d(){_=!0}function c(e){return h=i.extend({},h,e)}function u(){return window.File&&window.FormData&&window.XMLHttpRequest&&window.FileList}var h=i.extend({},{dragover:function(){},dragleave:function(){},announce:function(e){},initialize:function(e){},progress:function(e,t){},success:function(e,t){},error:function(e,t){alert(t)},complete:function(){}},e),f={},p=0,m=0,g=!1,_=!1,v=i(this).uploadinput({multiple:!0,onchange:function(e){n(e)},ondragover:e.ondragover,ondragleave:e.ondragleave});return{select:o,add:n,remove:s,start:r,stop:d,reset:l,configure:c,compatible:u}}}(e)}).call(t,i(0),i(1))},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={tool:"fa-wrench",data_input:"fa-file-o",data_collection_input:"fa-folder-o",subworkflow:"fa-sitemap fa-rotate-270",pause:"fa-pause"}},function(e,t,i){"use strict";function n(e,t){function i(){var i=['
              ','
              ','','loading...',"
              ","
              "].join("\n"),n=o(i).hide().css(t.css||{position:"fixed"}),s=n.children(".loading-indicator-text");return t.cover?(n.css({"z-index":2,top:e.css("top"),bottom:e.css("bottom"),left:e.css("left"),right:e.css("right"),opacity:.5,"background-color":"white","text-align":"center"}),s=n.children(".loading-indicator-text").css({"margin-top":"20px"})):(s=n.children(".loading-indicator-text").css({margin:"12px 0px 0px 10px",opacity:"0.85",color:"grey"}),s.children(".loading-indicator-message").css({margin:"0px 8px 0px 0px","font-style":"italic"})),n}var n=this;return t=a.default.extend({cover:!1},t||{}),n.show=function(t,s,a){return t=t||"loading...",s=s||"fast",e.parent().find(".loading-indicator").remove(),n.$indicator=i().insertBefore(e),n.message(t),n.$indicator.fadeIn(s,a),n},n.message=function(e){n.$indicator.find("i").text(e)},n.hide=function(e,t){return e=e||"fast",n.$indicator&&n.$indicator.length?n.$indicator.fadeOut(e,function(){n.$indicator.remove(),t&&t()}):t&&t(),n},n}Object.defineProperty(t,"__esModule",{value:!0});var s=i(0),a=function(e){return e&&e.__esModule?e:{default:e}}(s),o=a.default;t.default=n},function(e,t,i){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var s=i(41),a=n(s),o=i(97),l=n(o),r=i(10),d=n(r),c=i(3),u=n(c),h=a.default.HDCAListItemView,f=h.extend({_getFoldoutPanelClass:function(){return l.default.CollectionViewEdit},_renderPrimaryActions:function(){return this.log(this+"._renderPrimaryActions"),h.prototype._renderPrimaryActions.call(this).concat([this._renderDeleteButton()])},_renderDeleteButton:function(){var e=this,t=this.model.get("deleted");return(0,d.default)({title:t?(0,u.default)("Dataset collection is already deleted"):(0,u.default)("Delete"),classes:"delete-btn",faIcon:"fa-times",disabled:t,onclick:function(){e.$el.find(".icon-btn.delete-btn").trigger("mouseout"),e.model.delete()}})},toString:function(){return"HDCAListItemEdit("+(this.model?""+this.model:"(no model)")+")"}});t.default={HDCAListItemEdit:f}},function(e,t,i){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var s=i(59),a=n(s),o=i(29),l=(n(o),i(98)),r=n(l),d=i(4),c=(n(d),i(22)),u=n(c),h=i(10),f=(n(h),i(3)),p=n(f);i(15);var m=a.default.CollectionView,g=m.extend({DatasetDCEViewClass:r.default.DatasetDCEListItemEdit,NestedDCDCEViewClass:r.default.NestedDCDCEListItemEdit,getNestedDCDCEViewClass:function(){return r.default.NestedDCDCEListItemEdit.extend({foldoutPanelClass:g})},initialize:function(e){m.prototype.initialize.call(this,e)},_setUpBehaviors:function(e){if(e=e||this.$el,m.prototype._setUpBehaviors.call(this,e),this.model&&Galaxy.user&&!Galaxy.user.isAnonymous()){this.tagsEditorShown=!0;var t=this;e.find("> .controls .name").attr("title",(0,p.default)("Click to rename collection")).tooltip({placement:"bottom"}).make_text_editable({on_finish:function(e){var i=t.model.get("name");e&&e!==i?(t.$el.find("> .controls .name").text(e),t.model.save({name:e}).fail(function(){t.$el.find("> .controls .name").text(t.model.previous("name"))})):t.$el.find("> .controls .name").text(i)}}),this.tagsEditor=new u.default.TagsEditor({model:this.model,el:e.find(".tags-display"),onshowFirstTime:function(){this.render()},usePrompt:!1}),this.tagsEditor.toggle(!0)}},toString:function(){return"CollectionViewEdit("+(this.model?this.model.get("name"):"")+")"}});t.default={CollectionViewEdit:g}},function(e,t,i){"use strict";(function(e,n){function s(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=i(33),o=s(a),l=i(60),r=s(l),d=i(4),c=(s(d),i(3)),u=(s(c),o.default.DCListItemView),h=u.extend({initialize:function(e){u.prototype.initialize.call(this,e)},toString:function(){return"DCListItemEdit("+(this.model?""+this.model:"(no model)")+")"}}),f=o.default.DCEListItemView,p=f.extend({initialize:function(e){f.prototype.initialize.call(this,e)},toString:function(){return"DCEListItemEdit("+(this.model?""+this.model:"(no model)")+")"}}),m=r.default.DatasetListItemEdit.extend({initialize:function(e){r.default.DatasetListItemEdit.prototype.initialize.call(this,e)},_fetchModelDetails:function(){var t=this;return t.model.inReadyState()&&!t.model.hasDetails()?t.model.fetch({silent:!0}):e.when()},_renderDeleteButton:function(){return null},toString:function(){return"DatasetDCEListItemEdit("+(this.model?""+this.model:"(no model)")+")"}});m.prototype.templates=function(){return n.extend({},r.default.DatasetListItemEdit.prototype.templates,{titleBar:o.default.DatasetDCEListItemView.prototype.templates.titleBar})}();var g=o.default.NestedDCDCEListItemView.extend({toString:function(){return"NestedDCDCEListItemEdit("+(this.model?""+this.model:"(no model)")+")"}});t.default={DCListItemEdit:h,DCEListItemEdit:p,DatasetDCEListItemEdit:m,NestedDCDCEListItemEdit:g}}).call(t,i(0),i(1))},function(e,t,i){"use strict";function n(e,t){if(0===e.length)return t.length;if(0===t.length)return e.length;var i,n=[];for(i=0;i<=t.length;i++)n[i]=[i];var s;for(s=0;s<=e.length;s++)n[0][s]=s;for(i=1;i<=t.length;i++)for(s=1;s<=e.length;s++)t.charAt(i-1)===e.charAt(s-1)?n[i][s]=n[i-1][s-1]:n[i][s]=Math.min(n[i-1][s-1]+1,Math.min(n[i][s-1]+1,n[i-1][s]+1));return n[t.length][e.length]}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,i){"use strict";(function(e,n){function s(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=i(13),o=s(a),l=i(6),r=s(l),d=e.View.extend({initialize:function(t){this.model=new e.Model(t),this.url=this.model.get("url"),this.redirect=this.model.get("redirect"),this.setElement("
              "),this.render()},render:function(){var e=this;n.ajax({url:Galaxy.root+this.url,type:"GET"}).done(function(t){var i=n.extend({},e.model.attributes,t),s=new o.default({title:i.title,message:i.message,status:i.status||"warning",icon:i.icon,initial_errors:!0,errors:i.errors,inputs:i.inputs,buttons:{submit:new r.default.Button({tooltip:i.submit_tooltip,title:i.submit_title||"Save",icon:i.submit_icon||"fa-save",cls:"btn btn-primary ui-clear-float",onclick:function(){e._submit(s)}})}});e.$el.empty().append(s.$el)}).fail(function(t){e.$el.empty().append(new r.default.Message({message:"Failed to load resource "+e.url+".",status:"danger",persistent:!0}).$el)})},_submit:function(e){var t=this;n.ajax({url:Galaxy.root+t.url,data:JSON.stringify(e.data.create()),type:"PUT",contentType:"application/json"}).done(function(i){var s={};i.id?s.id=i.id:s={message:i.message,status:"success",persistent:!1},t.redirect?window.location=Galaxy.root+t.redirect+"?"+n.param(s):(e.data.matchModel(i,function(t,i){e.field_list[i].value(t.value)}),t._showMessage(e,success_message))}).fail(function(i){t._showMessage(e,{message:i.responseJSON.err_msg,status:"danger",persistent:!1})})},_showMessage:function(e,t){e.$el.parents().filter(function(){return-1!=["auto","scroll"].indexOf(n(this).css("overflow"))}).first().animate({scrollTop:0},500),e.message.update(t)}});t.default={View:d}}).call(t,i(2),i(0))},,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,function(e,t,i){"use strict";(function(e,n,s){function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(3),l=a(o),r=i(5),d=a(r),c=i(119),u=a(c),h=i(6),f=a(h),p=i(13),m=a(p),g=i(121),_=a(g),v=i(81),w=a(v),y=i(9),b=a(y),x=i(68),$=a(x),C=i(94),k=a(C),S=e.View.extend({initialize:function(t){var i=this;this.modal=parent.Galaxy.modal||new b.default.View,this.model=t&&t.model||new e.Model(t),this.deferred=new u.default,this.setElement(n("
              ").addClass("ui-form-composite").append(this.$message=n("
              ")).append(this.$header=n("
              ")).append(this.$steps=n("
              "))),n("body").append(this.$el),this._configure(),this.render(),n(window).resize(function(){i._refresh()})},_refresh:function(e){var t=s.reduce(this.$el.children(),function(e,t){return e+n(t).outerHeight()},0)-this.$steps.height()+90;this.$steps.css("height",n(window).height()-t)},_configure:function(){function e(e,n){for(var s,a=/\$\{(.+?)\}/g;s=a.exec(String(e));){var o=s[1];n(t.wp_inputs[o]=t.wp_inputs[o]||{label:o,name:o,type:"text",color:"hsl( "+100*++i+", 70%, 30% )",style:"ui-form-wp-source",links:[]})}}var t=this;this.forms=[],this.steps=[],this.links=[],this.parms=[],s.each(this.model.get("steps"),function(e,i){Galaxy.emit.debug("tool-form-composite::initialize()",i+" : Preparing workflow step.");var n=k.default[e.step_type],a=parseInt(i+1)+": "+(e.step_label||e.step_name);e.annotation&&(a+=" - "+e.annotation),e.step_version&&(a+=" (Galaxy Version "+e.step_version+")"),e=d.default.merge({index:i,fixed_title:s.escape(a),icon:n||"",help:null,citations:null,collapsible:!0,collapsed:i>0&&!t._isDataStep(e),sustain_version:!0,sustain_repeats:!0,sustain_conditionals:!0,narrow:!0,text_enable:"Edit",text_disable:"Undo",cls_enable:"fa fa-edit",cls_disable:"fa fa-undo",errors:e.messages,initial_errors:!0,cls:"ui-portlet-narrow",hide_operations:!0,needs_refresh:!1,always_refresh:"tool"!=e.step_type},e),t.steps[i]=e,t.links[i]=[],t.parms[i]={}}),s.each(this.steps,function(e,i){_.default.visitInputs(e.inputs,function(e,n){t.parms[i][n]=e})}),s.each(this.steps,function(e,i){s.each(e.output_connections,function(e){s.each(t.steps,function(n,s){n.step_index===e.input_step_index&&t.links[i].push(n)})})}),s.each(this.steps,function(e,i){s.each(t.steps,function(n,a){var o={};s.each(e.output_connections,function(e){n.step_index===e.input_step_index&&(o[e.input_name]=e)}),s.each(t.parms[a],function(t,n){var s=o[n];s&&(t.type="hidden",t.help=t.step_linked?t.help+", ":"",t.help+="Output dataset '"+s.output_name+"' from step "+(parseInt(i)+1),t.step_linked=t.step_linked||[],t.step_linked.push(e))})})});var i=0;this.wp_inputs={},s.each(this.steps,function(i,n){s.each(t.parms[n],function(t,n){e(t.value,function(e){e.links.push(i),t.wp_linked=!0,t.type="text",t.backdrop=!0,t.style="ui-form-wp-target"})}),s.each(i.post_job_actions,function(t){s.each(t.action_arguments,function(t){e(t,function(){})})})}),s.each(this.steps,function(e,i){if("tool"==e.step_type){var n=!0;_.default.visitInputs(e.inputs,function(i,s,a){var o=i.value&&"RuntimeValue"==i.value.__class__,l=-1!=["data","data_collection"].indexOf(i.type),r=a[i.data_ref];i.step_linked&&!t._isDataStep(i.step_linked)&&(n=!1),i.options&&(0==i.options.length&&!n||i.wp_linked)&&(i.is_workflow=!0),r&&(i.is_workflow=r.step_linked&&!t._isDataStep(r.step_linked)||i.wp_linked),(l||i.value&&"RuntimeValue"==i.value.__class__&&!i.step_linked)&&(e.collapsed=!1),o&&(i.value=i.default_value),i.flavor="workflow",o||l||"hidden"===i.type||i.wp_linked||(i.optional||!d.default.isEmpty(i.value)&&""!==i.value)&&(i.collapsible_value=i.value,i.collapsible_preview=!0)})}})},render:function(){var e=this;this.deferred.reset(),this._renderHeader(),this._renderMessage(),this._renderParameters(),this._renderHistory(),s.each(this.steps,function(t){e._renderStep(t)})},_renderHeader:function(){var e=this;this.execute_btn=new f.default.Button({icon:"fa-check",title:(0,l.default)("Run workflow"),cls:"btn btn-primary",onclick:function(){e._execute()}}),this.$header.addClass("ui-form-header").empty().append(new f.default.Label({title:"Workflow: "+this.model.get("name")}).$el).append(this.execute_btn.$el)},_renderMessage:function(){this.$message.empty(),this.model.get("has_upgrade_messages")&&this.$message.append(new f.default.Message({message:"Some tools in this workflow may have changed since it was last saved or some errors were found. The workflow may still run, but any new options will have default values. Please review the messages below to make a decision about whether the changes will affect your analysis.",status:"warning",persistent:!0,fade:!1}).$el);var e=this.model.get("step_version_changes");e&&e.length>0&&this.$message.append(new f.default.Message({message:"Some tools are being executed with different versions compared to those available when this workflow was last saved because the other versions are not or no longer available on this Galaxy instance. To upgrade your workflow and dismiss this message simply edit the workflow and re-save it.",status:"warning",persistent:!0,fade:!1}).$el)},_renderParameters:function(){var e=this;this.wp_form=null,s.isEmpty(this.wp_inputs)||(this.wp_form=new m.default({title:"Workflow Parameters",inputs:this.wp_inputs,cls:"ui-portlet-narrow",onchange:function(){s.each(e.wp_form.input_list,function(t,i){s.each(t.links,function(t){e._refreshStep(t)})})}}),this._append(this.$steps.empty(),this.wp_form.$el))},_renderHistory:function(){this.history_form=new m.default({cls:"ui-portlet-narrow",title:"History Options",inputs:[{type:"conditional",name:"new_history",test_param:{name:"check",label:"Send results to a new history",type:"boolean",value:"false",help:""},cases:[{value:"true",inputs:[{name:"name",label:"History name",type:"text",value:this.model.get("name")}]}]}]}),this._append(this.$steps,this.history_form.$el)},_renderStep:function(e){var t=this,i=null;this.deferred.execute(function(a){if(t.$steps.addClass("ui-steps"),"tool"==e.step_type)e.postchange=function(t,i){var s={tool_id:e.id,tool_version:e.version,inputs:n.extend(!0,{},i.data.create())};i.wait(!0),Galaxy.emit.debug("tool-form-composite::postchange()","Sending current state.",s),d.default.request({type:"POST",url:Galaxy.root+"api/tools/"+e.id+"/build",data:s,success:function(e){i.update(e),i.wait(!1),Galaxy.emit.debug("tool-form-composite::postchange()","Received new model.",e),t.resolve()},error:function(e){Galaxy.emit.debug("tool-form-composite::postchange()","Refresh request failed.",e),t.reject()}})},i=new w.default(e),e.post_job_actions&&e.post_job_actions.length&&i.portlet.append(n("
              ").addClass("ui-form-element-disabled").append(n("
              ").addClass("ui-form-title").html("Job Post Actions")).append(n("
              ").addClass("ui-form-preview").html(s.reduce(e.post_job_actions,function(e,t){return e+" "+t.short_str},""))));else{var o=-1!=["data_input","data_collection_input"].indexOf(e.step_type);s.each(e.inputs,function(e){e.flavor="module",e.hide_label=o}),i=new m.default(d.default.merge({title:e.fixed_title,onchange:function(){s.each(t.links[e.index],function(e){t._refreshStep(e)})},inputs:e.inputs&&e.inputs.length>0?e.inputs:[{type:"hidden",name:"No options available.",ignore:null}]},e))}t.forms[e.index]=i,t._append(t.$steps,i.$el),t._refresh(),e.needs_refresh&&t._refreshStep(e),i.portlet[t.show_progress?"disable":"enable"](),t.show_progress&&t.execute_btn.model.set({wait:!0,wait_text:"Preparing...",percentage:100*(e.index+1)/t.steps.length}),Galaxy.emit.debug("tool-form-composite::initialize()",e.index+" : Workflow step state ready.",e),setTimeout(function(){a.resolve()},0)})},_refreshStep:function(e){var t=this,i=this.forms[e.index];i?(s.each(t.parms[e.index],function(e,n){if(e.step_linked||e.wp_linked){var a=i.field_list[i.data.match(n)];if(a){var o=void 0;if(e.step_linked)o={values:[]},s.each(e.step_linked,function(e){if(t._isDataStep(e)){var i=t.forms[e.index].data.create().input;i&&s.each(i.values,function(e){o.values.push(e)})}}),!e.multiple&&o.values.length>0&&(o={values:[o.values[0]]});else if(e.wp_linked){o=e.value;for(var l,r=/\$\{(.+?)\}/g;l=r.exec(e.value);){var d=t.wp_form.field_list[t.wp_form.data.match(l[1])],c=d&&d.value();c&&(o=o.split(l[0]).join(c))}}void 0!==o&&a.value(o)}}}),i.trigger("change")):e.needs_refresh=!0},_refreshHistory:function(){var e=this,t=parent.Galaxy&&parent.Galaxy.currHistoryPanel&&parent.Galaxy.currHistoryPanel.model;this._refresh_history&&clearTimeout(this._refresh_history),t&&t.refresh().success(function(){0===t.numOfUnfinishedShownContents()&&(e._refresh_history=setTimeout(function(){e._refreshHistory()},t.UPDATE_DELAY))})},_execute:function(){var e=this;this.show_progress=!0,this._enabled(!1),this.deferred.execute(function(t){setTimeout(function(){t.resolve(),e._submit()},0)})},_submit:function(){var e=this,t=this.history_form.data.create(),i={new_history_name:t["new_history|name"]?t["new_history|name"]:null,history_id:t["new_history|name"]?null:this.model.get("history_id"),replacement_params:this.wp_form?this.wp_form.data.create():{},parameters:{},parameters_normalized:!0,batch:!0},s=!0;for(var a in this.forms){var o=this.forms[a],r=o.data.create(),c=e.steps[a],u=c.step_index;o.trigger("reset");for(var h in r){var f=r[h],p=o.data.match(h),m=(o.field_list[p],o.input_list[p]);if(!m.step_linked){if(!(s=this._isDataStep(c)?f&&f.values&&f.values.length>0:m.optional||m.is_workflow&&""!==f||!m.is_workflow&&null!==f)){o.highlight(p);break}i.parameters[u]=i.parameters[u]||{},i.parameters[u][h]=r[h]}}if(!s)break}s?(Galaxy.emit.debug("tool-form-composite::submit()","Validation complete.",i),d.default.request({type:"POST",url:Galaxy.root+"api/workflows/"+this.model.id+"/invocations",data:i,success:function(t){if(Galaxy.emit.debug("tool-form-composite::submit","Submission successful.",t),e.$el.children().hide(),e.$el.append(e._templateSuccess(t)),n.isArray(t)&&t.length>0){e.$el.append(n("
              ",{id:"webhook-view"}));new $.default.WebhookView({urlRoot:Galaxy.root+"api/webhooks/workflow",toolId:i.tool_id,toolVersion:i.tool_version})}e._refreshHistory()},error:function(t){Galaxy.emit.debug("tool-form-composite::submit","Submission failed.",t);var n=!1;if(t&&t.err_data)for(var s in e.forms){var a=e.forms[s],o=t.err_data[a.model.get("step_index")];if(o){var r=a.data.matchResponse(o);for(var d in r){a.highlight(d,r[d]),n=!0;break}}}n||e.modal.show({title:(0,l.default)("Workflow submission failed"),body:e._templateError(i,t&&t.err_msg),buttons:{Close:function(){e.modal.hide()}}})},complete:function(){e._enabled(!0)}})):(e._enabled(!0),Galaxy.emit.debug("tool-form-composite::submit()","Validation failed.",i))},_append:function(e,t){e.append("

              ").append(t)},_enabled:function(e){this.execute_btn.model.set({wait:!e,wait_text:"Sending...",percentage:-1}),this.wp_form&&this.wp_form.portlet[e?"enable":"disable"](),this.history_form&&this.history_form.portlet[e?"enable":"disable"](),s.each(this.forms,function(t){t&&t.portlet[e?"enable":"disable"]()})},_isDataStep:function(e){for(var t=n.isArray(e)?e:[e],i=0;i0?n("

              ").addClass("donemessagelarge").append(n("

              ").html("Successfully invoked workflow "+d.default.sanitize(this.model.get("name"))+""+(e.length>1?" "+e.length+" times":"")+".")).append(n("

              ").append("").text("You can check the status of queued jobs and view the resulting data by refreshing the History pane. When the job has been run the status will change from 'running' to 'finished' if completed successfully or 'error' if problems were encountered.")):this._templateError(e,"Invalid success response. No invocations found.")},_templateError:function(e,t){return n("

              ").addClass("errormessagelarge").append(n("

              ").text("The server could not complete the request. Please contact the Galaxy Team if this error persists. "+(JSON.stringify(t)||""))).append(n("

              ").text(JSON.stringify(e,null,4)))}});t.default={View:S}}).call(t,i(2),i(0),i(1))},,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,function(e,t,i){"use strict";(function(e){function t(e){return e&&e.__esModule?e:{default:e}}var n=i(0),s=t(n),a=i(64),o=t(a),l=i(89),r=t(l),d=i(252),c=t(d),u=i(262),h=t(u),f=i(43),p=t(f),m=i(118),g=t(m),_=i(100),v=t(_),w=i(265),y=t(w),b=i(266),x=t(b),$=i(113),C=t($),k=i(25),S=t(k),T=i(268),P=t(T),D=i(269),E=t(D),A=i(271),M=t(A),I=i(276),O=t(I),j=i(136),L=t(j),V=i(26),H=t(V),N=i(5),G=t(N),F=i(6),U=t(F),z=i(277),R=t(z),B=i(278),W=t(B),J=i(122),q=t(J),Y=i(63),Q=t(Y),K=s.default;window.app=function(t,i){window.Galaxy=new o.default.GalaxyApp(t,i),Galaxy.debug("analysis app");var n=r.default.extend({routes:{"(/)":"home","(/)root*":"home","(/)tours(/)(:tour_id)":"show_tours","(/)user(/)":"show_user","(/)user(/)(:form_id)":"show_user_form","(/)openids(/)list":"show_openids","(/)pages(/)create(/)":"show_pages_create","(/)pages(/)edit(/)":"show_pages_edit","(/)pages(/)(:action_id)":"show_pages","(/)visualizations(/)edit(/)":"show_visualizations_edit","(/)visualizations/(:action_id)":"show_visualizations","(/)workflows/import_workflow":"show_import_workflow","(/)workflows/run(/)":"show_run","(/)workflows(/)list":"show_workflows","(/)workflows/list_published(/)":"show_workflows_published","(/)workflows/create(/)":"show_workflows_create","(/)histories(/)citations(/)":"show_history_citations","(/)histories(/)rename(/)":"show_histories_rename","(/)histories(/)import(/)":"show_histories_import","(/)histories(/)permissions(/)":"show_histories_permissions","(/)histories(/)(:action_id)":"show_histories","(/)datasets(/)list(/)":"show_datasets","(/)custom_builds":"show_custom_builds","(/)datasets/edit":"show_dataset_edit_attributes","(/)datasets/error":"show_dataset_error"},require_login:["show_user","show_user_form","show_workflows"],authenticate:function(e,t){return Galaxy.user&&Galaxy.user.id||-1==this.require_login.indexOf(t)},show_tours:function(e){e?C.default.giveTourById(e):this.page.display(new C.default.ToursView)},show_user:function(){this.page.display(new y.default.View)},show_user_form:function(e){var t=new y.default.Model({user_id:Galaxy.params.id});this.page.display(new v.default.View(t.get(e)))},show_visualizations:function(e){this.page.display(new P.default.View({action_id:e,plural:"Visualizations",item:"visualization"}))},show_visualizations_edit:function(){this.page.display(new v.default.View({url:"visualization/edit?id="+H.default.get("id"),redirect:"visualizations/list"}))},show_workflows_published:function(){this.page.display(new S.default({url_base:Galaxy.root+"workflow/list_published"}))},show_histories:function(e){this.page.display(new O.default.View({action_id:e}))},show_history_citations:function(){var e=Q.default.extend(q.default),t=document.createElement("div");this.page.display(t),new e({propsData:{id:H.default.get("id"),source:"histories"}}).$mount(t)},show_histories_rename:function(){this.page.display(new v.default.View({url:"history/rename?id="+H.default.get("id"),redirect:"histories/list"}))},show_histories_import:function(){var e=Q.default.extend(M.default),t=document.createElement("div");this.page.display(t),(new e).$mount(t)},show_histories_permissions:function(){this.page.display(new v.default.View({url:"history/permissions?id="+H.default.get("id"),redirect:"histories/list"}))},show_openids:function(){this.page.display(new S.default({url_base:Galaxy.root+"user/openids_list"}))},show_datasets:function(){this.page.display(new S.default({url_base:Galaxy.root+"dataset/list"}))},show_pages:function(e){this.page.display(new P.default.View({action_id:e,plural:"Pages",item:"page"}))},show_pages_create:function(){this.page.display(new v.default.View({url:"page/create",redirect:"pages/list"}))},show_pages_edit:function(){this.page.display(new v.default.View({url:"page/edit?id="+H.default.get("id"),redirect:"pages/list"}))},show_workflows:function(){this.page.display(new E.default.View)},show_workflows_create:function(){this.page.display(new v.default.View({url:"workflow/create",redirect:"workflow/editor"}))},show_run:function(){this._loadWorkflow()},show_import_workflow:function(){this.page.display(new E.default.ImportWorkflowView)},show_custom_builds:function(){var e=this,t=this.page.historyPanel.historyView;if(!t||!t.model||!t.model.id)return void window.setTimeout(function(){e.show_custom_builds()},500);this.page.display(new x.default.View)},show_dataset_edit_attributes:function(){this.page.display(new W.default.View)},show_dataset_error:function(){this.page.display(new R.default.View)},home:function(e){e.tool_id||e.job_id?"upload1"===e.tool_id?(this.page.toolPanel.upload.show(),this._loadCenterIframe("welcome")):this._loadToolForm(e):e.workflow_id?this._loadWorkflow():e.m_c?this._loadCenterIframe(e.m_c+"/"+e.m_a):this._loadCenterIframe("welcome")},_loadToolForm:function(e){e.id=decodeURIComponent(e.tool_id),this.page.display(new g.default.View(e))},_loadCenterIframe:function(e,t){t=t||Galaxy.root,e=t+e,this.page.$("#galaxy_main").prop("src",e)},_loadWorkflow:function(){var e=this;G.default.get({url:Galaxy.root+"api/workflows/"+G.default.getQueryString("id")+"/download?style=run",success:function(t){e.page.display(new L.default.View(t))},error:function(t){var i=t.err_msg||"Error occurred while loading the resource.",n={message:i,status:"danger",persistent:!0};e.page.display(new U.default.Message(n))}})}});K(function(){Galaxy.page=new p.default.View(e.extend(t,{Left:c.default,Right:h.default,Router:n}))})}}).call(t,i(1))},function(e,t,i){"use strict";(function(e,n){function s(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=i(80),o=s(a),l=i(253),r=s(l),d=i(3),c=s(d),u=i(136),h=(s(u),e.View.extend({initialize:function(t,i){var n=i.config;this.root=i.root,this.stored_workflow_menu_entries=n.stored_workflow_menu_entries||[];var s=new o.default.ToolSearch({hidden:!1}),a=new o.default.ToolCollection(n.toolbox);this.tool_panel=new o.default.ToolPanel({tool_search:s,tools:a,layout:n.toolbox_in_panel}),this.tool_panel_view=new o.default.ToolPanelView({model:this.tool_panel}),this.upload_button=new r.default({nginx_upload_path:n.nginx_upload_path,ftp_upload_site:n.ftp_upload_site,default_genome:n.default_genome,default_extension:n.default_extension}),Galaxy.upload=this.upload_button,this.model=new e.Model({title:(0,c.default)("Tools"),buttons:[this.upload_button]}),this.setElement(this._template())},render:function(){var e=this;this.tool_panel.get("layout").size()>0&&(this.$el.prepend(this.tool_panel_view.$el),this.tool_panel_view.render()),e.$("#internal-workflows").append(e._templateAllWorkflow({title:(0,c.default)("All workflows"),href:"workflows/list"})),n.each(this.stored_workflow_menu_entries,function(t){e.$("#internal-workflows").append(e._templateWorkflowLink({title:t.stored_workflow.name,href:"workflows/run?id="+t.encoded_stored_workflow_id}))})},_templateTool:function(e){return['"].join("")},_templateAllWorkflow:function(e){return['"].join("")},_templateWorkflowLink:function(e){return['"].join("")},_template:function(){return['
              ','",'
              ','
              ','
              ',"",(0,c.default)("Workflows"),"","
              ",'
              ','
              ',"
              ","
              "].join("")},toString:function(){return"toolPanel"}}));t.default=h}).call(t,i(2),i(1))},function(e,t,i){"use strict";(function(e){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var s=i(3),a=n(s),o=i(5),l=n(o),r=i(9),d=n(r),c=i(78),u=n(c),h=i(254),f=n(h),p=i(255),m=n(p),g=i(258),_=n(g),v=i(260),w=n(v);t.default=e.View.extend({options:{nginx_upload_path:"",ftp_upload_site:"n/a",default_genome:"?",default_extension:"auto",height:500,width:900,auto:{id:"auto",text:"Auto-detect",description:"This system will try to detect the file type automatically. If your file is not detected properly as one of the known formats, it most likely means that it has some format problems (e.g., different number of columns on different rows). You can still coerce the system to set your data to the format you think it should be. You can also upload compressed files, which will automatically be decompressed."}},list_extensions:[],list_genomes:[],initialize:function(e){var t=this;this.options=l.default.merge(e,this.options),this.ui_button=new f.default.View({onclick:function(e){e.preventDefault(),t.show()},onunload:function(){var e=t.ui_button.model.get("percentage",0);if(e>0&&e<100)return"Several uploads are queued."}}),this.setElement(this.ui_button.$el);var t=this;l.default.get({url:Galaxy.root+"api/datatypes?extension_only=False",success:function(e){for(var i in e)t.list_extensions.push({id:e[i].extension,text:e[i].extension,description:e[i].description,description_url:e[i].description_url,composite_files:e[i].composite_files});t.list_extensions.sort(function(e,t){var i=e.text&&e.text.toLowerCase(),n=t.text&&t.text.toLowerCase();return i>n?1:ii.text?1:e.text0){var n={file_count:e.length,dbkey:e[0].get("genome","?"),file_type:e[0].get("extension","auto")};for(var s in e){var a=e[s];if(a.set("status","running"),!(a.get("file_size")>0)){i.error_message="Upload content incomplete.",a.set("status","error"),a.set("info",i.error_message);break}var o="files_"+s+"|";switch(n[o+"type"]="upload_dataset",n[o+"space_to_tab"]=a.get("space_to_tab")&&"Yes"||null,n[o+"to_posix_lines"]=a.get("to_posix_lines")&&"Yes"||null,n[o+"dbkey"]=a.get("genome",null),n[o+"file_type"]=a.get("extension",null),a.get("file_mode")){case"new":n[o+"url_paste"]=a.get("url_paste");break;case"ftp":n[o+"ftp_files"]=a.get("file_path");break;case"local":i.files.push({name:o+"file_data",file:a.get("file_data")})}}i.payload.inputs=JSON.stringify(n)}return i}})}).call(t,i(2))},function(e,t,i){"use strict";(function(e,n){Object.defineProperty(t,"__esModule",{value:!0});var s=i(3),a=function(e){return e&&e.__esModule?e:{default:e}}(s),o=e.View.extend({initialize:function(t){var i=this;this.model=t&&t.model||new e.Model({icon:"fa-upload",tooltip:(0,a.default)("Download from URL or upload files from disk"),label:"Load Data",percentage:0,status:"",onunload:function(){},onclick:function(){}}).set(t),this.setElement(this._template()),this.$progress=this.$(".progress-bar"),this.listenTo(this.model,"change",this.render,this),this.render(),n(window).on("beforeunload",function(){return i.model.get("onunload")()})},render:function(){var e=this.model.attributes;this.$el.off("click").on("click",function(t){e.onclick(t)}).tooltip({title:this.model.get("tooltip"),placement:"bottom"}),this.$progress.removeClass().addClass("progress-bar").addClass("progress-bar-notransition").addClass(""!=e.status&&"progress-bar-"+e.status).css({width:e.percentage+"%"})},_template:function(){return'
              '}});t.default={View:o}}).call(t,i(2),i(0))},function(e,t,i){"use strict";(function(e,n,s){function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(3),l=a(o),r=i(5),d=(a(r),i(49)),c=a(d),u=i(256),h=a(u),f=i(91),p=a(f),m=i(92),g=a(m),_=i(27),v=a(_),w=i(12),y=a(w),b=i(6),x=a(b),$=i(257),C=a($);i(93),t.default=e.View.extend({upload_size:0,collection:new c.default.Collection,counter:{announce:0,success:0,error:0,running:0,reset:function(){this.announce=this.success=this.error=this.running=0}},initialize:function(e){var t=this;this.app=e,this.options=e.options,this.list_extensions=e.list_extensions,this.list_genomes=e.list_genomes,this.ui_button=e.ui_button,this.ftp_upload_site=e.currentFtp(),this.setElement(this._template()),this.$uploadbox=this.$(".upload-box"),this.$uploadtable=this.$(".upload-table"),this.btnLocal=new x.default.Button({id:"btn-local",title:(0,l.default)("Choose local file"),onclick:function(){t.uploadbox.select()},icon:"fa fa-laptop"}),this.btnFtp=new x.default.Button({id:"btn-ftp",title:(0,l.default)("Choose FTP file"),onclick:function(){t._eventFtp()},icon:"fa fa-folder-open-o"}),this.btnCreate=new x.default.Button({id:"btn-new",title:"Paste/Fetch data",onclick:function(){t._eventCreate()},icon:"fa fa-edit"}),this.btnStart=new x.default.Button({id:"btn-start",title:(0,l.default)("Start"),onclick:function(){t._eventStart()}}),this.btnStop=new x.default.Button({id:"btn-stop",title:(0,l.default)("Pause"),onclick:function(){t._eventStop()}}),this.btnReset=new x.default.Button({id:"btn-reset",title:(0,l.default)("Reset"),onclick:function(){t._eventReset()}}),this.btnClose=new x.default.Button({id:"btn-close",title:(0,l.default)("Close"),onclick:function(){t.app.modal.hide()}}),n.each([this.btnLocal,this.btnFtp,this.btnCreate,this.btnStop,this.btnReset,this.btnStart,this.btnClose],function(e){t.$(".upload-buttons").prepend(e.$el)}),this.uploadbox=this.$uploadbox.uploadbox({url:this.app.options.nginx_upload_path,announce:function(e,i){t._eventAnnounce(e,i)},initialize:function(e){return t.app.toData([t.collection.get(e)],t.history_id)},progress:function(e,i){t._eventProgress(e,i)},success:function(e,i){t._eventSuccess(e,i)},error:function(e,i){t._eventError(e,i)},complete:function(){t._eventComplete()},ondragover:function(){t.$uploadbox.addClass("highlight")},ondragleave:function(){t.$uploadbox.removeClass("highlight")}}),this.ftp=new v.default.View({title:(0,l.default)("FTP files"),container:this.btnFtp.$el}),this.select_extension=new y.default.View({css:"upload-footer-selection",container:this.$(".upload-footer-extension"),data:n.filter(this.list_extensions,function(e){return!e.composite_files}),value:this.options.default_extension,onchange:function(e){t._changeExtension(e)}}),this.$(".upload-footer-extension-info").on("click",function(e){new g.default({$el:s(e.target),title:t.select_extension.text(),extension:t.select_extension.value(),list:t.list_extensions,placement:"top"})}).on("mousedown",function(e){e.preventDefault()}),this.select_genome=new y.default.View({css:"upload-footer-selection",container:this.$(".upload-footer-genome"),data:this.list_genomes,value:this.options.default_genome,onchange:function(e){t._changeGenome(e)}}),this.loader=new C.default({$container:this.$uploadbox,collection:this.collection,new_content:function(e){var i=new h.default(t,{model:e});return t.$uploadtable.find("> tbody:first").append(i.$el),i.render(),i}}),this.collection.on("remove",function(e){t._eventRemove(e)}),this.render()},render:function(){var e="";e=0==this.counter.announce?this.uploadbox.compatible()?" ":"Browser does not support Drag & Drop. Try Firefox 4+, Chrome 7+, IE 10+, Opera 12+ or Safari 6+.":0==this.counter.running?"You added "+this.counter.announce+" file(s) to the queue. Add more files or click 'Start' to proceed.":"Please wait..."+this.counter.announce+" out of "+this.counter.running+" remaining.",this.$(".upload-top-info").html(e);var t=0==this.counter.running&&this.counter.announce+this.counter.success+this.counter.error>0,i=0==this.counter.running&&this.counter.announce>0,n=0==this.counter.running,s=this.counter.announce+this.counter.success+this.counter.error>0;this.btnReset[t?"enable":"disable"](),this.btnStart[i?"enable":"disable"](),this.btnStart.$el[i?"addClass":"removeClass"]("btn-primary"),this.btnStop[this.counter.running>0?"enable":"disable"](),this.btnLocal[n?"enable":"disable"](),this.btnFtp[n?"enable":"disable"](),this.btnCreate[n?"enable":"disable"](),this.btnFtp.$el[this.ftp_upload_site?"show":"hide"](),this.$(".upload-table")[s?"show":"hide"](),this.$(".upload-helper")[s?"hide":"show"]()},_eventAnnounce:function(e,t){this.counter.announce++;var i=new c.default.Model({id:e,file_name:t.name,file_size:t.size,file_mode:t.mode||"local",file_path:t.path,file_data:t});this.render(),this.collection.add(i)},_eventProgress:function(e,t){var i=this.collection.get(e);i.set("percentage",t),this.ui_button.model.set("percentage",this._uploadPercentage(t,i.get("file_size")))},_eventSuccess:function(e,t){var i=this.collection.get(e);i.set({percentage:100,status:"success"}),this.ui_button.model.set("percentage",this._uploadPercentage(100,i.get("file_size"))),this.upload_completed+=100*i.get("file_size"),this.counter.announce--,this.counter.success++,this.render(),Galaxy.currHistoryPanel.refreshContents()},_eventError:function(e,t){var i=this.collection.get(e);i.set({percentage:100,status:"error",info:t}),this.ui_button.model.set({percentage:this._uploadPercentage(100,i.get("file_size")),status:"danger"}),this.upload_completed+=100*i.get("file_size"),this.counter.announce--,this.counter.error++,this.render()},_eventComplete:function(){this.collection.each(function(e){"queued"==e.get("status")&&e.set("status","init")}),this.counter.running=0,this.render()},_eventRemove:function(e){var t=e.get("status");"success"==t?this.counter.success--:"error"==t?this.counter.error--:this.counter.announce--,this.uploadbox.remove(e.id),this.render()},_eventFtp:function(){if(this.ftp.visible)this.ftp.hide();else{this.ftp.empty();var e=this;this.ftp.append(new p.default({collection:this.collection,ftp_upload_site:this.ftp_upload_site,onadd:function(t){return e.uploadbox.add([{mode:"ftp",name:t.path,size:t.size,path:t.path}])},onremove:function(t){e.collection.remove(t)}}).$el),this.ftp.show()}},_eventCreate:function(){this.uploadbox.add([{name:"New File",size:0,mode:"new"}])},_eventStart:function(){if(0!=this.counter.announce&&0==this.counter.running){var e=this;this.upload_size=0,this.upload_completed=0,this.collection.each(function(t){"init"==t.get("status")&&(t.set("status","queued"),e.upload_size+=t.get("file_size"))}),this.ui_button.model.set({percentage:0,status:"success"}),this.counter.running=this.counter.announce,this.history_id=this.app.currentHistory(),this._uploadFtp(),this.uploadbox.start(),this.render()}},_eventStop:function(){this.counter.running>0&&(this.ui_button.model.set("status","info"),s(".upload-top-info").html("Queue will pause after completing the current file..."),this.uploadbox.stop())},_eventReset:function(){if(0==this.counter.running){this.collection.reset(),this.counter.reset(),this.uploadbox.reset(),this.select_extension.value(this.options.default_extension),this.select_genome.value(this.options.default_genome),this.ui_button.model.set("percentage",0),this.render()}},_changeExtension:function(e,t){var i=this;this.collection.each(function(n){"init"!=n.get("status")||n.get("extension")!=i.options.default_extension&&t||n.set("extension",e)})},_changeGenome:function(e,t){var i=this;this.collection.each(function(n){"init"!=n.get("status")||n.get("genome")!=i.options.default_genome&&t||n.set("genome",e)})},_uploadFtp:function(){var e=this,t=[];this.collection.each(function(i){"queued"==i.get("status")&&"ftp"==i.get("file_mode")&&(e.uploadbox.remove(i.id),t.push(i))}),t.length>0&&s.uploadpost({data:this.app.toData(t),url:this.app.options.nginx_upload_path,success:function(i){n.each(t,function(t){e._eventSuccess(t.id)})},error:function(i){n.each(t,function(t){e._eventError(t.id,i)})}})},_uploadPercentage:function(e,t){return(this.upload_completed+e*t)/this.upload_size},_template:function(){return'
              Drop files here
              '}})}).call(t,i(2),i(1),i(0))},function(e,t,i){"use strict";(function(e,n,s){function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(3),l=a(o),r=i(5),d=a(r),c=i(49),u=(a(c),i(90)),h=a(u),f=i(27),p=a(f),m=i(12),g=a(m);t.default=e.View.extend({status_classes:{init:"upload-icon-button fa fa-trash-o",queued:"upload-icon fa fa-spinner fa-spin",running:"upload-icon fa fa-spinner fa-spin",success:"upload-icon-button fa fa-check",error:"upload-icon-button fa fa-exclamation-triangle"},initialize:function(e,t){var i=this;this.app=e,this.model=t.model,this.setElement(this._template(t.model)),this.$mode=this.$(".upload-mode"),this.$title=this.$(".upload-title"),this.$text=this.$(".upload-text"),this.$size=this.$(".upload-size"),this.$info_text=this.$(".upload-info-text"),this.$info_progress=this.$(".upload-info-progress"),this.$text_content=this.$(".upload-text-content"),this.$settings=this.$(".upload-settings"),this.$symbol=this.$(".upload-symbol"),this.$progress_bar=this.$(".upload-progress-bar"),this.$percentage=this.$(".upload-percentage"),this.settings=new p.default.View({title:(0,l.default)("Upload configuration"),container:this.$(".upload-settings"),placement:"bottom"});var s=this.app.select_genome.value(),a=this.app.select_extension.value();this.select_genome=new g.default.View({css:"upload-genome",data:i.app.list_genomes,container:this.$(".upload-genome"),value:s,onchange:function(e){i.model.set("genome",e)}}),this.select_extension=new g.default.View({css:"upload-extension",data:i.app.list_extensions,container:this.$(".upload-extension"),value:a,onchange:function(e){i.model.set("extension",e)}}),this.model.set({genome:s,extension:a}),this.$symbol.on("click",function(){i._removeRow()}),this.$(".upload-extension-info").on("click",function(e){i.app.showExtensionInfo({$el:n(e.target),title:i.select_extension.text(),extension:i.select_extension.value()})}).on("mousedown",function(e){e.preventDefault()}),this.$settings.on("click",function(e){i._showSettings()}).on("mousedown",function(e){e.preventDefault()}),this.$text_content.on("change input",function(e){i.model.set({url_paste:n(e.target).val(),file_size:n(e.target).val().length})}),this.listenTo(this.model,"change:percentage",function(){i._refreshPercentage()}),this.listenTo(this.model,"change:status",function(){i._refreshStatus()}),this.listenTo(this.model,"change:info",function(){i._refreshInfo()}),this.listenTo(this.model,"change:genome",function(){i._refreshGenome()}),this.listenTo(this.model,"change:extension",function(){i._refreshExtension()}),this.listenTo(this.model,"change:file_size",function(){i._refreshFileSize()})},render:function(){this._refreshType(),this._refreshPercentage(),this._refreshStatus(),this._refreshInfo(),this._refreshGenome(),this._refreshExtension(),this._refreshFileSize()},remove:function(){this.select_genome.remove(),this.select_extension.remove(),e.View.prototype.remove.apply(this)},_refreshType:function(){var e=this.model.attributes;this.$title.html(s.escape(e.file_name)),this.$size.html(d.default.bytesToString(e.file_size)),this.$mode.removeClass().addClass("upload-mode").addClass("text-primary"),"new"==e.file_mode?(this.$text.css({width:this.$el.width()-16+"px",top:this.$el.height()-8+"px"}).show(),this.$el.height(this.$el.height()-8+this.$text.height()+16),this.$mode.addClass("fa fa-edit")):"local"==e.file_mode?this.$mode.addClass("fa fa-laptop"):"ftp"==e.file_mode&&this.$mode.addClass("fa fa-folder-open-o")},_refreshExtension:function(){this.select_extension.value(this.model.get("extension"))},_refreshGenome:function(){this.select_genome.value(this.model.get("genome"))},_refreshInfo:function(){var e=this.model.get("info");e?this.$info_text.html("Failed: "+e).show():this.$info_text.hide()},_refreshPercentage:function(){var e=parseInt(this.model.get("percentage"));this.$progress_bar.css({width:e+"%"}),this.$percentage.html(100!=e?e+"%":"Adding to history...")},_refreshStatus:function(){var e=this.model.get("status");this.$symbol.removeClass().addClass("upload-symbol").addClass(this.status_classes[e]),this.model.set("enabled","init"==e);var t=this.model.get("enabled");this.$text_content.attr("disabled",!t),t?(this.select_genome.enable(),this.select_extension.enable()):(this.select_genome.disable(),this.select_extension.disable()),"success"==e&&(this.$el.addClass("success"),this.$percentage.html("100%")),"error"==e&&(this.$el.addClass("danger"),this.$info_progress.hide())},_refreshFileSize:function(){this.$size.html(d.default.bytesToString(this.model.get("file_size")))},_removeRow:function(){-1!==["init","success","error"].indexOf(this.model.get("status"))&&this.app.collection.remove(this.model)},_showSettings:function(){this.settings.visible?this.settings.hide():(this.settings.empty(),this.settings.append(new h.default(this).$el),this.settings.show())},_template:function(e){return'
              You can tell Galaxy to download data from web by entering URL in this box (one per line). You can also directly paste the contents of a file.
              ").attr({rows:s,cols:r}).text(o.trim(h)):o("").attr({value:o.trim(h),size:r}),t.attr("id","renaming-active"),t.blur(function(){o(this).remove(),c.show(),u&&u(t)}),t.keyup(function(r){if(27===r.keyCode)o(this).trigger("blur");else if(13===r.keyCode){var a={};a[n]=o(this).val(),o(this).trigger("blur"),o.ajax({url:i,data:a,error:function(){alert("Text editing for elt "+e+" failed")},success:function(e){""!==e?c.text(e):c.html("None"),u&&u(t)}})}}),l&&l(t),c.hide(),t.insertAfter(c),t.focus(),t.select()}})}Object.defineProperty(e,"__esModule",{value:!0});var r=i(0),a=function(t){return t&&t.__esModule?t:{default:t}}(r),o=a.default;e.default=n},function(t,e,i){"use strict";(function(t,n,r){function a(e){var i=f(e);sessionStorage.setItem("activeGalaxyTour",JSON.stringify(e));var n=new c(t.extend({steps:i.steps},d));return n.init(),n.goTo(0),n.restart(),n}function o(t){var e=h+"api/tours/"+t;n.getJSON(e,function(t){a(t)})}function s(){var e=JSON.parse(sessionStorage.getItem("activeGalaxyTour"));if(e&&(e=f(e))&&e.steps&&window&&window.self===window.top){var i=new c(t.extend({steps:e.steps},d));i.init(),i.restart()}}Object.defineProperty(e,"__esModule",{value:!0}),e.ToursView=void 0,e.giveTourWithData=a,e.giveTourById=o,e.activeGalaxyTourRunner=s;var l=i(3),u=function(t){return t&&t.__esModule?t:{default:t}}(l);i(108);var c=window.Tour,h="undefined"==typeof Galaxy?"/":Galaxy.root,d={storage:window.sessionStorage,onEnd:function(){sessionStorage.removeItem("activeGalaxyTour")},delay:150,orphan:!0},f=function(e){return t.each(e.steps,function(e){e.preclick&&(e.onShow=function(){t.each(e.preclick,function(t){n(t).click()})}),e.postclick&&(e.onHide=function(){t.each(e.postclick,function(t){n(t).click()})}),e.textinsert&&(e.onShown=function(){n(e.element).val(e.textinsert).trigger("change")})}),e},p=r.Model.extend({urlRoot:h+"api/tours"}),g=r.Collection.extend({url:h+"api/tours",model:p}),m=e.ToursView=r.View.extend({title:(0,u.default)("Tours"),initialize:function(){var t=this;this.setElement("
              "),this.model=new g,this.model.fetch({success:function(){t.render()},error:function(){console.error("Failed to fetch tours.")}})},render:function(){var e=t.template('

              Galaxy Tours

              \n

              This page presents a list of interactive tours available on this Galaxy server.\nSelect any tour to get started (and remember, you can click \'End Tour\' at any time).

              \n\n
              \n <% _.each(tourtagorder, function(tag) { %>\n \n <% }); %>\n
              \n\n<% _.each(tourtagorder, function(tourtagkey) { %>\n
              \n <% var tourtag = tourtags[tourtagkey]; %>\n

              \n <%- tourtag.name %>\n

              \n
                \n <% _.each(tourtag.tours, function(tour) { %>\n
              • \n >\n <%- tour.name || tour.id %>\n \n - <%- tour.attributes.description || "No description given." %>\n <% _.each(tour.attributes.tags, function(tag) { %>\n \n <%- tag.charAt(0).toUpperCase() + tag.slice(1) %>\n \n <% }); %>\n
              • \n <% }); %>\n
              \n
              \n<% }); %>'),i={};t.each(this.model.models,function(e){null===e.attributes.tags?(void 0===i.Untagged&&(i.Untagged={name:"Untagged",tours:[]}),i.Untagged.tours.push(e)):t.each(e.attributes.tags,function(t){t=t.charAt(0).toUpperCase()+t.slice(1),void 0===i[t]&&(i[t]={name:t,tours:[]}),i[t].tours.push(e)})});var r=Object.keys(i).sort();this.$el.html(e({tours:this.model.models,tourtags:i,tourtagorder:r})).on("click",".tourItem",function(t){t.preventDefault(),o(n(this).data("tour.id"))}).on("click",".tag-selector-button",function(t){var e=n(t.target),i="block",r=e.attr("tag-selector-button");e.toggleClass("btn-primary"),e.toggleClass("btn-secondary"),e.hasClass("btn-secondary")&&(i="none"),n("div[tag='"+r+"']").css({display:i})})}});e.default={ToursView:m,giveTourWithData:a,giveTourById:o,activeGalaxyTourRunner:s}}).call(e,i(1),i(0),i(2))},function(t,e,i){"use strict";(function(t,n,r){function a(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var o=i(5),s=a(o),l=i(79),u=a(l),c=t.View.extend({initialize:function(e){var i=this;this.data=[],this.data2=[],this.model=e&&e.model||new t.Model({id:s.default.uid(),cls:"ui-select",error_text:"No options available",empty_text:"Nothing selected",visible:!0,wait:!1,multiple:!1,searchable:!0,optional:!1,disabled:!1,readonly:!1,onchange:function(){},value:null,individual:!1,pagesize:20}).set(e),this.on("change",function(){i.model.get("onchange")&&i.model.get("onchange")(i.value())}),this.listenTo(this.model,"change:data",this._changeData,this),this.listenTo(this.model,"change:disabled",this._changeDisabled,this),this.listenTo(this.model,"change:wait",this._changeWait,this),this.listenTo(this.model,"change:visible",this._changeVisible,this),this.listenTo(this.model,"change:value",this._changeValue,this),this.listenTo(this.model,"change:multiple change:searchable change:cls change:id",this.render,this),this.render()},render:function(){var t=this;this.model.get("searchable")?this._renderSearchable():this._renderClassic(),this.$el.addClass(this.model.get("cls")).attr("id",this.model.get("id")),this.$select.empty().addClass("select").attr("id",this.model.get("id")+"_select").prop("multiple",this.model.get("multiple")).on("change",function(){t.value(t._getValue()),t.trigger("change")}),this._changeData(),this._changeWait(),this._changeVisible(),this._changeDisabled()},_renderClassic:function(){var t=this;this.$el.addClass(this.model.get("multiple")?"ui-select-multiple":"ui-select").append(this.$select=n("").attr({id:e,type:this.model.get("type"),name:this.model.id,value:t.value})).append(n("