# Buggy code
def collect_primary_datasets(job_context, output, input_ext):
    tool = job_context.tool
    job_working_directory = job_context.job_working_directory
    sa_session = job_context.sa_session

    # Loop through output file names, looking for generated primary
    # datasets in form specified by discover dataset patterns or in tool provided metadata.
    primary_output_assigned = False
    new_outdata_name = None
    primary_datasets = {}
    for output_index, (name, outdata) in enumerate(output.items()):
        dataset_collectors = [DEFAULT_DATASET_COLLECTOR]
        if name in tool.outputs:
            dataset_collectors = [
                dataset_collector(description)
                for description in tool.outputs[name].dataset_collector_descriptions
            ]
        filenames = odict.odict()
        for discovered_file in discover_files(
            name,
            job_context.tool_provided_metadata,
            dataset_collectors,
            job_working_directory,
            outdata,
        ):
            filenames[discovered_file.path] = discovered_file
        for filename_index, (filename, discovered_file) in enumerate(filenames.items()):
            extra_file_collector = discovered_file.collector
            fields_match = discovered_file.match
            if not fields_match:
                # Before I guess pop() would just have thrown an IndexError
                raise Exception(
                    "Problem parsing metadata fields for file %s" % filename
                )
            designation = fields_match.designation
            if (
                filename_index == 0
                and extra_file_collector.assign_primary_output
                and output_index == 0
            ):
                new_outdata_name = fields_match.name or "%s (%s)" % (
                    outdata.name,
                    designation,
                )
                # Move data from temp location to dataset location
                job_context.object_store.update_from_file(
                    outdata.dataset, file_name=filename, create=True
                )
                primary_output_assigned = True
                continue
            if name not in primary_datasets:
                primary_datasets[name] = odict.odict()
            visible = fields_match.visible
            ext = fields_match.ext
            if ext == "input":
                ext = input_ext
            dbkey = fields_match.dbkey
            if dbkey == INPUT_DBKEY_TOKEN:
                dbkey = job_context.input_dbkey
            # Create new primary dataset
            new_primary_name = fields_match.name or "%s (%s)" % (
                outdata.name,
                designation,
            )
            info = outdata.info

            # TODO: should be able to disambiguate files in different directories...
            new_primary_filename = os.path.split(filename)[-1]
            new_primary_datasets_attributes = (
                job_context.tool_provided_metadata.get_new_dataset_meta_by_basename(
                    name, new_primary_filename
                )
            )

            primary_data = job_context.create_dataset(
                ext,
                designation,
                visible,
                dbkey,
                new_primary_name,
                filename,
                info=info,
                init_from=outdata,
                dataset_attributes=new_primary_datasets_attributes,
            )
            # Associate new dataset with job
            job_context.add_output_dataset_association(
                "__new_primary_file_%s|%s__" % (name, designation), primary_data
            )

            if new_primary_datasets_attributes:
                extra_files_path = new_primary_datasets_attributes.get(
                    "extra_files", None
                )
                if extra_files_path:
                    extra_files_path_joined = os.path.join(
                        job_working_directory, extra_files_path
                    )
                    for root, dirs, files in os.walk(extra_files_path_joined):
                        extra_dir = os.path.join(
                            primary_data.extra_files_path,
                            root.replace(extra_files_path_joined, "", 1).lstrip(
                                os.path.sep
                            ),
                        )
                        extra_dir = os.path.normpath(extra_dir)
                        for f in files:
                            job_context.object_store.update_from_file(
                                primary_data.dataset,
                                extra_dir=extra_dir,
                                alt_name=f,
                                file_name=os.path.join(root, f),
                                create=True,
                                preserve_symlinks=True,
                            )
            job_context.add_datasets_to_history(
                [primary_data], for_output_dataset=outdata
            )
            # Add dataset to return dict
            primary_datasets[name][designation] = primary_data
        if primary_output_assigned:
            outdata.name = new_outdata_name
            outdata.init_meta()
            outdata.set_meta()
            outdata.set_peek()
            sa_session.add(outdata)

    sa_session.flush()
    return primary_datasets


