diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 5aa0d60fbaa..43c74e9f4b8 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -4185,7 +4185,7 @@ class WorkflowInvocation(object, UsesCreateAndUpdateTime, Dictifiable): else: raise Exception("Unknown output type encountered") - def to_dict(self, view='collection', value_mapper=None, step_details=False): + def to_dict(self, view='collection', value_mapper=None, step_details=False, legacy_job_state=False): rval = super(WorkflowInvocation, self).to_dict(view=view, value_mapper=value_mapper) if view == 'element': steps = [] @@ -4194,7 +4194,19 @@ class WorkflowInvocation(object, UsesCreateAndUpdateTime, Dictifiable): v = step.to_dict(view='element') else: v = step.to_dict(view='collection') - steps.append(v) + if legacy_job_state: + step_jobs = step.jobs + if step_jobs: + for step_job in step_jobs: + v_clone = v.copy() + v_clone["state"] = step_job.state + v_clone["job_id"] = step_job.id + steps.append(v_clone) + else: + v["state"] = None + steps.append(v) + else: + steps.append(v) rval['steps'] = steps inputs = {} diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index 3d006f09a5d..0f50d19b7ff 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -690,12 +690,30 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn :param invocation_id: the invocation id (required) :type invocation_id: str + :param step_details: fetch details about individual invocation steps + and populate a steps attribute in the resulting + dictionary. Defaults to false. + :type step_details: bool + + :param legacy_job_state: If step_details is rrue, and this is set to true + populate the invocation step state with the job state + instead of the invocation step state. This will also + produce one step per job in mapping jobs to mimic the + older behavior with respect to collections. Partially + scheduled steps may provide incomplete information + and the listed steps outputs are the mapped over + step outputs but the individual job outputs + when this is set - at least for now. + :type legacy_job_state: bool + :raises: exceptions.MessageException, exceptions.ObjectNotFound """ decoded_workflow_invocation_id = self.decode_id(invocation_id) workflow_invocation = self.workflow_manager.get_invocation(trans, decoded_workflow_invocation_id) if workflow_invocation: - return self.__encode_invocation(trans, workflow_invocation, step_details=kwd.get('step_details', False)) + step_details = util.string_as_bool(kwd.get('step_details', 'False')) + legacy_job_state = util.string_as_bool(kwd.get('legacy_job_state', 'False')) + return self.__encode_invocation(trans, workflow_invocation, step_details=step_details, legacy_job_state=legacy_job_state) return None @expose_api @@ -785,9 +803,9 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn def __get_stored_workflow(self, trans, workflow_id): return self.workflow_manager.get_stored_workflow(trans, workflow_id) - def __encode_invocation(self, trans, invocation, view="element", step_details=False): + def __encode_invocation(self, trans, invocation, view="element", step_details=False, legacy_job_state=False): return self.encode_all_ids( trans, - invocation.to_dict(view, step_details=step_details), + invocation.to_dict(view, step_details=step_details, legacy_job_state=legacy_job_state), True ) diff --git a/test/api/test_workflows.py b/test/api/test_workflows.py index 0e6bab5436d..4eebede79a5 100644 --- a/test/api/test_workflows.py +++ b/test/api/test_workflows.py @@ -193,8 +193,8 @@ class BaseWorkflowsApiTestCase(api.ApiTestCase): workflow_inputs = workflow_show_resposne.json()["inputs"] return workflow_inputs - def _invocation_details(self, workflow_id, invocation_id): - invocation_details_response = self._get("workflows/%s/usage/%s" % (workflow_id, invocation_id)) + def _invocation_details(self, workflow_id, invocation_id, **kwds): + invocation_details_response = self._get("workflows/%s/usage/%s" % (workflow_id, invocation_id), data=kwds) self._assert_status_code_is(invocation_details_response, 200) invocation_details = invocation_details_response.json() return invocation_details @@ -2900,6 +2900,77 @@ steps: assert invocation_tool_step_response.json()["job_id"] == job_id + def test_invocation_with_collection_mapping(self): + workflow_id, invocation_id = self._run_mapping_workflow() + + usage_details = self._invocation_details(workflow_id, invocation_id) + # Assert some high-level things about the structure of data returned. + self._assert_has_keys(usage_details, "inputs", "steps", "workflow_id") + + invocation_steps = usage_details["steps"] + invocation_input_step, invocation_tool_step = None, None + for invocation_step in invocation_steps: + self._assert_has_keys(invocation_step, "workflow_step_id", "order_index", "id") + order_index = invocation_step["order_index"] + assert order_index in [0, 1] + if invocation_step["order_index"] == 0: + assert invocation_input_step is None + invocation_input_step = invocation_step + else: + assert invocation_tool_step is None + invocation_tool_step = invocation_step + + # Tool steps have non-null job_ids (deprecated though they may be) + assert invocation_input_step.get("job_id", None) is None + assert invocation_tool_step.get("job_id", None) is None + assert invocation_tool_step["state"] == "scheduled" + + usage_details = self._invocation_details(workflow_id, invocation_id, legacy_job_state="true") + # Assert some high-level things about the structure of data returned. + self._assert_has_keys(usage_details, "inputs", "steps", "workflow_id") + + invocation_steps = usage_details["steps"] + invocation_input_step = None + invocation_tool_steps = [] + for invocation_step in invocation_steps: + self._assert_has_keys(invocation_step, "workflow_step_id", "order_index", "id") + order_index = invocation_step["order_index"] + assert order_index in [0, 1] + if invocation_step["order_index"] == 0: + assert invocation_input_step is None + invocation_input_step = invocation_step + else: + invocation_tool_steps.append(invocation_step) + + assert len(invocation_tool_steps) == 2 + assert invocation_tool_steps[0]["state"] == "ok" + + def _run_mapping_workflow(self): + history_id = self.dataset_populator.new_history() + summary = self._run_jobs(""" +class: GalaxyWorkflow +inputs: + - type: collection + label: input_c +steps: + - label: cat1 + tool_id: cat1 + state: + input1: + $link: input_c +test_data: + input_c: + type: list + elements: + - identifier: i1 + content: "0" + - identifier: i2 + content: "1" +""", history_id=history_id, wait=True, assert_ok=True) + workflow_id = summary.workflow_id + invocation_id = summary.invocation_id + return workflow_id, invocation_id + @skip_without_tool("cat1") def test_invocations_accessible_imported_workflow(self): workflow_id = self.workflow_populator.simple_workflow("test_usage", publish=True)