diff --git a/qiita_db/processing_job.py b/qiita_db/processing_job.py index e521b701d..a972576be 100644 --- a/qiita_db/processing_job.py +++ b/qiita_db/processing_job.py @@ -1974,7 +1974,7 @@ def from_default_workflow(cls, user, dflt_wf, req_params, name=None, parent_ids.append(source_id) # Get the connections between the job and the source connections = data['connections'].connections - for out, in_param in connections: + for out, in_param, _ in connections: # We take advantage of the fact the parameters are # stored in JSON to encode the name of the output # artifact from the previous job diff --git a/qiita_db/software.py b/qiita_db/software.py index ae0ee6c3b..73824d7c3 100644 --- a/qiita_db/software.py +++ b/qiita_db/software.py @@ -1783,12 +1783,13 @@ def connections(self): the destination command. """ with qdb.sql_connection.TRN: - sql = """SELECT name, parameter_name + sql = """SELECT name, parameter_name, artifact_type FROM qiita.default_workflow_edge_connections c JOIN qiita.command_output o ON c.parent_output_id = o.command_output_id JOIN qiita.command_parameter p ON c.child_input_id = p.command_parameter_id + LEFT JOIN qiita.artifact_type USING (artifact_type_id) WHERE default_workflow_edge_id = %s""" qdb.sql_connection.TRN.add(sql, [self.id]) return qdb.sql_connection.TRN.execute_fetchindex() @@ -1832,8 +1833,8 @@ def iter(cls, active=True): def active(self): """Retrieves active status of the default workflow - Retruns - ---------- + Returns + ------- active : bool active value """ @@ -1866,9 +1867,38 @@ def name(self): qdb.sql_connection.TRN.add(sql, [self.id]) return qdb.sql_connection.TRN.execute_fetchlast() + @property + def description(self): + """Retrieves the description of the default workflow + + Returns + ------- + str + description value + """ + with qdb.sql_connection.TRN: + sql = """SELECT description + FROM qiita.default_workflow + WHERE default_workflow_id = %s""" + qdb.sql_connection.TRN.add(sql, [self.id]) + return qdb.sql_connection.TRN.execute_fetchlast() + + @description.setter + def description(self, description): + """Changes the description of the default workflow + + Parameters + ---------- + description : str + New description value + """ + sql = """UPDATE qiita.default_workflow SET description = %s + WHERE default_workflow_id = %s""" + qdb.sql_connection.perform_as_transaction(sql, [description, self._id]) + @property def data_type(self): - """Retrieves all the data_types the workflow accepts + """Retrieves all the data_types accepted by the default workflow Returns ---------- @@ -1897,7 +1927,8 @@ def graph(self): # Retrieve all graph workflow nodes sql = """SELECT default_workflow_node_id FROM qiita.default_workflow_node - WHERE default_workflow_id = %s""" + WHERE default_workflow_id = %s + ORDER BY default_workflow_node_id""" qdb.sql_connection.TRN.add(sql, [self.id]) db_nodes = qdb.sql_connection.TRN.execute_fetchflatten() @@ -1910,7 +1941,8 @@ def graph(self): JOIN qiita.default_workflow_node n ON e.parent_id = n.default_workflow_node_id OR e.child_id = n.default_workflow_node_id - WHERE default_workflow_id = %s""" + WHERE default_workflow_id = %s + ORDER BY default_workflow_edge_id""" qdb.sql_connection.TRN.add(sql, [self.id]) db_edges = qdb.sql_connection.TRN.execute_fetchindex() diff --git a/qiita_db/support_files/patches/81.sql b/qiita_db/support_files/patches/81.sql index 00aaf8dde..9157be92d 100644 --- a/qiita_db/support_files/patches/81.sql +++ b/qiita_db/support_files/patches/81.sql @@ -10,7 +10,8 @@ ALTER TABLE qiita.prep_template ADD modification_timestamp TIMESTAMP DEFAULT CUR -- a. Removing software_id from qiita.default_workflow and replacing it by a -- table which will like different data_types with the default_workflow + --- adding an active flag in case we need to deprecate default_workflows +-- adding an active flag in case we need to deprecate default_workflows + +-- adding a description column ALTER TABLE qiita.default_workflow DROP software_id; CREATE TABLE qiita.default_workflow_data_type ( default_workflow_id BIGINT NOT NULL, @@ -20,6 +21,7 @@ CREATE TABLE qiita.default_workflow_data_type ( PRIMARY KEY(default_workflow_id, data_type_id) ); ALTER TABLE qiita.default_workflow ADD active BOOL DEFAULT TRUE; +ALTER TABLE qiita.default_workflow ADD description TEXT; -- b. Removing command_id from qiita.default_workflow_node and default_parameter_set as this information -- can be accessed via the default_parameter object (the info is duplicated) @@ -37,3 +39,11 @@ INSERT INTO qiita.default_workflow_data_type (default_workflow_id, data_type_id) (1, 2), (2, 2), (3, 3); + +-- d. adding descriptions +UPDATE qiita.default_workflow + SET description = 'This accepts html Qiita!

BYE!' + WHERE default_workflow_id = 1; +UPDATE qiita.default_workflow + SET description = 'This is another description' + WHERE default_workflow_id = 2; diff --git a/qiita_db/support_files/qiita-db.dbs b/qiita_db/support_files/qiita-db.dbs index 3d25c33b2..d1efe586f 100644 --- a/qiita_db/support_files/qiita-db.dbs +++ b/qiita_db/support_files/qiita-db.dbs @@ -526,6 +526,7 @@ + @@ -1710,7 +1711,7 @@ Controlled Vocabulary]]> - + diff --git a/qiita_db/support_files/qiita-db.html b/qiita_db/support_files/qiita-db.html index 5f0a8e768..6a0241fb4 100644 --- a/qiita_db/support_files/qiita-db.html +++ b/qiita_db/support_files/qiita-db.html @@ -2,7 +2,7 @@ -qiita-db +qiita @@ -299,7 +299,7 @@ - qiita-db + qiita (c) DbSchema Hover columns to read the comments. @@ -629,13 +629,13 @@ default_parameter_set ref software_command ( command_id ) command_id - + Fk fk_default_workflow_id default_workflow_data_type ref default_workflow ( default_workflow_id ) -Fk fk_default_workflow_id +<path transform='translate(8,0)' marker-start='url(#foot1p)' marker-end='url(#arrow1)' d='M 2672,1088L 2672,1104' ><title>Fk fk_default_workflow_id default_workflow_data_type ref default_workflow ( default_workflow_id ) -default_workflow_id +default_workflow_id Fk fk_default_workflow_edge @@ -1481,12 +1481,12 @@ <path d='M 704 1140 L 704 1120 Q 704 1112 712 1112 L 824 1112 Q 832 1112 832 1120 L 832 1140 L704 1140 ' style='fill:url(#tbg_D0DEF5);stroke:1;stroke-opacity:0.1;' /> <rect class='entity' x='704' y='1112' width='128' height='80' rx='8' ry='8' style='fill:none;stroke:#27282C'/> <a xlink:href='#qiita.data_type'><text x='732' y='1131' >data_type</text><title>Table qiita.data_type - Pk pk_data_type ( data_type_id ) data_type_iddata_type_id + <use id='nn' x='706' y='1149' xlink:href='#nn'/><use id='pk' x='706' y='1148' xlink:href='#pk'><title>Pk pk_data_type ( data_type_id ) data_type_iddata_type_id * bigserial Referred by analysis_filepath ( data_type_id ) Referred by artifact ( data_type_id ) -Referred by prep_template ( data_type_id ) -Referred by default_workflow_data_type ( data_type_id ) +Referred by default_workflow_data_type ( data_type_id ) +Referred by prep_template ( data_type_id ) Unq idx_data_type ( data_type ) data_typedata_type * varchar Data type (16S, metabolome, etc) the job will use @@ -1509,19 +1509,21 @@ This is a varchar here - but is of type JSON in postgresql. t - - - -default_workflowTable qiita.default_workflow - Pk pk_default_workflow ( default_workflow_id ) default_workflow_iddefault_workflow_id +<rect class='entity' style='stroke:none;' x='2656' y='1112' width='176' height='112' rx='8' ry='8' /> +<path d='M 2656 1140 L 2656 1120 Q 2656 1112 2664 1112 L 2824 1112 Q 2832 1112 2832 1120 L 2832 1140 L2656 1140 ' style='fill:url(#tbg_B2CDF7);stroke:1;stroke-opacity:0.1;' /> +<rect class='entity' x='2656' y='1112' width='176' height='112' rx='8' ry='8' style='fill:none;stroke:#27282C'/> +<a xlink:href='#qiita.default_workflow'><text x='2685' y='1131' >default_workflow</text><title>Table qiita.default_workflow + Pk pk_default_workflow ( default_workflow_id ) default_workflow_iddefault_workflow_id * bigserial -Referred by default_workflow_node ( default_workflow_id ) -Referred by default_workflow_data_type ( default_workflow_id ) - Unq idx_default_workflow ( name ) namename +<a xlink:href='#qiita.default_workflow.default_workflow_id'><use id='ref' x='2820' y='1148' xlink:href='#ref'/><title>Referred by default_workflow_data_type ( default_workflow_id ) +Referred by default_workflow_node ( default_workflow_id ) + Unq idx_default_workflow ( name ) namename * varchar -t activeactive +<text x='2829' y='1172' text-anchor='end' class='colType'>t</text> <a xlink:href='#qiita.default_workflow_active'><text id='qiita.default_workflow.active' x='2675' y='1191'>active</text><title>active boolean -b +b descriptiondescription +text +t @@ -3696,7 +3698,7 @@

-
Table default_workflow
+
Table default_workflow
@@ -3717,6 +3719,11 @@ + + + + + diff --git a/qiita_db/test/test_software.py b/qiita_db/test/test_software.py index f50479bee..afd1f1524 100644 --- a/qiita_db/test/test_software.py +++ b/qiita_db/test/test_software.py @@ -609,6 +609,14 @@ def test_default_workflows(self): exp = ['18S'] self.assertEqual(obs, exp) + dw = qdb.software.DefaultWorkflow(1) + exp = ('This accepts html Qiita!' + '

BYE!') + self.assertEqual(dw.description, exp) + exp = 'bla!' + dw.description = exp + self.assertEqual(dw.description, exp) + def test_type(self): self.assertEqual(qdb.software.Software(1).type, "artifact transformation") @@ -1106,7 +1114,8 @@ class DefaultWorkflowEdgeTests(TestCase): def test_connections(self): tester = qdb.software.DefaultWorkflowEdge(1) obs = tester.connections - self.assertEqual(obs, [['demultiplexed', 'input_data']]) + self.assertEqual( + obs, [['demultiplexed', 'input_data', 'Demultiplexed']]) class DefaultWorkflowTests(TestCase): diff --git a/qiita_pet/handlers/software.py b/qiita_pet/handlers/software.py index fde3d15be..fdd37c7bf 100644 --- a/qiita_pet/handlers/software.py +++ b/qiita_pet/handlers/software.py @@ -27,6 +27,117 @@ def get(self): self.render("software.html", software=software) +def _retrive_workflows(active): + # helper method to avoid duplication of code + def _default_parameters_parsing(node): + dp = node.default_parameter + cmd = dp.command + cmd_name = 'params_%d' % node.id + rp = deepcopy(cmd.required_parameters) + op = deepcopy(cmd.optional_parameters) + params = dict() + for param, value in dp.values.items(): + if param in rp: + del rp[param] + if param in op: + del op[param] + params[param] = str(value) + + inputs = [] + outputs = [] + for input in rp.values(): + accepted_values = ' | '.join(input[1]) + inputs.append([cmd.id, accepted_values]) + for output in cmd.outputs: + outputs.append([cmd.id, ' | '.join(output)]) + + return ([cmd_name, cmd.id, cmd.name, dp.name, params], + inputs, outputs) + + workflows = [] + for w in DefaultWorkflow.iter(active=active): + # getting the main default parameters + nodes = [] + edges = [] + + # first get edges as this will give us the main connected commands + # and their order + graph = w.graph + # inputs is {input_type: node_name, ...} for easy look up of + # raw_inputs and reuse of the node_name + inputs = dict() + # main_nodes is {main_node_name: { + # output_type: output_node_name}, ...} + # for easy look up and merge of output_names + main_nodes = dict() + for x, y in graph.edges: + connections = [] + for a, _, c in graph[x][y]['connections'].connections: + connections.append("%s | %s" % (a, c)) + + vals_x, input_x, output_x = _default_parameters_parsing(x) + vals_y, input_y, output_y = _default_parameters_parsing(y) + name_x = vals_x[0] + name_y = vals_y[0] + if vals_x not in (nodes): + nodes.append(vals_x) + if name_x not in main_nodes: + main_nodes[name_x] = dict() + for a, b in input_x: + name = 'input_%s_%s' % (name_x, b) + if b in inputs: + name = inputs[b] + else: + name = 'input_%s_%s' % (name_x, b) + vals = [name, a, b] + if vals not in nodes: + inputs[b] = name + nodes.append(vals) + edges.append([name, vals_x[0]]) + for a, b in output_x: + name = 'output_%s_%s' % (name_x, b) + vals = [name, a, b] + if vals not in nodes: + nodes.append(vals) + edges.append([name_x, name]) + main_nodes[name_x][b] = name + + if vals_y not in (nodes): + nodes.append(vals_y) + if name_y not in main_nodes: + main_nodes[name_y] = dict() + for a, b in input_y: + # checking if there is an overlap between the parameter + # and the connections; if there is, use the connection + overlap = set(main_nodes[name_x]) & set(connections) + if overlap: + # use the first hit + b = list(overlap)[0] + + if b in main_nodes[name_x]: + name = main_nodes[name_x][b] + else: + name = 'input_%s_%s' % (name_y, b) + vals = [name, a, b] + if vals not in nodes: + nodes.append(vals) + edges.append([name, name_y]) + for a, b in output_y: + name = 'output_%s_%s' % (name_y, b) + vals = [name, a, b] + if vals not in nodes: + nodes.append(vals) + edges.append([name_y, name]) + main_nodes[name_y][b] = name + + workflows.append( + {'name': w.name, 'id': w.id, 'data_types': w.data_type, + 'description': w.description, + 'nodes': nodes, 'edges': edges}) + + return workflows + + class WorkflowsHandler(BaseHandler): @coroutine @execute_as_transaction @@ -37,50 +148,6 @@ def get(self): if user is not None and user.level in {'admin', 'dev'}: active = False - workflows = [] - previous_outputs = [] - for w in DefaultWorkflow.iter(active=active): - # getting the main default parameters - nodes = [] - edges = [] - for order, n in enumerate(w.graph.nodes): - dp = n.default_parameter - cmd = dp.command - - # looping over the default parameters to make sure we got them - # all from required and optional parameters; whatever is left - # from required, are our inputs - rp = deepcopy(cmd.required_parameters) - op = deepcopy(cmd.optional_parameters) - params = dict() - for param, value in dp.values.items(): - if param in rp: - del rp[param] - if param in op: - del op[param] - params[param] = str(value) - - # cmd_name, command id, command name, - # default params name, default parameters - cmd_name = 'command_%d' % order - nodes.append([cmd_name, cmd.id, cmd.name, - dp.name, params]) - for input in rp.values(): - accepted_values = ' | '.join(input[1]) - if order == 0: - name = 'input_%d' % order - nodes.append([name, cmd.id, accepted_values]) - else: - name = 'output_%d_%s' % (order - 1, accepted_values) - edges.append([name, cmd_name]) - - for output in cmd.outputs: - previous_outputs.append(output[1]) - name = 'output_%d_%s' % (order, output[1]) - nodes.append([name, cmd.id, output[1]]) - edges.append([cmd_name, name]) - - workflows.append( - {'name': w.name, 'id': w.id, 'data_types': w.data_type, - 'nodes': nodes, 'edges': edges}) + workflows = _retrive_workflows(active) + self.render("workflows.html", workflows=workflows) diff --git a/qiita_pet/templates/study_ajax/study_analyses.html b/qiita_pet/templates/study_ajax/study_analyses.html index 815851626..c0c1728ca 100644 --- a/qiita_pet/templates/study_ajax/study_analyses.html +++ b/qiita_pet/templates/study_ajax/study_analyses.html @@ -6,7 +6,7 @@

Analyses that have used this study

-Note that if an analysis doesn't have a link, it means that is a "User default analysis"; which means that the analysis belongs to that user but the user has only selected artifacts and hasn't created the actual analysis. Please contact the user and ask them to delete that artifact from their analysis creation page. +Note that if an analysis doesn't have a link, it means that it is a "User default analysis". This means that the analysis belongs to the indicated user but the user has only selected artifacts and the actual analysis has not been created. Please contact the user and ask them to delete that artifact from their analysis creation page.

IdxField NameData Type
active boolean
 description text
Indexes
pk_default_workflow ON default_workflow_id
diff --git a/qiita_pet/templates/workflows.html b/qiita_pet/templates/workflows.html index 301513dc5..61cc583b2 100644 --- a/qiita_pet/templates/workflows.html +++ b/qiita_pet/templates/workflows.html @@ -2,12 +2,13 @@ {% block head %} {% from qiita_core.qiita_settings import qiita_config %} - +
{% end %} {% else %}
diff --git a/qiita_pet/test/test_software.py b/qiita_pet/test/test_software.py index 4b396b372..a52941b32 100644 --- a/qiita_pet/test/test_software.py +++ b/qiita_pet/test/test_software.py @@ -10,10 +10,13 @@ from qiita_pet.test.tornado_test_base import TestHandlerBase from mock import Mock +from copy import deepcopy from qiita_db.user import User from qiita_db.software import DefaultWorkflow +from qiita_db.sql_connection import perform_as_transaction from qiita_pet.handlers.base_handlers import BaseHandler +from qiita_pet.handlers.software import _retrive_workflows class TestSoftware(TestHandlerBase): @@ -51,6 +54,168 @@ def test_get(self): self.assertNotEqual(body, "") # checking that this software is displayed self.assertIn('FASTA upstream workflow', body) + DefaultWorkflow(2).active = True + + def test_retrive_workflows(self): + # we should see all 3 workflows + DefaultWorkflow(2).active = False + exp = deepcopy(WORKFLOWS) + self.assertCountEqual(_retrive_workflows(False), exp) + + # we should not see the middle one + del exp[1] + self.assertCountEqual(_retrive_workflows(True), exp) + + # let's create a couple of more complex scenarios so we touch all code + # by adding multiple paths, that should connect and get separate + # -- adds a new path that should be kept separate all the way; this is + # to emulate what happens with different trimming (different + # default parameter) and deblur (same for each of the previous + # steps) + sql = """ + INSERT INTO qiita.default_workflow_node ( + default_workflow_id, default_parameter_set_id) + VALUES (1, 2), (1, 10); + INSERT INTO qiita.default_workflow_edge ( + parent_id, child_id) + VALUES (7, 8); + INSERT INTO qiita.default_workflow_edge_connections ( + default_workflow_edge_id, parent_output_id, child_input_id) + VALUES (4, 1, 3)""" + perform_as_transaction(sql) + # -- adds a new path that should be kept together and then separate; + # this is to simulate what happens with MTX/WGS processing, one + # single QC step (together) and 2 separete profilers + sql = """ + INSERT INTO qiita.default_parameter_set ( + command_id, parameter_set_name, parameter_set) + VALUES (3, '100%', + ('{"reference":1,"sortmerna_e_value":1,' + || '"sortmerna_max_pos":' + || '10000,"similarity":1.0,"sortmerna_coverage":1.00,' + || '"threads":1}')::json); + INSERT INTO qiita.default_workflow_node ( + default_workflow_id, default_parameter_set_id) + VALUES (2, 17); + INSERT INTO qiita.default_workflow_edge ( + parent_id, child_id) + VALUES (3, 9); + INSERT INTO qiita.default_workflow_edge_connections ( + default_workflow_edge_id, parent_output_id, child_input_id) + VALUES (5, 1, 3)""" + perform_as_transaction(sql) + + # adding new expected values + exp = deepcopy(WORKFLOWS) + obs = _retrive_workflows(False) + exp[0]['nodes'].extend([ + ['params_7', 1, 'Split libraries FASTQ', 'Defaults with reverse ' + 'complement mapping file barcodes', { + 'max_bad_run_length': '3', + 'min_per_read_length_fraction': '0.75', + 'sequence_max_n': '0', 'rev_comp_barcode': 'False', + 'rev_comp_mapping_barcodes': 'True', 'rev_comp': 'False', + 'phred_quality_threshold': '3', 'barcode_type': 'golay_12', + 'max_barcode_errors': '1.5', 'phred_offset': 'auto'}], + ['output_params_7_demultiplexed | Demultiplexed', 1, + 'demultiplexed | Demultiplexed'], + ['params_8', 3, 'Pick closed-reference OTUs', 'Defaults', { + 'reference': '1', 'sortmerna_e_value': '1', + 'sortmerna_max_pos': '10000', 'similarity': '0.97', + 'sortmerna_coverage': '0.97', 'threads': '1'}], + ['output_params_8_OTU table | BIOM', 3, 'OTU table | BIOM']]) + exp[0]['edges'].extend([ + ['input_params_1_FASTQ | per_sample_FASTQ', 'params_7'], + ['params_7', 'output_params_7_demultiplexed | Demultiplexed'], + ['output_params_7_demultiplexed | Demultiplexed', 'params_8'], + ['params_8', 'output_params_8_OTU table | BIOM']]) + exp[1]['nodes'].extend([ + ['params_9', 3, 'Pick closed-reference OTUs', '100%', { + 'reference': '1', 'sortmerna_e_value': '1', + 'sortmerna_max_pos': '10000', 'similarity': '1.0', + 'sortmerna_coverage': '1.0', 'threads': '1'}], + ['output_params_9_OTU table | BIOM', 3, 'OTU table | BIOM']]) + exp[1]['edges'].extend([ + ['output_params_3_demultiplexed | Demultiplexed', 'params_9'], + ['params_9', 'output_params_9_OTU table | BIOM'] + ]) + self.assertCountEqual(obs, exp) + + +WORKFLOWS = [ + {'name': 'FASTQ upstream workflow', 'id': 1, 'data_types': ['16S', '18S'], + 'description': 'This accepts html Qiita!' + '

BYE!', + 'nodes': [ + ['params_1', 1, 'Split libraries FASTQ', 'Defaults', { + 'max_bad_run_length': '3', 'min_per_read_length_fraction': '0.75', + 'sequence_max_n': '0', 'rev_comp_barcode': 'False', + 'rev_comp_mapping_barcodes': 'False', 'rev_comp': 'False', + 'phred_quality_threshold': '3', 'barcode_type': 'golay_12', + 'max_barcode_errors': '1.5', 'phred_offset': 'auto'}], + ['input_params_1_FASTQ | per_sample_FASTQ', 1, + 'FASTQ | per_sample_FASTQ'], + ['output_params_1_demultiplexed | Demultiplexed', 1, + 'demultiplexed | Demultiplexed'], + ['params_2', 3, 'Pick closed-reference OTUs', 'Defaults', { + 'reference': '1', 'sortmerna_e_value': '1', + 'sortmerna_max_pos': '10000', 'similarity': '0.97', + 'sortmerna_coverage': '0.97', 'threads': '1'}], + ['output_params_2_OTU table | BIOM', 3, 'OTU table | BIOM']], + 'edges': [ + ['input_params_1_FASTQ | per_sample_FASTQ', 'params_1'], + ['params_1', 'output_params_1_demultiplexed | Demultiplexed'], + ['output_params_1_demultiplexed | Demultiplexed', 'params_2'], + ['params_2', 'output_params_2_OTU table | BIOM']]}, + {'name': 'FASTA upstream workflow', 'id': 2, 'data_types': ['18S'], + 'description': 'This is another description', + 'nodes': [ + ['params_3', 2, 'Split libraries', 'Defaults with Golay 12 barcodes', { + 'min_seq_len': '200', 'max_seq_len': '1000', + 'trim_seq_length': 'False', 'min_qual_score': '25', + 'max_ambig': '6', 'max_homopolymer': '6', + 'max_primer_mismatch': '0', 'barcode_type': 'golay_12', + 'max_barcode_errors': '1.5', 'disable_bc_correction': 'False', + 'qual_score_window': '0', 'disable_primers': 'False', + 'reverse_primers': 'disable', 'reverse_primer_mismatches': '0', + 'truncate_ambi_bases': 'False'}], + ['input_params_3_FASTA | FASTA_Sanger | SFF', 2, + 'FASTA | FASTA_Sanger | SFF'], + ['output_params_3_demultiplexed | Demultiplexed', 2, + 'demultiplexed | Demultiplexed'], + ['params_4', 3, 'Pick closed-reference OTUs', 'Defaults', { + 'reference': '1', 'sortmerna_e_value': '1', + 'sortmerna_max_pos': '10000', 'similarity': '0.97', + 'sortmerna_coverage': '0.97', 'threads': '1'}], + ['output_params_4_OTU table | BIOM', 3, 'OTU table | BIOM']], + 'edges': [ + ['input_params_3_FASTA | FASTA_Sanger | SFF', 'params_3'], + ['params_3', 'output_params_3_demultiplexed | Demultiplexed'], + ['output_params_3_demultiplexed | Demultiplexed', 'params_4'], + ['params_4', 'output_params_4_OTU table | BIOM']]}, + {'name': 'Per sample FASTQ upstream workflow', 'id': 3, + 'data_types': ['ITS'], 'description': None, + 'nodes': [ + ['params_5', 1, 'Split libraries FASTQ', 'per sample FASTQ defaults', { + 'max_bad_run_length': '3', 'min_per_read_length_fraction': '0.75', + 'sequence_max_n': '0', 'rev_comp_barcode': 'False', + 'rev_comp_mapping_barcodes': 'False', 'rev_comp': 'False', + 'phred_quality_threshold': '3', 'barcode_type': 'not-barcoded', + 'max_barcode_errors': '1.5', 'phred_offset': 'auto'}], + ['input_params_5_FASTQ | per_sample_FASTQ', 1, + 'FASTQ | per_sample_FASTQ'], + ['output_params_5_demultiplexed | Demultiplexed', 1, + 'demultiplexed | Demultiplexed'], + ['params_6', 3, 'Pick closed-reference OTUs', 'Defaults', { + 'reference': '1', 'sortmerna_e_value': '1', + 'sortmerna_max_pos': '10000', 'similarity': '0.97', + 'sortmerna_coverage': '0.97', 'threads': '1'}], + ['output_params_6_OTU table | BIOM', 3, 'OTU table | BIOM']], + 'edges': [ + ['input_params_5_FASTQ | per_sample_FASTQ', 'params_5'], + ['params_5', 'output_params_5_demultiplexed | Demultiplexed'], + ['output_params_5_demultiplexed | Demultiplexed', 'params_6'], + ['params_6', 'output_params_6_OTU table | BIOM']]}] if __name__ == "__main__":