# Traceback (Exception)
galaxy.workflow.run_request INFO 2019-02-13 14:22:26,638 [p:336,w:1,m:0] [uWSGIWorker1Core3] Creating a step_state for step.id 150481
galaxy.workflow.run_request INFO 2019-02-13 14:22:26,639 [p:336,w:1,m:0] [uWSGIWorker1Core3] Creating a step_state for step.id 150482
galaxy.workflow.run_request INFO 2019-02-13 14:22:26,640 [p:336,w:1,m:0] [uWSGIWorker1Core3] Creating a step_state for step.id 150483
galaxy.workflow.run_request INFO 2019-02-13 14:22:26,641 [p:336,w:1,m:0] [uWSGIWorker1Core3] Creating a step_state for step.id 150484
galaxy.workflow.run_request INFO 2019-02-13 14:22:26,642 [p:336,w:1,m:0] [uWSGIWorker1Core3] Creating a step_state for step.id 150485
galaxy.workflow.run_request INFO 2019-02-13 14:22:26,642 [p:336,w:1,m:0] [uWSGIWorker1Core3] Creating a step_state for step.id 150486
galaxy.workflow.run_request INFO 2019-02-13 14:22:26,643 [p:336,w:1,m:0] [uWSGIWorker1Core3] Creating a step_state for step.id 150487
galaxy.workflow.run_request INFO 2019-02-13 14:22:26,644 [p:336,w:1,m:0] [uWSGIWorker1Core3] Creating a step_state for step.id 150488
galaxy.workflow.scheduling_manager INFO 2019-02-13 14:22:26,645 [p:336,w:1,m:0] [uWSGIWorker1Core3] Queueing workflow invocation for handler [main.web.1]
98.236.80.203 - - [13/Feb/2019:14:22:26 -0500] "POST /api/workflows/f293ecb6146177b0/invocations HTTP/1.1" 200 - "https://galaxyp.msi.umn.edu/workflows/run?id=f293ecb6146177b0" "Mozilla/5.0 (Macintosh; Intel Mac
OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.81 Safari/537.36"
[pid: 336|app: 0|req: 9300/93021] 127.0.0.1 () {56 vars in 1491 bytes} [Wed Feb 13 14:22:26 2019] POST /api/workflows/f293ecb6146177b0/invocations => generated 243 bytes in 458 msecs (HTTP/1.1 200) 3 headers in
124 bytes (1 switches on core 3)
98.236.80.203 - - [13/Feb/2019:14:22:26 -0500] "GET /api/webhooks HTTP/1.1" 200 - "https://galaxyp.msi.umn.edu/workflows/run?id=f293ecb6146177b0" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.
36 (KHTML, like Gecko) Chrome/72.0.3626.81 Safari/537.36"
[pid: 343|app: 0|req: 9298/93022] 127.0.0.1 () {50 vars in 1334 bytes} [Wed Feb 13 14:22:26 2019] GET /api/webhooks => generated 2 bytes in 20 msecs (HTTP/1.1 200) 3 headers in 124 bytes (1 switches on core 2)
98.236.80.203 - - [13/Feb/2019:14:22:26 -0500] "GET /api/histories/e8e7e8330bf2145b/contents?details=a2cb428649a1c4a2&amp;order=hid&amp;v=dev&amp;q=update_time-ge&amp;q=deleted&amp;q=purged&amp;qv=2019-02-13T20%3A22%3A24.000Z&amp;qv=False&amp;
qv=False HTTP/1.1" 200 - "https://galaxyp.msi.umn.edu/workflows/run?id=f293ecb6146177b0" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.81 Safari/537.36"
[pid: 434|app: 0|req: 9304/93023] 127.0.0.1 () {50 vars in 1641 bytes} [Wed Feb 13 14:22:26 2019] GET /api/histories/e8e7e8330bf2145b/contents?details=a2cb428649a1c4a2&amp;order=hid&amp;v=dev&amp;q=update_time-ge&amp;q=deleted&amp;
q=purged&amp;qv=2019-02-13T20%3A22%3A24.000Z&amp;qv=False&amp;qv=False => generated 2 bytes in 66 msecs (HTTP/1.1 200) 3 headers in 124 bytes (1 switches on core 3)
galaxy.workflow.run DEBUG 2019-02-13 14:22:27,590 [p:336,w:1,m:0] [WorkflowRequestMonitor.monitor_thread] Workflow step 150481 of invocation 17854 invoked (29.118 ms)
galaxy.workflow.run DEBUG 2019-02-13 14:22:27,610 [p:336,w:1,m:0] [WorkflowRequestMonitor.monitor_thread] Workflow step 150482 of invocation 17854 invoked (19.367 ms)
galaxy.managers.collections DEBUG 2019-02-13 14:22:27,672 [p:336,w:1,m:0] [WorkflowRequestMonitor.monitor_thread] Created collection with 2 elements
/panfs/roc/website/galaxyp.msi.umn.edu/GALAXYP/.venv/lib/python2.7/site-packages/sqlalchemy/sql/sqltypes.py:226: SAWarning: Unicode type received non-unicode bind param value 'output'. (this warning may be suppr
essed after 10 occurrences)
(util.ellipses_string(value),))
/panfs/roc/website/galaxyp.msi.umn.edu/GALAXYP/.venv/lib/python2.7/site-packages/sqlalchemy/sql/sqltypes.py:226: SAWarning: Unicode type received non-unicode bind param value 'input'. (this warning may be suppre
ssed after 10 occurrences)
(util.ellipses_string(value),))
galaxy.tools.actions INFO 2019-02-13 14:22:27,980 [p:336,w:1,m:0] [WorkflowRequestMonitor.monitor_thread] Handled output named output for tool toolshed.g2.bx.psu.edu/repos/galaxyp/msconvert_nix/msconvert_nix/3.0
.9992.0 (73.258 ms)
galaxy.tools.actions INFO 2019-02-13 14:22:27,995 [p:336,w:1,m:0] [WorkflowRequestMonitor.monitor_thread] Added output datasets to history (12.906 ms)
galaxy.tools.actions INFO 2019-02-13 14:22:27,998 [p:336,w:1,m:0] [WorkflowRequestMonitor.monitor_thread] Setup for job Job[unflushed,tool_id=toolshed.g2.bx.psu.edu/repos/galaxyp/msconvert_nix/msconvert_nix/3.0.
9992.0] complete, ready to flush (3.279 ms)
galaxy.tools.actions INFO 2019-02-13 14:22:28,096 [p:336,w:1,m:0] [WorkflowRequestMonitor.monitor_thread] Flushed transaction for job Job[id=191675,tool_id=toolshed.g2.bx.psu.edu/repos/galaxyp/msconvert_nix/msco
nvert_nix/3.0.9992.0] (97.376 ms)
galaxy.tools.execute DEBUG 2019-02-13 14:22:28,096 [p:336,w:1,m:0] [WorkflowRequestMonitor.monitor_thread] Tool [toolshed.g2.bx.psu.edu/repos/galaxyp/msconvert_nix/msconvert_nix/3.0.9992.0] created job [191675]
(286.212 ms)
galaxy.tools.actions INFO 2019-02-13 14:22:28,329 [p:336,w:1,m:0] [WorkflowRequestMonitor.monitor_thread] Handled output named output for tool toolshed.g2.bx.psu.edu/repos/galaxyp/msconvert_nix/msconvert_nix/3.0.9992.0 (40.918 ms)
galaxy.tools.actions INFO 2019-02-13 14:22:28,343 [p:336,w:1,m:0] [WorkflowRequestMonitor.monitor_thread] Added output datasets to history (12.881 ms)
galaxy.tools.actions INFO 2019-02-13 14:22:28,348 [p:336,w:1,m:0] [WorkflowRequestMonitor.monitor_thread] Setup for job Job[unflushed,tool_id=toolshed.g2.bx.psu.edu/repos/galaxyp/msconvert_nix/msconvert_nix/3.0.9992.0] complete, ready to flush (5.537 ms)
galaxy.tools.actions INFO 2019-02-13 14:22:28,434 [p:336,w:1,m:0] [WorkflowRequestMonitor.monitor_thread] Flushed transaction for job Job[id=191676,tool_id=toolshed.g2.bx.psu.edu/repos/galaxyp/msconvert_nix/msconvert_nix/3.0.9992.0] (85.546 ms)
galaxy.tools.execute DEBUG 2019-02-13 14:22:28,435 [p:336,w:1,m:0] [WorkflowRequestMonitor.monitor_thread] Tool [toolshed.g2.bx.psu.edu/repos/galaxyp/msconvert_nix/msconvert_nix/3.0.9992.0] created job [191676] (165.391 ms)
galaxy.tools.execute DEBUG 2019-02-13 14:22:28,631 [p:336,w:1,m:0] [WorkflowRequestMonitor.monitor_thread] Executed 2 job(s) for tool toolshed.g2.bx.psu.edu/repos/galaxyp/msconvert_nix/msconvert_nix/3.0.9992.0 request: (983.069 ms)
galaxy.workflow.run DEBUG 2019-02-13 14:22:28,672 [p:336,w:1,m:0] [WorkflowRequestMonitor.monitor_thread] Workflow step 150483 of invocation 17854 invoked (1061.588 ms)
galaxy.tools ERROR 2019-02-13 14:22:28,865 [p:336,w:1,m:0] [WorkflowRequestMonitor.monitor_thread] Exception caught while attempting tool execution:
Traceback (most recent call last):
File "lib/galaxy/tools/__init__.py", line 1436, in handle_single_execution
collection_info=collection_info,
File "lib/galaxy/tools/__init__.py", line 1517, in execute
return self.tool_action.execute(self, trans, incoming=incoming, set_output_hid=set_output_hid, history=history, **kwargs)
File "lib/galaxy/tools/actions/__init__.py", line 331, in execute
params=wrapped_params.params,
File "lib/galaxy/tools/parameters/wrapped.py", line 44, in params
self.wrap_values(self.tool.inputs, params, skip_missing_values=not self.tool.check_values)
File "lib/galaxy/tools/parameters/wrapped.py", line 79, in wrap_values
name=input.name)
File "lib/galaxy/tools/wrappers.py", line 343, in __init__
list.__init__(self, map(to_wrapper, datasets))
File "lib/galaxy/tools/wrappers.py", line 341, in to_wrapper
return self._dataset_wrapper(dataset, dataset_paths, **kwargs)
File "lib/galaxy/tools/wrappers.py", line 314, in _dataset_wrapper
real_path = dataset.file_name
File "lib/galaxy/model/__init__.py", line 2181, in get_file_name
return self.dataset.get_file_name()
File "lib/galaxy/model/__init__.py", line 1969, in get_file_name
filename = self.object_store.get_filename(self)
File "lib/galaxy/objectstore/__init__.py", line 506, in get_filename
return self._call_method('get_filename', obj, ObjectNotFound, True, **kwargs)
File "lib/galaxy/objectstore/__init__.py", line 534, in _call_method
% (method, self._repr_object_for_exception(obj), str(kwargs)))
ObjectNotFound: objectstore, _call_method failed: get_filename on Dataset(id=241288), kwargs: {}
galaxy.tools.execute WARNING 2019-02-13 14:22:28,870 [p:336,w:1,m:0] [WorkflowRequestMonitor.monitor_thread] There was a failure executing a job for tool [toolshed.g2.bx.psu.edu/repos/galaxyp/peptideshaker/search_gui/3.3.10.1] - Error executing tool: objectstore, _call_method failed: get_filename on Dataset(id=241288), kwargs: {}
galaxy.tools.execute DEBUG 2019-02-13 14:22:28,896 [p:336,w:1,m:0] [WorkflowRequestMonitor.monitor_thread] Executed 1 job(s) for tool toolshed.g2.bx.psu.edu/repos/galaxyp/peptideshaker/search_gui/3.3.10.1 request: (162.061 ms)
galaxy.workflow.run ERROR 2019-02-13 14:22:28,931 [p:336,w:1,m:0] [WorkflowRequestMonitor.monitor_thread] Failed to schedule Workflow[id=7155,name=iMetaQuantome_workflow (imported from uploaded file)], problem occurred on WorkflowStep[index=3,type=tool].
Traceback (most recent call last):
File "lib/galaxy/workflow/run.py", line 190, in invoke
incomplete_or_none = self._invoke_step(workflow_invocation_step)
File "lib/galaxy/workflow/run.py", line 266, in _invoke_step
use_cached_job=self.workflow_invocation.use_cached_job)
File "lib/galaxy/workflow/modules.py", line 1011, in execute
raise Exception(message)
Exception: Failed to create one or more job(s) for workflow step.
galaxy.workflow.run ERROR 2019-02-13 14:22:28,932 [p:336,w:1,m:0] [WorkflowRequestMonitor.monitor_thread] Failed to execute scheduled workflow.
Traceback (most recent call last):
File "lib/galaxy/workflow/run.py", line 83, in __invoke
outputs = invoker.invoke()
File "lib/galaxy/workflow/run.py", line 190, in invoke
incomplete_or_none = self._invoke_step(workflow_invocation_step)
File "lib/galaxy/workflow/run.py", line 266, in _invoke_step
use_cached_job=self.workflow_invocation.use_cached_job)
File "lib/galaxy/workflow/modules.py", line 1011, in execute
raise Exception(message)
Exception: Failed to create one or more job(s) for workflow step.
galaxy.jobs.rules.destinations DEBUG 2019-02-13 14:22:29,710 [p:336,w:1,m:0] [JobHandlerQueue.monitor_thread] In default runner handling tool msconvert_nix
galaxy.jobs.rules.destinations DEBUG 2019-02-13 14:22:29,710 [p:336,w:1,m:0] [JobHandlerQueue.monitor_thread] JobDestination created with values {'-l': 'walltime=1:00:00,nodes=1:ppn=2,mem=10gb', '-q': 'small@mesabihn3'}
galaxy.jobs.mapper DEBUG 2019-02-13 14:22:29,710 [p:336,w:1,m:0] [JobHandlerQueue.monitor_thread] (191675) Mapped job to destination id: dynamic-destination-msconvert_nix
10.21.31.51 - - [13/Feb/2019:14:22:29 -0500] "GET /api/histories/26b7bfc3888698b1/contents?order=hid&amp;v=dev&amp;q=update_time-ge&amp;q=deleted&amp;q=purged&amp;qv=2019-02-13T20%3A22%3A24.000Z&amp;qv=False&amp;qv=False HTTP/1.1" 200 - "https://galaxyp.msi.umn.edu/?tool_id=toolshed.g2.bx.psu.edu%2Frepos%2Fgalaxyp%2Fpeptideshaker%2Fsearch_gui%2F3.3.10.1&amp;version=3.3.10.1&amp;__identifer=n8xwa2bomdwb" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299"
[pid: 351|app: 0|req: 9303/93024] 127.0.0.1 () {50 vars in 1830 bytes} [Wed Feb 13 14:22:29 2019] GET /api/histories/26b7bfc3888698b1/contents?order=hid&amp;v=dev&amp;q=update_time-ge&amp;q=deleted&amp;q=purged&amp;qv=2019-02-13T20%3A22%3A24.000Z&amp;qv=False&amp;qv=False => generated 2 bytes in 73 msecs (HTTP/1.1 200) 3 headers in 124 bytes (1 switches on core 1)
galaxy.jobs.handler DEBUG 2019-02-13 14:22:29,818 [p:336,w:1,m:0] [JobHandlerQueue.monitor_thread] (191675) Dispatching to pbs-mesabi runner
galaxy.jobs DEBUG 2019-02-13 14:22:29,842 [p:336,w:1,m:0] [JobHandlerQueue.monitor_thread] (191675) Persisting job destination (destination id: dynamic-destination-msconvert_nix)
galaxy.objectstore.s3 DEBUG 2019-02-13 14:22:29,856 [p:336,w:1,m:0] [JobHandlerQueue.monitor_thread] Pushing cache file '/panfs/roc/website/galaxyp.msi.umn.edu/GALAXYP/database/files/000/241/dataset_241288.dat' of size 0 bytes to key '000/241/dataset_241288.dat'
galaxy.objectstore.s3 DEBUG 2019-02-13 14:22:29,905 [p:336,w:1,m:0] [JobHandlerQueue.monitor_thread] Pushed cache file '/panfs/roc/website/galaxyp.msi.umn.edu/GALAXYP/database/files/000/241/dataset_241288.dat' to key '000/241/dataset_241288.dat' (0 bytes transfered in 0:00:00.048339 sec)
galaxy.jobs DEBUG 2019-02-13 14:22:29,927 [p:336,w:1,m:0] [JobHandlerQueue.monitor_thread] (191675) Working directory for job is: /panfs/roc/website/galaxyp.msi.umn.edu/GALAXYP/database/job_working_directory/000/191/191675
galaxy.jobs.runners DEBUG 2019-02-13 14:22:29,958 [p:336,w:1,m:0] [JobHandlerQueue.monitor_thread] Job [191675] queued (139.791 ms)
galaxy.jobs.handler INFO 2019-02-13 14:22:29,986 [p:336,w:1,m:0] [JobHandlerQueue.monitor_thread] (191675) Job dispatched
galaxy.jobs.rules.destinations DEBUG 2019-02-13 14:22:30,024 [p:336,w:1,m:0] [JobHandlerQueue.monitor_thread] In default runner handling tool msconvert_nix
galaxy.jobs.rules.destinations DEBUG 2019-02-13 14:22:30,024 [p:336,w:1,m:0] [JobHandlerQueue.monitor_thread] JobDestination created with values {'-l': 'walltime=1:00:00,nodes=1:ppn=2,mem=10gb', '-q': 'small@mesabihn3'}
galaxy.jobs.mapper DEBUG 2019-02-13 14:22:30,025 [p:336,w:1,m:0] [JobHandlerQueue.monitor_thread] (191676) Mapped job to destination id: dynamic-destination-msconvert_nix
galaxy.jobs.handler DEBUG 2019-02-13 14:22:30,080 [p:336,w:1,m:0] [JobHandlerQueue.monitor_thread] (191676) Dispatching to pbs-mesabi runner
galaxy.jobs DEBUG 2019-02-13 14:22:30,100 [p:336,w:1,m:0] [JobHandlerQueue.monitor_thread] (191676) Persisting job destination (destination id: dynamic-destination-msconvert_nix)
galaxy.objectstore.s3 DEBUG 2019-02-13 14:22:30,112 [p:336,w:1,m:0] [JobHandlerQueue.monitor_thread] Pushing cache file '/panfs/roc/website/galaxyp.msi.umn.edu/GALAXYP/database/files/000/241/dataset_241289.dat' :
of size 0 bytes to key '000/241/dataset_241289.dat'
galaxy.objectstore.s3 DEBUG 2019-02-13 14:22:30,153 [p:336,w:1,m:0] [JobHandlerQueue.monitor_thread] Pushed cache file '/panfs/roc/website/galaxyp.msi.umn.edu/GALAXYP/database/files/000/241/dataset_241289.dat' to key '000/241/dataset_241289.dat' (0 bytes transfered in 0:00:00.041692 sec)
galaxy.jobs DEBUG 2019-02-13 14:22:30,169 [p:336,w:1,m:0] [JobHandlerQueue.monitor_thread] (191676) Working directory for job is: /panfs/roc/website/galaxyp.msi.umn.edu/GALAXYP/database/job_working_directory/000/191/191676
galaxy.jobs.runners DEBUG 2019-02-13 14:22:30,186 [p:336,w:1,m:0] [JobHandlerQueue.monitor_thread] Job [191676] queued (106.436 ms)
galaxy.jobs.handler INFO 2019-02-13 14:22:30,209 [p:336,w:1,m:0] [JobHandlerQueue.monitor_thread] (191676) Job dispatched
galaxy.tools.deps DEBUG 2019-02-13 14:22:30,411 [p:336,w:1,m:0] [PBSRunner.work_thread-0] Using dependency proteowizard version 3.0.9992 of type conda
galaxy.jobs.command_factory INFO 2019-02-13 14:22:30,518 [p:336,w:1,m:0] [PBSRunner.work_thread-0] Built script [/panfs/roc/website/galaxyp.msi.umn.edu/GALAXYP/database/job_working_directory/000/191/191675/tool_script.sh] for tool command [[ "$CONDA_DEFAULT_ENV" = "/home/galaxy/galaxy/miniconda2/envs/__proteowizard@3.0.9992" ] ||

# Fixed code
def collect_primary_datasets(job_context, output, input_ext):
    tool = job_context.tool
    job_working_directory = job_context.job_working_directory
    sa_session = job_context.sa_session

    # Loop through output file names, looking for generated primary
    # datasets in form specified by discover dataset patterns or in tool provided metadata.
    primary_output_assigned = False
    new_outdata_name = None
    primary_datasets = {}
    for output_index, (name, outdata) in enumerate(output.items()):
        dataset_collectors = [DEFAULT_DATASET_COLLECTOR]
        if name in tool.outputs:
            dataset_collectors = [
                dataset_collector(description)
                for description in tool.outputs[name].dataset_collector_descriptions
            ]
        filenames = odict.odict()
        for discovered_file in discover_files(
            name,
            job_context.tool_provided_metadata,
            dataset_collectors,
            job_working_directory,
            outdata,
        ):
            filenames[discovered_file.path] = discovered_file
        for filename_index, (filename, discovered_file) in enumerate(filenames.items()):
            extra_file_collector = discovered_file.collector
            fields_match = discovered_file.match
            if not fields_match:
                # Before I guess pop() would just have thrown an IndexError
                raise Exception(
                    "Problem parsing metadata fields for file %s" % filename
                )
            designation = fields_match.designation
            if (
                filename_index == 0
                and extra_file_collector.assign_primary_output
                and output_index == 0
            ):
                new_outdata_name = fields_match.name or "%s (%s)" % (
                    outdata.name,
                    designation,
                )
                # Move data from temp location to dataset location
                job_context.object_store.update_from_file(
                    outdata.dataset, file_name=filename, create=True
                )
                primary_output_assigned = True
                continue
            if name not in primary_datasets:
                primary_datasets[name] = odict.odict()
            visible = fields_match.visible
            ext = fields_match.ext
            if ext == "input":
                ext = input_ext
            dbkey = fields_match.dbkey
            if dbkey == INPUT_DBKEY_TOKEN:
                dbkey = job_context.input_dbkey
            # Create new primary dataset
            new_primary_name = fields_match.name or "%s (%s)" % (
                outdata.name,
                designation,
            )
            info = outdata.info

            # TODO: should be able to disambiguate files in different directories...
            new_primary_filename = os.path.split(filename)[-1]
            new_primary_datasets_attributes = (
                job_context.tool_provided_metadata.get_new_dataset_meta_by_basename(
                    name, new_primary_filename
                )
            )

            primary_data = job_context.create_dataset(
                ext,
                designation,
                visible,
                dbkey,
                new_primary_name,
                filename,
                info=info,
                init_from=outdata,
                dataset_attributes=new_primary_datasets_attributes,
            )
            # Associate new dataset with job
            job_context.add_output_dataset_association(
                "__new_primary_file_%s|%s__" % (name, designation), primary_data
            )

            if new_primary_datasets_attributes:
                extra_files_path = new_primary_datasets_attributes.get(
                    "extra_files", None
                )
                if extra_files_path:
                    extra_files_path_joined = os.path.join(
                        job_working_directory, extra_files_path
                    )
                    primary_data.dataset.create_extra_files_path()
                    for root, dirs, files in os.walk(extra_files_path_joined):
                        extra_dir = os.path.join(
                            primary_data.extra_files_path,
                            root.replace(extra_files_path_joined, "", 1).lstrip(
                                os.path.sep
                            ),
                        )
                        extra_dir = os.path.normpath(extra_dir)
                        for f in files:
                            job_context.object_store.update_from_file(
                                primary_data.dataset,
                                extra_dir=extra_dir,
                                alt_name=f,
                                file_name=os.path.join(root, f),
                                create=True,
                                preserve_symlinks=True,
                            )
            job_context.add_datasets_to_history(
                [primary_data], for_output_dataset=outdata
            )
            # Add dataset to return dict
            primary_datasets[name][designation] = primary_data
        if primary_output_assigned:
            outdata.name = new_outdata_name
            outdata.init_meta()
            outdata.set_meta()
            outdata.set_peek()
            sa_session.add(outdata)

    sa_session.flush()
    return primary_datasets