# Buggy code
def __cleanup_param_values(inputs, values):
    """
    Remove 'Data' values from `param_values`, along with metadata cruft,
    but track the associations.
    """
    associations = []
    # dbkey is pushed in by the framework
    if "dbkey" in values:
        del values["dbkey"]
    root_values = values
    root_input_keys = inputs.keys()

    # Recursively clean data inputs and dynamic selects
    def cleanup(prefix, inputs, values):
        for key, input in inputs.items():
            if isinstance(input, DataToolParameter) or isinstance(
                input, DataCollectionToolParameter
            ):
                tmp = values[key]
                values[key] = None
                # HACK: Nested associations are not yet working, but we
                #       still need to clean them up so we can serialize
                # if not( prefix ):
                if isinstance(tmp, model.DatasetCollectionElement):
                    tmp = tmp.first_dataset_instance()
                if tmp:  # this is false for a non-set optional dataset
                    if not isinstance(tmp, list):
                        associations.append((tmp.hid, prefix + key))
                    else:
                        associations.extend([(t.hid, prefix + key) for t in tmp])

                # Cleanup the other deprecated crap associated with datasets
                # as well. Worse, for nested datasets all the metadata is
                # being pushed into the root. FIXME: MUST REMOVE SOON
                key = prefix + key + "_"
                for k in root_values.keys():
                    if k not in root_input_keys and k.startswith(key):
                        del root_values[k]
            elif isinstance(input, Repeat):
                if key in values:
                    group_values = values[key]
                    for i, rep_values in enumerate(group_values):
                        rep_index = rep_values["__index__"]
                        cleanup(
                            "%s%s_%d|" % (prefix, key, rep_index),
                            input.inputs,
                            group_values[i],
                        )
            elif isinstance(input, Conditional):
                # Scrub dynamic resource related parameters from workflows,
                # they cause problems and the workflow probably should include
                # their state in workflow encoding.
                if input.name == "__job_resource":
                    if input.name in values:
                        del values[input.name]
                    return
                if input.name in values:
                    group_values = values[input.name]
                    current_case = group_values["__current_case__"]
                    cleanup(
                        "%s%s|" % (prefix, key),
                        input.cases[current_case].inputs,
                        group_values,
                    )
            elif isinstance(input, Section):
                if input.name in values:
                    cleanup("%s%s|" % (prefix, key), input.inputs, values[input.name])

    cleanup("", inputs, values)
    return associations


# Traceback (AttributeError)
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: 80.138.30.147 - - [18/Oct/2020:23:43:09 +0200] "POST /workflow/build_from_current_history HTTP/1.1" 500 - "https://usegalaxy.eu/workflow/build_from_current_history" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0"
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: Traceback (most recent call last):
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: File "lib/galaxy/web/framework/middleware/error.py", line 154, in __call__
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: app_iter = self.application(environ, sr_checker)
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: File "/opt/galaxy/venv/lib64/python3.6/site-packages/paste/recursive.py", line 85, in __call__
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: return self.application(environ, start_response)
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: File "lib/galaxy/web/framework/middleware/statsd.py", line 34, in __call__
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: req = self.application(environ, start_response)
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: File "/opt/galaxy/venv/lib64/python3.6/site-packages/paste/httpexceptions.py", line 640, in __call__
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: return self.application(environ, start_response)
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: File "lib/galaxy/web/framework/base.py", line 145, in __call__
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: return self.handle_request(environ, start_response)
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: File "lib/galaxy/web/framework/base.py", line 224, in handle_request
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: body = method(trans, **kwargs)
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: File "lib/galaxy/webapps/galaxy/controllers/workflow.py", line 854, in build_from_current_history
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: dataset_collection_names=dataset_collection_names
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: File "lib/galaxy/workflow/extract.py", line 29, in extract_workflow
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: steps = extract_steps(trans, history=history, job_ids=job_ids, dataset_ids=dataset_ids, dataset_collection_ids=dataset_collection_ids, dataset_names=dataset_names, dataset_collection_names=None)
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: File "lib/galaxy/workflow/extract.py", line 109, in extract_steps
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: tool_inputs, associations = step_inputs(trans, job)
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: File "lib/galaxy/workflow/extract.py", line 310, in step_inputs
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: associations = __cleanup_param_values(tool.inputs, param_values)
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: File "lib/galaxy/workflow/extract.py", line 372, in __cleanup_param_values
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: cleanup("", inputs, values)
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: File "lib/galaxy/workflow/extract.py", line 368, in cleanup
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: cleanup("%s%s|" % (prefix, key), input.cases[current_case].inputs, group_values)
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: File "lib/galaxy/workflow/extract.py", line 342, in cleanup
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: associations.extend([(t.hid, prefix + key) for t in tmp])
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: File "lib/galaxy/workflow/extract.py", line 342, in <listcomp>
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: associations.extend([(t.hid, prefix + key) for t in tmp])
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: AttributeError: 'DatasetCollectionElement' object has no attribute 'hid'
Oct 18 23:43:10 sn04.bi.uni-freiburg.de uwsgi[1008399]: The above exception was the direct cause of the

# Fixed code
def __cleanup_param_values(inputs, values):
    """
    Remove 'Data' values from `param_values`, along with metadata cruft,
    but track the associations.
    """
    associations = []
    # dbkey is pushed in by the framework
    if "dbkey" in values:
        del values["dbkey"]
    root_values = values
    root_input_keys = inputs.keys()

    # Recursively clean data inputs and dynamic selects
    def cleanup(prefix, inputs, values):
        for key, input in inputs.items():
            if isinstance(input, DataToolParameter) or isinstance(
                input, DataCollectionToolParameter
            ):
                items = values[key]
                values[key] = None
                # HACK: Nested associations are not yet working, but we
                #       still need to clean them up so we can serialize
                # if not( prefix ):
                for item in listify(items):
                    if isinstance(item, model.DatasetCollectionElement):
                        item = item.first_dataset_instance()
                    if item:  # this is false for a non-set optional dataset
                        associations.append((item.hid, prefix + key))

                # Cleanup the other deprecated crap associated with datasets
                # as well. Worse, for nested datasets all the metadata is
                # being pushed into the root. FIXME: MUST REMOVE SOON
                key = prefix + key + "_"
                for k in root_values.keys():
                    if k not in root_input_keys and k.startswith(key):
                        del root_values[k]
            elif isinstance(input, Repeat):
                if key in values:
                    group_values = values[key]
                    for i, rep_values in enumerate(group_values):
                        rep_index = rep_values["__index__"]
                        cleanup(
                            "%s%s_%d|" % (prefix, key, rep_index),
                            input.inputs,
                            group_values[i],
                        )
            elif isinstance(input, Conditional):
                # Scrub dynamic resource related parameters from workflows,
                # they cause problems and the workflow probably should include
                # their state in workflow encoding.
                if input.name == "__job_resource":
                    if input.name in values:
                        del values[input.name]
                    return
                if input.name in values:
                    group_values = values[input.name]
                    current_case = group_values["__current_case__"]
                    cleanup(
                        "%s%s|" % (prefix, key),
                        input.cases[current_case].inputs,
                        group_values,
                    )
            elif isinstance(input, Section):
                if input.name in values:
                    cleanup("%s%s|" % (prefix, key), input.inputs, values[input.name])

    cleanup("", inputs, values)
    return associations