Skip to content

Commit ab4644c

Browse files
authored
allow multiple standalone steps in workflows (#3490)
* allow multiple standalone steps in workflows * flake8 * adding a test * 2 commands from start & extra commands * more tests * artifact.parents
1 parent b8206e4 commit ab4644c

File tree

3 files changed

+82
-12
lines changed

3 files changed

+82
-12
lines changed

qiita_pet/handlers/artifact_handlers/base_handlers.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,9 @@ def artifact_summary_get_request(user, artifact_id):
134134
# Check if the artifact is editable by the given user
135135
study = artifact.study
136136
analysis = artifact.analysis
137-
if artifact_type == 'job-output-folder':
137+
# if is a folder and has no parents, it means that is an SPP job and
138+
# nobody should be able to change anything about it
139+
if artifact_type == 'job-output-folder' and not artifact.parents:
138140
editable = False
139141
else:
140142
editable = study.can_edit(user) if study else analysis.can_edit(user)

qiita_pet/handlers/software.py

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ def _default_parameters_parsing(node):
7474
# for easy look up and merge of output_names
7575
main_nodes = dict()
7676
not_used_nodes = {n.id: n for n in graph.nodes}
77+
standalone_input = None
7778
for i, (x, y) in enumerate(graph.edges):
7879
if x.id in not_used_nodes:
7980
del not_used_nodes[x.id]
@@ -89,10 +90,16 @@ def _default_parameters_parsing(node):
8990
if i == 0:
9091
# we are in the first element so we can specifically select
9192
# the type we are looking for
92-
if at in input_x[0][1]:
93+
if input_x and at in input_x[0][1]:
9394
input_x[0][1] = at
94-
else:
95+
elif input_x:
9596
input_x[0][1] = '** WARNING, NOT DEFINED **'
97+
else:
98+
# if we get to this point it means that the workflow has a
99+
# multiple commands starting from the main single input,
100+
# thus is fine to link them to the same raw data
101+
standalone_input = vals_x[0]
102+
input_x = [['', at]]
96103

97104
name_x = vals_x[0]
98105
name_y = vals_y[0]
@@ -106,6 +113,8 @@ def _default_parameters_parsing(node):
106113
name = inputs[b]
107114
else:
108115
name = 'input_%s_%s' % (name_x, b)
116+
if standalone_input is not None:
117+
standalone_input = name
109118
vals = [name, a, b]
110119
if vals not in nodes:
111120
inputs[b] = name
@@ -149,21 +158,25 @@ def _default_parameters_parsing(node):
149158

150159
wparams = w.parameters
151160

152-
# adding nodes without edges
153-
# as a first step if not_used_nodes is not empty we'll confirm that
154-
# nodes/edges are empty; in theory we should never hit this
155-
if not_used_nodes and (nodes or edges):
156-
raise ValueError(
157-
'Error, please check your workflow configuration')
161+
# This case happens when a workflow has 2 commands from the initial
162+
# artifact and one of them has more processing after
163+
if not_used_nodes and (nodes or edges) and standalone_input is None:
164+
standalone_input = edges[0][0]
158165

159166
# note that this block is similar but not identical to adding connected
160167
# nodes
161168
for i, (_, x) in enumerate(not_used_nodes.items()):
162169
vals_x, input_x, output_x = _default_parameters_parsing(x)
163-
if at in input_x[0][1]:
170+
if input_x and at in input_x[0][1]:
164171
input_x[0][1] = at
165-
else:
172+
elif input_x:
166173
input_x[0][1] = '** WARNING, NOT DEFINED **'
174+
else:
175+
# if we get to this point it means that these are "standalone"
176+
# commands, thus is fine to link them to the same raw data
177+
if standalone_input is None:
178+
standalone_input = vals_x[0]
179+
input_x = [['', at]]
167180

168181
name_x = vals_x[0]
169182
if vals_x not in (nodes):
@@ -173,7 +186,16 @@ def _default_parameters_parsing(node):
173186
name = inputs[b]
174187
else:
175188
name = 'input_%s_%s' % (name_x, b)
176-
nodes.append([name, a, b])
189+
# if standalone_input == name_x then this is the first time
190+
# we are processing a standalone command so we need to add
191+
# the node and store the name of the node for future usage
192+
if standalone_input is None:
193+
nodes.append([name, a, b])
194+
elif standalone_input == name_x:
195+
nodes.append([name, a, b])
196+
standalone_input = name
197+
else:
198+
name = standalone_input
177199
edges.append([name, vals_x[0]])
178200
for a, b in output_x:
179201
name = 'output_%s_%s' % (name_x, b)

qiita_pet/test/test_software.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,52 @@ def test_get(self):
5757
self.assertIn('FASTA upstream workflow', body)
5858
DefaultWorkflow(2).active = True
5959

60+
def test_retrive_workflows_standalone(self):
61+
# let's create a new workflow, add 1 commands, and make parameters not
62+
# required to make sure the stanalone is "active"
63+
with TRN:
64+
# 5 per_sample_FASTQ
65+
sql = """INSERT INTO qiita.default_workflow
66+
(name, artifact_type_id, description, parameters)
67+
VALUES ('', 5, '', '{"prep": {}, "sample": {}}')
68+
RETURNING default_workflow_id"""
69+
TRN.add(sql)
70+
wid = TRN.execute_fetchlast()
71+
# 11 is per-sample-FASTQ split libraries commands
72+
sql = """INSERT INTO qiita.default_workflow_node
73+
(default_workflow_id, default_parameter_set_id)
74+
VALUES (%s, 11)
75+
RETURNING default_workflow_node_id"""
76+
TRN.add(sql, [wid])
77+
nid = TRN.execute_fetchflatten()
78+
sql = """UPDATE qiita.command_parameter SET required = false"""
79+
TRN.add(sql)
80+
TRN.execute()
81+
82+
# here we expect 1 input node and 1 edge
83+
obs = _retrive_workflows(True)[-1]
84+
exp_value = f'input_params_{nid[0]}_per_sample_FASTQ'
85+
self.assertEqual(1, len(
86+
[x for x in obs['nodes'] if x[0] == exp_value]))
87+
self.assertEqual(1, len(
88+
[x for x in obs['edges'] if x[0] == exp_value]))
89+
90+
# now let's insert another command using the same input
91+
with TRN:
92+
# 12 is per-sample-FASTQ split libraries commands
93+
sql = """INSERT INTO qiita.default_workflow_node
94+
(default_workflow_id, default_parameter_set_id)
95+
VALUES (%s, 12)"""
96+
TRN.add(sql, [wid])
97+
TRN.execute()
98+
99+
# we should still have 1 node but now with 2 edges
100+
obs = _retrive_workflows(True)[-1]
101+
self.assertEqual(1, len(
102+
[x for x in obs['nodes'] if x[0] == exp_value]))
103+
self.assertEqual(2, len(
104+
[x for x in obs['edges'] if x[0] == exp_value]))
105+
60106
def test_retrive_workflows(self):
61107
# we should see all 3 workflows
62108
DefaultWorkflow(2).active = False

0 commit comments

Comments
 (0)