Skip to content

Commit 3051113

Browse files
josenavasantgonza
authored andcommitted
Analysis refactor allow users change dflt params (#2136)
* fix #1505 * improving some GUI stuff * improving some GUI stuff - missing lines * addressing all comments * ready for review * fix #1987 * initial commit * requested changes * fix filter job list * Fixing server cert (#2051) * fix get_studies * flake8 * fix #503 * fix #2010 * fix #1913 * fix errors * addressing @josenavas comment * flake8 * fix #1010 * fix #1066 (#2058) * addressing @josenavas comments * fix #1961 * fix #1837 * Automatic jobs & new stats (#2057) * fix #814, fix #1636 * fixing error in test-env * fixing stats.html call * adding img * addressing @josenavas comments * rm for loops * addresssing @ElDeveloper comments * generalizing this functionality * fix #1816 * fix #1959 * addressing @josenavas comments * addressing @josenavas comments * fixing error * fixed? * addressing @josenavas comments * addressing @wasade comments * fix flake8 * generate biom and metadata release (#2066) * initial commit * adding portal * addressing @josenavas comments * pid -> qiita_artifact_id * addressing @josenavas comments * addressing @ElDeveloper comments * rm 50.sql * database changes to fix 969 * adding delete * addressing @josenavas comments * addressing @ElDeveloper comments * duh! * fix generate_biom_and_metadata_release (#2072) * fix generate_biom_and_metadata_release * addressing @ElDeveloper comment * Removing qiita ware code that will not be used anymore * Organizing the handlers and new analysis description page * fixing timestamp * rm formats * st -> pt * Connecting the analysis creation and making interface responsive * Addressing @antgonza's comments * Initial artifact GUI refactor * Removing unused code * moving to ISO 8601 - wow :'( * fix errors * addressing @wasade comments * Adding can_edit call to the analysis * Fixing artifact rest API since not all artifacts have study * Adding can_be_publicized call to analysis * Adding QiitaHTTPError to handle errors gracefully * Adding safe_execution contextmanager * Fixing typo * Adding qiita test checker * Adapting some artifact handlers * Abstracting the graph reloading and adding some documentation * Fixing typo * Fixing changing artifact visibility * Fixing delete * Fixing artifact deletion * Adding default parameters to the commands * Fixing processing page * Fixing variable name * fixing private/public studies * Changing bdiv metrics to single choice * sanbox-to-sandbox * flake8 * Fixing patch * fixing other issues * adding share documentation * psycopg2 <= 2.7 * psycopg2 < 2.7 * Various small fixes to be able to run tests on the plugins * Adding private module * Fixing processing job completion * Fixing patch 52 * Fixing call * Fixing complete * small fixes * Adding processing handlers * Fixing url and bug on processing job workflow * Adding the private script runner * Adding is_analysis column to the command * Adding retrieval of commands excluding analysis commands * Addressing bug on retrieving information from redis * Enabling the command register endpoint to provide if the command is analysis only * Addressing @antgonza's comments * Addressing @wasade's comments * Supporting multiple choice * Adding documentation * Modifying handler to pass allow_change_optionals * returning optional parameters * Addressing bug found by @antgonza * Enabling changing the default parameters * Adding correct class * Allowing user to change default parameters * Fixing bug with commands listing * Addressing @wasade's comments
1 parent 39a03e3 commit 3051113

File tree

10 files changed

+268
-89
lines changed

10 files changed

+268
-89
lines changed

qiita_db/handlers/plugin.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ def post(self, name, version):
100100
cmd_desc = self.get_argument('description')
101101
req_params = loads(self.get_argument('required_parameters'))
102102
opt_params = loads(self.get_argument('optional_parameters'))
103+
104+
for p_name, (p_type, dflt) in opt_params.items():
105+
if p_type.startswith('mchoice'):
106+
opt_params[p_name] = [p_type, loads(dflt)]
107+
103108
outputs = self.get_argument('outputs', None)
104109
if outputs:
105110
outputs = loads(outputs)

qiita_db/handlers/tests/test_plugin.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,12 @@ def test_post(self):
7474
'description': 'Command added for testing',
7575
'required_parameters': dumps(
7676
{'in_data': ['artifact:["FASTA"]', None]}),
77-
'optional_parameters': dumps({'param1': ['string', ''],
78-
'param2': ['float', '1.5'],
79-
'param3': ['boolean', 'True']}),
77+
'optional_parameters': dumps(
78+
{'param1': ['string', ''],
79+
'param2': ['float', '1.5'],
80+
'param3': ['boolean', 'True'],
81+
'param4': ['mchoice:["opt1", "opt2", "opt3"]',
82+
dumps(['opt1', 'opt2'])]}),
8083
'outputs': dumps({'out1': 'BIOM'}),
8184
'default_parameter_sets': dumps(
8285
{'dflt1': {'param1': 'test',

qiita_db/software.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ def get_commands_by_input_type(cls, artifact_types, active_only=True,
5555
active_only : bool, optional
5656
If True, return only active commands, otherwise return all commands
5757
Default: True
58+
exclude_analysis : bool, optional
59+
If True, return commands that are not part of the analysis pipeline
5860
5961
Returns
6062
-------
@@ -272,16 +274,25 @@ def create(cls, software, name, description, parameters, outputs=None,
272274
supported_types = ['string', 'integer', 'float', 'reference',
273275
'boolean', 'prep_template']
274276
if ptype not in supported_types and not ptype.startswith(
275-
('choice', 'artifact')):
276-
supported_types.extend(['choice', 'artifact'])
277+
('choice', 'mchoice', 'artifact')):
278+
supported_types.extend(['choice', 'mchoice', 'artifact'])
277279
raise qdb.exceptions.QiitaDBError(
278280
"Unsupported parameters type '%s' for parameter %s. "
279281
"Supported types are: %s"
280282
% (ptype, pname, ', '.join(supported_types)))
281283

282-
if ptype.startswith('choice') and dflt is not None:
283-
choices = loads(ptype.split(':')[1])
284-
if dflt not in choices:
284+
if ptype.startswith(('choice', 'mchoice')) and dflt is not None:
285+
choices = set(loads(ptype.split(':')[1]))
286+
dflt_val = dflt
287+
if ptype.startswith('choice'):
288+
# In the choice case, the dflt value is a single string,
289+
# create a list with it the string on it to use the
290+
# issuperset call below
291+
dflt_val = [dflt_val]
292+
else:
293+
# jsonize the list to store it in the DB
294+
dflt = dumps(dflt)
295+
if not choices.issuperset(dflt_val):
285296
raise qdb.exceptions.QiitaDBError(
286297
"The default value '%s' for the parameter %s is not "
287298
"listed in the available choices: %s"
@@ -452,7 +463,17 @@ def optional_parameters(self):
452463
WHERE command_id = %s AND required = false"""
453464
qdb.sql_connection.TRN.add(sql, [self.id])
454465
res = qdb.sql_connection.TRN.execute_fetchindex()
455-
return {pname: [ptype, dflt] for pname, ptype, dflt in res}
466+
467+
# Define a function to load the json storing the default parameters
468+
# if ptype is multiple choice. When I added it to the for loop as
469+
# a one liner if, made the code a bit hard to read
470+
def dflt_fmt(dflt, ptype):
471+
if ptype.startswith('mchoice'):
472+
return loads(dflt)
473+
return dflt
474+
475+
return {pname: [ptype, dflt_fmt(dflt, ptype)]
476+
for pname, ptype, dflt in res}
456477

457478
@property
458479
def default_parameter_sets(self):

qiita_db/test/test_software.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ def setUp(self):
2828
'req_param': ['string', None],
2929
'opt_int_param': ['integer', '4'],
3030
'opt_choice_param': ['choice:["opt1", "opt2"]', 'opt1'],
31+
'opt_mchoice_param': ['mchoice:["opt1", "opt2", "opt3"]',
32+
['opt1', 'opt2']],
3133
'opt_bool': ['boolean', 'False']}
3234
self.outputs = {'out1': 'BIOM'}
3335

@@ -291,6 +293,8 @@ def test_create(self):
291293
exp_optional = {
292294
'opt_int_param': ['integer', '4'],
293295
'opt_choice_param': ['choice:["opt1", "opt2"]', 'opt1'],
296+
'opt_mchoice_param': ['mchoice:["opt1", "opt2", "opt3"]',
297+
['opt1', 'opt2']],
294298
'opt_bool': ['boolean', 'False']}
295299
self.assertEqual(obs.optional_parameters, exp_optional)
296300
self.assertFalse(obs.analysis_only)
@@ -300,13 +304,7 @@ def test_create(self):
300304
self.parameters, analysis_only=True)
301305
self.assertEqual(obs.name, "Test Command 2")
302306
self.assertEqual(obs.description, "This is a command for testing")
303-
exp_required = {'req_param': ('string', [None]),
304-
'req_art': ('artifact', ['BIOM'])}
305307
self.assertEqual(obs.required_parameters, exp_required)
306-
exp_optional = {
307-
'opt_int_param': ['integer', '4'],
308-
'opt_choice_param': ['choice:["opt1", "opt2"]', 'opt1'],
309-
'opt_bool': ['boolean', 'False']}
310308
self.assertEqual(obs.optional_parameters, exp_optional)
311309
self.assertTrue(obs.analysis_only)
312310

qiita_pet/handlers/api_proxy/processing.py

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,15 @@ def process_artifact_handler_get_req(artifact_id):
4141
'study_id': artifact.study.id}
4242

4343

44-
def list_commands_handler_get_req(artifact_types):
44+
def list_commands_handler_get_req(artifact_types, exclude_analysis):
4545
"""Retrieves the commands that can process the given artifact types
4646
4747
Parameters
4848
----------
4949
artifact_types : str
5050
Comma-separated list of artifact types
51+
exclude_analysis : bool
52+
If True, return commands that are not part of the analysis pipeline
5153
5254
Returns
5355
-------
@@ -62,7 +64,8 @@ def list_commands_handler_get_req(artifact_types):
6264
artifact_types = artifact_types.split(',')
6365
cmd_info = [
6466
{'id': cmd.id, 'command': cmd.name, 'output': cmd.outputs}
65-
for cmd in Command.get_commands_by_input_type(artifact_types)]
67+
for cmd in Command.get_commands_by_input_type(
68+
artifact_types, exclude_analysis=exclude_analysis)]
6669

6770
return {'status': 'success',
6871
'message': '',
@@ -92,21 +95,22 @@ def list_options_handler_get_req(command_id):
9295
return {'status': 'success',
9396
'message': '',
9497
'options': options,
95-
'req_options': command.required_parameters}
98+
'req_options': command.required_parameters,
99+
'opt_options': command.optional_parameters}
96100

97101

98-
def workflow_handler_post_req(user_id, dflt_params_id, req_params):
102+
def workflow_handler_post_req(user_id, command_id, params):
99103
"""Creates a new workflow in the system
100104
101105
Parameters
102106
----------
103107
user_id : str
104108
The user creating the workflow
105-
dflt_params_id : int
106-
The default parameters to use for the first command of the workflow
107-
req_params : str
108-
JSON representations of the required parameters for the first
109-
command of the workflow
109+
command_id : int
110+
The first command to execute in the workflow
111+
params : str
112+
JSON representations of the parameters for the first command of
113+
the workflow
110114
111115
Returns
112116
-------
@@ -116,9 +120,7 @@ def workflow_handler_post_req(user_id, dflt_params_id, req_params):
116120
'message': str,
117121
'workflow_id': int}
118122
"""
119-
dflt_params = DefaultParameters(dflt_params_id)
120-
req_params = loads(req_params)
121-
parameters = Parameters.from_default_params(dflt_params, req_params)
123+
parameters = Parameters.load(Command(command_id), json_str=params)
122124
wf = ProcessingWorkflow.from_scratch(User(user_id), parameters)
123125
# this is safe as we are creating the workflow for the first time and there
124126
# is only one node. Remember networkx doesn't assure order of nodes
@@ -136,14 +138,14 @@ def workflow_handler_post_req(user_id, dflt_params_id, req_params):
136138

137139
def workflow_handler_patch_req(req_op, req_path, req_value=None,
138140
req_from=None):
139-
"""Patches an ontology
141+
"""Patches a workflow
140142
141143
Parameters
142144
----------
143145
req_op : str
144-
The operation to perform on the ontology
146+
The operation to perform on the workflow
145147
req_path : str
146-
The ontology to patch
148+
Path parameter with the workflow to patch
147149
req_value : str, optional
148150
The value that needs to be modified
149151
req_from : str, optional

qiita_pet/handlers/api_proxy/tests/test_processing.py

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from json import dumps
1010

1111
from qiita_core.util import qiita_test_checker
12-
from qiita_db.util import get_count
1312
from qiita_db.processing_job import ProcessingWorkflow
1413
from qiita_db.software import Command, Parameters
1514
from qiita_db.user import User
@@ -38,20 +37,36 @@ def test_process_artifact_handler_get_req(self):
3837
self.assertEqual(obs, exp)
3938

4039
def test_list_commands_handler_get_req(self):
41-
obs = list_commands_handler_get_req('FASTQ')
40+
obs = list_commands_handler_get_req('FASTQ', True)
4241
exp = {'status': 'success',
4342
'message': '',
4443
'commands': [{'id': 1, 'command': 'Split libraries FASTQ',
4544
'output': [['demultiplexed', 'Demultiplexed']]}]}
4645
self.assertEqual(obs, exp)
4746

48-
obs = list_commands_handler_get_req('Demultiplexed')
47+
obs = list_commands_handler_get_req('Demultiplexed', True)
4948
exp = {'status': 'success',
5049
'message': '',
5150
'commands': [{'id': 3, 'command': 'Pick closed-reference OTUs',
5251
'output': [['OTU table', 'BIOM']]}]}
5352
self.assertEqual(obs, exp)
5453

54+
obs = list_commands_handler_get_req('BIOM', False)
55+
exp = {'status': 'success',
56+
'message': '',
57+
'commands': [
58+
{'command': 'Summarize Taxa', 'id': 11,
59+
'output': [['taxa_summary', 'taxa_summary']]},
60+
{'command': 'Beta Diversity', 'id': 12,
61+
'output': [['distance_matrix', 'distance_matrix']]},
62+
{'command': 'Alpha Rarefaction', 'id': 13,
63+
'output': [['rarefaction_curves', 'rarefaction_curves']]},
64+
{'command': 'Single Rarefaction', 'id': 14,
65+
'output': [['rarefied_table', 'BIOM']]}]}
66+
# since the order of the commands can change, test them separately
67+
self.assertItemsEqual(obs.pop('commands'), exp.pop('commands'))
68+
self.assertEqual(obs, exp)
69+
5570
def test_list_options_handler_get_req(self):
5671
obs = list_options_handler_get_req(3)
5772
exp = {'status': 'success',
@@ -64,11 +79,20 @@ def test_list_options_handler_get_req(self):
6479
'sortmerna_e_value': 1,
6580
'sortmerna_max_pos': 10000,
6681
'threads': 1}}],
67-
'req_options': {'input_data': ('artifact', ['Demultiplexed'])}}
82+
'req_options': {'input_data': ('artifact', ['Demultiplexed'])},
83+
'opt_options': {'reference': ['reference', '1'],
84+
'similarity': ['float', '0.97'],
85+
'sortmerna_coverage': ['float', '0.97'],
86+
'sortmerna_e_value': ['float', '1'],
87+
'sortmerna_max_pos': ['integer', '10000'],
88+
'threads': ['integer', '1']}}
89+
# First check that the keys are the same
90+
self.assertItemsEqual(obs, exp)
6891
self.assertEqual(obs['status'], exp['status'])
6992
self.assertEqual(obs['message'], exp['message'])
7093
self.assertEqual(obs['options'], exp['options'])
7194
self.assertEqual(obs['req_options'], exp['req_options'])
95+
self.assertEqual(obs['opt_options'], exp['opt_options'])
7296

7397
def test_job_ajax_get_req(self):
7498
obs = job_ajax_get_req("063e553b-327c-4818-ab4a-adfe58e49860")
@@ -94,15 +118,21 @@ def test_job_ajax_get_req(self):
94118
@qiita_test_checker()
95119
class TestProcessingAPI(TestCase):
96120
def test_workflow_handler_post_req(self):
97-
next_id = get_count('qiita.processing_job_workflow_root') + 1
98-
obs = workflow_handler_post_req("test@foo.bar", 1, '{"input_data": 1}')
99-
wf = ProcessingWorkflow(next_id)
121+
params = ('{"max_barcode_errors": 1.5, "barcode_type": "golay_12", '
122+
'"max_bad_run_length": 3, "phred_offset": "auto", '
123+
'"rev_comp": false, "phred_quality_threshold": 3, '
124+
'"input_data": 1, "rev_comp_barcode": false, '
125+
'"rev_comp_mapping_barcodes": false, '
126+
'"min_per_read_length_fraction": 0.75, "sequence_max_n": 0}')
127+
obs = workflow_handler_post_req("test@foo.bar", 1, params)
128+
wf_id = obs['workflow_id']
129+
wf = ProcessingWorkflow(wf_id)
100130
nodes = wf.graph.nodes()
101131
self.assertEqual(len(nodes), 1)
102132
job = nodes[0]
103133
exp = {'status': 'success',
104134
'message': '',
105-
'workflow_id': next_id,
135+
'workflow_id': wf_id,
106136
'job': {'id': job.id,
107137
'inputs': [1],
108138
'label': "Split libraries FASTQ",

qiita_pet/handlers/artifact_handlers/process_handlers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ def process_artifact_handler_get_req(artifact_id):
3636
'message': '',
3737
'name': artifact.name,
3838
'type': artifact.artifact_type,
39-
'artifact_id': artifact.id}
39+
'artifact_id': artifact.id,
40+
'allow_change_optionals': artifact.analysis is not None}
4041

4142

4243
class ProcessArtifactHandler(BaseHandler):

qiita_pet/handlers/artifact_handlers/tests/test_process_handlers.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from unittest import TestCase, main
1010

1111
from qiita_core.util import qiita_test_checker
12+
from qiita_pet.test.tornado_test_base import TestHandlerBase
1213
from qiita_pet.handlers.artifact_handlers.process_handlers import (
1314
process_artifact_handler_get_req)
1415

@@ -17,9 +18,36 @@
1718
class TestProcessHandlersUtils(TestCase):
1819
def test_process_artifact_handler_get_req(self):
1920
obs = process_artifact_handler_get_req(1)
20-
exp = {}
21+
exp = {'status': 'success',
22+
'message': '',
23+
'name': 'Raw data 1',
24+
'type': 'FASTQ',
25+
'artifact_id': 1,
26+
'allow_change_optionals': False}
2127
self.assertEqual(obs, exp)
2228

29+
obs = process_artifact_handler_get_req(8)
30+
exp = {'status': 'success',
31+
'message': '',
32+
'name': 'noname',
33+
'type': 'BIOM',
34+
'artifact_id': 8,
35+
'allow_change_optionals': True}
36+
self.assertEqual(obs, exp)
37+
38+
39+
class TestProcessHandlers(TestHandlerBase):
40+
def test_get_process_artifact_handler(self):
41+
response = self.get("/artifact/1/process/")
42+
self.assertEqual(response.code, 200)
43+
self.assertNotEqual(response.body, "")
44+
self.assertIn('load_artifact_type(params.nodes, false);',
45+
response.body)
46+
47+
response = self.get("/artifact/8/process/")
48+
self.assertEqual(response.code, 200)
49+
self.assertNotEqual(response.body, "")
50+
self.assertIn('load_artifact_type(params.nodes, true);', response.body)
2351

2452
if __name__ == '__main__':
2553
main()

qiita_pet/handlers/study_handlers/processing.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ def get(self):
2626
# Fun fact - if the argument is a list, JS adds '[]' to the
2727
# argument name
2828
artifact_types = self.get_argument("artifact_types[]")
29-
self.write(list_commands_handler_get_req(artifact_types))
29+
exclude_analysis = self.get_argument('include_analysis') == 'false'
30+
self.write(
31+
list_commands_handler_get_req(artifact_types, exclude_analysis))
3032

3133

3234
class ListOptionsHandler(BaseHandler):
@@ -46,10 +48,10 @@ def post(self):
4648
class WorkflowHandler(BaseHandler):
4749
@authenticated
4850
def post(self):
49-
dflt_params_id = self.get_argument('dflt_params_id')
50-
req_params = self.get_argument('req_params')
51+
command_id = self.get_argument('command_id')
52+
params = self.get_argument('params')
5153
self.write(workflow_handler_post_req(
52-
self.current_user.id, dflt_params_id, req_params))
54+
self.current_user.id, command_id, params))
5355

5456
@authenticated
5557
def patch(self):

0 commit comments

Comments
 (0)