diff --git a/relation_engine_server/api_versions/api_v1.py b/relation_engine_server/api_versions/api_v1.py index 515dd7a5..426eac1b 100644 --- a/relation_engine_server/api_versions/api_v1.py +++ b/relation_engine_server/api_versions/api_v1.py @@ -71,41 +71,50 @@ def run_query(): - public stored queries (these have access controls within them based on params) """ json_body = parse_json.get_json_body() or {} - # Don't allow the user to set the special 'ws_ids' field - json_body['ws_ids'] = [] - auth_token = auth.get_auth_header() - # Fetch any authorized workspace IDs using a KBase auth token, if present - ws_ids = auth.get_workspace_ids(auth_token) # fetch number of documents to return batch_size = int(flask.request.args.get('batch_size', 10000)) full_count = flask.request.args.get('full_count', False) + if 'query' in json_body: # Run an adhoc query for a sysadmin auth.require_auth_token(roles=['RE_ADMIN']) query_text = _preprocess_stored_query(json_body['query'], json_body) del json_body['query'] - json_body['ws_ids'] = ws_ids + if 'ws_ids' in query_text: + # Fetch any authorized workspace IDs using a KBase auth token, if present + auth_token = auth.get_auth_header() + json_body['ws_ids'] = auth.get_workspace_ids(auth_token) + resp_body = arango_client.run_query(query_text=query_text, bind_vars=json_body, batch_size=batch_size, full_count=full_count) return flask.jsonify(resp_body) + if 'stored_query' in flask.request.args or 'view' in flask.request.args: # Run a query from a query name # Note: we are maintaining backwards compatibility here with the "view" arg. # "stored_query" is the more accurate name query_name = flask.request.args.get('stored_query') or flask.request.args.get('view') stored_query = spec_loader.get_stored_query(query_name) - stored_query_source = _preprocess_stored_query(stored_query['query'], stored_query) + if 'params' in stored_query: # Validate the user params for the query - run_validator(schema=stored_query['params'], data=json_body) - json_body['ws_ids'] = ws_ids + stored_query_path = spec_loader.get_stored_query(query_name, path_only=True) + run_validator(schema_file=stored_query_path, data=json_body, validate_at='/params') + + stored_query_source = _preprocess_stored_query(stored_query['query'], stored_query) + if 'ws_ids' in stored_query_source: + # Fetch any authorized workspace IDs using a KBase auth token, if present + auth_token = auth.get_auth_header() + json_body['ws_ids'] = auth.get_workspace_ids(auth_token) + resp_body = arango_client.run_query(query_text=stored_query_source, bind_vars=json_body, batch_size=batch_size, full_count=full_count) return flask.jsonify(resp_body) + if 'cursor_id' in flask.request.args: # Run a query from a cursor ID cursor_id = flask.request.args['cursor_id'] @@ -167,9 +176,9 @@ def show_config(): def _preprocess_stored_query(query_text, config): """Inject some default code into each stored query.""" + ws_id_text = " LET ws_ids = @ws_ids " if 'ws_ids' in query_text else "" return ( config.get('query_prefix', '') + - " LET ws_ids = @ws_ids " + - # " LET maxint = 9007199254740991 " + + ws_id_text + query_text ) diff --git a/spec/datasets/distance.yaml b/spec/datasets/distance.yaml new file mode 100644 index 00000000..98b927b3 --- /dev/null +++ b/spec/datasets/distance.yaml @@ -0,0 +1,7 @@ +name: distance +type: integer +title: Traversal Distance +description: How many hops to find neighbors and neighbors-of-neighbors +default: 1 +minimum: 0 +maximum: 100 diff --git a/spec/datasets/djornl/definitions.yaml b/spec/datasets/djornl/definitions.yaml index 81ee5e29..ceaa5546 100644 --- a/spec/datasets/djornl/definitions.yaml +++ b/spec/datasets/djornl/definitions.yaml @@ -35,7 +35,7 @@ definitions: _key: type: string title: Key - examples: ["AT1G01010"] + examples: ["AT1G01010", "As2"] clusters: type: array title: Clusters diff --git a/spec/stored_queries/djornl/djornl_fetch_all.yaml b/spec/stored_queries/djornl/djornl_fetch_all.yaml index 0d918c2f..c663efeb 100644 --- a/spec/stored_queries/djornl/djornl_fetch_all.yaml +++ b/spec/stored_queries/djornl/djornl_fetch_all.yaml @@ -2,7 +2,16 @@ name: djornl_fetch_all description: Fetch all node and edge data from the djornl subgraph params: type: object -# additionalProperties: false + additionalProperties: false + properties: + edge_types: + type: array + title: Edge types + description: Permitted edge types + items: + $ref: "../../datasets/djornl/edge_type.yaml" + default: [] + examples: [['AraNetv2-HT_high-throughput-ppi', 'AraNetv2-LC_lit-curated-ppi'],['AraGWAS-Phenotype_Associations']] query: | LET nodes = ( FOR v IN djornl_node @@ -10,6 +19,7 @@ query: | ) LET edges = ( FOR e IN djornl_edge + FILTER length(@edge_types) > 0 && e.edge_type IN @edge_types || length(@edge_types) == 0 RETURN e ) RETURN {nodes, edges} diff --git a/spec/stored_queries/djornl/djornl_fetch_clusters.yaml b/spec/stored_queries/djornl/djornl_fetch_clusters.yaml index 498dc62b..f8b099c6 100644 --- a/spec/stored_queries/djornl/djornl_fetch_clusters.yaml +++ b/spec/stored_queries/djornl/djornl_fetch_clusters.yaml @@ -2,6 +2,7 @@ name: djornl_fetch_clusters description: Fetch all nodes that are members of the specified cluster(s), and the edges and nodes within the specified distance (number of hops) of those nodes. params: type: object + additionalProperties: false required: [cluster_ids] properties: cluster_ids: @@ -9,18 +10,11 @@ params: title: Cluster IDs description: Cluster IDs, in the form "clustering_system_name:cluster_id" items: - type: string - format: regex - pattern: ^\w+:\d+$ - examples: [['markov_i2:5', 'markov_i6:2'],['markov_i6:1']] + $ref: "../../datasets/djornl/definitions.yaml#definitions/cluster_id" minItems: 1 + examples: [['markov_i2:5', 'markov_i6:2'],['markov_i6:1']] distance: - type: integer - title: Traversal Distance - description: How many hops to find neighbors and neighbors-of-neighbors - default: 1 - minimum: 0 - maximum: 100 + $ref: "../../datasets/distance.yaml" query: | LET node_ids = ( FOR n IN djornl_node diff --git a/spec/stored_queries/djornl/djornl_fetch_genes.yaml b/spec/stored_queries/djornl/djornl_fetch_genes.yaml index 6b8a8639..c0e5867b 100644 --- a/spec/stored_queries/djornl/djornl_fetch_genes.yaml +++ b/spec/stored_queries/djornl/djornl_fetch_genes.yaml @@ -2,22 +2,18 @@ name: djornl_fetch_genes description: Fetch a gene or list of genes by key, and the edges and nodes within the specified distance (number of hops) of those genes. params: type: object + additionalProperties: false required: [keys] properties: - distance: - type: integer - title: Traversal Distance - description: How many hops to find neighbors and neighbors-of-neighbors - default: 1 - minimum: 0 - maximum: 100 keys: type: array - items: - type: string title: Gene Keys + items: + $ref: "../../datasets/djornl/definitions.yaml#definitions/djornl_node/_key" minItems: 1 examples: [["AT1G01010"],["AT1G01020","AT1G01070"]] + distance: + $ref: "../../datasets/distance.yaml" query: | LET node_ids = ( FOR n IN djornl_node diff --git a/spec/stored_queries/djornl/djornl_fetch_phenotypes.yaml b/spec/stored_queries/djornl/djornl_fetch_phenotypes.yaml index 41482924..b76337a2 100644 --- a/spec/stored_queries/djornl/djornl_fetch_phenotypes.yaml +++ b/spec/stored_queries/djornl/djornl_fetch_phenotypes.yaml @@ -2,22 +2,18 @@ name: djornl_fetch_phenotypes description: Fetch a phenotype or list of phenotypes by key, and the edges and nodes within the specified distance (number of hops) of those phenotype nodes. params: type: object + additionalProperties: false required: [keys] properties: - distance: - type: integer - title: Traversal Distance - description: How many hops to find neighbors and neighbors-of-neighbors - default: 1 - minimum: 0 - maximum: 100 keys: type: array - items: - type: string title: Phenotype Keys + items: + $ref: "../../datasets/djornl/definitions.yaml#definitions/djornl_node/_key" minItems: 1 examples: [["As2"],["As2", "Na23"]] + distance: + $ref: "../../datasets/distance.yaml" query: | LET node_ids = ( FOR n IN djornl_node diff --git a/spec/stored_queries/djornl/djornl_search_nodes.yaml b/spec/stored_queries/djornl/djornl_search_nodes.yaml index 9c8d6a1d..db70cf2d 100644 --- a/spec/stored_queries/djornl/djornl_search_nodes.yaml +++ b/spec/stored_queries/djornl/djornl_search_nodes.yaml @@ -2,19 +2,15 @@ name: djornl_search_nodes description: Search for nodes using a simple fuzzy search on node metadata; return the matching nodes, and the edges and nodes within the specified distance (number of hops) of those nodes. params: type: object + additionalProperties: false required: [search_text] properties: - distance: - type: integer - title: Traversal Distance - description: How many hops to find neighbors and neighbors-of-neighbors - default: 1 - minimum: 0 - maximum: 100 search_text: type: string title: Search text examples: ['GO:0005515', 'organelle machinery'] + distance: + $ref: "../../datasets/distance.yaml" query: | LET node_ids = ( FOR g IN djornl_node_view diff --git a/spec/test/djornl/results.json b/spec/test/djornl/results.json index 80c42a9c..4a3cbed2 100644 --- a/spec/test/djornl/results.json +++ b/spec/test/djornl/results.json @@ -104,8 +104,109 @@ { "params": {"musical": "Mary Poppins"}, "error": { - "message": "ArangoDB server error.", - "arango_message": "AQL: bind parameter 'musical' was not declared in the query (while parsing)" + "failed_validator": "additionalProperties", + "message": "Additional properties are not allowed ('musical' was unexpected)", + "path": [], + "value": {"musical": "Mary Poppins"} + } + }, + { + "params": {"edge_types": ["straight", "curved"]}, + "error": { + "failed_validator": "oneOf", + "message": "'straight' is not valid under any of the given schemas", + "path": ["edge_types", 0], + "value": "straight" + } + }, + { + "params": {"edge_types": []}, + "results": { + "nodes": [ + "As2", + "As75", + "AT1G01010", + "AT1G01020", + "AT1G01030", + "AT1G01040", + "AT1G01050", + "AT1G01060", + "AT1G01070", + "AT1G01080", + "AT1G01090", + "AT1G01100", + "Na23", + "SDV" + ], + "edges": [ + "As2__AT1G01020__AraGWAS-Phenotype_Associations__8.4", + "As2__AT1G01040__AraGWAS-Phenotype_Associations__5.4", + "As75__AT1G01020__AraGWAS-Phenotype_Associations__39.9", + "AT1G01010__AT1G01020__AraNetv2-HT_high-throughput-ppi__2.3", + "AT1G01010__AT1G01030__AraNetv2-HT_high-throughput-ppi__2.4", + "AT1G01010__AT1G01040__AraNetv2-DC_domain-co-occurrence__2.5", + "AT1G01010__AT1G01040__AraNetv2-LC_lit-curated-ppi__170.5", + "AT1G01030__AT1G01050__AraNetv2-CX_pairwise-gene-coexpression__2.6", + "AT1G01050__AT1G01060__AraNetv2-LC_lit-curated-ppi__2.7", + "AT1G01080__AT1G01090__AraNetv2-LC_lit-curated-ppi__2.8" + ] + } + }, + { + "params": {"edge_types": ["AraGWAS-Phenotype_Associations"]}, + "results": { + "nodes": [ + "As2", + "As75", + "AT1G01010", + "AT1G01020", + "AT1G01030", + "AT1G01040", + "AT1G01050", + "AT1G01060", + "AT1G01070", + "AT1G01080", + "AT1G01090", + "AT1G01100", + "Na23", + "SDV" + ], + "edges": [ + "As2__AT1G01020__AraGWAS-Phenotype_Associations__8.4", + "As2__AT1G01040__AraGWAS-Phenotype_Associations__5.4", + "As75__AT1G01020__AraGWAS-Phenotype_Associations__39.9" + ] + } + }, + { + "params": {"edge_types": ["AraGWAS-Phenotype_Associations", "AraNetv2-HT_high-throughput-ppi", "AraNetv2-LC_lit-curated-ppi"]}, + "results": { + "nodes": [ + "As2", + "As75", + "AT1G01010", + "AT1G01020", + "AT1G01030", + "AT1G01040", + "AT1G01050", + "AT1G01060", + "AT1G01070", + "AT1G01080", + "AT1G01090", + "AT1G01100", + "Na23", + "SDV" + ], + "edges": [ + "As2__AT1G01020__AraGWAS-Phenotype_Associations__8.4", + "As2__AT1G01040__AraGWAS-Phenotype_Associations__5.4", + "As75__AT1G01020__AraGWAS-Phenotype_Associations__39.9", + "AT1G01010__AT1G01020__AraNetv2-HT_high-throughput-ppi__2.3", + "AT1G01010__AT1G01030__AraNetv2-HT_high-throughput-ppi__2.4", + "AT1G01010__AT1G01040__AraNetv2-LC_lit-curated-ppi__170.5", + "AT1G01050__AT1G01060__AraNetv2-LC_lit-curated-ppi__2.7", + "AT1G01080__AT1G01090__AraNetv2-LC_lit-curated-ppi__2.8" + ] } } ], @@ -116,7 +217,7 @@ "failed_validator": "required", "message": "'keys' is a required property", "path": [], - "value": {"ws_ids": []} + "value": {} } }, { @@ -181,6 +282,23 @@ ] } }, + { + "params": { "keys": ["AT1G01010"], "distance": 5 }, + "results": { + "nodes": ["As2", "As75", "AT1G01010", "AT1G01020", "AT1G01030", "AT1G01040", "AT1G01050", "AT1G01060"], + "edges": [ + "As2__AT1G01020__AraGWAS-Phenotype_Associations__8.4", + "As2__AT1G01040__AraGWAS-Phenotype_Associations__5.4", + "As75__AT1G01020__AraGWAS-Phenotype_Associations__39.9", + "AT1G01010__AT1G01020__AraNetv2-HT_high-throughput-ppi__2.3", + "AT1G01010__AT1G01030__AraNetv2-HT_high-throughput-ppi__2.4", + "AT1G01010__AT1G01040__AraNetv2-DC_domain-co-occurrence__2.5", + "AT1G01010__AT1G01040__AraNetv2-LC_lit-curated-ppi__170.5", + "AT1G01030__AT1G01050__AraNetv2-CX_pairwise-gene-coexpression__2.6", + "AT1G01050__AT1G01060__AraNetv2-LC_lit-curated-ppi__2.7" + ] + } + }, { "params": {"keys": ["AT1G01020", "AT1G01070"], "distance": 0 }, "results": {