From ec2d1d3ae3cc989bfcd227e7f43b4b4abde4a128 Mon Sep 17 00:00:00 2001 From: mboudet Date: Thu, 28 Oct 2021 17:48:17 +0200 Subject: [PATCH 001/113] Onto 1 --- askomics/libaskomics/TriplestoreExplorer.py | 3 ++- askomics/react/src/routes/query/query.jsx | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/askomics/libaskomics/TriplestoreExplorer.py b/askomics/libaskomics/TriplestoreExplorer.py index 0773ceaf..b941a88e 100644 --- a/askomics/libaskomics/TriplestoreExplorer.py +++ b/askomics/libaskomics/TriplestoreExplorer.py @@ -328,7 +328,7 @@ def get_abstraction_entities(self): GRAPH ?graph {{ ?graph prov:atLocation ?endpoint . ?entity_uri a ?entity_type . - VALUES ?entity_type {{ askomics:entity askomics:bnode }} . + VALUES ?entity_type {{ askomics:entity askomics:bnode askomics:ontology}} . # Faldo OPTIONAL {{ ?entity_uri a ?entity_faldo . @@ -362,6 +362,7 @@ def get_abstraction_entities(self): "label": label, "instancesHaveLabels": True if "have_no_label" not in result else False if result["have_no_label"] == "1" else True, "faldo": True if "entity_faldo" in result else False, + "ontology": True if result["entity_type"] == "{}ontology".format(self.settings.get("triplestore", "namespace_internal")) else False, "endpoints": [result["endpoint"]], "graphs": [result["graph"]], } diff --git a/askomics/react/src/routes/query/query.jsx b/askomics/react/src/routes/query/query.jsx index a516105a..5fd3f09b 100644 --- a/askomics/react/src/routes/query/query.jsx +++ b/askomics/react/src/routes/query/query.jsx @@ -212,6 +212,12 @@ export default class Query extends Component { }) } + isOntoEntity (entityUri) { + return this.state.abstraction.entities.some(entity => { + return (entity.uri == entityUri && entity.ontology) + }) + } + attributeExist (attrUri, nodeId) { return this.graphState.attr.some(attr => { return (attr.uri == attrUri && attr.nodeId == nodeId) @@ -398,6 +404,7 @@ export default class Query extends Component { specialPreviousIds: specialPreviousIds, label: this.getLabel(uri), faldo: this.isFaldoEntity(uri), + onto: this.isOntoEntity(uri), selected: selected, suggested: suggested } From c0e62dc800ddd38fa6795ceb7de34a5841b96523 Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 29 Oct 2021 16:20:44 +0200 Subject: [PATCH 002/113] React onto 1 --- .../react/src/routes/query/ontolinkview.jsx | 47 +++++++++++++++++++ askomics/react/src/routes/query/query.jsx | 43 +++++++++++++++-- 2 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 askomics/react/src/routes/query/ontolinkview.jsx diff --git a/askomics/react/src/routes/query/ontolinkview.jsx b/askomics/react/src/routes/query/ontolinkview.jsx new file mode 100644 index 00000000..72589303 --- /dev/null +++ b/askomics/react/src/routes/query/ontolinkview.jsx @@ -0,0 +1,47 @@ +import React, { Component } from 'react' +import axios from 'axios' +import { Input, FormGroup, CustomInput, Col, Row, Button } from 'reactstrap' +import { Redirect } from 'react-router-dom' +import ErrorDiv from '../error/error' +import WaitingDiv from '../../components/waiting' +import update from 'react-addons-update' +import Visualization from './visualization' +import PropTypes from 'prop-types' + +export default class OntoLinkView extends Component { + constructor (props) { + super(props) + this.handleChangeOntologyType = this.props.handleChangeOntologyType.bind(this) + } + + + render () { + return ( +
+
Ontological Relation
+
+

Search on ...

+ + + + + +
+ + + + + + + + a term
+
+
+ ) + } +} + +OntoLinkView.propTypes = { + link: PropTypes.object, + handleChangeOntologyType: PropTypes.func, +} diff --git a/askomics/react/src/routes/query/query.jsx b/askomics/react/src/routes/query/query.jsx index 5fd3f09b..5901d631 100644 --- a/askomics/react/src/routes/query/query.jsx +++ b/askomics/react/src/routes/query/query.jsx @@ -9,6 +9,7 @@ import ReactTooltip from "react-tooltip"; import Visualization from './visualization' import AttributeBox from './attribute' import LinkView from './linkview' +import OntoLinkView from './ontolinkview' import GraphFilters from './graphfilters' import ResultsTable from '../sparql/resultstable' import PropTypes from 'prop-types' @@ -518,7 +519,7 @@ export default class Query extends Component { // push suggested link this.graphState.links.push({ uri: relation.uri, - type: "link", + type: this.isOntoEntity(relation.target) ? "ontoLink" : "link", sameStrand: this.nodeHaveStrand(node.uri) && this.nodeHaveStrand(relation.target), sameRef: this.nodeHaveRef(node.uri) && this.nodeHaveRef(relation.target), strict: true, @@ -563,7 +564,7 @@ export default class Query extends Component { // push suggested link this.graphState.links.push({ uri: relation.uri, - type: "link", + type: this.isOntoEntity(relation.target) ? "ontoLink" : "link", sameStrand: this.nodeHaveStrand(node.id) && this.nodeHaveStrand(relation.source), sameRef: this.nodeHaveRef(node.id) && this.nodeHaveRef(relation.source), id: this.getId(), @@ -641,7 +642,9 @@ export default class Query extends Component { if (link.source.id == node1.id && link.target.id == node2.id) { newLink = { uri: link.uri, - type: ["included_in", "overlap_with"].includes(link.uri) ? "posLink" : "link", + // What's the point of this? + // type: ["included_in", "overlap_with"].includes(link.uri) ? "posLink" : "link", + type: link.type, sameStrand: this.nodeHaveStrand(node1.uri) && this.nodeHaveStrand(node2.uri), sameRef: this.nodeHaveRef(node1.uri) && this.nodeHaveRef(node2.uri), strict: true, @@ -658,7 +661,9 @@ export default class Query extends Component { if (link.source.id == node2.id && link.target.id == node1.id) { newLink = { uri: link.uri, - type: ["included_in", "overlap_with"].includes(link.uri) ? "posLink" : "link", + // What's the point of this? + // type: ["included_in", "overlap_with"].includes(link.uri) ? "posLink" : "link", + type: link.type, sameStrand: this.nodeHaveStrand(node1.uri) && this.nodeHaveStrand(node2.uri), sameRef: this.nodeHaveRef(node1.uri) && this.nodeHaveRef(node2.uri), strict: true, @@ -1296,6 +1301,18 @@ export default class Query extends Component { return result } + // Ontology link methods ----------------------------- + + handleChangeOntologyType (event) { + this.graphState.links.map(link => { + if (link.id == event.target.id) { + link.uri = event.target.value + link.label = event.target.value == 'specific' ? "specific" : event.target.value + " of" + } + }) + this.updateGraphState() + } + // ------------------------------------------------ // Preview results and Launch query buttons ------- @@ -1535,6 +1552,24 @@ export default class Query extends Component { nodesHaveStrands={p => this.nodesHaveStrands(p)} /> } + + if (this.currentSelected.type == "ontoLink") { + + let link = Object.assign(this.currentSelected) + this.state.graphState.nodes.map(node => { + if (node.id == this.currentSelected.target) { + link.target = node + } + if (node.id == this.currentSelected.source) { + link.source = node + } + }) + + linkView = this.handleChangeOntologyType(p)} + /> + } } // visualization (left view) From cab338db46ce78edfc23065929d1140510cac990 Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 29 Oct 2021 14:24:31 +0000 Subject: [PATCH 003/113] Fix react --- askomics/react/src/routes/query/query.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/askomics/react/src/routes/query/query.jsx b/askomics/react/src/routes/query/query.jsx index 5901d631..e80456cc 100644 --- a/askomics/react/src/routes/query/query.jsx +++ b/askomics/react/src/routes/query/query.jsx @@ -740,7 +740,7 @@ export default class Query extends Component { handleLinkSelection (clickedLink) { // Only position link are clickabl - if (clickedLink.type == "posLink") { + if (clickedLink.type == "posLink" || clickedLink.type == "ontoLink") { // case 1: link is selected, so deselect it if (clickedLink.selected) { // Update current and previous @@ -1307,7 +1307,6 @@ export default class Query extends Component { this.graphState.links.map(link => { if (link.id == event.target.id) { link.uri = event.target.value - link.label = event.target.value == 'specific' ? "specific" : event.target.value + " of" } }) this.updateGraphState() From b049693414d35209dc9df6d35cd6ab12f323f402 Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 29 Oct 2021 17:16:47 +0200 Subject: [PATCH 004/113] Onto 2 --- askomics/libaskomics/SparqlQuery.py | 13 ++++++++++++- askomics/react/src/routes/query/query.jsx | 20 +++++++++++++------- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index 75fb681d..67783e15 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -420,7 +420,8 @@ def set_graphs_and_endpoints(self, entities=None, graphs=None, endpoints=None): ?graph dc:creator ?creator . GRAPH ?graph_abstraction {{ ?graph_abstraction prov:atLocation ?endpoint . - ?entity_uri a askomics:entity . + ?entity_uri a ?askomics_type . + VALUES ?askomics_type {{askomics:entity askomics:ontology}} }} GRAPH ?graph {{ [] a ?entity_uri . @@ -1085,6 +1086,16 @@ def build_query_from_json(self, preview=False, for_editor=False): equalsign=equal_sign ), block_id, sblock_id, pblock_ids) + elif link["type"] == "ontoLink": + relation = "<{}>".format(link["uri"]) + triple = { + "subject": source, + "predicate": relation, + "object": target, + "optional": False + } + + self.store_triple(triple, block_id, sblock_id, pblock_ids) # Classic relation else: relation = "<{}>".format(link["uri"]) diff --git a/askomics/react/src/routes/query/query.jsx b/askomics/react/src/routes/query/query.jsx index e80456cc..fef3756f 100644 --- a/askomics/react/src/routes/query/query.jsx +++ b/askomics/react/src/routes/query/query.jsx @@ -529,7 +529,8 @@ export default class Query extends Component { target: targetId, selected: false, suggested: true, - directed: true + directed: true, + onto_type: "specific" }) incrementSpecialNodeGroupId ? specialNodeGroupId += 1 : specialNodeGroupId = specialNodeGroupId } @@ -573,7 +574,8 @@ export default class Query extends Component { target: node.id, selected: false, suggested: true, - directed: true + directed: true, + onto_type: "specific" }) incrementSpecialNodeGroupId ? specialNodeGroupId += 1 : specialNodeGroupId = specialNodeGroupId } @@ -601,7 +603,8 @@ export default class Query extends Component { label: entity.label, faldo: entity.faldo, selected: false, - suggested: true + suggested: true, + onto_type: "specific" }) // push suggested link this.graphState.links.push({ @@ -616,7 +619,8 @@ export default class Query extends Component { target: new_id, selected: false, suggested: true, - directed: true + directed: true, + onto_type: "specific" }) incrementSpecialNodeGroupId ? specialNodeGroupId += 1 : specialNodeGroupId = specialNodeGroupId } @@ -654,7 +658,8 @@ export default class Query extends Component { target: node2.id, selected: false, suggested: false, - directed: true + directed: true, + onto_type: "specific" } } @@ -673,7 +678,8 @@ export default class Query extends Component { target: node1.id, selected: false, suggested: false, - directed: true + directed: true, + onto_type: "specific" } } }) @@ -1306,7 +1312,7 @@ export default class Query extends Component { handleChangeOntologyType (event) { this.graphState.links.map(link => { if (link.id == event.target.id) { - link.uri = event.target.value + link.onto_type = event.target.value } }) this.updateGraphState() From 8d28a5ffbd0eabad2d9b37a14313ba9c4448372f Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 29 Oct 2021 19:18:39 +0200 Subject: [PATCH 005/113] First try --- askomics/libaskomics/SparqlQuery.py | 100 +++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index 67783e15..71191577 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -967,6 +967,8 @@ def build_query_from_json(self, preview=False, for_editor=False): start_end = [] strands = [] + ontological_nodes = [] + var_to_replace = [] # Browse attributes to get entities @@ -1087,6 +1089,26 @@ def build_query_from_json(self, preview=False, for_editor=False): ), block_id, sblock_id, pblock_ids) elif link["type"] == "ontoLink": + # If its specific, proceed as usual + if link["onto_type"] == "specific": + relation = "<{}>".format(link["uri"]) + triple = { + "subject": source, + "predicate": relation, + "object": target, + "optional": False + } + + self.store_triple(triple, block_id, sblock_id, pblock_ids) + continue + + # Else, Manage hierarchical search + # Add the ontological node to a list to avoid managing attributes later + + target_id = link["target"]["id"] + ontological_nodes.append(target_id) + + common_block = self.format_sparql_variable("block_{}_{}".format(link["source"]["id"], link["target"]["id"])) relation = "<{}>".format(link["uri"]) triple = { "subject": source, @@ -1094,8 +1116,83 @@ def build_query_from_json(self, preview=False, for_editor=False): "object": target, "optional": False } + self.store_triple(triple, block_id, sblock_id, pblock_ids) + triple = { + "subject": target, + "predicate": "rdfs:subClassOf*", + "object": common_block, + "optional": False + } self.store_triple(triple, block_id, sblock_id, pblock_ids) + + for attribute in [attrib for attrib in self.json["attr"] if attrib["nodeId"] == target_id]: + if attribute["type"] == "uri": + subject = self.format_sparql_variable("{}{}_uri".format(attribute["entityLabel"], attribute["nodeId"])) + predicate = attribute["uri"] + obj = "<{}>".format(attribute["entityUris"][0]) + if not self.is_bnode(attribute["entityUris"][0], self.json["nodes"]): + self.store_triple({ + "subject": subject, + "predicate": predicate, + "object": obj, + "optional": False + }, block_id, sblock_id, pblock_ids) + self.store_triple({ + "subject": common_block, + "predicate": predicate, + "object": obj, + "optional": False + }, block_id, sblock_id, pblock_ids) + + if attribute["visible"]: + self.selects.append(subject) + # filters/values + if attribute["filterValue"] != "": + filter_value = self.get_uri_filter_value(attribute["filterValue"]) + if attribute["filterType"] == "regexp": + negative_sign = "" + if attribute["negative"]: + negative_sign = "!" + self.store_filter("FILTER ({}regex({}, {}, 'i'))".format(negative_sign, common_block, filter_value), block_id, sblock_id, pblock_ids) + elif attribute["filterType"] == "exact": + if attribute["negative"]: + self.store_filter("FILTER (str({}) != {}) .".format(common_block, filter_value), block_id, sblock_id, pblock_ids) + else: + self.store_value("VALUES {} {{ {} }} .".format(common_block, filter_value), block_id, sblock_id, pblock_ids) + + if attribute["type"] == "text": + if attribute["visible"] or attribute["filterValue"] != "": + subject = self.format_sparql_variable("{}{}_uri".format(attribute["entityLabel"], attribute["nodeId"])) + predicate = attribute["uri"] + obj = self.format_sparql_variable("{}{}_{}".format(attribute["entityLabel"], attribute["humanNodeId"], attribute["label"])) + onto_obj = self.format_sparql_variable("{}{}_{}".format(common_block, attribute["humanNodeId"], attribute["label"])) + if attribute["visible"]: + self.store_triple({ + "subject": subject, + "predicate": predicate, + "object": obj, + "optional": True if attribute["optional"] else False + }, block_id, sblock_id, pblock_ids) + self.selects.append(obj) + # filters/values + if attribute["filterValue"] != "" and not attribute["optional"]: + self.store_triple({ + "subject": common_block, + "predicate": predicate, + "object": onto_obj, + "optional": True if attribute["optional"] else False + }, block_id, sblock_id, pblock_ids) + if attribute["filterType"] == "regexp": + negative_sign = "" + if attribute["negative"]: + negative_sign = "!" + self.store_filter("FILTER ({}regex({}, '{}', 'i'))".format(negative_sign, onto_obj, attribute["filterValue"]), block_id, sblock_id, pblock_ids) + elif attribute["filterType"] == "exact": + if attribute["negative"]: + self.store_filter("FILTER (str({}) != '{}') .".format(onto_obj, attribute["filterValue"]), block_id, sblock_id, pblock_ids) + else: + self.store_value("VALUES {} {{ '{}' }} .".format(onto_obj, attribute["filterValue"]), block_id, sblock_id, pblock_ids) # Classic relation else: relation = "<{}>".format(link["uri"]) @@ -1120,7 +1217,8 @@ def build_query_from_json(self, preview=False, for_editor=False): # Browse attributes for attribute in self.json["attr"]: - + if attribute["nodeId"] in ontological_nodes: + continue # Get blockid and sblockid of the attribute node block_id, sblock_id, pblock_ids = self.get_block_ids(attribute["nodeId"]) From 6c03d30ecd5e4f0f6f5c53476b973a180cef0112 Mon Sep 17 00:00:00 2001 From: mboudet Date: Mon, 1 Nov 2021 15:53:06 +0100 Subject: [PATCH 006/113] Testing another way --- askomics/libaskomics/SparqlQuery.py | 109 ---------------------- askomics/react/src/routes/query/query.jsx | 18 ++-- 2 files changed, 7 insertions(+), 120 deletions(-) diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index 71191577..143e430e 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -967,8 +967,6 @@ def build_query_from_json(self, preview=False, for_editor=False): start_end = [] strands = [] - ontological_nodes = [] - var_to_replace = [] # Browse attributes to get entities @@ -1088,111 +1086,6 @@ def build_query_from_json(self, preview=False, for_editor=False): equalsign=equal_sign ), block_id, sblock_id, pblock_ids) - elif link["type"] == "ontoLink": - # If its specific, proceed as usual - if link["onto_type"] == "specific": - relation = "<{}>".format(link["uri"]) - triple = { - "subject": source, - "predicate": relation, - "object": target, - "optional": False - } - - self.store_triple(triple, block_id, sblock_id, pblock_ids) - continue - - # Else, Manage hierarchical search - # Add the ontological node to a list to avoid managing attributes later - - target_id = link["target"]["id"] - ontological_nodes.append(target_id) - - common_block = self.format_sparql_variable("block_{}_{}".format(link["source"]["id"], link["target"]["id"])) - relation = "<{}>".format(link["uri"]) - triple = { - "subject": source, - "predicate": relation, - "object": target, - "optional": False - } - self.store_triple(triple, block_id, sblock_id, pblock_ids) - - triple = { - "subject": target, - "predicate": "rdfs:subClassOf*", - "object": common_block, - "optional": False - } - self.store_triple(triple, block_id, sblock_id, pblock_ids) - - for attribute in [attrib for attrib in self.json["attr"] if attrib["nodeId"] == target_id]: - if attribute["type"] == "uri": - subject = self.format_sparql_variable("{}{}_uri".format(attribute["entityLabel"], attribute["nodeId"])) - predicate = attribute["uri"] - obj = "<{}>".format(attribute["entityUris"][0]) - if not self.is_bnode(attribute["entityUris"][0], self.json["nodes"]): - self.store_triple({ - "subject": subject, - "predicate": predicate, - "object": obj, - "optional": False - }, block_id, sblock_id, pblock_ids) - self.store_triple({ - "subject": common_block, - "predicate": predicate, - "object": obj, - "optional": False - }, block_id, sblock_id, pblock_ids) - - if attribute["visible"]: - self.selects.append(subject) - # filters/values - if attribute["filterValue"] != "": - filter_value = self.get_uri_filter_value(attribute["filterValue"]) - if attribute["filterType"] == "regexp": - negative_sign = "" - if attribute["negative"]: - negative_sign = "!" - self.store_filter("FILTER ({}regex({}, {}, 'i'))".format(negative_sign, common_block, filter_value), block_id, sblock_id, pblock_ids) - elif attribute["filterType"] == "exact": - if attribute["negative"]: - self.store_filter("FILTER (str({}) != {}) .".format(common_block, filter_value), block_id, sblock_id, pblock_ids) - else: - self.store_value("VALUES {} {{ {} }} .".format(common_block, filter_value), block_id, sblock_id, pblock_ids) - - if attribute["type"] == "text": - if attribute["visible"] or attribute["filterValue"] != "": - subject = self.format_sparql_variable("{}{}_uri".format(attribute["entityLabel"], attribute["nodeId"])) - predicate = attribute["uri"] - obj = self.format_sparql_variable("{}{}_{}".format(attribute["entityLabel"], attribute["humanNodeId"], attribute["label"])) - onto_obj = self.format_sparql_variable("{}{}_{}".format(common_block, attribute["humanNodeId"], attribute["label"])) - if attribute["visible"]: - self.store_triple({ - "subject": subject, - "predicate": predicate, - "object": obj, - "optional": True if attribute["optional"] else False - }, block_id, sblock_id, pblock_ids) - self.selects.append(obj) - # filters/values - if attribute["filterValue"] != "" and not attribute["optional"]: - self.store_triple({ - "subject": common_block, - "predicate": predicate, - "object": onto_obj, - "optional": True if attribute["optional"] else False - }, block_id, sblock_id, pblock_ids) - if attribute["filterType"] == "regexp": - negative_sign = "" - if attribute["negative"]: - negative_sign = "!" - self.store_filter("FILTER ({}regex({}, '{}', 'i'))".format(negative_sign, onto_obj, attribute["filterValue"]), block_id, sblock_id, pblock_ids) - elif attribute["filterType"] == "exact": - if attribute["negative"]: - self.store_filter("FILTER (str({}) != '{}') .".format(onto_obj, attribute["filterValue"]), block_id, sblock_id, pblock_ids) - else: - self.store_value("VALUES {} {{ '{}' }} .".format(onto_obj, attribute["filterValue"]), block_id, sblock_id, pblock_ids) # Classic relation else: relation = "<{}>".format(link["uri"]) @@ -1217,8 +1110,6 @@ def build_query_from_json(self, preview=False, for_editor=False): # Browse attributes for attribute in self.json["attr"]: - if attribute["nodeId"] in ontological_nodes: - continue # Get blockid and sblockid of the attribute node block_id, sblock_id, pblock_ids = self.get_block_ids(attribute["nodeId"]) diff --git a/askomics/react/src/routes/query/query.jsx b/askomics/react/src/routes/query/query.jsx index fef3756f..a735ede2 100644 --- a/askomics/react/src/routes/query/query.jsx +++ b/askomics/react/src/routes/query/query.jsx @@ -213,9 +213,12 @@ export default class Query extends Component { }) } - isOntoEntity (entityUri) { + isOntoRelation (currentUri, targetUri) { + if (! currentUri == targetUri){ + return false + } return this.state.abstraction.entities.some(entity => { - return (entity.uri == entityUri && entity.ontology) + return (entity.uri == currentUri && entity.ontology) }) } @@ -405,7 +408,6 @@ export default class Query extends Component { specialPreviousIds: specialPreviousIds, label: this.getLabel(uri), faldo: this.isFaldoEntity(uri), - onto: this.isOntoEntity(uri), selected: selected, suggested: suggested } @@ -519,7 +521,7 @@ export default class Query extends Component { // push suggested link this.graphState.links.push({ uri: relation.uri, - type: this.isOntoEntity(relation.target) ? "ontoLink" : "link", + type: this.isOntoRelation(relation.source, relation.target) ? "ontoLink" : "link", sameStrand: this.nodeHaveStrand(node.uri) && this.nodeHaveStrand(relation.target), sameRef: this.nodeHaveRef(node.uri) && this.nodeHaveRef(relation.target), strict: true, @@ -530,7 +532,6 @@ export default class Query extends Component { selected: false, suggested: true, directed: true, - onto_type: "specific" }) incrementSpecialNodeGroupId ? specialNodeGroupId += 1 : specialNodeGroupId = specialNodeGroupId } @@ -565,7 +566,7 @@ export default class Query extends Component { // push suggested link this.graphState.links.push({ uri: relation.uri, - type: this.isOntoEntity(relation.target) ? "ontoLink" : "link", + type: this.isOntoRelation(relation.source, relation.target) && ? "ontoLink" : "link", sameStrand: this.nodeHaveStrand(node.id) && this.nodeHaveStrand(relation.source), sameRef: this.nodeHaveRef(node.id) && this.nodeHaveRef(relation.source), id: this.getId(), @@ -575,7 +576,6 @@ export default class Query extends Component { selected: false, suggested: true, directed: true, - onto_type: "specific" }) incrementSpecialNodeGroupId ? specialNodeGroupId += 1 : specialNodeGroupId = specialNodeGroupId } @@ -604,7 +604,6 @@ export default class Query extends Component { faldo: entity.faldo, selected: false, suggested: true, - onto_type: "specific" }) // push suggested link this.graphState.links.push({ @@ -620,7 +619,6 @@ export default class Query extends Component { selected: false, suggested: true, directed: true, - onto_type: "specific" }) incrementSpecialNodeGroupId ? specialNodeGroupId += 1 : specialNodeGroupId = specialNodeGroupId } @@ -659,7 +657,6 @@ export default class Query extends Component { selected: false, suggested: false, directed: true, - onto_type: "specific" } } @@ -679,7 +676,6 @@ export default class Query extends Component { selected: false, suggested: false, directed: true, - onto_type: "specific" } } }) From 01d25b30134ade2c6eab7238e7ca7ad4ccf15008 Mon Sep 17 00:00:00 2001 From: mboudet Date: Mon, 1 Nov 2021 21:46:54 +0100 Subject: [PATCH 007/113] One way --- askomics/react/src/routes/query/query.jsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/askomics/react/src/routes/query/query.jsx b/askomics/react/src/routes/query/query.jsx index a735ede2..80eb8e3b 100644 --- a/askomics/react/src/routes/query/query.jsx +++ b/askomics/react/src/routes/query/query.jsx @@ -492,6 +492,8 @@ export default class Query extends Component { let specialNodeGroupId = incrementSpecialNodeGroupId ? incrementSpecialNodeGroupId : node.specialNodeGroupId + let isOnto = this.isOntoRelation(relation.source, relation.target) + this.state.abstraction.relations.map(relation => { if (relation.source == node.uri) { if (this.entityExist(relation.target)) { @@ -521,7 +523,7 @@ export default class Query extends Component { // push suggested link this.graphState.links.push({ uri: relation.uri, - type: this.isOntoRelation(relation.source, relation.target) ? "ontoLink" : "link", + type: isOnto ? "ontoLink" : "link", sameStrand: this.nodeHaveStrand(node.uri) && this.nodeHaveStrand(relation.target), sameRef: this.nodeHaveRef(node.uri) && this.nodeHaveRef(relation.target), strict: true, @@ -538,7 +540,7 @@ export default class Query extends Component { } } - if (relation.target == node.uri) { + if (relation.target == node.uri && ! isOnto) { if (this.entityExist(relation.source)) { sourceId = this.getId() linkId = this.getId() @@ -566,7 +568,7 @@ export default class Query extends Component { // push suggested link this.graphState.links.push({ uri: relation.uri, - type: this.isOntoRelation(relation.source, relation.target) && ? "ontoLink" : "link", + type: "link", sameStrand: this.nodeHaveStrand(node.id) && this.nodeHaveStrand(relation.source), sameRef: this.nodeHaveRef(node.id) && this.nodeHaveRef(relation.source), id: this.getId(), From 935871a20f46f3b8d40112e73579a456548c2da5 Mon Sep 17 00:00:00 2001 From: mboudet Date: Tue, 2 Nov 2021 11:07:42 +0100 Subject: [PATCH 008/113] Update --- askomics/libaskomics/SparqlQuery.py | 15 ++++++++++++++- askomics/react/src/routes/query/ontolinkview.jsx | 9 ++++----- askomics/react/src/routes/query/query.jsx | 12 ++++++++++-- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index 143e430e..2bb5a16b 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -1088,7 +1088,20 @@ def build_query_from_json(self, preview=False, for_editor=False): # Classic relation else: - relation = "<{}>".format(link["uri"]) + # Manage ontology stuff + inverse = "" + recurrence = "" + relation = link["uri"] + + if relation.startswith("^"): + inverse = "^" + relation = relation.lstrip("^") + + if relation.endswith("*"): + recurrence = "*" + relation = relation.rstrip("*") + + relation = inverse + "<{}>".format(relation) + recurrence triple = { "subject": source, "predicate": relation, diff --git a/askomics/react/src/routes/query/ontolinkview.jsx b/askomics/react/src/routes/query/ontolinkview.jsx index 72589303..bc8e6751 100644 --- a/askomics/react/src/routes/query/ontolinkview.jsx +++ b/askomics/react/src/routes/query/ontolinkview.jsx @@ -25,11 +25,10 @@ export default class OntoLinkView extends Component { - - - - - + + + + a term diff --git a/askomics/react/src/routes/query/query.jsx b/askomics/react/src/routes/query/query.jsx index 80eb8e3b..1ea44d85 100644 --- a/askomics/react/src/routes/query/query.jsx +++ b/askomics/react/src/routes/query/query.jsx @@ -528,7 +528,7 @@ export default class Query extends Component { sameRef: this.nodeHaveRef(node.uri) && this.nodeHaveRef(relation.target), strict: true, id: linkId, - label: relation.label, + label: isOnto ? getOntoLabel(relation.uri) : relation.label, source: node.id, target: targetId, selected: false, @@ -1308,14 +1308,22 @@ export default class Query extends Component { // Ontology link methods ----------------------------- handleChangeOntologyType (event) { + let labels = {subClassOf: "Children of", subClassOf*: "Descendants of", ^subClassOf: "Parents of", ^subClassOf*:"Ancestors of"} + this.graphState.links.map(link => { if (link.id == event.target.id) { - link.onto_type = event.target.value + link.uri = event.target.value + link.label = getOntoLabel(event.target.value) } }) this.updateGraphState() } + getOntoLabel (uri) { + let labels = {subClassOf: "Children of", subClassOf*: "Descendants of", ^subClassOf: "Parents of", ^subClassOf*:"Ancestors of"} + return labels[uri] + } + // ------------------------------------------------ // Preview results and Launch query buttons ------- From 6b61a4d2fa0d23fcc8d59129ed845258bf8c0bb6 Mon Sep 17 00:00:00 2001 From: mboudet Date: Tue, 2 Nov 2021 10:35:08 +0000 Subject: [PATCH 009/113] Fixes & typos --- .../react/src/routes/query/ontolinkview.jsx | 8 ++++---- askomics/react/src/routes/query/query.jsx | 18 +++++++++++------- .../react/src/routes/query/visualization.jsx | 2 +- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/askomics/react/src/routes/query/ontolinkview.jsx b/askomics/react/src/routes/query/ontolinkview.jsx index bc8e6751..b50dfb27 100644 --- a/askomics/react/src/routes/query/ontolinkview.jsx +++ b/askomics/react/src/routes/query/ontolinkview.jsx @@ -25,10 +25,10 @@ export default class OntoLinkView extends Component { - - - - + + + + a term diff --git a/askomics/react/src/routes/query/query.jsx b/askomics/react/src/routes/query/query.jsx index 1ea44d85..14394b46 100644 --- a/askomics/react/src/routes/query/query.jsx +++ b/askomics/react/src/routes/query/query.jsx @@ -492,9 +492,8 @@ export default class Query extends Component { let specialNodeGroupId = incrementSpecialNodeGroupId ? incrementSpecialNodeGroupId : node.specialNodeGroupId - let isOnto = this.isOntoRelation(relation.source, relation.target) - this.state.abstraction.relations.map(relation => { + let isOnto = this.isOntoRelation(relation.source, relation.target) if (relation.source == node.uri) { if (this.entityExist(relation.target)) { targetId = this.getId() @@ -528,7 +527,7 @@ export default class Query extends Component { sameRef: this.nodeHaveRef(node.uri) && this.nodeHaveRef(relation.target), strict: true, id: linkId, - label: isOnto ? getOntoLabel(relation.uri) : relation.label, + label: isOnto ? this.getOntoLabel(relation.uri) : relation.label, source: node.id, target: targetId, selected: false, @@ -1308,19 +1307,24 @@ export default class Query extends Component { // Ontology link methods ----------------------------- handleChangeOntologyType (event) { - let labels = {subClassOf: "Children of", subClassOf*: "Descendants of", ^subClassOf: "Parents of", ^subClassOf*:"Ancestors of"} - this.graphState.links.map(link => { if (link.id == event.target.id) { link.uri = event.target.value - link.label = getOntoLabel(event.target.value) + link.label = this.getOntoLabel(event.target.value) } }) this.updateGraphState() } getOntoLabel (uri) { - let labels = {subClassOf: "Children of", subClassOf*: "Descendants of", ^subClassOf: "Parents of", ^subClassOf*:"Ancestors of"} + let labels = {} + labels["http://www.w3.org/2000/01/rdf-schema#subClassOf"] = "Children of" + labels["http://www.w3.org/2000/01/rdf-schema#subClassOf*"] = "Descendants of" + labels["^http://www.w3.org/2000/01/rdf-schema#subClassOf"] = "Parents of" + labels["^http://www.w3.org/2000/01/rdf-schema#subClassOf*"] = "Ancestors of" + + console.log(uri) + console.log(labels) return labels[uri] } diff --git a/askomics/react/src/routes/query/visualization.jsx b/askomics/react/src/routes/query/visualization.jsx index 90ee0ef0..c2a62360 100644 --- a/askomics/react/src/routes/query/visualization.jsx +++ b/askomics/react/src/routes/query/visualization.jsx @@ -169,7 +169,7 @@ export default class Visualization extends Component { link.suggested ? ctx.setLineDash([this.lineWidth, this.lineWidth]) : ctx.setLineDash([]) let greenArray = ["included_in", "overlap_with"] - let unselectedColor = greenArray.indexOf(link.uri) >= 0 ? this.colorGreen : this.colorGrey + let unselectedColor = greenArray.indexOf(link.uri) >= 0 || link.type == "ontoLink" ? this.colorGreen : this.colorGrey let unselectedColorText = greenArray.indexOf(link.uri) >= 0 ? this.colorGreen : this.colorDarkGrey ctx.strokeStyle = link.selected ? this.colorFirebrick : unselectedColor From a98bac79e32c56278f9e804c979c7cb79afd5008 Mon Sep 17 00:00:00 2001 From: mboudet Date: Tue, 2 Nov 2021 11:57:59 +0100 Subject: [PATCH 010/113] Test hide endNode relations --- askomics/react/src/routes/query/query.jsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/askomics/react/src/routes/query/query.jsx b/askomics/react/src/routes/query/query.jsx index 14394b46..5675cdc9 100644 --- a/askomics/react/src/routes/query/query.jsx +++ b/askomics/react/src/routes/query/query.jsx @@ -222,6 +222,12 @@ export default class Query extends Component { }) } + isOntoEndNode (currentId) { + return this.graphState.links.some(link => { + return (link.type == "ontoLink" && link.target == currentId) + }) + } + attributeExist (attrUri, nodeId) { return this.graphState.attr.some(attr => { return (attr.uri == attrUri && attr.nodeId == nodeId) @@ -492,6 +498,10 @@ export default class Query extends Component { let specialNodeGroupId = incrementSpecialNodeGroupId ? incrementSpecialNodeGroupId : node.specialNodeGroupId + if this.isOntoEndNode(node.id){ + return + } + this.state.abstraction.relations.map(relation => { let isOnto = this.isOntoRelation(relation.source, relation.target) if (relation.source == node.uri) { @@ -1319,8 +1329,8 @@ export default class Query extends Component { getOntoLabel (uri) { let labels = {} labels["http://www.w3.org/2000/01/rdf-schema#subClassOf"] = "Children of" - labels["http://www.w3.org/2000/01/rdf-schema#subClassOf*"] = "Descendants of" - labels["^http://www.w3.org/2000/01/rdf-schema#subClassOf"] = "Parents of" + labels["http://www.w3.org/2000/01/rdf-schema#subClassOf*"] = "Descendants of" + labels["^http://www.w3.org/2000/01/rdf-schema#subClassOf"] = "Parents of" labels["^http://www.w3.org/2000/01/rdf-schema#subClassOf*"] = "Ancestors of" console.log(uri) From a225df9d4198b50743b1a5f609ab1516b8fc57cc Mon Sep 17 00:00:00 2001 From: mboudet Date: Tue, 2 Nov 2021 14:27:37 +0000 Subject: [PATCH 011/113] Improved layout & fixes --- askomics/react/src/routes/query/attribute.jsx | 26 ++++++++++++++++--- askomics/react/src/routes/query/query.jsx | 16 +++++++----- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/askomics/react/src/routes/query/attribute.jsx b/askomics/react/src/routes/query/attribute.jsx index b6cb21ca..5536edde 100644 --- a/askomics/react/src/routes/query/attribute.jsx +++ b/askomics/react/src/routes/query/attribute.jsx @@ -151,15 +151,32 @@ export default class AttributeBox extends Component { ) } - return ( -
- + + let attrIcons + + if (this.props.isOnto){ + attrIcons = ( +
+ +
+ ) + + } else { + attrIcons = (
{this.props.config.user.admin ? : } {this.props.attribute.uri == "rdf:type" || this.props.attribute.uri == "rdfs:label" ? : }
+ ) + } + + + return ( +
+ + {attrIcons} {form}
) @@ -476,5 +493,6 @@ AttributeBox.propTypes = { handleDateFilter: PropTypes.func, attribute: PropTypes.object, graph: PropTypes.object, - config: PropTypes.object + config: PropTypes.object, + isOnto: PropTypes.bool } diff --git a/askomics/react/src/routes/query/query.jsx b/askomics/react/src/routes/query/query.jsx index 5675cdc9..a1a3df60 100644 --- a/askomics/react/src/routes/query/query.jsx +++ b/askomics/react/src/routes/query/query.jsx @@ -223,8 +223,9 @@ export default class Query extends Component { } isOntoEndNode (currentId) { - return this.graphState.links.some(link => { - return (link.type == "ontoLink" && link.target == currentId) + + return this.graphState.nodes.some(node => { + return (node.id == currentId && node.ontology) }) } @@ -498,7 +499,7 @@ export default class Query extends Component { let specialNodeGroupId = incrementSpecialNodeGroupId ? incrementSpecialNodeGroupId : node.specialNodeGroupId - if this.isOntoEndNode(node.id){ + if (this.isOntoEndNode(node.id)){ return } @@ -527,7 +528,8 @@ export default class Query extends Component { label: label, faldo: this.isFaldoEntity(relation.target), selected: false, - suggested: true + suggested: true, + ontology: isOnto }) // push suggested link this.graphState.links.push({ @@ -1332,9 +1334,6 @@ export default class Query extends Component { labels["http://www.w3.org/2000/01/rdf-schema#subClassOf*"] = "Descendants of" labels["^http://www.w3.org/2000/01/rdf-schema#subClassOf"] = "Parents of" labels["^http://www.w3.org/2000/01/rdf-schema#subClassOf*"] = "Ancestors of" - - console.log(uri) - console.log(labels) return labels[uri] } @@ -1507,6 +1506,7 @@ export default class Query extends Component { let visualizationDiv let uriLabelBoxes let AttributeBoxes + let isOnto let linkView let previewButton let faldoButton @@ -1526,6 +1526,7 @@ export default class Query extends Component { if (!this.state.waiting) { // attribute boxes (right view) only for node if (this.currentSelected) { + isOnto = this.isOntoEndNode(this.currentSelected.id) AttributeBoxes = this.state.graphState.attr.map(attribute => { if (attribute.nodeId == this.currentSelected.id && this.currentSelected.type == "node") { return ( @@ -1549,6 +1550,7 @@ export default class Query extends Component { handleFilterDateValue={p => this.handleFilterDateValue(p)} handleDateFilter={p => this.handleDateFilter(p)} config={this.state.config} + isOnto={isOnto} /> ) } From 482c5e753b1160ae564324648bc848fe6b90c74b Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 5 Nov 2021 18:54:49 +0100 Subject: [PATCH 012/113] prefixes --- askomics/libaskomics/Database.py | 15 +++++++++++ askomics/libaskomics/PrefixManager.py | 39 +++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/askomics/libaskomics/Database.py b/askomics/libaskomics/Database.py index 16b138f0..ebc58e05 100644 --- a/askomics/libaskomics/Database.py +++ b/askomics/libaskomics/Database.py @@ -76,6 +76,7 @@ def init_database(self): self.create_files_table() self.create_datasets_table() self.create_abstraction_table() + self.create_prefixes_table() def create_user_table(self): """Create the user table""" @@ -383,3 +384,17 @@ def create_abstraction_table(self): ) """ self.execute_sql_query(query) + + def create_prefixes_table(self): + """Create the prefix table""" + query = ''' + CREATE TABLE IF NOT EXISTS prefixes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + prefix text NOT NULL, + namespace text NOT NULL + ) + ''' + self.execute_sql_query(query) + + def create_ontologies_table(self): + pass diff --git a/askomics/libaskomics/PrefixManager.py b/askomics/libaskomics/PrefixManager.py index 3dc71dfc..f816431d 100644 --- a/askomics/libaskomics/PrefixManager.py +++ b/askomics/libaskomics/PrefixManager.py @@ -1,3 +1,4 @@ +from askomics.libaskomics.Database import Database from askomics.libaskomics.Params import Params import rdflib @@ -79,4 +80,42 @@ def get_namespace(self, prefix): try: return prefix_cc[prefix] except Exception: + prefixes = self.get_custom_namespaces(prefix) + if prefixes: + return prefixes[0]["namespace"] return "" + + def get_custom_namespaces(self, prefix=None): + """Get custom (admin-defined) prefixes + + Returns + ------- + list of dict + Prefixes information + """ + database = Database(self.app, self.session) + + query_args = () + subquery = "" + + if prefix: + query_args = (prefix, ) + subquery = "WHERE prefix = ?" + + query = ''' + SELECT prefix, namespace + FROM prefixes + {} + '''.format(subquery) + + rows = database.execute_sql_query(query, query_args) + + prefixes = [] + for row in rows: + prefix = { + 'prefix': row[0], + 'namespace': row[1], + } + prefixes.append(prefix) + + return prefixes From a82947f3807271683ef760ef13341f8a10fc6afe Mon Sep 17 00:00:00 2001 From: mboudet Date: Mon, 8 Nov 2021 17:28:58 +0100 Subject: [PATCH 013/113] Prefixes --- askomics/api/admin.py | 128 ++++++++++++++++++++++++++ askomics/libaskomics/PrefixManager.py | 41 ++++++++- 2 files changed, 167 insertions(+), 2 deletions(-) diff --git a/askomics/api/admin.py b/askomics/api/admin.py index f464182d..d4f39df4 100644 --- a/askomics/api/admin.py +++ b/askomics/api/admin.py @@ -7,6 +7,7 @@ from askomics.libaskomics.FilesHandler import FilesHandler from askomics.libaskomics.LocalAuth import LocalAuth from askomics.libaskomics.Mailer import Mailer +from askomics.libaskomics.PrefixManager import PrefixManager from askomics.libaskomics.Result import Result from askomics.libaskomics.ResultsHandler import ResultsHandler @@ -510,3 +511,130 @@ def delete_datasets(): 'error': False, 'errorMessage': '' }) + + +@admin_bp.route("/api/admin/get_prefixes", methods=["GET"]) +@api_auth +@admin_required +def get_prefixes(): + """Get all custom prefixes + + Returns + ------- + json + prefixes: list of all custom prefixes + error: True if error, else False + errorMessage: the error message of error, else an empty string + """ + try: + pm = PrefixManager() + prefixes = pm.get_custom_prefixes() + except Exception as e: + traceback.print_exc(file=sys.stdout) + return jsonify({ + 'prefixes': [], + 'error': True, + 'errorMessage': str(e) + }), 500 + + return jsonify({ + 'prefixes': prefixes, + 'error': False, + 'errorMessage': '' + }) + + +@admin_bp.route("/api/admin/add_prefix", methods=["POST"]) +@api_auth +@admin_required +def add_prefix(): + """Create a new prefix + + Returns + ------- + json + prefixes: list of all custom prefixes + error: True if error, else False + errorMessage: the error message of error, else an empty string + """ + + data = request.get_json() + if not data or not (data.get("prefix") and data.get("namespace")): + return jsonify({ + 'prefixes': [], + 'error': True, + 'errorMessage': "Missing parameter" + }), 400 + + pm = PrefixManager() + prefixes = pm.get_custom_prefixes() + + prefix = data.get("prefix") + namespace = data.get("namespace") + + if any([prefix == custom['prefix'] for custom in prefixes]): + return jsonify({ + 'prefixes': [], + 'error': True, + 'errorMessage': "Prefix {} is already in use".format(prefix) + }), 400 + + try: + pm = PrefixManager() + pm.add_custom_prefix(prefix, namespace) + prefixes = pm.get_custom_prefixes() + except Exception as e: + traceback.print_exc(file=sys.stdout) + return jsonify({ + 'prefixes': [], + 'error': True, + 'errorMessage': str(e) + }), 500 + + return jsonify({ + 'prefixes': prefixes, + 'error': False, + 'errorMessage': '' + }) + + +@admin_bp.route("/api/admin/delete_prefix", methods=["POST"]) +@api_auth +@admin_required +def delete_prefix(): + """Delete a prefix + + Returns + ------- + json + prefixes: list of all custom prefixes + error: True if error, else False + errorMessage: the error message of error, else an empty string + """ + + data = request.get_json() + if not data or not data.get("prefix_id"): + return jsonify({ + 'prefixes': [], + 'error': True, + 'errorMessage': "Missing prefix_id parameter" + }), 400 + + pm = PrefixManager() + try: + pm = PrefixManager() + pm.delete_custom_prefix(data.get("prefix_id")) + prefixes = pm.get_custom_prefixes() + except Exception as e: + traceback.print_exc(file=sys.stdout) + return jsonify({ + 'prefixes': [], + 'error': True, + 'errorMessage': str(e) + }), 500 + + return jsonify({ + 'prefixes': prefixes, + 'error': False, + 'errorMessage': '' + }) diff --git a/askomics/libaskomics/PrefixManager.py b/askomics/libaskomics/PrefixManager.py index f816431d..7ed838d9 100644 --- a/askomics/libaskomics/PrefixManager.py +++ b/askomics/libaskomics/PrefixManager.py @@ -80,12 +80,12 @@ def get_namespace(self, prefix): try: return prefix_cc[prefix] except Exception: - prefixes = self.get_custom_namespaces(prefix) + prefixes = self.get_custom_prefixes(prefix) if prefixes: return prefixes[0]["namespace"] return "" - def get_custom_namespaces(self, prefix=None): + def get_custom_prefixes(self, prefix=None): """Get custom (admin-defined) prefixes Returns @@ -119,3 +119,40 @@ def get_custom_namespaces(self, prefix=None): prefixes.append(prefix) return prefixes + + def add_custom_prefix(self, prefix, namespace): + """Create a new custom (admin-defined) prefixes + + Returns + ------- + list of dict + Prefixes information + """ + database = Database(self.app, self.session) + + query = ''' + INSERT INTO prefixes VALUES( + NULL, + ?, + ? + ) + ''' + + database.execute_sql_query(query, (prefix, namespace)) + + def remove_custom_prefix(self, prefix_id): + """Create a new custom (admin-defined) prefixes + + Returns + ------- + list of dict + Prefixes information + """ + database = Database(self.app, self.session) + + query = ''' + DELETE FROM prefixes + WHERE id = ? + ''' + + database.execute_sql_query(query, (prefix_id)) From 78c083ae2e269edcebd48f4be44591b215bdb559 Mon Sep 17 00:00:00 2001 From: mboudet Date: Tue, 9 Nov 2021 11:46:17 +0100 Subject: [PATCH 014/113] React prefixes --- askomics/api/admin.py | 12 +- askomics/libaskomics/PrefixManager.py | 12 +- askomics/react/src/routes/admin/admin.jsx | 134 +++++++++++++++++- .../react/src/routes/admin/prefixestable.jsx | 104 ++++++++++++++ 4 files changed, 250 insertions(+), 12 deletions(-) create mode 100644 askomics/react/src/routes/admin/prefixestable.jsx diff --git a/askomics/api/admin.py b/askomics/api/admin.py index d4f39df4..368bf72a 100644 --- a/askomics/api/admin.py +++ b/askomics/api/admin.py @@ -513,7 +513,7 @@ def delete_datasets(): }) -@admin_bp.route("/api/admin/get_prefixes", methods=["GET"]) +@admin_bp.route("/api/admin/getprefixes", methods=["GET"]) @api_auth @admin_required def get_prefixes(): @@ -544,7 +544,7 @@ def get_prefixes(): }) -@admin_bp.route("/api/admin/add_prefix", methods=["POST"]) +@admin_bp.route("/api/admin/addprefix", methods=["POST"]) @api_auth @admin_required def add_prefix(): @@ -598,7 +598,7 @@ def add_prefix(): }) -@admin_bp.route("/api/admin/delete_prefix", methods=["POST"]) +@admin_bp.route("/api/admin/delete_prefixes", methods=["POST"]) @api_auth @admin_required def delete_prefix(): @@ -613,17 +613,17 @@ def delete_prefix(): """ data = request.get_json() - if not data or not data.get("prefix_id"): + if not data or not data.get("prefixesIdToDelete"): return jsonify({ 'prefixes': [], 'error': True, - 'errorMessage': "Missing prefix_id parameter" + 'errorMessage': "Missing prefixesIdToDelete parameter" }), 400 pm = PrefixManager() try: pm = PrefixManager() - pm.delete_custom_prefix(data.get("prefix_id")) + pm.delete_custom_prefixes(data.get("prefixesIdToDelete")) prefixes = pm.get_custom_prefixes() except Exception as e: traceback.print_exc(file=sys.stdout) diff --git a/askomics/libaskomics/PrefixManager.py b/askomics/libaskomics/PrefixManager.py index 7ed838d9..e52c21a5 100644 --- a/askomics/libaskomics/PrefixManager.py +++ b/askomics/libaskomics/PrefixManager.py @@ -103,7 +103,7 @@ def get_custom_prefixes(self, prefix=None): subquery = "WHERE prefix = ?" query = ''' - SELECT prefix, namespace + SELECT id, prefix, namespace FROM prefixes {} '''.format(subquery) @@ -113,8 +113,9 @@ def get_custom_prefixes(self, prefix=None): prefixes = [] for row in rows: prefix = { - 'prefix': row[0], - 'namespace': row[1], + 'id': row[0], + 'prefix': row[1], + 'namespace': row[2], } prefixes.append(prefix) @@ -140,7 +141,7 @@ def add_custom_prefix(self, prefix, namespace): database.execute_sql_query(query, (prefix, namespace)) - def remove_custom_prefix(self, prefix_id): + def remove_custom_prefixes(self, prefixes_id): """Create a new custom (admin-defined) prefixes Returns @@ -155,4 +156,5 @@ def remove_custom_prefix(self, prefix_id): WHERE id = ? ''' - database.execute_sql_query(query, (prefix_id)) + for prefix_id in prefixes_id: + database.execute_sql_query(query, (prefix_id)) diff --git a/askomics/react/src/routes/admin/admin.jsx b/askomics/react/src/routes/admin/admin.jsx index 17cebd31..f296610d 100644 --- a/askomics/react/src/routes/admin/admin.jsx +++ b/askomics/react/src/routes/admin/admin.jsx @@ -6,6 +6,7 @@ import DatasetsTable from './datasetstable' import FilesTable from './filestable' import QueriesTable from './queriestable' import UsersTable from './userstable' +import PrefixesTable from './prefixestable' import Utils from '../../classes/utils' import { Redirect } from 'react-router-dom' import WaitingDiv from '../../components/waiting' @@ -29,12 +30,18 @@ export default class Admin extends Component { datasetErrorMessage: '', queryError: false, queryErrorMessage: '', + prefixError: false, + prefixErrorMessage: '', + newprefixError: false, + newprefixErrorMessage: '', users: [], datasets: [], files: [], queries: [], fname: "", lname: "", + prefix: "", + namespace: "", username: "", email: "", newUser: {}, @@ -43,16 +50,21 @@ export default class Admin extends Component { instanceUrl: "", usersSelected: [], filesSelected: [], - datasetsSelected: [] + datasetsSelected: [], + prefixesSelected: [] } this.handleChangeUserInput = this.handleChangeUserInput.bind(this) this.handleChangeFname = this.handleChangeFname.bind(this) this.handleChangeLname = this.handleChangeLname.bind(this) this.handleAddUser = this.handleAddUser.bind(this) + this.handleChangePrefix = this.handleChangePrefix.bind(this) + this.handleChangeNamespace = this.handleChangeNamespace.bind(this) + this.handleAddPrefix = this.handleAddPrefix.bind(this) this.dismissMessage = this.dismissMessage.bind(this) this.deleteSelectedUsers = this.deleteSelectedUsers.bind(this) this.deleteSelectedFiles = this.deleteSelectedFiles.bind(this) this.deleteSelectedDatasets = this.deleteSelectedDatasets.bind(this) + this.deleteSelectedPrefixes = this.deleteSelectedPrefixes.bind(this) this.cancelRequest } @@ -68,6 +80,10 @@ export default class Admin extends Component { return this.state.datasetsSelected.length == 0 } + isPrefixesDisabled () { + return this.state.prefixesSelected.length == 0 + } + deleteSelectedUsers () { let requestUrl = '/api/admin/delete_users' let data = { @@ -113,6 +129,21 @@ export default class Admin extends Component { }) } + deleteSelectedPrefixes () { + let requestUrl = '/api/admin/delete_prefixes' + let data = { + prefixesIdToDelete: this.state.prefixesSelected + } + axios.post(requestUrl, data, { baseURL: this.props.config.proxyPath, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) + .then(response => { + console.log(requestUrl, response.data) + this.setState({ + prefixes: response.data.prefixes, + prefixesSelected: [], + }) + }) + } + dismissMessage () { this.setState({ newUser: {}, @@ -140,6 +171,18 @@ export default class Admin extends Component { }) } + handleChangePrefix (event) { + this.setState({ + prefix: event.target.value + }) + } + + handleChangeNamespace (event) { + this.setState({ + namespace: event.target.value + }) + } + validateEmail (email) { let re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ return re.test(String(email).toLowerCase()) @@ -154,6 +197,13 @@ export default class Admin extends Component { ) } + validatePrefixForm () { + return ( + this.state.prefix.length > 0 && + this.state.namespace.length > 0 && + ) + } + handleAddUser(event) { let requestUrl = "/api/admin/adduser" @@ -192,12 +242,45 @@ export default class Admin extends Component { event.preventDefault() } + handleAddPrefix(event) { + + let requestUrl = "/api/admin/addprefix" + let data = { + prefix: this.state.prefix, + namespace: this.state.namespace, + } + + axios.post(requestUrl, data, { baseURL: this.props.config.proxyPath, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) + .then(response => { + console.log(requestUrl, response.data) + this.setState({ + newprefixError: response.data.error, + newprefixErrorMessage: response.data.errorMessage, + prefixes: response.data.prefixes, + newprefixStatus: error.response.status, + }) + if (!response.data.error) { + this.loadUsers() + } + }) + .catch(error => { + console.log(error, error.response.data.errorMessage) + this.setState({ + newprefixError: true, + newprefixErrorMessage: error.response.data.errorMessage, + newprefixStatus: error.response.status, + }) + }) + event.preventDefault() + } + componentDidMount () { if (!this.props.waitForStart) { this.loadUsers() this.loadDataSets() this.loadFiles() this.loadQueries() + this.loadPrefixes() this.interval = setInterval(() => { this.loadDataSets() }, 5000) @@ -292,6 +375,27 @@ export default class Admin extends Component { }) } + loadPrefixes() { + let requestUrl = '/api/admin/getprefixes' + axios.get(requestUrl, { baseURL: this.props.config.proxyPath, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) + .then(response => { + console.log(requestUrl, response.data) + this.setState({ + prefixesLoading: false, + prefixes: response.data.prefixes + }) + }) + .catch(error => { + console.log(error, error.response.data.errorMessage) + this.setState({ + prefixError: true, + prefixErrorMessage: error.response.data.errorMessage, + prefixStatus: error.response.status, + success: !error.response.data.error + }) + }) + } + componentWillUnmount () { clearInterval(this.interval) if (!this.props.waitForStart) { @@ -404,6 +508,34 @@ export default class Admin extends Component { this.setState(p)} queriesLoading={this.state.queriesLoading} />
+ +

Prefixes

+
Add a prefix
+
+
+ + + + + + + + + + + + + + + +
+ +
+
+ this.setState(p)} prefixesLoading={this.state.prefixesLoading} /> +
+ +
) } diff --git a/askomics/react/src/routes/admin/prefixestable.jsx b/askomics/react/src/routes/admin/prefixestable.jsx new file mode 100644 index 00000000..ec30db07 --- /dev/null +++ b/askomics/react/src/routes/admin/prefixestable.jsx @@ -0,0 +1,104 @@ +import React, { Component } from 'react' +import axios from 'axios' +import {Badge, Button, Form, FormGroup, Label, Input, Alert, Row, Col, CustomInput } from 'reactstrap' +import BootstrapTable from 'react-bootstrap-table-next' +import paginationFactory from 'react-bootstrap-table2-paginator' +import cellEditFactory from 'react-bootstrap-table2-editor' +import update from 'react-addons-update' +import PropTypes from 'prop-types' +import Utils from '../../classes/utils' +import WaitingDiv from '../../components/waiting' +import pretty from 'pretty-time' + +export default class PrefixesTable extends Component { + constructor (props) { + super(props) + this.utils = new Utils() + this.handlePrefixSelection = this.handlePrefixSelection.bind(this) + this.handlePrefixSelectionAll = this.handlePrefixSelectionAll.bind(this) + } + + handlePrefixSelection (row, isSelect) { + if (isSelect) { + this.props.setStateUsers({ + prefixesSelected: [...this.props.prefixesSelected, row.id] + }) + } else { + this.props.setStateUsers({ + prefixesSelected: this.props.prefixesSelected.filter(x => x !== row.id) + }) + } + } + + handlePrefixSelectionAll (isSelect, rows) { + const prefixes = rows.map(r => r.id) + if (isSelect) { + this.props.setStateUsers({ + prefixesSelected: prefixes + }) + } else { + this.props.setStateUsers({ + prefixesSelected: [] + }) + } + } + + render () { + let prefixesColumns = [{ + editable: false, + dataField: 'prefix', + text: 'Prefix', + sort: true + }, { + editable: false, + dataField: 'namespace', + text: 'Namespace', + sort: true + }] + + let prefixesDefaultSorted = [{ + dataField: 'prefix', + order: 'asc' + }] + + let prefixesSelectRow = { + mode: 'checkbox', + clickToSelect: false, + selected: this.props.prefixesSelected, + onSelect: this.handleUserSelection, + onSelectAll: this.handleUserSelectionAll, + nonSelectable: [this.props.config.user.username] + } + + let prefixesNoDataIndication = 'No custom prefixes' + if (this.props.prefixesLoading) { + prefixesNoDataIndication = + } + + return ( +
+ +
+ ) + } +} + +PrefixesTable.propTypes = { + setStatePrefixes: PropTypes.func, + prefixesSelected: PropTypes.object, + prefixesLoading: PropTypes.bool, + prefixes: PropTypes.list, + config: PropTypes.object +} From 988c068e636e0f78581da7f3ef0334bb3e75db1d Mon Sep 17 00:00:00 2001 From: mboudet Date: Tue, 16 Nov 2021 08:35:38 +0000 Subject: [PATCH 015/113] typo --- askomics/api/admin.py | 8 +++----- askomics/react/src/routes/admin/admin.jsx | 3 ++- askomics/react/src/routes/admin/prefixestable.jsx | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/askomics/api/admin.py b/askomics/api/admin.py index 368bf72a..313633fa 100644 --- a/askomics/api/admin.py +++ b/askomics/api/admin.py @@ -527,7 +527,7 @@ def get_prefixes(): errorMessage: the error message of error, else an empty string """ try: - pm = PrefixManager() + pm = PrefixManager(current_app, session) prefixes = pm.get_custom_prefixes() except Exception as e: traceback.print_exc(file=sys.stdout) @@ -566,7 +566,7 @@ def add_prefix(): 'errorMessage': "Missing parameter" }), 400 - pm = PrefixManager() + pm = PrefixManager(current_app, session) prefixes = pm.get_custom_prefixes() prefix = data.get("prefix") @@ -580,7 +580,6 @@ def add_prefix(): }), 400 try: - pm = PrefixManager() pm.add_custom_prefix(prefix, namespace) prefixes = pm.get_custom_prefixes() except Exception as e: @@ -620,9 +619,8 @@ def delete_prefix(): 'errorMessage': "Missing prefixesIdToDelete parameter" }), 400 - pm = PrefixManager() + pm = PrefixManager(current_app, session) try: - pm = PrefixManager() pm.delete_custom_prefixes(data.get("prefixesIdToDelete")) prefixes = pm.get_custom_prefixes() except Exception as e: diff --git a/askomics/react/src/routes/admin/admin.jsx b/askomics/react/src/routes/admin/admin.jsx index f296610d..872ef306 100644 --- a/askomics/react/src/routes/admin/admin.jsx +++ b/askomics/react/src/routes/admin/admin.jsx @@ -38,6 +38,7 @@ export default class Admin extends Component { datasets: [], files: [], queries: [], + prefixes: [], fname: "", lname: "", prefix: "", @@ -200,7 +201,7 @@ export default class Admin extends Component { validatePrefixForm () { return ( this.state.prefix.length > 0 && - this.state.namespace.length > 0 && + this.state.namespace.length > 0 ) } diff --git a/askomics/react/src/routes/admin/prefixestable.jsx b/askomics/react/src/routes/admin/prefixestable.jsx index ec30db07..90417352 100644 --- a/askomics/react/src/routes/admin/prefixestable.jsx +++ b/askomics/react/src/routes/admin/prefixestable.jsx @@ -87,7 +87,7 @@ export default class PrefixesTable extends Component { columns={prefixesColumns} defaultSorted={prefixesDefaultSorted} pagination={paginationFactory()} - noDataIndication={datasetsNoDataIndication} + noDataIndication={prefixesNoDataIndication} selectRow={ prefixesSelectRow } /> @@ -99,6 +99,6 @@ PrefixesTable.propTypes = { setStatePrefixes: PropTypes.func, prefixesSelected: PropTypes.object, prefixesLoading: PropTypes.bool, - prefixes: PropTypes.list, + prefixes: PropTypes.array, config: PropTypes.object } From 142d783db2d88ba9547445cb2343a4e178731733 Mon Sep 17 00:00:00 2001 From: mboudet Date: Tue, 16 Nov 2021 09:53:10 +0100 Subject: [PATCH 016/113] Typo(2) --- askomics/react/src/routes/admin/admin.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/askomics/react/src/routes/admin/admin.jsx b/askomics/react/src/routes/admin/admin.jsx index 872ef306..b2c968b3 100644 --- a/askomics/react/src/routes/admin/admin.jsx +++ b/askomics/react/src/routes/admin/admin.jsx @@ -261,7 +261,9 @@ export default class Admin extends Component { newprefixStatus: error.response.status, }) if (!response.data.error) { - this.loadUsers() + this.setState({ + prefixes: response.data.prefixes + }) } }) .catch(error => { @@ -535,6 +537,7 @@ export default class Admin extends Component { this.setState(p)} prefixesLoading={this.state.prefixesLoading} />
+ From 0224efa3ee845f1c1bf450a82e8d1036afd6c6bb Mon Sep 17 00:00:00 2001 From: mboudet Date: Tue, 16 Nov 2021 14:28:09 +0000 Subject: [PATCH 017/113] Fix routes --- askomics/api/admin.py | 2 +- askomics/libaskomics/PrefixManager.py | 4 ++-- askomics/react/src/routes/admin/admin.jsx | 9 ++------- .../react/src/routes/admin/prefixestable.jsx | 16 ++++++++-------- 4 files changed, 13 insertions(+), 18 deletions(-) diff --git a/askomics/api/admin.py b/askomics/api/admin.py index 313633fa..0822ecc7 100644 --- a/askomics/api/admin.py +++ b/askomics/api/admin.py @@ -621,7 +621,7 @@ def delete_prefix(): pm = PrefixManager(current_app, session) try: - pm.delete_custom_prefixes(data.get("prefixesIdToDelete")) + pm.remove_custom_prefixes(data.get("prefixesIdToDelete")) prefixes = pm.get_custom_prefixes() except Exception as e: traceback.print_exc(file=sys.stdout) diff --git a/askomics/libaskomics/PrefixManager.py b/askomics/libaskomics/PrefixManager.py index e52c21a5..b3ff0999 100644 --- a/askomics/libaskomics/PrefixManager.py +++ b/askomics/libaskomics/PrefixManager.py @@ -139,7 +139,7 @@ def add_custom_prefix(self, prefix, namespace): ) ''' - database.execute_sql_query(query, (prefix, namespace)) + database.execute_sql_query(query, (prefix, namespace,)) def remove_custom_prefixes(self, prefixes_id): """Create a new custom (admin-defined) prefixes @@ -157,4 +157,4 @@ def remove_custom_prefixes(self, prefixes_id): ''' for prefix_id in prefixes_id: - database.execute_sql_query(query, (prefix_id)) + database.execute_sql_query(query, (prefix_id,)) diff --git a/askomics/react/src/routes/admin/admin.jsx b/askomics/react/src/routes/admin/admin.jsx index b2c968b3..18aa0438 100644 --- a/askomics/react/src/routes/admin/admin.jsx +++ b/askomics/react/src/routes/admin/admin.jsx @@ -258,13 +258,8 @@ export default class Admin extends Component { newprefixError: response.data.error, newprefixErrorMessage: response.data.errorMessage, prefixes: response.data.prefixes, - newprefixStatus: error.response.status, + newprefixStatus: response.status, }) - if (!response.data.error) { - this.setState({ - prefixes: response.data.prefixes - }) - } }) .catch(error => { console.log(error, error.response.data.errorMessage) @@ -535,7 +530,7 @@ export default class Admin extends Component {
- this.setState(p)} prefixesLoading={this.state.prefixesLoading} /> + this.setState(p)} prefixesSelected={this.state.prefixesSelected} prefixesLoading={this.state.prefixesLoading} />
diff --git a/askomics/react/src/routes/admin/prefixestable.jsx b/askomics/react/src/routes/admin/prefixestable.jsx index 90417352..809d2ed9 100644 --- a/askomics/react/src/routes/admin/prefixestable.jsx +++ b/askomics/react/src/routes/admin/prefixestable.jsx @@ -19,12 +19,13 @@ export default class PrefixesTable extends Component { } handlePrefixSelection (row, isSelect) { + console.log('triggered') if (isSelect) { - this.props.setStateUsers({ + this.props.setStatePrefixes({ prefixesSelected: [...this.props.prefixesSelected, row.id] }) } else { - this.props.setStateUsers({ + this.props.setStatePrefixes({ prefixesSelected: this.props.prefixesSelected.filter(x => x !== row.id) }) } @@ -33,11 +34,11 @@ export default class PrefixesTable extends Component { handlePrefixSelectionAll (isSelect, rows) { const prefixes = rows.map(r => r.id) if (isSelect) { - this.props.setStateUsers({ + this.props.setStatePrefixes({ prefixesSelected: prefixes }) } else { - this.props.setStateUsers({ + this.props.setStatePrefixes({ prefixesSelected: [] }) } @@ -65,9 +66,8 @@ export default class PrefixesTable extends Component { mode: 'checkbox', clickToSelect: false, selected: this.props.prefixesSelected, - onSelect: this.handleUserSelection, - onSelectAll: this.handleUserSelectionAll, - nonSelectable: [this.props.config.user.username] + onSelect: this.handlePrefixSelection, + onSelectAll: this.handlePrefixSelectionAll } let prefixesNoDataIndication = 'No custom prefixes' @@ -82,7 +82,7 @@ export default class PrefixesTable extends Component { wrapperClasses="asko-table-wrapper" tabIndexCell bootstrap4 - keyField='prefix' + keyField='id' data={this.props.prefixes} columns={prefixesColumns} defaultSorted={prefixesDefaultSorted} From cddbcc23d4e7d9f42fe4790a17f86cbef9191543 Mon Sep 17 00:00:00 2001 From: mboudet Date: Wed, 17 Nov 2021 15:26:40 +0100 Subject: [PATCH 018/113] Move prefixes to their own tab --- askomics/react/src/navbar.jsx | 3 +- askomics/react/src/routes.jsx | 2 + askomics/react/src/routes/admin/admin.jsx | 130 --------- askomics/react/src/routes/admin/prefixes.jsx | 256 ++++++++++++++++++ .../react/src/routes/admin/prefixestable.jsx | 104 ------- 5 files changed, 260 insertions(+), 235 deletions(-) create mode 100644 askomics/react/src/routes/admin/prefixes.jsx delete mode 100644 askomics/react/src/routes/admin/prefixestable.jsx diff --git a/askomics/react/src/navbar.jsx b/askomics/react/src/navbar.jsx index ff3062fc..5c48f518 100644 --- a/askomics/react/src/navbar.jsx +++ b/askomics/react/src/navbar.jsx @@ -43,6 +43,7 @@ export default class AskoNavbar extends Component { adminLinks = ( Admin + Prefixes ) } @@ -104,4 +105,4 @@ export default class AskoNavbar extends Component { AskoNavbar.propTypes = { waitForStart: PropTypes.bool, config: PropTypes.object -} \ No newline at end of file +} diff --git a/askomics/react/src/routes.jsx b/askomics/react/src/routes.jsx index 6555c070..488b439c 100644 --- a/askomics/react/src/routes.jsx +++ b/askomics/react/src/routes.jsx @@ -14,6 +14,7 @@ import Logout from './routes/login/logout' import PasswordReset from './routes/login/passwordreset' import Account from './routes/account/account' import Admin from './routes/admin/admin' +import Prefixes from './routes/admin/prefixes' import Sparql from './routes/sparql/sparql' import FormQuery from './routes/form/query' import FormEditQuery from './routes/form_edit/query' @@ -113,6 +114,7 @@ export default class Routes extends Component { this.setState(p)} {...props}/>}/> ( this.setState(p)} />)} /> ( this.setState(p)} />)} /> + ( this.setState(p)} />)} /> diff --git a/askomics/react/src/routes/admin/admin.jsx b/askomics/react/src/routes/admin/admin.jsx index 18aa0438..9f4bfb63 100644 --- a/askomics/react/src/routes/admin/admin.jsx +++ b/askomics/react/src/routes/admin/admin.jsx @@ -6,7 +6,6 @@ import DatasetsTable from './datasetstable' import FilesTable from './filestable' import QueriesTable from './queriestable' import UsersTable from './userstable' -import PrefixesTable from './prefixestable' import Utils from '../../classes/utils' import { Redirect } from 'react-router-dom' import WaitingDiv from '../../components/waiting' @@ -30,19 +29,12 @@ export default class Admin extends Component { datasetErrorMessage: '', queryError: false, queryErrorMessage: '', - prefixError: false, - prefixErrorMessage: '', - newprefixError: false, - newprefixErrorMessage: '', users: [], datasets: [], files: [], queries: [], - prefixes: [], fname: "", lname: "", - prefix: "", - namespace: "", username: "", email: "", newUser: {}, @@ -52,20 +44,15 @@ export default class Admin extends Component { usersSelected: [], filesSelected: [], datasetsSelected: [], - prefixesSelected: [] } this.handleChangeUserInput = this.handleChangeUserInput.bind(this) this.handleChangeFname = this.handleChangeFname.bind(this) this.handleChangeLname = this.handleChangeLname.bind(this) this.handleAddUser = this.handleAddUser.bind(this) - this.handleChangePrefix = this.handleChangePrefix.bind(this) - this.handleChangeNamespace = this.handleChangeNamespace.bind(this) - this.handleAddPrefix = this.handleAddPrefix.bind(this) this.dismissMessage = this.dismissMessage.bind(this) this.deleteSelectedUsers = this.deleteSelectedUsers.bind(this) this.deleteSelectedFiles = this.deleteSelectedFiles.bind(this) this.deleteSelectedDatasets = this.deleteSelectedDatasets.bind(this) - this.deleteSelectedPrefixes = this.deleteSelectedPrefixes.bind(this) this.cancelRequest } @@ -81,10 +68,6 @@ export default class Admin extends Component { return this.state.datasetsSelected.length == 0 } - isPrefixesDisabled () { - return this.state.prefixesSelected.length == 0 - } - deleteSelectedUsers () { let requestUrl = '/api/admin/delete_users' let data = { @@ -130,21 +113,6 @@ export default class Admin extends Component { }) } - deleteSelectedPrefixes () { - let requestUrl = '/api/admin/delete_prefixes' - let data = { - prefixesIdToDelete: this.state.prefixesSelected - } - axios.post(requestUrl, data, { baseURL: this.props.config.proxyPath, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) - .then(response => { - console.log(requestUrl, response.data) - this.setState({ - prefixes: response.data.prefixes, - prefixesSelected: [], - }) - }) - } - dismissMessage () { this.setState({ newUser: {}, @@ -172,18 +140,6 @@ export default class Admin extends Component { }) } - handleChangePrefix (event) { - this.setState({ - prefix: event.target.value - }) - } - - handleChangeNamespace (event) { - this.setState({ - namespace: event.target.value - }) - } - validateEmail (email) { let re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ return re.test(String(email).toLowerCase()) @@ -198,13 +154,6 @@ export default class Admin extends Component { ) } - validatePrefixForm () { - return ( - this.state.prefix.length > 0 && - this.state.namespace.length > 0 - ) - } - handleAddUser(event) { let requestUrl = "/api/admin/adduser" @@ -243,42 +192,12 @@ export default class Admin extends Component { event.preventDefault() } - handleAddPrefix(event) { - - let requestUrl = "/api/admin/addprefix" - let data = { - prefix: this.state.prefix, - namespace: this.state.namespace, - } - - axios.post(requestUrl, data, { baseURL: this.props.config.proxyPath, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) - .then(response => { - console.log(requestUrl, response.data) - this.setState({ - newprefixError: response.data.error, - newprefixErrorMessage: response.data.errorMessage, - prefixes: response.data.prefixes, - newprefixStatus: response.status, - }) - }) - .catch(error => { - console.log(error, error.response.data.errorMessage) - this.setState({ - newprefixError: true, - newprefixErrorMessage: error.response.data.errorMessage, - newprefixStatus: error.response.status, - }) - }) - event.preventDefault() - } - componentDidMount () { if (!this.props.waitForStart) { this.loadUsers() this.loadDataSets() this.loadFiles() this.loadQueries() - this.loadPrefixes() this.interval = setInterval(() => { this.loadDataSets() }, 5000) @@ -373,27 +292,6 @@ export default class Admin extends Component { }) } - loadPrefixes() { - let requestUrl = '/api/admin/getprefixes' - axios.get(requestUrl, { baseURL: this.props.config.proxyPath, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) - .then(response => { - console.log(requestUrl, response.data) - this.setState({ - prefixesLoading: false, - prefixes: response.data.prefixes - }) - }) - .catch(error => { - console.log(error, error.response.data.errorMessage) - this.setState({ - prefixError: true, - prefixErrorMessage: error.response.data.errorMessage, - prefixStatus: error.response.status, - success: !error.response.data.error - }) - }) - } - componentWillUnmount () { clearInterval(this.interval) if (!this.props.waitForStart) { @@ -507,34 +405,6 @@ export default class Admin extends Component {
-

Prefixes

-
Add a prefix
-
-
- - - - - - - - - - - - - - - -
- -
-
- this.setState(p)} prefixesSelected={this.state.prefixesSelected} prefixesLoading={this.state.prefixesLoading} /> -
- - - ) } diff --git a/askomics/react/src/routes/admin/prefixes.jsx b/askomics/react/src/routes/admin/prefixes.jsx new file mode 100644 index 00000000..bdb1bbed --- /dev/null +++ b/askomics/react/src/routes/admin/prefixes.jsx @@ -0,0 +1,256 @@ +import React, { Component } from 'react' +import axios from 'axios' +import {Button, Form, FormGroup, Label, Input, Alert, Row, Col, CustomInput } from 'reactstrap' +import PropTypes from 'prop-types' +import Utils from '../../classes/utils' +import { Redirect } from 'react-router-dom' +import WaitingDiv from '../../components/waiting' +import ErrorDiv from '../error/error' + +export default class Prefixes extends Component { + constructor (props) { + super(props) + this.utils = new Utils() + this.state = { + error: false, + errorMessage: '', + prefixError: false, + prefixErrorMessage: '', + newprefixError: false, + newprefixErrorMessage: '', + prefixes: [], + prefix: "", + namespace: "", + prefixesSelected: [] + } + this.handleChangePrefix = this.handleChangePrefix.bind(this) + this.handleChangeNamespace = this.handleChangeNamespace.bind(this) + this.handleAddPrefix = this.handleAddPrefix.bind(this) + this.dismissMessage = this.dismissMessage.bind(this) + this.deleteSelectedPrefixes = this.deleteSelectedPrefixes.bind(this) + this.handlePrefixSelection = this.handlePrefixSelection.bind(this) + this.handlePrefixSelectionAll = this.handlePrefixSelectionAll.bind(this) + this.cancelRequest + } + + isPrefixesDisabled () { + return this.state.prefixesSelected.length == 0 + } + + deleteSelectedPrefixes () { + let requestUrl = '/api/admin/delete_prefixes' + let data = { + prefixesIdToDelete: this.state.prefixesSelected + } + axios.post(requestUrl, data, { baseURL: this.props.config.proxyPath, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) + .then(response => { + console.log(requestUrl, response.data) + this.setState({ + prefixes: response.data.prefixes, + prefixesSelected: [], + }) + }) + } + + handleChangePrefix (event) { + this.setState({ + prefix: event.target.value + }) + } + + handleChangeNamespace (event) { + this.setState({ + namespace: event.target.value + }) + } + + validatePrefixForm () { + return ( + this.state.prefix.length > 0 && + this.state.namespace.length > 0 + ) + } + + handlePrefixSelection (row, isSelect) { + if (isSelect) { + this.setState({ + prefixesSelected: [...this.state.prefixesSelected, row.id] + }) + } else { + this.setState({ + prefixesSelected: this.state.prefixesSelected.filter(x => x !== row.id) + }) + } + } + + handlePrefixSelectionAll (isSelect, rows) { + const prefixes = rows.map(r => r.id) + if (isSelect) { + this.setState({ + prefixesSelected: prefixes + }) + } else { + this.setState({ + prefixesSelected: [] + }) + } + } + + handleAddPrefix(event) { + + let requestUrl = "/api/admin/addprefix" + let data = { + prefix: this.state.prefix, + namespace: this.state.namespace, + } + + axios.post(requestUrl, data, { baseURL: this.props.config.proxyPath, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) + .then(response => { + console.log(requestUrl, response.data) + this.setState({ + newprefixError: response.data.error, + newprefixErrorMessage: response.data.errorMessage, + prefixes: response.data.prefixes, + newprefixStatus: response.status, + }) + }) + .catch(error => { + console.log(error, error.response.data.errorMessage) + this.setState({ + newprefixError: true, + newprefixErrorMessage: error.response.data.errorMessage, + newprefixStatus: error.response.status, + }) + }) + event.preventDefault() + } + + componentDidMount () { + if (!this.props.waitForStart) { + this.loadPrefixes() + } + } + + loadPrefixes() { + let requestUrl = '/api/admin/getprefixes' + axios.get(requestUrl, { baseURL: this.props.config.proxyPath, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) + .then(response => { + this.setState({ + prefixesLoading: false, + prefixes: response.data.prefixes + }) + }) + .catch(error => { + console.log(error, error.response.data.errorMessage) + this.setState({ + prefixError: true, + prefixErrorMessage: error.response.data.errorMessage, + prefixStatus: error.response.status, + success: !error.response.data.error + }) + }) + } + + componentWillUnmount () { + if (!this.props.waitForStart) { + this.cancelRequest() + } + } + + render () { + + if (!this.props.waitForStart && !this.props.config.logged) { + return + } + if (!this.props.waitForStart && this.props.config.user.admin != 1) { + return + } + + if (this.props.waitForStart) { + return + } + + let prefixesColumns = [{ + editable: false, + dataField: 'prefix', + text: 'Prefix', + sort: true + }, { + editable: false, + dataField: 'namespace', + text: 'Namespace', + sort: true + }] + + let prefixesDefaultSorted = [{ + dataField: 'prefix', + order: 'asc' + }] + + let prefixesSelectRow = { + mode: 'checkbox', + clickToSelect: false, + selected: this.state.prefixesSelected, + onSelect: this.handlePrefixSelection, + onSelectAll: this.handlePrefixSelectionAll + } + + let prefixesNoDataIndication = 'No custom prefixes' + if (this.state.prefixesLoading) { + prefixesNoDataIndication = + } + + return ( +
+

Admin

+
+

Add a prefix

+
+
+ + + + + + + + + + + + + + + +
+ +
+
+
+
+ +
+
+ + +
+ ) + } +} + +Prefixes.propTypes = { + waitForStart: PropTypes.bool, + config: PropTypes.object +} diff --git a/askomics/react/src/routes/admin/prefixestable.jsx b/askomics/react/src/routes/admin/prefixestable.jsx deleted file mode 100644 index 809d2ed9..00000000 --- a/askomics/react/src/routes/admin/prefixestable.jsx +++ /dev/null @@ -1,104 +0,0 @@ -import React, { Component } from 'react' -import axios from 'axios' -import {Badge, Button, Form, FormGroup, Label, Input, Alert, Row, Col, CustomInput } from 'reactstrap' -import BootstrapTable from 'react-bootstrap-table-next' -import paginationFactory from 'react-bootstrap-table2-paginator' -import cellEditFactory from 'react-bootstrap-table2-editor' -import update from 'react-addons-update' -import PropTypes from 'prop-types' -import Utils from '../../classes/utils' -import WaitingDiv from '../../components/waiting' -import pretty from 'pretty-time' - -export default class PrefixesTable extends Component { - constructor (props) { - super(props) - this.utils = new Utils() - this.handlePrefixSelection = this.handlePrefixSelection.bind(this) - this.handlePrefixSelectionAll = this.handlePrefixSelectionAll.bind(this) - } - - handlePrefixSelection (row, isSelect) { - console.log('triggered') - if (isSelect) { - this.props.setStatePrefixes({ - prefixesSelected: [...this.props.prefixesSelected, row.id] - }) - } else { - this.props.setStatePrefixes({ - prefixesSelected: this.props.prefixesSelected.filter(x => x !== row.id) - }) - } - } - - handlePrefixSelectionAll (isSelect, rows) { - const prefixes = rows.map(r => r.id) - if (isSelect) { - this.props.setStatePrefixes({ - prefixesSelected: prefixes - }) - } else { - this.props.setStatePrefixes({ - prefixesSelected: [] - }) - } - } - - render () { - let prefixesColumns = [{ - editable: false, - dataField: 'prefix', - text: 'Prefix', - sort: true - }, { - editable: false, - dataField: 'namespace', - text: 'Namespace', - sort: true - }] - - let prefixesDefaultSorted = [{ - dataField: 'prefix', - order: 'asc' - }] - - let prefixesSelectRow = { - mode: 'checkbox', - clickToSelect: false, - selected: this.props.prefixesSelected, - onSelect: this.handlePrefixSelection, - onSelectAll: this.handlePrefixSelectionAll - } - - let prefixesNoDataIndication = 'No custom prefixes' - if (this.props.prefixesLoading) { - prefixesNoDataIndication = - } - - return ( -
- -
- ) - } -} - -PrefixesTable.propTypes = { - setStatePrefixes: PropTypes.func, - prefixesSelected: PropTypes.object, - prefixesLoading: PropTypes.bool, - prefixes: PropTypes.array, - config: PropTypes.object -} From e4f00c77261391e9a47d6b6ea4d3faace369ac89 Mon Sep 17 00:00:00 2001 From: mboudet Date: Wed, 17 Nov 2021 14:35:58 +0000 Subject: [PATCH 019/113] typo --- askomics/react/src/routes/admin/prefixes.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/askomics/react/src/routes/admin/prefixes.jsx b/askomics/react/src/routes/admin/prefixes.jsx index bdb1bbed..8be17418 100644 --- a/askomics/react/src/routes/admin/prefixes.jsx +++ b/askomics/react/src/routes/admin/prefixes.jsx @@ -1,6 +1,8 @@ import React, { Component } from 'react' import axios from 'axios' import {Button, Form, FormGroup, Label, Input, Alert, Row, Col, CustomInput } from 'reactstrap' +import BootstrapTable from 'react-bootstrap-table-next' +import paginationFactory from 'react-bootstrap-table2-paginator' import PropTypes from 'prop-types' import Utils from '../../classes/utils' import { Redirect } from 'react-router-dom' @@ -26,7 +28,6 @@ export default class Prefixes extends Component { this.handleChangePrefix = this.handleChangePrefix.bind(this) this.handleChangeNamespace = this.handleChangeNamespace.bind(this) this.handleAddPrefix = this.handleAddPrefix.bind(this) - this.dismissMessage = this.dismissMessage.bind(this) this.deleteSelectedPrefixes = this.deleteSelectedPrefixes.bind(this) this.handlePrefixSelection = this.handlePrefixSelection.bind(this) this.handlePrefixSelectionAll = this.handlePrefixSelectionAll.bind(this) @@ -226,7 +227,7 @@ export default class Prefixes extends Component {
-
+
Date: Wed, 17 Nov 2021 17:11:02 +0100 Subject: [PATCH 020/113] Fix test (1) --- tests/results/abstraction.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/results/abstraction.json b/tests/results/abstraction.json index 4aa59849..c7efb162 100644 --- a/tests/results/abstraction.json +++ b/tests/results/abstraction.json @@ -571,6 +571,7 @@ "instancesHaveLabels": true, "label": "transcript", "type": "node", + "ontology": false, "uri": "http://askomics.org/test/data/transcript" }, { @@ -585,6 +586,7 @@ "instancesHaveLabels": true, "label": "gene", "type": "node", + "ontology": false, "uri": "http://askomics.org/test/data/gene" }, { @@ -598,6 +600,7 @@ "instancesHaveLabels": true, "label": "QTL", "type": "node", + "ontology": false, "uri": "http://askomics.org/test/data/QTL" }, { @@ -611,6 +614,7 @@ "instancesHaveLabels": true, "label": "DifferentialExpression", "type": "node", + "ontology": false, "uri": "http://askomics.org/test/data/DifferentialExpression" } ], From b61299f16eb5749402a60788e1e52d067a3cc1ce Mon Sep 17 00:00:00 2001 From: mboudet Date: Thu, 18 Nov 2021 09:56:52 +0100 Subject: [PATCH 021/113] Tests for custom prefixes --- tests/conftest.py | 6 +++ tests/test_api_admin.py | 88 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index fb30c607..bfcb7cf2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,6 +14,7 @@ from askomics.libaskomics.FilesHandler import FilesHandler from askomics.libaskomics.FilesUtils import FilesUtils from askomics.libaskomics.LocalAuth import LocalAuth +from askomics.libaskomics.PrefixManager import PrefixManager from askomics.libaskomics.SparqlQueryLauncher import SparqlQueryLauncher from askomics.libaskomics.Start import Start from askomics.libaskomics.Result import Result @@ -574,6 +575,11 @@ def delete_galaxy_history(self): galaxy = GalaxyInstance(self.gurl, self.gkey) galaxy.histories.delete_history(self.galaxy_history["id"], purge=True) + def create_prefix(self): + """Create custom prefix""" + pm = PrefixManager(self.app, self.session) + pm.add_custom_prefix("OBO", "http://purl.obolibrary.org/obo/") + @staticmethod def get_random_string(number): """return a random string of n character diff --git a/tests/test_api_admin.py b/tests/test_api_admin.py index a6adf370..60f43ef7 100644 --- a/tests/test_api_admin.py +++ b/tests/test_api_admin.py @@ -491,3 +491,91 @@ def test_delete_datasets(self, client): assert response.json["datasets"][0]["status"] == "queued" assert response.json["datasets"][1]["status"] == "queued" assert response.json["datasets"][2]["status"] == "queued" + + def test_view_custom_prefixes(self, client): + """test /api/admin/getprefixes route""" + client.create_two_users() + client.log_user("jsmith") + + response = client.client.get('/api/admin/api/admin/getprefixes') + assert response.status_code == 401 + + client.log_user("jdoe") + + expected_empty = { + "error": False, + "errorMessage": "", + "prefixes": [] + } + + response = client.client.get('/api/admin/api/admin/getprefixes') + assert response.status_code == 200 + assert response.json == expected_empty + + client.create_prefix() + + response = client.client.get('/api/admin/api/admin/getprefixes') + + expected = { + "error": False, + "errorMessage": "", + "prefixes": [{ + "id": 1, + "namespace": "http://purl.obolibrary.org/obo/", + "prefix": "OBO" + }] + } + + assert response.status_code == 200 + assert response.json == expected + + def test_add_custom_prefix(self, client): + """test /api/admin/addprefix route""" + client.create_two_users() + client.log_user("jsmith") + + data = {"prefix": "OBO", "namespace": "http://purl.obolibrary.org/obo/"} + + response = client.client.post('/api/admin/addprefix', json=data) + assert response.status_code == 401 + + client.log_user("jdoe") + + response = client.client.post('/api/admin/addprefix', json=data) + + expected = { + "error": False, + "errorMessage": "", + "prefixes": [{ + "id": 1, + "namespace": "http://purl.obolibrary.org/obo/", + "prefix": "OBO" + }] + } + + assert response.status_code == 200 + assert response.json == expected + + def test_delete_custom_prefix(self, client): + """test /api/admin/delete_prefixes route""" + client.create_two_users() + client.log_user("jsmith") + + data = {"prefixesIdToDelete": [1]} + + response = client.client.post('/api/admin/delete_prefixes', json=data) + assert response.status_code == 401 + + client.log_user("jdoe") + client.create_prefix() + + response = client.client.post('/api/admin/delete_prefixes', json=data) + + expected = { + "error": False, + "errorMessage": "", + "prefixes": [] + } + + assert response.status_code == 200 + assert response.json == expected From c6a1b450c4d4470967d6043613e80678f299b6f0 Mon Sep 17 00:00:00 2001 From: mboudet Date: Thu, 18 Nov 2021 11:01:20 +0100 Subject: [PATCH 022/113] Typo in tests --- tests/test_api_admin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_api_admin.py b/tests/test_api_admin.py index 60f43ef7..126cc404 100644 --- a/tests/test_api_admin.py +++ b/tests/test_api_admin.py @@ -497,7 +497,7 @@ def test_view_custom_prefixes(self, client): client.create_two_users() client.log_user("jsmith") - response = client.client.get('/api/admin/api/admin/getprefixes') + response = client.client.get('/api/admin/getprefixes') assert response.status_code == 401 client.log_user("jdoe") @@ -508,13 +508,13 @@ def test_view_custom_prefixes(self, client): "prefixes": [] } - response = client.client.get('/api/admin/api/admin/getprefixes') + response = client.client.get('/api/admin/getprefixes') assert response.status_code == 200 assert response.json == expected_empty client.create_prefix() - response = client.client.get('/api/admin/api/admin/getprefixes') + response = client.client.get('/api/admin/getprefixes') expected = { "error": False, From 9f499d2e44c0986c79b17f5b34cbfbdb3206f9ae Mon Sep 17 00:00:00 2001 From: mboudet Date: Thu, 18 Nov 2021 17:18:36 +0100 Subject: [PATCH 023/113] Test ontologies management --- askomics/api/file.py | 6 + askomics/libaskomics/CsvFile.py | 27 +++++ askomics/libaskomics/Database.py | 13 ++- askomics/libaskomics/OntologyManager.py | 129 +++++++++++++++++++++ tests/results/preview_files.json | 3 +- tests/results/preview_malformed_files.json | 3 +- 6 files changed, 178 insertions(+), 3 deletions(-) create mode 100644 askomics/libaskomics/OntologyManager.py diff --git a/askomics/api/file.py b/askomics/api/file.py index 45be76a0..2a588370 100644 --- a/askomics/api/file.py +++ b/askomics/api/file.py @@ -8,6 +8,7 @@ from askomics.libaskomics.FilesHandler import FilesHandler from askomics.libaskomics.FilesUtils import FilesUtils from askomics.libaskomics.Dataset import Dataset +from askomics.libaskomics.OntologyManager import OntologyManager from askomics.libaskomics.RdfFile import RdfFile from flask import (Blueprint, current_app, jsonify, request, send_from_directory, session) @@ -237,6 +238,9 @@ def get_preview(): }), 400 try: + ontologies_manager = OntologyManager(current_app, session) + ontologies = ontologies_manager.list_ontologies() + files_handler = FilesHandler(current_app, session) files_handler.handle_files(data['filesId']) @@ -249,12 +253,14 @@ def get_preview(): traceback.print_exc(file=sys.stdout) return jsonify({ 'previewFiles': [], + 'ontologies': [], 'error': True, 'errorMessage': str(e) }), 500 return jsonify({ 'previewFiles': results, + 'ontologies': ontologies, 'error': False, 'errorMessage': '' }) diff --git a/askomics/libaskomics/CsvFile.py b/askomics/libaskomics/CsvFile.py index 64d8aa37..00a53c87 100644 --- a/askomics/libaskomics/CsvFile.py +++ b/askomics/libaskomics/CsvFile.py @@ -8,6 +8,7 @@ from rdflib import BNode from askomics.libaskomics.File import File +from askomics.libaskomics.OntologyManager import OntologyManager from askomics.libaskomics.Utils import cached_property @@ -396,6 +397,10 @@ def set_rdf_abstraction(self): if self.columns_type[0] == 'start_entity': self.graph_abstraction_dk.add((entity, rdflib.RDF.type, self.namespace_internal['startPoint'])) + available_ontologies = {} + for ontology in OntologyManager(self.app, self.session).list_ontologies(): + available_ontologies[ontology['name']] = ontology['uri'] + # Attributes and relations for index, attribute_name in enumerate(self.header): @@ -436,6 +441,28 @@ def set_rdf_abstraction(self): continue + # Manage ontologies + if self.columns_type[index] in available_ontologies: + + attribute = self.rdfize(attribute_name) + label = rdflib.Literal(attribute_name) + rdf_range = self.rdfize(available_ontologies[self.columns_type[index]]) + rdf_type = rdflib.OWL.ObjectProperty + + # New way of storing relations (starting from 4.4.0) + blank = BNode() + endpoint = rdflib.Literal(self.external_endpoint) if self.external_endpoint else rdflib.Literal(self.settings.get('triplestore', 'endpoint')) + self.graph_abstraction_dk.add((blank, rdflib.RDF.type, rdflib.OWL.ObjectProperty)) + self.graph_abstraction_dk.add((blank, rdflib.RDF.type, self.namespace_internal["AskomicsRelation"])) + self.graph_abstraction_dk.add((blank, self.namespace_internal["uri"], attribute)) + self.graph_abstraction_dk.add((blank, rdflib.RDFS.label, label)) + self.graph_abstraction_dk.add((blank, rdflib.RDFS.domain, entity)) + self.graph_abstraction_dk.add((blank, rdflib.RDFS.range, rdf_range)) + self.graph_abstraction_dk.add((blank, rdflib.DCAT.endpointURL, endpoint)) + self.graph_abstraction_dk.add((blank, rdflib.DCAT.dataset, rdflib.Literal(self.name))) + + continue + # Category elif self.columns_type[index] in ('category', 'reference', 'strand'): attribute = self.rdfize(attribute_name) diff --git a/askomics/libaskomics/Database.py b/askomics/libaskomics/Database.py index ebc58e05..29b63917 100644 --- a/askomics/libaskomics/Database.py +++ b/askomics/libaskomics/Database.py @@ -77,6 +77,7 @@ def init_database(self): self.create_datasets_table() self.create_abstraction_table() self.create_prefixes_table() + self.create_ontologies_table() def create_user_table(self): """Create the user table""" @@ -397,4 +398,14 @@ def create_prefixes_table(self): self.execute_sql_query(query) def create_ontologies_table(self): - pass + """Create the ontologies table""" + query = ''' + CREATE TABLE IF NOT EXISTS ontologies ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name text NOT NULL, + uri text NOT NULL, + full_name text NOT NULL, + type text DEFAULT 'local', + ) + ''' + self.execute_sql_query(query) diff --git a/askomics/libaskomics/OntologyManager.py b/askomics/libaskomics/OntologyManager.py new file mode 100644 index 00000000..f2cc439a --- /dev/null +++ b/askomics/libaskomics/OntologyManager.py @@ -0,0 +1,129 @@ +from askomics.libaskomics.Database import Database +from askomics.libaskomics.Params import Params + + +class OntologyManager(Params): + """Manage ontologies + + Attributes + ---------- + namespace_internal : str + askomics namespace, from config file + namespace_data : str + askomics prefix, from config file + prefix : dict + dict of all prefixes + """ + + def __init__(self, app, session): + """init + + Parameters + ---------- + app : Flask + Flask app + session : + AskOmics session + """ + Params.__init__(self, app, session) + + def list_ontologies(self): + """Get all ontologies + + Returns + ------- + list + ontologies + """ + + database = Database(self.app, self.session) + + query = ''' + SELECT id, name, uri, full_name, type + FROM ontologies + ''' + + rows = database.execute_sql_query(query) + + ontologies = [] + for row in rows: + prefix = { + 'id': row[0], + 'name': row[1], + 'uri': row[2], + 'full_name': row[3], + 'type': row[4] + } + ontologies.append(prefix) + + return ontologies + + def get_ontology(self, ontology_name): + """Get a specific ontology based on name + + Returns + ------- + dict + ontology + """ + + database = Database(self.app, self.session) + + query = ''' + SELECT id, name, uri, full_name, type + FROM ontologies + WHERE name = ? + ''' + + rows = database.execute_sql_query(query, (ontology_name,)) + + if not rows: + return None + + ontology = rows[0] + return { + 'id': ontology[0], + 'name': ontology[1], + 'uri': ontology[2], + 'full_name': ontology[3], + 'type': ontology[4] + } + + def add_ontology(self, name, uri, full_name, type="local"): + """Create a new ontology + + Returns + ------- + list of dict + Prefixes information + """ + database = Database(self.app, self.session) + + query = ''' + INSERT INTO ontologies VALUES( + NULL, + ?, + ?, + ?, + ? + ) + ''' + + database.execute_sql_query(query, (name, uri, full_name, type,)) + + def remove_ontologies(self, ontology_ids): + """Remove ontologies + + Returns + ------- + None + """ + database = Database(self.app, self.session) + + query = ''' + DELETE FROM ontologies + WHERE id = ? + ''' + + for ontology_id in ontology_ids: + database.execute_sql_query(query, (ontology_id,)) diff --git a/tests/results/preview_files.json b/tests/results/preview_files.json index beeb18c4..a73688db 100644 --- a/tests/results/preview_files.json +++ b/tests/results/preview_files.json @@ -181,5 +181,6 @@ "error": false, "error_message": "" } - ] +], +"ontologies": [] } diff --git a/tests/results/preview_malformed_files.json b/tests/results/preview_malformed_files.json index 2035e650..b99f0f2c 100644 --- a/tests/results/preview_malformed_files.json +++ b/tests/results/preview_malformed_files.json @@ -22,5 +22,6 @@ "error": true, "error_message": "Malformated CSV/TSV (Empty column in header)" } - ] +], + "ontologies": [] } From 0186471a75b6d2260968b7c5a26ed77bc41bf904 Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 19 Nov 2021 09:56:02 +0100 Subject: [PATCH 024/113] Typos --- askomics/libaskomics/Database.py | 2 +- askomics/libaskomics/OntologyManager.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/askomics/libaskomics/Database.py b/askomics/libaskomics/Database.py index 29b63917..9b9a54ff 100644 --- a/askomics/libaskomics/Database.py +++ b/askomics/libaskomics/Database.py @@ -405,7 +405,7 @@ def create_ontologies_table(self): name text NOT NULL, uri text NOT NULL, full_name text NOT NULL, - type text DEFAULT 'local', + type text DEFAULT 'local' ) ''' self.execute_sql_query(query) diff --git a/askomics/libaskomics/OntologyManager.py b/askomics/libaskomics/OntologyManager.py index f2cc439a..da33ab5f 100644 --- a/askomics/libaskomics/OntologyManager.py +++ b/askomics/libaskomics/OntologyManager.py @@ -82,12 +82,12 @@ def get_ontology(self, ontology_name): ontology = rows[0] return { - 'id': ontology[0], - 'name': ontology[1], - 'uri': ontology[2], - 'full_name': ontology[3], - 'type': ontology[4] - } + 'id': ontology[0], + 'name': ontology[1], + 'uri': ontology[2], + 'full_name': ontology[3], + 'type': ontology[4] + } def add_ontology(self, name, uri, full_name, type="local"): """Create a new ontology From b9b60aab02a401e0f345ed6ade37a007116a70a5 Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 19 Nov 2021 10:11:11 +0100 Subject: [PATCH 025/113] Fix test --- tests/test_api_file.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_api_file.py b/tests/test_api_file.py index a3a0f708..5e645d89 100644 --- a/tests/test_api_file.py +++ b/tests/test_api_file.py @@ -393,7 +393,8 @@ def test_get_preview(self, client): assert response.json == { 'error': False, 'errorMessage': '', - 'previewFiles': [] + 'previewFiles': [], + 'ontologies': [] } response = client.client.post('/api/files/preview', json=malformed_data) @@ -421,7 +422,8 @@ def test_get_preview(self, client): 'type': 'gff/gff3', 'error': False, 'error_message': '' - }] + }], + 'ontologies': [] } def test_delete_files(self, client): From 929a9c515307221163abf0a38b0638c6d21882a0 Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 19 Nov 2021 15:13:28 +0100 Subject: [PATCH 026/113] Test ontology interface --- askomics/api/admin.py | 127 +++++++++ askomics/libaskomics/Database.py | 2 +- askomics/libaskomics/OntologyManager.py | 20 +- askomics/react/src/navbar.jsx | 1 + askomics/react/src/routes.jsx | 2 + .../react/src/routes/admin/ontologies.jsx | 265 ++++++++++++++++++ .../react/src/routes/integration/csvtable.jsx | 17 +- .../src/routes/integration/integration.jsx | 6 +- 8 files changed, 426 insertions(+), 14 deletions(-) create mode 100644 askomics/react/src/routes/admin/ontologies.jsx diff --git a/askomics/api/admin.py b/askomics/api/admin.py index 0822ecc7..a54fa5b9 100644 --- a/askomics/api/admin.py +++ b/askomics/api/admin.py @@ -8,6 +8,7 @@ from askomics.libaskomics.LocalAuth import LocalAuth from askomics.libaskomics.Mailer import Mailer from askomics.libaskomics.PrefixManager import PrefixManager +from askomics.libaskomics.OntologyManager import OntologyManager from askomics.libaskomics.Result import Result from askomics.libaskomics.ResultsHandler import ResultsHandler @@ -636,3 +637,129 @@ def delete_prefix(): 'error': False, 'errorMessage': '' }) + + +@admin_bp.route("/api/admin/getontologies", methods=["GET"]) +@api_auth +@admin_required +def get_ontologies(): + """Get all ontologies + + Returns + ------- + json + prefixes: list of all custom prefixes + error: True if error, else False + errorMessage: the error message of error, else an empty string + """ + try: + om = OntologyManager(current_app, session) + ontologies = om.list_ontologies() + except Exception as e: + traceback.print_exc(file=sys.stdout) + return jsonify({ + 'ontologies': [], + 'error': True, + 'errorMessage': str(e) + }), 500 + + return jsonify({ + 'ontologies': ontologies, + 'error': False, + 'errorMessage': '' + }) + + +@admin_bp.route("/api/admin/addontology", methods=["POST"]) +@api_auth +@admin_required +def add_ontology(): + """Create a new ontology + + Returns + ------- + json + ontologies: list of all ontologies + error: True if error, else False + errorMessage: the error message of error, else an empty string + """ + + data = request.get_json() + if not data or not (data.get("name") and data.get("uri") and data.get("shortName")): + return jsonify({ + 'ontologies': [], + 'error': True, + 'errorMessage': "Missing parameter" + }), 400 + + om = OntologyManager(current_app, session) + ontologies = om.list_ontologies() + + name = data.get("name") + uri = data.get("uri") + short_name = data.get("shortName") + + if any([name == onto['name'] or short_name == onto['short_name'] for onto in ontologies]): + return jsonify({ + 'ontologies': [], + 'error': True, + 'errorMessage': "Name and short name must be unique" + }), 400 + + try: + om.add_ontology(name, uri, short_name) + ontologies = om.list_ontologies() + except Exception as e: + traceback.print_exc(file=sys.stdout) + return jsonify({ + 'ontologies': [], + 'error': True, + 'errorMessage': str(e) + }), 500 + + return jsonify({ + 'ontologies': ontologies, + 'error': False, + 'errorMessage': '' + }) + + +@admin_bp.route("/api/admin/delete_ontologies", methods=["POST"]) +@api_auth +@admin_required +def delete_ontologies(): + """Delete one or more ontologies + + Returns + ------- + json + ontologies: list of all ontologies + error: True if error, else False + errorMessage: the error message of error, else an empty string + """ + + data = request.get_json() + if not data or not data.get("ontologiesIdToDelete"): + return jsonify({ + 'ontologies': [], + 'error': True, + 'errorMessage': "Missing ontologiesIdToDelete parameter" + }), 400 + + om = OntologyManager(current_app, session) + try: + om.remove_ontologies(data.get("ontologiesIdToDelete")) + ontologies = om.list_ontologies() + except Exception as e: + traceback.print_exc(file=sys.stdout) + return jsonify({ + 'ontologies': [], + 'error': True, + 'errorMessage': str(e) + }), 500 + + return jsonify({ + 'ontologies': ontologies, + 'error': False, + 'errorMessage': '' + }) diff --git a/askomics/libaskomics/Database.py b/askomics/libaskomics/Database.py index 9b9a54ff..9d8cf269 100644 --- a/askomics/libaskomics/Database.py +++ b/askomics/libaskomics/Database.py @@ -404,7 +404,7 @@ def create_ontologies_table(self): id INTEGER PRIMARY KEY AUTOINCREMENT, name text NOT NULL, uri text NOT NULL, - full_name text NOT NULL, + short_name text NOT NULL, type text DEFAULT 'local' ) ''' diff --git a/askomics/libaskomics/OntologyManager.py b/askomics/libaskomics/OntologyManager.py index da33ab5f..0f1767e7 100644 --- a/askomics/libaskomics/OntologyManager.py +++ b/askomics/libaskomics/OntologyManager.py @@ -39,7 +39,7 @@ def list_ontologies(self): database = Database(self.app, self.session) query = ''' - SELECT id, name, uri, full_name, type + SELECT id, name, uri, short_name, type FROM ontologies ''' @@ -51,15 +51,15 @@ def list_ontologies(self): 'id': row[0], 'name': row[1], 'uri': row[2], - 'full_name': row[3], + 'short_name': row[3], 'type': row[4] } ontologies.append(prefix) return ontologies - def get_ontology(self, ontology_name): - """Get a specific ontology based on name + def get_ontology(self, short_name): + """Get a specific ontology based on short name Returns ------- @@ -70,12 +70,12 @@ def get_ontology(self, ontology_name): database = Database(self.app, self.session) query = ''' - SELECT id, name, uri, full_name, type + SELECT id, name, uri, short_name, type FROM ontologies - WHERE name = ? + WHERE short_name = ? ''' - rows = database.execute_sql_query(query, (ontology_name,)) + rows = database.execute_sql_query(query, (short_name,)) if not rows: return None @@ -85,11 +85,11 @@ def get_ontology(self, ontology_name): 'id': ontology[0], 'name': ontology[1], 'uri': ontology[2], - 'full_name': ontology[3], + 'short_name': ontology[3], 'type': ontology[4] } - def add_ontology(self, name, uri, full_name, type="local"): + def add_ontology(self, name, uri, short_name, type="local"): """Create a new ontology Returns @@ -109,7 +109,7 @@ def add_ontology(self, name, uri, full_name, type="local"): ) ''' - database.execute_sql_query(query, (name, uri, full_name, type,)) + database.execute_sql_query(query, (name, uri, short_name, type,)) def remove_ontologies(self, ontology_ids): """Remove ontologies diff --git a/askomics/react/src/navbar.jsx b/askomics/react/src/navbar.jsx index 5c48f518..9124eda0 100644 --- a/askomics/react/src/navbar.jsx +++ b/askomics/react/src/navbar.jsx @@ -44,6 +44,7 @@ export default class AskoNavbar extends Component { Admin Prefixes + Ontologies ) } diff --git a/askomics/react/src/routes.jsx b/askomics/react/src/routes.jsx index 488b439c..bcb79f54 100644 --- a/askomics/react/src/routes.jsx +++ b/askomics/react/src/routes.jsx @@ -15,6 +15,7 @@ import PasswordReset from './routes/login/passwordreset' import Account from './routes/account/account' import Admin from './routes/admin/admin' import Prefixes from './routes/admin/prefixes' +import Ontologies from './routes/admin/ontologies' import Sparql from './routes/sparql/sparql' import FormQuery from './routes/form/query' import FormEditQuery from './routes/form_edit/query' @@ -115,6 +116,7 @@ export default class Routes extends Component { ( this.setState(p)} />)} /> ( this.setState(p)} />)} /> ( this.setState(p)} />)} /> + ( this.setState(p)} />)} /> diff --git a/askomics/react/src/routes/admin/ontologies.jsx b/askomics/react/src/routes/admin/ontologies.jsx new file mode 100644 index 00000000..d99caafe --- /dev/null +++ b/askomics/react/src/routes/admin/ontologies.jsx @@ -0,0 +1,265 @@ +import React, { Component } from 'react' +import axios from 'axios' +import {Button, Form, FormGroup, Label, Input, Alert, Row, Col, CustomInput } from 'reactstrap' +import BootstrapTable from 'react-bootstrap-table-next' +import paginationFactory from 'react-bootstrap-table2-paginator' +import PropTypes from 'prop-types' +import Utils from '../../classes/utils' +import { Redirect } from 'react-router-dom' +import WaitingDiv from '../../components/waiting' +import ErrorDiv from '../error/error' + +export default class Ontologies extends Component { + constructor (props) { + super(props) + this.utils = new Utils() + this.state = { + error: false, + errorMessage: '', + ontologyError: false, + ontologyErrorMessage: '', + newontologyError: false, + newontologyErrorMessage: '', + ontologies: [], + name: "", + uri: "", + shortName:, + ontologiesSelected: [] + } + this.handleChangeValue = this.handleChangeValue.bind(this) + this.handleAddOntology = this.handleAddOntology.bind(this) + this.deleteSelectedOntologies = this.deleteSelectedOntologies.bind(this) + this.handleOntologySelection = this.handleOntologySelection.bind(this) + this.handleOntologySelectionAll = this.handleOntologySelectionAll.bind(this) + this.cancelRequest + } + + isOntologiesDisabled () { + return this.state.ontologiesSelected.length == 0 + } + + deleteSelectedOntologies () { + let requestUrl = '/api/admin/delete_ontologies' + let data = { + ontologiesIdToDelete: this.state.ontologiesSelected + } + axios.post(requestUrl, data, { baseURL: this.props.config.proxyPath, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) + .then(response => { + console.log(requestUrl, response.data) + this.setState({ + ontologies: response.data.ontologies, + ontologiesSelected: [], + }) + }) + } + + handleChangeValue (event) { + let data = {} + data[event.target.id] = event.target.value + this.setState(data) + } + + validateOntologyForm () { + return ( + this.state.name.length > 0 && + this.state.uri.length > 0 && + this.state.shortName.length > 0 + ) + } + + handleOntologySelection (row, isSelect) { + if (isSelect) { + this.setState({ + ontologiesSelected: [...this.state.ontologiesSelected, row.id] + }) + } else { + this.setState({ + ontologiesSelected: this.state.ontologiesSelected.filter(x => x !== row.id) + }) + } + } + + handleOntologySelectionAll (isSelect, rows) { + const ontologies = rows.map(r => r.id) + if (isSelect) { + this.setState({ + ontologiesSelected: ontologies + }) + } else { + this.setState({ + ontologiesSelected: [] + }) + } + } + + handleAddOntology(event) { + + let requestUrl = "/api/admin/addontology" + let data = { + name: this.state.name, + uri: this.state.uri, + shortName: this.state.shortName + } + + axios.post(requestUrl, data, { baseURL: this.props.config.proxyPath, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) + .then(response => { + console.log(requestUrl, response.data) + this.setState({ + newontologyError: response.data.error, + newontologyErrorMessage: response.data.errorMessage, + ontologies: response.data.ontologies, + newontologyStatus: response.status, + }) + }) + .catch(error => { + console.log(error, error.response.data.errorMessage) + this.setState({ + newontologyError: true, + newontologyErrorMessage: error.response.data.errorMessage, + newontologyStatus: error.response.status, + }) + }) + event.preventDefault() + } + + componentDidMount () { + if (!this.props.waitForStart) { + this.loadOntologies() + } + } + + loadOntologies() { + let requestUrl = '/api/admin/getontologies' + axios.get(requestUrl, { baseURL: this.props.config.proxyPath, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) + .then(response => { + this.setState({ + ontologiesLoading: false, + ontologies: response.data.ontologies + }) + }) + .catch(error => { + console.log(error, error.response.data.errorMessage) + this.setState({ + ontologyError: true, + ontologyErrorMessage: error.response.data.errorMessage, + ontologyStatus: error.response.status, + success: !error.response.data.error + }) + }) + } + + componentWillUnmount () { + if (!this.props.waitForStart) { + this.cancelRequest() + } + } + + render () { + + if (!this.props.waitForStart && !this.props.config.logged) { + return + } + if (!this.props.waitForStart && this.props.config.user.admin != 1) { + return + } + + if (this.props.waitForStart) { + return + } + + let ontologiesColumns = [{ + editable: false, + dataField: 'name', + text: 'Name', + sort: true + }, { + editable: false, + dataField: 'short_name', + text: 'Short name', + sort: true + }, { + editable: false, + dataField: 'uri', + text: 'Uri', + sort: true + } + ] + + let ontologiesDefaultSorted = [{ + dataField: 'short_name', + order: 'asc' + }] + + let ontologiesSelectRow = { + mode: 'checkbox', + clickToSelect: false, + selected: this.state.ontologiesSelected, + onSelect: this.handleOntologySelection, + onSelectAll: this.handleOntologySelectionAll + } + + let ontologiesNoDataIndication = 'No ontologies' + if (this.state.ontologiesLoading) { + ontologiesNoDataIndication = + } + + return ( +
+

Admin

+
+

Add a ontology

+
+
+ + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+
+ +
+
+ + +
+ ) + } +} + +Ontologies.propTypes = { + waitForStart: PropTypes.bool, + config: PropTypes.object +} diff --git a/askomics/react/src/routes/integration/csvtable.jsx b/askomics/react/src/routes/integration/csvtable.jsx index efee1315..269c4119 100644 --- a/askomics/react/src/routes/integration/csvtable.jsx +++ b/askomics/react/src/routes/integration/csvtable.jsx @@ -91,6 +91,18 @@ export default class CsvTable extends Component { ) } + let ontoInput + + if (props.ontologies.length > 0){ + ontoInput = ( + + {props.ontologies.map(onto => { + return + })} + + ) + } + if (colIndex == 1) { return (
@@ -117,6 +129,7 @@ export default class CsvTable extends Component { + {ontoInput}
@@ -145,6 +158,7 @@ export default class CsvTable extends Component { + {ontoInput}
@@ -297,5 +311,6 @@ export default class CsvTable extends Component { CsvTable.propTypes = { file: PropTypes.object, - config: PropTypes.object + config: PropTypes.object, + ontologies: PropTypes.array } diff --git a/askomics/react/src/routes/integration/integration.jsx b/askomics/react/src/routes/integration/integration.jsx index ada6f4af..d8b49da3 100644 --- a/askomics/react/src/routes/integration/integration.jsx +++ b/askomics/react/src/routes/integration/integration.jsx @@ -19,7 +19,8 @@ export default class Integration extends Component { errorMessage: null, config: this.props.location.state.config, filesId: this.props.location.state.filesId, - previewFiles: [] + previewFiles: [], + ontologies: [] } this.cancelRequest } @@ -35,6 +36,7 @@ export default class Integration extends Component { console.log(requestUrl, response.data) this.setState({ previewFiles: response.data.previewFiles, + ontologies: response.data.ontologies, waiting: false, error: response.data.error, errorMessage: response.data.errorMessage @@ -74,7 +76,7 @@ export default class Integration extends Component { this.state.previewFiles.map(file => { console.log(file) if (file.type == 'csv/tsv') { - return + return } if (["rdf/ttl", "rdf/xml", "rdf/nt"].includes(file.type)) { return From 968b936f3ed322645ce8abac07f8be7cfae9315d Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 19 Nov 2021 14:54:19 +0000 Subject: [PATCH 027/113] Fixes --- askomics/libaskomics/CsvFile.py | 12 +++++++++++- askomics/react/src/routes/admin/ontologies.jsx | 2 +- askomics/react/src/routes/integration/csvtable.jsx | 4 ++-- .../react/src/routes/integration/integration.jsx | 2 +- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/askomics/libaskomics/CsvFile.py b/askomics/libaskomics/CsvFile.py index 00a53c87..42943eb9 100644 --- a/askomics/libaskomics/CsvFile.py +++ b/askomics/libaskomics/CsvFile.py @@ -399,7 +399,7 @@ def set_rdf_abstraction(self): available_ontologies = {} for ontology in OntologyManager(self.app, self.session).list_ontologies(): - available_ontologies[ontology['name']] = ontology['uri'] + available_ontologies[ontology['short_name']] = ontology['uri'] # Attributes and relations for index, attribute_name in enumerate(self.header): @@ -520,6 +520,10 @@ def generate_rdf_content(self): """ total_lines = sum(1 for line in open(self.path)) + available_ontologies = {} + for ontology in OntologyManager(self.app, self.session).list_ontologies(): + available_ontologies[ontology['short_name']] = ontology['uri'] + with open(self.path, 'r', encoding='utf-8') as file: reader = csv.reader(file, dialect=self.dialect) @@ -597,6 +601,12 @@ def generate_rdf_content(self): relation = self.rdfize(splitted[0]) attribute = self.rdfize(cell) + # Ontology + if current_type in available_ontologies: + symetric_relation = False + relation = self.rdfize(current_header) + attribute = self.rdfize(cell) + # Category elif current_type in ('category', 'reference', 'strand'): potential_relation = self.rdfize(current_header) diff --git a/askomics/react/src/routes/admin/ontologies.jsx b/askomics/react/src/routes/admin/ontologies.jsx index d99caafe..015eeadc 100644 --- a/askomics/react/src/routes/admin/ontologies.jsx +++ b/askomics/react/src/routes/admin/ontologies.jsx @@ -23,7 +23,7 @@ export default class Ontologies extends Component { ontologies: [], name: "", uri: "", - shortName:, + shortName: "", ontologiesSelected: [] } this.handleChangeValue = this.handleChangeValue.bind(this) diff --git a/askomics/react/src/routes/integration/csvtable.jsx b/askomics/react/src/routes/integration/csvtable.jsx index 269c4119..03c04591 100644 --- a/askomics/react/src/routes/integration/csvtable.jsx +++ b/askomics/react/src/routes/integration/csvtable.jsx @@ -93,10 +93,10 @@ export default class CsvTable extends Component { let ontoInput - if (props.ontologies.length > 0){ + if (this.props.ontologies.length > 0){ ontoInput = ( - {props.ontologies.map(onto => { + {this.props.ontologies.map(onto => { return })} diff --git a/askomics/react/src/routes/integration/integration.jsx b/askomics/react/src/routes/integration/integration.jsx index d8b49da3..b81af4b1 100644 --- a/askomics/react/src/routes/integration/integration.jsx +++ b/askomics/react/src/routes/integration/integration.jsx @@ -76,7 +76,7 @@ export default class Integration extends Component { this.state.previewFiles.map(file => { console.log(file) if (file.type == 'csv/tsv') { - return + return } if (["rdf/ttl", "rdf/xml", "rdf/nt"].includes(file.type)) { return From 06de0c562bea7ea9781696372b66b67bc9840c3c Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 19 Nov 2021 15:25:59 +0000 Subject: [PATCH 028/113] If -> Elif --- askomics/libaskomics/CsvFile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/askomics/libaskomics/CsvFile.py b/askomics/libaskomics/CsvFile.py index 42943eb9..7356a5fe 100644 --- a/askomics/libaskomics/CsvFile.py +++ b/askomics/libaskomics/CsvFile.py @@ -602,7 +602,7 @@ def generate_rdf_content(self): attribute = self.rdfize(cell) # Ontology - if current_type in available_ontologies: + elif current_type in available_ontologies: symetric_relation = False relation = self.rdfize(current_header) attribute = self.rdfize(cell) From ffecfbc6c3f2cf6542b708b93ba6f38809ee6fc3 Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 19 Nov 2021 16:51:22 +0100 Subject: [PATCH 029/113] Forgot the actual fix --- askomics/react/src/routes/integration/csvtable.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/askomics/react/src/routes/integration/csvtable.jsx b/askomics/react/src/routes/integration/csvtable.jsx index 03c04591..b1d3e047 100644 --- a/askomics/react/src/routes/integration/csvtable.jsx +++ b/askomics/react/src/routes/integration/csvtable.jsx @@ -97,7 +97,7 @@ export default class CsvTable extends Component { ontoInput = ( {this.props.ontologies.map(onto => { - return + return })} ) From 67b8d4db18c68a4d027a7636c843e9ea70095f50 Mon Sep 17 00:00:00 2001 From: mboudet Date: Mon, 22 Nov 2021 11:27:44 +0100 Subject: [PATCH 030/113] Add ontology test --- tests/conftest.py | 6 +++ tests/test_api_admin.py | 92 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index bfcb7cf2..b3c94001 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -15,6 +15,7 @@ from askomics.libaskomics.FilesUtils import FilesUtils from askomics.libaskomics.LocalAuth import LocalAuth from askomics.libaskomics.PrefixManager import PrefixManager +from askomics.libaskomics.OntologyManager import OntologyManager from askomics.libaskomics.SparqlQueryLauncher import SparqlQueryLauncher from askomics.libaskomics.Start import Start from askomics.libaskomics.Result import Result @@ -580,6 +581,11 @@ def create_prefix(self): pm = PrefixManager(self.app, self.session) pm.add_custom_prefix("OBO", "http://purl.obolibrary.org/obo/") + def create_ontology(self): + """Create ontology""" + om = OntologyManager(self.app, self.session) + om.add_ontology("Open Biological and Biomedical Ontology", "http://purl.obolibrary.org/obo/agro.owl", "OBO") + @staticmethod def get_random_string(number): """return a random string of n character diff --git a/tests/test_api_admin.py b/tests/test_api_admin.py index 126cc404..94b320e0 100644 --- a/tests/test_api_admin.py +++ b/tests/test_api_admin.py @@ -579,3 +579,95 @@ def test_delete_custom_prefix(self, client): assert response.status_code == 200 assert response.json == expected + + def test_view_ontologies(self, client): + """test /api/admin/getontologies route""" + client.create_two_users() + client.log_user("jsmith") + + response = client.client.get('/api/admin/getontologies') + assert response.status_code == 401 + + client.log_user("jdoe") + + expected_empty = { + "error": False, + "errorMessage": "", + "ontologies": [] + } + + response = client.client.get('/api/admin/getontologies') + assert response.status_code == 200 + assert response.json == expected_empty + + client.create_ontology() + + response = client.client.get('/api/admin/getontologies') + + expected = { + "error": False, + "errorMessage": "", + "ontologies": [{ + "id": 1, + "name": "Open Biological and Biomedical Ontology", + "uri": "http://purl.obolibrary.org/obo/agro.owl", + "short_name": "OBO", + "type": "local" + }] + } + + assert response.status_code == 200 + assert response.json == expected + + def test_add_ontology(self, client): + """test /api/admin/addontology route""" + client.create_two_users() + client.log_user("jsmith") + + data = {"shortName": "OBO", "uri": "http://purl.obolibrary.org/obo/agro.owl", "name": "Open Biological and Biomedical Ontology"} + + response = client.client.post('/api/admin/addprefix', json=data) + assert response.status_code == 401 + + client.log_user("jdoe") + + response = client.client.post('/api/admin/addprefix', json=data) + + expected = { + "error": False, + "errorMessage": "", + "ontologies": [{ + "id": 1, + "name": "Open Biological and Biomedical Ontology", + "uri": "http://purl.obolibrary.org/obo/agro.owl", + "short_name": "OBO", + "type": "local" + }] + } + + assert response.status_code == 200 + assert response.json == expected + + def test_delete_ontologies(self, client): + """test /api/admin/delete_ontologies route""" + client.create_two_users() + client.log_user("jsmith") + + data = {"ontologiesIdToDelete": [1]} + + response = client.client.post('/api/admin/delete_ontologies', json=data) + assert response.status_code == 401 + + client.log_user("jdoe") + client.create_ontology() + + response = client.client.post('/api/admin/delete_ontologies', json=data) + + expected = { + "error": False, + "errorMessage": "", + "ontologies": [] + } + + assert response.status_code == 200 + assert response.json == expected From 555c4cfbe5936f03e8ea76b91cfa25f1763b213d Mon Sep 17 00:00:00 2001 From: mboudet Date: Mon, 22 Nov 2021 11:50:14 +0100 Subject: [PATCH 031/113] Typo --- tests/test_api_admin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_api_admin.py b/tests/test_api_admin.py index 94b320e0..d5a2be70 100644 --- a/tests/test_api_admin.py +++ b/tests/test_api_admin.py @@ -626,12 +626,12 @@ def test_add_ontology(self, client): data = {"shortName": "OBO", "uri": "http://purl.obolibrary.org/obo/agro.owl", "name": "Open Biological and Biomedical Ontology"} - response = client.client.post('/api/admin/addprefix', json=data) + response = client.client.post('/api/admin/addontology', json=data) assert response.status_code == 401 client.log_user("jdoe") - response = client.client.post('/api/admin/addprefix', json=data) + response = client.client.post('/api/admin/addontology', json=data) expected = { "error": False, From e7c54e22811769048d965a6d3dd53c8ed8570e29 Mon Sep 17 00:00:00 2001 From: mboudet Date: Tue, 23 Nov 2021 09:53:01 +0100 Subject: [PATCH 032/113] Local autocomplete endpoint? --- askomics/api/ontology.py | 54 +++++++++++++++++++++++++ askomics/libaskomics/OntologyManager.py | 15 +++++++ askomics/libaskomics/SparqlQuery.py | 40 ++++++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 askomics/api/ontology.py diff --git a/askomics/api/ontology.py b/askomics/api/ontology.py new file mode 100644 index 00000000..b2e62c33 --- /dev/null +++ b/askomics/api/ontology.py @@ -0,0 +1,54 @@ +import traceback +import sys +from askomics.api.auth import api_auth, login_required +from askomics.libaskomics.OntologyManager import OntologyManager + +from flask import (Blueprint, current_app, jsonify, request, session) + +onto_bp = Blueprint('ontology', __name__, url_prefix='/') + + +@onto_bp.route("/api/ontology//autocomplete", methods=["GET"]) +@api_auth +@login_required +def autocomplete(short_ontology): + """Get the default sparql query + + Returns + ------- + json + """ + + if not request.args.get("q"): + return jsonify({ + "error": True, + "errorMessage": "Missing q parameter", + "results": [] + }), 400 + + try: + # Disk space + om = OntologyManager(current_app, session) + ontology = om.get_ontology(short_ontology) + if not ontology: + return jsonify({ + "error": True, + "errorMessage": "Ontology {} not found".format(short_ontology), + "results": [] + }), 404 + + results = om.autocomplete(ontology["uri"], ontology["type"], request.args.get("q")) + + except Exception as e: + traceback.print_exc(file=sys.stdout) + return jsonify({ + "error": True, + "errorMessage": str(e), + "results": [] + }), 404 + + return jsonify({ + "error": True, + "errorMessage": "", + "results": results + }), 200 diff --git a/askomics/libaskomics/OntologyManager.py b/askomics/libaskomics/OntologyManager.py index 0f1767e7..9d324007 100644 --- a/askomics/libaskomics/OntologyManager.py +++ b/askomics/libaskomics/OntologyManager.py @@ -1,4 +1,5 @@ from askomics.libaskomics.Database import Database +from askomics.libaskomics.SparqlQuery import SparqlQuery from askomics.libaskomics.Params import Params @@ -127,3 +128,17 @@ def remove_ontologies(self, ontology_ids): for ontology_id in ontology_ids: database.execute_sql_query(query, (ontology_id,)) + + def autocomplete(self, ontology_uri, ontology_type, query): + """Search in ontology + + Returns + ------- + list of dict + Results + """ + if ontology_type == "local": + query = SparqlQuery(self.app, self.session, get_graphs=False) + # TODO: Actually store the graph in the ontology to quicken search + graphs, endpoints = query.set_graphs_and_endpoints(entities=[ontology_uri]) + return query.autocomplete_local_ontology(ontology_uri, ontology_type, endpoints) diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index 2bb5a16b..9d69c3e4 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -523,6 +523,46 @@ def get_uri_parameters(self, uri, endpoints): return formated_data + def autocomplete_local_ontology(self, uri, query, endpoints): + """Get results for a specific query + + Parameters + ---------- + uri : string + ontology uri + query : string + query to search + + Returns + ------- + dict + The corresponding parameters + """ + raw_query = ''' + SELECT DISTINCT ?label ?uri + WHERE {{ + ?uri rdf:type <{}> . + ?uri rdfs:label ?label . + FILTER(contains(?label, "{}")) + }} + '''.format(uri, query) + + raw_query = self.prefix_query(raw_query) + + sparql = self.format_query(raw_query, limit=5, replace_froms=True, federated=False) + + query_launcher = SparqlQueryLauncher(self.app, self.session, get_result_query=True, federated=False, endpoints=endpoints) + _, data = query_launcher.process_query(sparql) + + formated_data = [] + for row in data: + formated_data.append({ + 'uri': row['uri'], + 'label': row['label'] + }) + + return formated_data + def format_sparql_variable(self, name): """Format a name into a sparql variable by remove spacial char and add a ? From 2065a1a50fa3b090030e05f84e8eed27dce83802 Mon Sep 17 00:00:00 2001 From: mboudet Date: Tue, 23 Nov 2021 09:56:55 +0000 Subject: [PATCH 033/113] Fix ontologies --- askomics/app.py | 4 +++- askomics/libaskomics/OntologyManager.py | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/askomics/app.py b/askomics/app.py index 1c23650b..e26b858c 100644 --- a/askomics/app.py +++ b/askomics/app.py @@ -19,6 +19,7 @@ from askomics.api.view import view_bp from askomics.api.results import results_bp from askomics.api.galaxy import galaxy_bp +from askomics.api.ontology import onto_bp from celery import Celery from kombu import Exchange, Queue @@ -46,7 +47,8 @@ datasets_bp, query_bp, results_bp, - galaxy_bp + galaxy_bp, + onto_bp ) diff --git a/askomics/libaskomics/OntologyManager.py b/askomics/libaskomics/OntologyManager.py index 9d324007..9378f06a 100644 --- a/askomics/libaskomics/OntologyManager.py +++ b/askomics/libaskomics/OntologyManager.py @@ -129,7 +129,7 @@ def remove_ontologies(self, ontology_ids): for ontology_id in ontology_ids: database.execute_sql_query(query, (ontology_id,)) - def autocomplete(self, ontology_uri, ontology_type, query): + def autocomplete(self, ontology_uri, ontology_type, query_term): """Search in ontology Returns @@ -140,5 +140,5 @@ def autocomplete(self, ontology_uri, ontology_type, query): if ontology_type == "local": query = SparqlQuery(self.app, self.session, get_graphs=False) # TODO: Actually store the graph in the ontology to quicken search - graphs, endpoints = query.set_graphs_and_endpoints(entities=[ontology_uri]) - return query.autocomplete_local_ontology(ontology_uri, ontology_type, endpoints) + query.set_graphs_and_endpoints(entities=[ontology_uri]) + return query.autocomplete_local_ontology(ontology_uri, query_term, query.endpoints) From 52b8e82e4754e692504ec8ff2a2beec49e3889ca Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 26 Nov 2021 16:08:20 +0100 Subject: [PATCH 034/113] Moved ontology to config --- askomics/api/file.py | 5 ----- askomics/api/start.py | 7 ++++++- askomics/react/src/routes.jsx | 3 ++- askomics/react/src/routes/integration/integration.jsx | 3 +-- askomics/react/src/routes/query/query.jsx | 1 + tests/results/preview_files.json | 3 +-- tests/results/preview_malformed_files.json | 3 +-- tests/test_api.py | 9 ++++++--- tests/test_api_file.py | 6 ++---- 9 files changed, 20 insertions(+), 20 deletions(-) diff --git a/askomics/api/file.py b/askomics/api/file.py index 2a588370..6addb781 100644 --- a/askomics/api/file.py +++ b/askomics/api/file.py @@ -8,7 +8,6 @@ from askomics.libaskomics.FilesHandler import FilesHandler from askomics.libaskomics.FilesUtils import FilesUtils from askomics.libaskomics.Dataset import Dataset -from askomics.libaskomics.OntologyManager import OntologyManager from askomics.libaskomics.RdfFile import RdfFile from flask import (Blueprint, current_app, jsonify, request, send_from_directory, session) @@ -238,8 +237,6 @@ def get_preview(): }), 400 try: - ontologies_manager = OntologyManager(current_app, session) - ontologies = ontologies_manager.list_ontologies() files_handler = FilesHandler(current_app, session) files_handler.handle_files(data['filesId']) @@ -253,14 +250,12 @@ def get_preview(): traceback.print_exc(file=sys.stdout) return jsonify({ 'previewFiles': [], - 'ontologies': [], 'error': True, 'errorMessage': str(e) }), 500 return jsonify({ 'previewFiles': results, - 'ontologies': ontologies, 'error': False, 'errorMessage': '' }) diff --git a/askomics/api/start.py b/askomics/api/start.py index eca8f91b..15564c9e 100644 --- a/askomics/api/start.py +++ b/askomics/api/start.py @@ -5,6 +5,7 @@ from askomics.api.auth import api_auth from askomics.libaskomics.LocalAuth import LocalAuth from askomics.libaskomics.Start import Start +from askomics.libaskomics.OntologyManager import OntologyManager from flask import (Blueprint, current_app, jsonify, session) @@ -64,6 +65,9 @@ def start(): except Exception: pass + ontologies_manager = OntologyManager(current_app, session) + ontologies = ontologies_manager.list_ontologies() + config = { "footerMessage": current_app.iniconfig.get('askomics', 'footer_message'), "frontMessage": front_message, @@ -79,7 +83,8 @@ def start(): "namespaceInternal": current_app.iniconfig.get('triplestore', 'namespace_internal'), "proxyPath": proxy_path, "user": {}, - "logged": False + "logged": False, + "ontologies": ontologies } json = { diff --git a/askomics/react/src/routes.jsx b/askomics/react/src/routes.jsx index bcb79f54..6b910055 100644 --- a/askomics/react/src/routes.jsx +++ b/askomics/react/src/routes.jsx @@ -46,7 +46,8 @@ export default class Routes extends Component { gitUrl: null, disableIntegration: null, namespaceData: null, - namespaceInternal: null + namespaceInternal: null, + ontologies: [] } } this.cancelRequest diff --git a/askomics/react/src/routes/integration/integration.jsx b/askomics/react/src/routes/integration/integration.jsx index b81af4b1..7d3f50f7 100644 --- a/askomics/react/src/routes/integration/integration.jsx +++ b/askomics/react/src/routes/integration/integration.jsx @@ -20,7 +20,7 @@ export default class Integration extends Component { config: this.props.location.state.config, filesId: this.props.location.state.filesId, previewFiles: [], - ontologies: [] + ontologies: this.props.location.state.config.ontologies } this.cancelRequest } @@ -36,7 +36,6 @@ export default class Integration extends Component { console.log(requestUrl, response.data) this.setState({ previewFiles: response.data.previewFiles, - ontologies: response.data.ontologies, waiting: false, error: response.data.error, errorMessage: response.data.errorMessage diff --git a/askomics/react/src/routes/query/query.jsx b/askomics/react/src/routes/query/query.jsx index a1a3df60..9e7796ba 100644 --- a/askomics/react/src/routes/query/query.jsx +++ b/askomics/react/src/routes/query/query.jsx @@ -43,6 +43,7 @@ export default class Query extends Component { // Preview icons disablePreview: false, previewIcon: "table", + ontologies: this.props.location.state.config.ontologies } this.graphState = { diff --git a/tests/results/preview_files.json b/tests/results/preview_files.json index a73688db..6497c378 100644 --- a/tests/results/preview_files.json +++ b/tests/results/preview_files.json @@ -181,6 +181,5 @@ "error": false, "error_message": "" } -], -"ontologies": [] +] } diff --git a/tests/results/preview_malformed_files.json b/tests/results/preview_malformed_files.json index b99f0f2c..44312c87 100644 --- a/tests/results/preview_malformed_files.json +++ b/tests/results/preview_malformed_files.json @@ -22,6 +22,5 @@ "error": true, "error_message": "Malformated CSV/TSV (Empty column in header)" } -], - "ontologies": [] +] } diff --git a/tests/test_api.py b/tests/test_api.py index 7e00b700..1a40cec1 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -42,7 +42,8 @@ def test_start(self, client): "namespaceInternal": client.get_config('triplestore', 'namespace_internal'), "proxyPath": "/", "user": {}, - "logged": False + "logged": False, + "ontologies": [] } response = client.client.get('/api/start') assert response.status_code == 200 @@ -72,7 +73,8 @@ def test_start(self, client): "quota": 0, 'apikey': "0000000001", 'galaxy': {"url": "http://localhost:8081", "apikey": "fakekey"}, - 'last_action': None + 'last_action': None, + "ontologies": [] } response = client.client.get('/api/start') @@ -100,7 +102,8 @@ def test_start(self, client): "quota": 0, 'apikey': "0000000002", 'galaxy': None, - 'last_action': None + 'last_action': None, + "ontologies": [] } response = client.client.get('/api/start') diff --git a/tests/test_api_file.py b/tests/test_api_file.py index 5e645d89..a3a0f708 100644 --- a/tests/test_api_file.py +++ b/tests/test_api_file.py @@ -393,8 +393,7 @@ def test_get_preview(self, client): assert response.json == { 'error': False, 'errorMessage': '', - 'previewFiles': [], - 'ontologies': [] + 'previewFiles': [] } response = client.client.post('/api/files/preview', json=malformed_data) @@ -422,8 +421,7 @@ def test_get_preview(self, client): 'type': 'gff/gff3', 'error': False, 'error_message': '' - }], - 'ontologies': [] + }] } def test_delete_files(self, client): From bc6c45937bef2e956489689998678959eb769754 Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 26 Nov 2021 20:15:28 +0100 Subject: [PATCH 035/113] Typo --- tests/test_api.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index 1a40cec1..af30f8bd 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -73,8 +73,7 @@ def test_start(self, client): "quota": 0, 'apikey': "0000000001", 'galaxy': {"url": "http://localhost:8081", "apikey": "fakekey"}, - 'last_action': None, - "ontologies": [] + 'last_action': None } response = client.client.get('/api/start') @@ -102,8 +101,7 @@ def test_start(self, client): "quota": 0, 'apikey': "0000000002", 'galaxy': None, - 'last_action': None, - "ontologies": [] + 'last_action': None } response = client.client.get('/api/start') From f243d204202f82de568f76d9f628c17418d03bb9 Mon Sep 17 00:00:00 2001 From: mboudet Date: Tue, 30 Nov 2021 11:37:28 +0100 Subject: [PATCH 036/113] autocomplete --- askomics/react/src/routes/query/attribute.jsx | 20 ++++++++++++++++++- askomics/react/src/routes/query/query.jsx | 1 + 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/askomics/react/src/routes/query/attribute.jsx b/askomics/react/src/routes/query/attribute.jsx index 5536edde..6a673980 100644 --- a/askomics/react/src/routes/query/attribute.jsx +++ b/askomics/react/src/routes/query/attribute.jsx @@ -46,6 +46,23 @@ export default class AttributeBox extends Component { return newStr } + + isRegisteredOnto () { + return this.props.config.ontologies.some(onto => { + return (onto.uri == this.props.entityUri) + }) + } + + getAutoComplete(){ + return this.props.config.ontologies.map(onto => { + if (onto.uri == this.props.entityUri) { + return onto.short_name + } else { + return null + } + }) + } + renderLinker () { let options = [] @@ -494,5 +511,6 @@ AttributeBox.propTypes = { attribute: PropTypes.object, graph: PropTypes.object, config: PropTypes.object, - isOnto: PropTypes.bool + isOnto: PropTypes.bool, + entityUri: PropTypes.string } diff --git a/askomics/react/src/routes/query/query.jsx b/askomics/react/src/routes/query/query.jsx index a1a3df60..b5dfbdc7 100644 --- a/askomics/react/src/routes/query/query.jsx +++ b/askomics/react/src/routes/query/query.jsx @@ -1551,6 +1551,7 @@ export default class Query extends Component { handleDateFilter={p => this.handleDateFilter(p)} config={this.state.config} isOnto={isOnto} + entityUri={this.currentSelected.uri} /> ) } From 420e357ae5d533af6b0bb166a15c074be3e96180 Mon Sep 17 00:00:00 2001 From: mboudet Date: Thu, 2 Dec 2021 09:34:58 +0100 Subject: [PATCH 037/113] Test react --- askomics/api/ontology.py | 7 -- askomics/libaskomics/SparqlQuery.py | 11 ++- askomics/react/src/routes/query/attribute.jsx | 86 +++++++++++++------ package.json | 1 + 4 files changed, 71 insertions(+), 34 deletions(-) diff --git a/askomics/api/ontology.py b/askomics/api/ontology.py index b2e62c33..28800a84 100644 --- a/askomics/api/ontology.py +++ b/askomics/api/ontology.py @@ -19,13 +19,6 @@ def autocomplete(short_ontology): json """ - if not request.args.get("q"): - return jsonify({ - "error": True, - "errorMessage": "Missing q parameter", - "results": [] - }), 400 - try: # Disk space om = OntologyManager(current_app, session) diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index 9d69c3e4..9b3f41b4 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -538,14 +538,21 @@ def autocomplete_local_ontology(self, uri, query, endpoints): dict The corresponding parameters """ + + subquery = "" + + if query: + subquery = 'FILTER(contains(?label, "{}"))'.format(query) raw_query = ''' SELECT DISTINCT ?label ?uri WHERE {{ ?uri rdf:type <{}> . ?uri rdfs:label ?label . - FILTER(contains(?label, "{}")) + {} }} - '''.format(uri, query) + + LIMIT 5 + '''.format(uri, subquery) raw_query = self.prefix_query(raw_query) diff --git a/askomics/react/src/routes/query/attribute.jsx b/askomics/react/src/routes/query/attribute.jsx index 6a673980..66d8cccb 100644 --- a/askomics/react/src/routes/query/attribute.jsx +++ b/askomics/react/src/routes/query/attribute.jsx @@ -10,12 +10,17 @@ import update from 'react-addons-update' import Visualization from './visualization' import PropTypes from 'prop-types' import Utils from '../../classes/utils' +import TextInput from 'react-autocomplete-input'; +import 'react-autocomplete-input/dist/bundle.css'; export default class AttributeBox extends Component { constructor (props) { super(props) this.utils = new Utils() - this.state = {} + this.state = { + ontologyShort: this.getAutoComplete(), + options: [] + } this.toggleVisibility = this.props.toggleVisibility.bind(this) this.handleNegative = this.props.handleNegative.bind(this) @@ -33,6 +38,8 @@ export default class AttributeBox extends Component { this.toggleAddNumFilter = this.props.toggleAddNumFilter.bind(this) this.toggleAddDateFilter = this.props.toggleAddDateFilter.bind(this) this.handleDateFilter = this.props.handleDateFilter.bind(this) + this.autocompleteOntology = this.autocompleteOntology.bind(this) + this.cancelRequest } subNums (id) { @@ -53,7 +60,7 @@ export default class AttributeBox extends Component { }) } - getAutoComplete(){ + getAutoComplete () { return this.props.config.ontologies.map(onto => { if (onto.uri == this.props.entityUri) { return onto.short_name @@ -63,6 +70,31 @@ export default class AttributeBox extends Component { }) } + autocompleteOntology (event) { + this.handleFilterValue(event) + let userInput = event.target + let requestUrl = '/api/ontology/' + this.state.ontologyShort + "/autocomplete" + axios.get(requestUrl, {baseURL: this.props.config.proxyPath, params:{q: userInput}, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) + .then(response => { + console.log(requestUrl, response.data) + // set state of resultsPreview + this.setState({ + options: response.data.results + }) + }) + .catch(error => { + console.log(error, error.response.data.errorMessage) + this.setState({ + error: true, + errorMessage: error.response.data.errorMessage, + status: error.response.status, + waiting: false + }) + }) + + + } + renderLinker () { let options = [] @@ -140,6 +172,32 @@ export default class AttributeBox extends Component { let form + let input + let attrIcons + + if (this.props.isOnto){ + attrIcons = ( +
+ +
+ ) + input = ( + + ) + + } else { + attrIcons = ( +
+ {this.props.config.user.admin ? : } + + {this.props.attribute.uri == "rdf:type" || this.props.attribute.uri == "rdfs:label" ? : } + +
+ ) + input = () + } + + if (this.props.attribute.linked) { form = this.renderLinker() } else { @@ -161,35 +219,13 @@ export default class AttributeBox extends Component { - + {input} ) } - - let attrIcons - - if (this.props.isOnto){ - attrIcons = ( -
- -
- ) - - } else { - attrIcons = ( -
- {this.props.config.user.admin ? : } - - {this.props.attribute.uri == "rdf:type" || this.props.attribute.uri == "rdfs:label" ? : } - -
- ) - } - - return (
diff --git a/package.json b/package.json index 479003cc..a2b8d346 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "qs": "^6.9.4", "react": "^16.13.1", "react-ace": "^9.1.3", + "react-autocomplete-input": "1.0.18", "react-addons-update": "^15.6.3", "react-bootstrap-table-next": "^4.0.3", "react-bootstrap-table2-editor": "^1.4.0", From 0098c3a3505c15ceaa04117cec952ef69b972557 Mon Sep 17 00:00:00 2001 From: mboudet Date: Thu, 2 Dec 2021 11:36:12 +0000 Subject: [PATCH 038/113] Update --- askomics/libaskomics/SparqlQuery.py | 7 ++----- askomics/react/src/routes/query/attribute.jsx | 15 +++++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index 9b3f41b4..41565a1e 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -544,7 +544,7 @@ def autocomplete_local_ontology(self, uri, query, endpoints): if query: subquery = 'FILTER(contains(?label, "{}"))'.format(query) raw_query = ''' - SELECT DISTINCT ?label ?uri + SELECT DISTINCT ?label WHERE {{ ?uri rdf:type <{}> . ?uri rdfs:label ?label . @@ -563,10 +563,7 @@ def autocomplete_local_ontology(self, uri, query, endpoints): formated_data = [] for row in data: - formated_data.append({ - 'uri': row['uri'], - 'label': row['label'] - }) + formated_data.append(row['label']) return formated_data diff --git a/askomics/react/src/routes/query/attribute.jsx b/askomics/react/src/routes/query/attribute.jsx index 66d8cccb..d07a9b3f 100644 --- a/askomics/react/src/routes/query/attribute.jsx +++ b/askomics/react/src/routes/query/attribute.jsx @@ -70,9 +70,8 @@ export default class AttributeBox extends Component { }) } - autocompleteOntology (event) { - this.handleFilterValue(event) - let userInput = event.target + autocompleteOntology (value) { + let userInput = value let requestUrl = '/api/ontology/' + this.state.ontologyShort + "/autocomplete" axios.get(requestUrl, {baseURL: this.props.config.proxyPath, params:{q: userInput}, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) .then(response => { @@ -181,9 +180,13 @@ export default class AttributeBox extends Component {
) - input = ( - - ) + if (this.props.attribute.uri == "rdfs:label"){ + input = ( + this.handleFilterValue({target: {value: e, id: this.props.attribute.id}})} id={this.props.attribute.id} value={this.props.attribute.filterValue} onRequestOptions={this.autocompleteOntology}/> + ) + } else { + input = () + } } else { attrIcons = ( From b3ec961657fb937982c3653835f21e9c9c68c8ca Mon Sep 17 00:00:00 2001 From: mboudet Date: Thu, 2 Dec 2021 15:31:18 +0100 Subject: [PATCH 039/113] Move autocomplete to its own --- .../react/src/components/autocomplete.jsx | 80 +++++++++++++++++++ askomics/react/src/routes/query/attribute.jsx | 42 +--------- 2 files changed, 83 insertions(+), 39 deletions(-) create mode 100644 askomics/react/src/components/autocomplete.jsx diff --git a/askomics/react/src/components/autocomplete.jsx b/askomics/react/src/components/autocomplete.jsx new file mode 100644 index 00000000..4b789026 --- /dev/null +++ b/askomics/react/src/components/autocomplete.jsx @@ -0,0 +1,80 @@ +import React, { Component} from 'react' +import axios from 'axios' +import { Input, FormGroup, CustomInput, FormFeedback } from 'reactstrap' +import { Redirect } from 'react-router-dom' +import DatePicker from "react-datepicker"; +import ReactTooltip from "react-tooltip"; +import ErrorDiv from '../error/error' +import WaitingDiv from '../../components/waiting' +import update from 'react-addons-update' +import Visualization from './visualization' +import PropTypes from 'prop-types' +import Utils from '../../classes/utils' +import TextInput from 'react-autocomplete-input'; +import 'react-autocomplete-input/dist/bundle.css'; + +export default class Autocomplete extends Component { + constructor (props) { + super(props) + this.utils = new Utils() + this.state = { + ontologyShort: this.getAutoComplete(), + options: [] + } + + this.handleFilterValue = this.props.handleFilterValue.bind(this) + this.autocompleteOntology = this.autocompleteOntology.bind(this) + this.cancelRequest + } + + getAutoComplete () { + return this.props.config.ontologies.map(onto => { + if (onto.uri == this.props.entityUri) { + return onto.short_name + } else { + return null + } + }) + } + + autocompleteOntology (value) { + let userInput = value + let requestUrl = '/api/ontology/' + this.state.ontologyShort + "/autocomplete" + axios.get(requestUrl, {baseURL: this.props.config.proxyPath, params:{q: userInput}, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) + .then(response => { + console.log(requestUrl, response.data) + // set state of resultsPreview + this.setState({ + options: response.data.results + }) + }) + .catch(error => { + console.log(error, error.response.data.errorMessage) + this.setState({ + error: true, + errorMessage: error.response.data.errorMessage, + status: error.response.status, + waiting: false + }) + }) + } + + renderAutocomplete () { + let input = ( + this.handleFilterValue({target: {value: e, id: this.props.attributeId}})} id={this.props.attributeId} value={this.props.filterValue} onRequestOptions={this.autocompleteOntology}/> + ) + + return input + } + + render () { + return this.renderAutocomplete() + } +} + +Autocomplete.propTypes = { + handleFilterValue: PropTypes.func, + entityUri: PropTypes.string, + attributeId: PropTypes.string, + filterValue: PropTypes.string +} diff --git a/askomics/react/src/routes/query/attribute.jsx b/askomics/react/src/routes/query/attribute.jsx index d07a9b3f..01ed50f2 100644 --- a/askomics/react/src/routes/query/attribute.jsx +++ b/askomics/react/src/routes/query/attribute.jsx @@ -12,15 +12,13 @@ import PropTypes from 'prop-types' import Utils from '../../classes/utils' import TextInput from 'react-autocomplete-input'; import 'react-autocomplete-input/dist/bundle.css'; +import Autocomplete from '../../components/autocomplete' export default class AttributeBox extends Component { constructor (props) { super(props) this.utils = new Utils() - this.state = { - ontologyShort: this.getAutoComplete(), - options: [] - } + this.state = {} this.toggleVisibility = this.props.toggleVisibility.bind(this) this.handleNegative = this.props.handleNegative.bind(this) @@ -60,40 +58,6 @@ export default class AttributeBox extends Component { }) } - getAutoComplete () { - return this.props.config.ontologies.map(onto => { - if (onto.uri == this.props.entityUri) { - return onto.short_name - } else { - return null - } - }) - } - - autocompleteOntology (value) { - let userInput = value - let requestUrl = '/api/ontology/' + this.state.ontologyShort + "/autocomplete" - axios.get(requestUrl, {baseURL: this.props.config.proxyPath, params:{q: userInput}, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) - .then(response => { - console.log(requestUrl, response.data) - // set state of resultsPreview - this.setState({ - options: response.data.results - }) - }) - .catch(error => { - console.log(error, error.response.data.errorMessage) - this.setState({ - error: true, - errorMessage: error.response.data.errorMessage, - status: error.response.status, - waiting: false - }) - }) - - - } - renderLinker () { let options = [] @@ -182,7 +146,7 @@ export default class AttributeBox extends Component { ) if (this.props.attribute.uri == "rdfs:label"){ input = ( - this.handleFilterValue({target: {value: e, id: this.props.attribute.id}})} id={this.props.attribute.id} value={this.props.attribute.filterValue} onRequestOptions={this.autocompleteOntology}/> + this.handleChangePosition(p)}/> ) } else { input = () From 73aeef7b757469ba25e98113e4821ad0374d622f Mon Sep 17 00:00:00 2001 From: mboudet Date: Thu, 2 Dec 2021 16:57:59 +0000 Subject: [PATCH 040/113] Override position --- .../react/src/components/autocomplete.jsx | 20 ++++++------------- askomics/react/src/routes/query/attribute.jsx | 3 +-- askomics/static/css/askomics.css | 7 ++++++- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/askomics/react/src/components/autocomplete.jsx b/askomics/react/src/components/autocomplete.jsx index 4b789026..dca247f8 100644 --- a/askomics/react/src/components/autocomplete.jsx +++ b/askomics/react/src/components/autocomplete.jsx @@ -1,22 +1,12 @@ import React, { Component} from 'react' import axios from 'axios' -import { Input, FormGroup, CustomInput, FormFeedback } from 'reactstrap' -import { Redirect } from 'react-router-dom' -import DatePicker from "react-datepicker"; -import ReactTooltip from "react-tooltip"; -import ErrorDiv from '../error/error' -import WaitingDiv from '../../components/waiting' -import update from 'react-addons-update' -import Visualization from './visualization' import PropTypes from 'prop-types' -import Utils from '../../classes/utils' import TextInput from 'react-autocomplete-input'; import 'react-autocomplete-input/dist/bundle.css'; export default class Autocomplete extends Component { constructor (props) { super(props) - this.utils = new Utils() this.state = { ontologyShort: this.getAutoComplete(), options: [] @@ -60,9 +50,10 @@ export default class Autocomplete extends Component { } renderAutocomplete () { - let input = ( + + let input = (
this.handleFilterValue({target: {value: e, id: this.props.attributeId}})} id={this.props.attributeId} value={this.props.filterValue} onRequestOptions={this.autocompleteOntology}/> - ) +
) return input } @@ -75,6 +66,7 @@ export default class Autocomplete extends Component { Autocomplete.propTypes = { handleFilterValue: PropTypes.func, entityUri: PropTypes.string, - attributeId: PropTypes.string, - filterValue: PropTypes.string + attributeId: PropTypes.number, + filterValue: PropTypes.string, + config: PropTypes.object, } diff --git a/askomics/react/src/routes/query/attribute.jsx b/askomics/react/src/routes/query/attribute.jsx index 01ed50f2..002fa626 100644 --- a/askomics/react/src/routes/query/attribute.jsx +++ b/askomics/react/src/routes/query/attribute.jsx @@ -36,7 +36,6 @@ export default class AttributeBox extends Component { this.toggleAddNumFilter = this.props.toggleAddNumFilter.bind(this) this.toggleAddDateFilter = this.props.toggleAddDateFilter.bind(this) this.handleDateFilter = this.props.handleDateFilter.bind(this) - this.autocompleteOntology = this.autocompleteOntology.bind(this) this.cancelRequest } @@ -146,7 +145,7 @@ export default class AttributeBox extends Component { ) if (this.props.attribute.uri == "rdfs:label"){ input = ( - this.handleChangePosition(p)}/> + this.handleFilterValue(p)}/> ) } else { input = () diff --git a/askomics/static/css/askomics.css b/askomics/static/css/askomics.css index 7f7bb842..c6525b2a 100644 --- a/askomics/static/css/askomics.css +++ b/askomics/static/css/askomics.css @@ -276,4 +276,9 @@ button.input-with-icon { display: block; } -/***********************************************************************/ \ No newline at end of file +.react-autocomplete-input { + left: auto !important; + top: auto !important; +} + +/***********************************************************************/ From eed6e6a03d7ee1988b2482fb613e98a936230f84 Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 3 Dec 2021 08:31:59 +0000 Subject: [PATCH 041/113] changed logic --- askomics/react/src/components/autocomplete.jsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/askomics/react/src/components/autocomplete.jsx b/askomics/react/src/components/autocomplete.jsx index dca247f8..b5ce690b 100644 --- a/askomics/react/src/components/autocomplete.jsx +++ b/askomics/react/src/components/autocomplete.jsx @@ -15,6 +15,7 @@ export default class Autocomplete extends Component { this.handleFilterValue = this.props.handleFilterValue.bind(this) this.autocompleteOntology = this.autocompleteOntology.bind(this) this.cancelRequest + this.handleOntoValue = this.handleOntoValue.bind(this) } getAutoComplete () { @@ -32,7 +33,6 @@ export default class Autocomplete extends Component { let requestUrl = '/api/ontology/' + this.state.ontologyShort + "/autocomplete" axios.get(requestUrl, {baseURL: this.props.config.proxyPath, params:{q: userInput}, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) .then(response => { - console.log(requestUrl, response.data) // set state of resultsPreview this.setState({ options: response.data.results @@ -49,10 +49,16 @@ export default class Autocomplete extends Component { }) } + handleOntoValue (event) { + this.handleFilterValue(event) + this.autocompleteOntology(event.target.value) + } + + renderAutocomplete () { let input = (
- this.handleFilterValue({target: {value: e, id: this.props.attributeId}})} id={this.props.attributeId} value={this.props.filterValue} onRequestOptions={this.autocompleteOntology}/> + this.handleOntoValue({target: {value: e, id: this.props.attributeId}})} id={this.props.attributeId} value={this.props.filterValue}/>
) return input From adb7f5fc766514f8386e54d0202b52f091813c39 Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 3 Dec 2021 08:34:03 +0000 Subject: [PATCH 042/113] Limit to 3 chars --- askomics/react/src/components/autocomplete.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/askomics/react/src/components/autocomplete.jsx b/askomics/react/src/components/autocomplete.jsx index b5ce690b..d13f29e4 100644 --- a/askomics/react/src/components/autocomplete.jsx +++ b/askomics/react/src/components/autocomplete.jsx @@ -31,6 +31,9 @@ export default class Autocomplete extends Component { autocompleteOntology (value) { let userInput = value let requestUrl = '/api/ontology/' + this.state.ontologyShort + "/autocomplete" + + if (value.length < 3) { return } + axios.get(requestUrl, {baseURL: this.props.config.proxyPath, params:{q: userInput}, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) .then(response => { // set state of resultsPreview From 3557b1e32a9a7eead69e1907bbb37a43d25c89e2 Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 3 Dec 2021 12:02:42 +0100 Subject: [PATCH 043/113] Remove arrows on ontolink --- askomics/react/src/routes/query/attribute.jsx | 2 +- askomics/react/src/routes/query/query.jsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/askomics/react/src/routes/query/attribute.jsx b/askomics/react/src/routes/query/attribute.jsx index 002fa626..8167bb41 100644 --- a/askomics/react/src/routes/query/attribute.jsx +++ b/askomics/react/src/routes/query/attribute.jsx @@ -53,7 +53,7 @@ export default class AttributeBox extends Component { isRegisteredOnto () { return this.props.config.ontologies.some(onto => { - return (onto.uri == this.props.entityUri) + return (onto.uri == this.props.entityUri && onto.type == "local") }) } diff --git a/askomics/react/src/routes/query/query.jsx b/askomics/react/src/routes/query/query.jsx index a6825d94..b5d20391 100644 --- a/askomics/react/src/routes/query/query.jsx +++ b/askomics/react/src/routes/query/query.jsx @@ -545,7 +545,7 @@ export default class Query extends Component { target: targetId, selected: false, suggested: true, - directed: true, + directed: isOnto ? false : true, }) incrementSpecialNodeGroupId ? specialNodeGroupId += 1 : specialNodeGroupId = specialNodeGroupId } @@ -670,7 +670,7 @@ export default class Query extends Component { target: node2.id, selected: false, suggested: false, - directed: true, + directed: link.directed, } } @@ -689,7 +689,7 @@ export default class Query extends Component { target: node1.id, selected: false, suggested: false, - directed: true, + directed: link.direct, } } }) From 94c9e32164047c9eeb08d50d955e7b48d7f72e24 Mon Sep 17 00:00:00 2001 From: mboudet Date: Mon, 6 Dec 2021 17:04:52 +0100 Subject: [PATCH 044/113] Onto type --- askomics/api/admin.py | 10 +++- askomics/api/ontology.py | 2 +- askomics/libaskomics/OntologyManager.py | 51 ++++++++++++++++++- .../react/src/routes/admin/ontologies.jsx | 29 +++++++++-- askomics/react/src/routes/query/attribute.jsx | 2 +- 5 files changed, 86 insertions(+), 8 deletions(-) diff --git a/askomics/api/admin.py b/askomics/api/admin.py index a54fa5b9..2da1979b 100644 --- a/askomics/api/admin.py +++ b/askomics/api/admin.py @@ -698,6 +698,14 @@ def add_ontology(): name = data.get("name") uri = data.get("uri") short_name = data.get("shortName") + type = data.get("type") + + if type not in ["none", "local", "ols"]: + return jsonify({ + 'ontologies': [], + 'error': True, + 'errorMessage': "Invalid type" + }), 400 if any([name == onto['name'] or short_name == onto['short_name'] for onto in ontologies]): return jsonify({ @@ -707,7 +715,7 @@ def add_ontology(): }), 400 try: - om.add_ontology(name, uri, short_name) + om.add_ontology(name, uri, short_name, type) ontologies = om.list_ontologies() except Exception as e: traceback.print_exc(file=sys.stdout) diff --git a/askomics/api/ontology.py b/askomics/api/ontology.py index 28800a84..2790b901 100644 --- a/askomics/api/ontology.py +++ b/askomics/api/ontology.py @@ -30,7 +30,7 @@ def autocomplete(short_ontology): "results": [] }), 404 - results = om.autocomplete(ontology["uri"], ontology["type"], request.args.get("q")) + results = om.autocomplete(ontology["uri"], ontology["type"], request.args.get("q"), short_ontology) except Exception as e: traceback.print_exc(file=sys.stdout) diff --git a/askomics/libaskomics/OntologyManager.py b/askomics/libaskomics/OntologyManager.py index 9378f06a..6ef9d095 100644 --- a/askomics/libaskomics/OntologyManager.py +++ b/askomics/libaskomics/OntologyManager.py @@ -1,3 +1,5 @@ +import requests + from askomics.libaskomics.Database import Database from askomics.libaskomics.SparqlQuery import SparqlQuery from askomics.libaskomics.Params import Params @@ -129,7 +131,7 @@ def remove_ontologies(self, ontology_ids): for ontology_id in ontology_ids: database.execute_sql_query(query, (ontology_id,)) - def autocomplete(self, ontology_uri, ontology_type, query_term): + def autocomplete(self, ontology_uri, ontology_type, query_term, onto_short_name): """Search in ontology Returns @@ -142,3 +144,50 @@ def autocomplete(self, ontology_uri, ontology_type, query_term): # TODO: Actually store the graph in the ontology to quicken search query.set_graphs_and_endpoints(entities=[ontology_uri]) return query.autocomplete_local_ontology(ontology_uri, query_term, query.endpoints) + elif ontology_type == "ols": + base_url = "https://www.ebi.ac.uk/ols/api/search" + arguments = { + "q": query_term, + "ontology": onto_short_name, + "rows": 5, + "queryFields": "label", + "type": "class", + "fieldList": "label" + } + + r = requests.get(base_url, params=arguments) + + data = [] + + if not r.status_code == 200: + return data + + res = r.json() + if res['response']['docs']: + data = [term['label'] for term in res['response']['docs']] + + return data + + elif ontology_type == "ols": + base_url = "https://www.ebi.ac.uk/ols/api/search" + arguments = { + "q": query_term, + "ontology": onto_short_name, + "rows": 5, + "queryFields": "label", + "type": "class", + "fieldList": "label" + } + + r = requests.get(base_url, params=arguments) + + data = [] + + if not r.status_code == 200: + return data + + res = r.json() + if res['response']['docs']: + data = [term['label'] for term in res['response']['docs']] + + return data diff --git a/askomics/react/src/routes/admin/ontologies.jsx b/askomics/react/src/routes/admin/ontologies.jsx index 015eeadc..17478905 100644 --- a/askomics/react/src/routes/admin/ontologies.jsx +++ b/askomics/react/src/routes/admin/ontologies.jsx @@ -24,6 +24,7 @@ export default class Ontologies extends Component { name: "", uri: "", shortName: "", + type: "local", ontologiesSelected: [] } this.handleChangeValue = this.handleChangeValue.bind(this) @@ -98,7 +99,8 @@ export default class Ontologies extends Component { let data = { name: this.state.name, uri: this.state.uri, - shortName: this.state.shortName + shortName: this.state.shortName, + type: this.state.type } axios.post(requestUrl, data, { baseURL: this.props.config.proxyPath, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) @@ -109,6 +111,10 @@ export default class Ontologies extends Component { newontologyErrorMessage: response.data.errorMessage, ontologies: response.data.ontologies, newontologyStatus: response.status, + name: "", + uri: "", + shortName: "", + type: "local" }) }) .catch(error => { @@ -182,7 +188,13 @@ export default class Ontologies extends Component { dataField: 'uri', text: 'Uri', sort: true + }, { + editable: false, + dataField: 'type', + text: 'Autocomplete type', + sort: true } + ] let ontologiesDefaultSorted = [{ @@ -211,24 +223,33 @@ export default class Ontologies extends Component {
- + - + - + + + Autocomplete type + + + + + + +
diff --git a/askomics/react/src/routes/query/attribute.jsx b/askomics/react/src/routes/query/attribute.jsx index 8167bb41..fab02271 100644 --- a/askomics/react/src/routes/query/attribute.jsx +++ b/askomics/react/src/routes/query/attribute.jsx @@ -53,7 +53,7 @@ export default class AttributeBox extends Component { isRegisteredOnto () { return this.props.config.ontologies.some(onto => { - return (onto.uri == this.props.entityUri && onto.type == "local") + return (onto.uri == this.props.entityUri && onto.type != "none") }) } From e6b7cb9441619908d8a2b1b28541a6a0c3c8571d Mon Sep 17 00:00:00 2001 From: mboudet Date: Mon, 6 Dec 2021 17:21:01 +0100 Subject: [PATCH 045/113] Fix test --- askomics/react/src/routes/admin/ontologies.jsx | 3 ++- tests/test_api_admin.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/askomics/react/src/routes/admin/ontologies.jsx b/askomics/react/src/routes/admin/ontologies.jsx index 17478905..50632410 100644 --- a/askomics/react/src/routes/admin/ontologies.jsx +++ b/askomics/react/src/routes/admin/ontologies.jsx @@ -242,7 +242,8 @@ export default class Ontologies extends Component { - Autocomplete type + + diff --git a/tests/test_api_admin.py b/tests/test_api_admin.py index d5a2be70..5b435d92 100644 --- a/tests/test_api_admin.py +++ b/tests/test_api_admin.py @@ -624,7 +624,7 @@ def test_add_ontology(self, client): client.create_two_users() client.log_user("jsmith") - data = {"shortName": "OBO", "uri": "http://purl.obolibrary.org/obo/agro.owl", "name": "Open Biological and Biomedical Ontology"} + data = {"shortName": "OBO", "uri": "http://purl.obolibrary.org/obo/agro.owl", "name": "Open Biological and Biomedical Ontology", "type": "local"} response = client.client.post('/api/admin/addontology', json=data) assert response.status_code == 401 From a85f717a7a3a9d7cee5e4a5cd955c0c6ab40450d Mon Sep 17 00:00:00 2001 From: mboudet Date: Mon, 6 Dec 2021 17:45:56 +0100 Subject: [PATCH 046/113] ontology column to dataset --- askomics/libaskomics/Database.py | 11 +++++++++++ askomics/libaskomics/Dataset.py | 3 ++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/askomics/libaskomics/Database.py b/askomics/libaskomics/Database.py index 9d8cf269..3c10ab57 100644 --- a/askomics/libaskomics/Database.py +++ b/askomics/libaskomics/Database.py @@ -198,6 +198,17 @@ def update_datasets_table(self): except Exception: pass + query = ''' + ALTER TABLE datasets + ADD ontology boolean NULL + DEFAULT(0) + ''' + + try: + self.execute_sql_query(query) + except Exception: + pass + def create_integration_table(self): """Create the integration table""" query = ''' diff --git a/askomics/libaskomics/Dataset.py b/askomics/libaskomics/Dataset.py index 927f4896..4d31cbd4 100644 --- a/askomics/libaskomics/Dataset.py +++ b/askomics/libaskomics/Dataset.py @@ -93,7 +93,8 @@ def save_in_db(self): ?, NULL, NULL, - NULL + NULL, + 0 ) ''' From 014984d5ef969331de0eb289546c0f5ce1a94654 Mon Sep 17 00:00:00 2001 From: mboudet Date: Tue, 7 Dec 2021 17:26:48 +0100 Subject: [PATCH 047/113] Onto link to datasets (1/?) --- askomics/api/admin.py | 46 +++++-- askomics/api/ontology.py | 2 +- askomics/libaskomics/Database.py | 5 +- askomics/libaskomics/Dataset.py | 7 + askomics/libaskomics/OntologyManager.py | 120 +++++++++++++----- askomics/libaskomics/SparqlQuery.py | 4 +- .../react/src/routes/admin/ontologies.jsx | 81 +++++++++--- tests/conftest.py | 3 +- tests/test_api_admin.py | 10 +- 9 files changed, 212 insertions(+), 66 deletions(-) diff --git a/askomics/api/admin.py b/askomics/api/admin.py index 2da1979b..49a911c7 100644 --- a/askomics/api/admin.py +++ b/askomics/api/admin.py @@ -654,7 +654,7 @@ def get_ontologies(): """ try: om = OntologyManager(current_app, session) - ontologies = om.list_ontologies() + ontologies = om.list_full_ontologies() except Exception as e: traceback.print_exc(file=sys.stdout) return jsonify({ @@ -685,20 +685,30 @@ def add_ontology(): """ data = request.get_json() - if not data or not (data.get("name") and data.get("uri") and data.get("shortName")): + if not data or not (data.get("name") and data.get("uri") and data.get("shortName") and data.get("type") and data.get("datasetId")): return jsonify({ 'ontologies': [], 'error': True, 'errorMessage': "Missing parameter" }), 400 - om = OntologyManager(current_app, session) - ontologies = om.list_ontologies() - name = data.get("name") uri = data.get("uri") short_name = data.get("shortName") type = data.get("type") + dataset_id = data.get("datasetId") + + om = OntologyManager(current_app, session) + + if type == "ols" and not om.test_ols_ontology(short_name): + return jsonify({ + 'ontologies': [], + 'error': True, + 'errorMessage': "{} ontology not found in OLS".format(short_name) + }), 400 + + om = OntologyManager(current_app, session) + ontologies = om.list_full_ontologies() if type not in ["none", "local", "ols"]: return jsonify({ @@ -707,6 +717,20 @@ def add_ontology(): 'errorMessage': "Invalid type" }), 400 + datasets_info = [{'id': dataset_id}] + + datasets_handler = DatasetsHandler(current_app, session, datasets_info=datasets_info) + datasets_handler.handle_datasets() + + if not len(datasets_handler.datasets) == 1 or not datasets_handler.datasets[0]['public']: + return jsonify({ + 'ontologies': [], + 'error': True, + 'errorMessage': "Invalid dataset id" + }), 400 + + dataset = datasets_handler.datasets[0] + if any([name == onto['name'] or short_name == onto['short_name'] for onto in ontologies]): return jsonify({ 'ontologies': [], @@ -715,8 +739,8 @@ def add_ontology(): }), 400 try: - om.add_ontology(name, uri, short_name, type) - ontologies = om.list_ontologies() + om.add_ontology(name, uri, short_name, dataset['id'], dataset['graph_name'], type) + ontologies = om.list_full_ontologies() except Exception as e: traceback.print_exc(file=sys.stdout) return jsonify({ @@ -755,9 +779,13 @@ def delete_ontologies(): }), 400 om = OntologyManager(current_app, session) + + ontologies = om.list_full_ontologies() + onto_to_delete = [{"id": ontology.id, "dataset_id": ontology.dataset_id} for ontology in ontologies if ontology.id in data.get("ontologiesIdToDelete")] + try: - om.remove_ontologies(data.get("ontologiesIdToDelete")) - ontologies = om.list_ontologies() + om.remove_ontologies(onto_to_delete) + ontologies = om.list_full_ontologies() except Exception as e: traceback.print_exc(file=sys.stdout) return jsonify({ diff --git a/askomics/api/ontology.py b/askomics/api/ontology.py index 2790b901..5f17043a 100644 --- a/askomics/api/ontology.py +++ b/askomics/api/ontology.py @@ -30,7 +30,7 @@ def autocomplete(short_ontology): "results": [] }), 404 - results = om.autocomplete(ontology["uri"], ontology["type"], request.args.get("q"), short_ontology) + results = om.autocomplete(ontology["uri"], ontology["type"], request.args.get("q"), short_ontology, ontology["graph"]) except Exception as e: traceback.print_exc(file=sys.stdout) diff --git a/askomics/libaskomics/Database.py b/askomics/libaskomics/Database.py index 3c10ab57..bd81202f 100644 --- a/askomics/libaskomics/Database.py +++ b/askomics/libaskomics/Database.py @@ -416,7 +416,10 @@ def create_ontologies_table(self): name text NOT NULL, uri text NOT NULL, short_name text NOT NULL, - type text DEFAULT 'local' + type text DEFAULT 'local', + dataset_id INTEGER NOT NULL, + graph text NOT NULL, + FOREIGN KEY(dataset_id) REFERENCES datasets(id) ) ''' self.execute_sql_query(query) diff --git a/askomics/libaskomics/Dataset.py b/askomics/libaskomics/Dataset.py index 4d31cbd4..7a7c7b81 100644 --- a/askomics/libaskomics/Dataset.py +++ b/askomics/libaskomics/Dataset.py @@ -229,3 +229,10 @@ def delete_from_db(self, admin=False): '''.format(where_query) database.execute_sql_query(query, query_params) + + query = ''' + DELETE FROM ontologies + WHERE dataset_id = ? + ''' + + database.execute_sql_query(query, (self.id,)) diff --git a/askomics/libaskomics/OntologyManager.py b/askomics/libaskomics/OntologyManager.py index 6ef9d095..f9e5ff4e 100644 --- a/askomics/libaskomics/OntologyManager.py +++ b/askomics/libaskomics/OntologyManager.py @@ -1,5 +1,9 @@ import requests +from collections import defaultdict +from urllib.parse import quote + + from askomics.libaskomics.Database import Database from askomics.libaskomics.SparqlQuery import SparqlQuery from askomics.libaskomics.Params import Params @@ -61,6 +65,39 @@ def list_ontologies(self): return ontologies + def list_full_ontologies(self): + """Get all ontologies for admin + + Returns + ------- + list + ontologies + """ + + database = Database(self.app, self.session) + + query = ''' + SELECT id, name, uri, short_name, type, dataset_id, graph + FROM ontologies + ''' + + rows = database.execute_sql_query(query) + + ontologies = [] + for row in rows: + prefix = { + 'id': row[0], + 'name': row[1], + 'uri': row[2], + 'short_name': row[3], + 'type': row[4], + 'dataset_id': row[5], + 'graph': row[6] + } + ontologies.append(prefix) + + return ontologies + def get_ontology(self, short_name): """Get a specific ontology based on short name @@ -73,7 +110,7 @@ def get_ontology(self, short_name): database = Database(self.app, self.session) query = ''' - SELECT id, name, uri, short_name, type + SELECT id, name, uri, short_name, type, dataset_id, graph FROM ontologies WHERE short_name = ? ''' @@ -89,10 +126,12 @@ def get_ontology(self, short_name): 'name': ontology[1], 'uri': ontology[2], 'short_name': ontology[3], - 'type': ontology[4] + 'type': ontology[4], + 'dataset_id': ontology[5], + 'graph': ontology[6] } - def add_ontology(self, name, uri, short_name, type="local"): + def add_ontology(self, name, uri, short_name, dataset_id, graph, type="local"): """Create a new ontology Returns @@ -108,11 +147,21 @@ def add_ontology(self, name, uri, short_name, type="local"): ?, ?, ?, + ?, + ?, ? ) ''' - database.execute_sql_query(query, (name, uri, short_name, type,)) + database.execute_sql_query(query, (name, uri, short_name, type, dataset_id, graph)) + + query = ''' + UPDATE datasets SET + ontology=1 + WHERE id=? + ''' + + database.execute_sql_query(query, (dataset_id,)) def remove_ontologies(self, ontology_ids): """Remove ontologies @@ -121,6 +170,19 @@ def remove_ontologies(self, ontology_ids): ------- None """ + # Make sure we only remove the 'ontology' tag to datasets without any ontologies + ontologies = self.list_full_ontologies() + datasets = defaultdict(set) + datasets_to_modify = set() + ontos_to_delete = [ontology['id'] for ontology in ontology_ids] + + for onto in ontologies: + datasets[onto.dataset_id].add(onto.id) + + for key, values in datasets.items(): + if values.issubset(ontos_to_delete): + datasets_to_modify.add(key) + database = Database(self.app, self.session) query = ''' @@ -128,10 +190,24 @@ def remove_ontologies(self, ontology_ids): WHERE id = ? ''' - for ontology_id in ontology_ids: - database.execute_sql_query(query, (ontology_id,)) + dataset_query = ''' + UPDATE datasets SET + ontology=0 + WHERE id=? + ''' + + for ontology in ontology_ids: + database.execute_sql_query(query, (ontology['id'],)) + if ontology['dataset_id'] in datasets_to_modify: + database.execute_sql_query(dataset_query, (ontology['dataset_id'],)) + + def test_ols_ontology(self, shortname): + base_url = "https://www.ebi.ac.uk/ols/api/ontologies/" + quote(shortname.lower()) - def autocomplete(self, ontology_uri, ontology_type, query_term, onto_short_name): + r = requests.get(base_url) + return r.status_code == 200 + + def autocomplete(self, ontology_uri, ontology_type, query_term, onto_short_name, onto_graph): """Search in ontology Returns @@ -142,37 +218,13 @@ def autocomplete(self, ontology_uri, ontology_type, query_term, onto_short_name) if ontology_type == "local": query = SparqlQuery(self.app, self.session, get_graphs=False) # TODO: Actually store the graph in the ontology to quicken search - query.set_graphs_and_endpoints(entities=[ontology_uri]) - return query.autocomplete_local_ontology(ontology_uri, query_term, query.endpoints) - elif ontology_type == "ols": - base_url = "https://www.ebi.ac.uk/ols/api/search" - arguments = { - "q": query_term, - "ontology": onto_short_name, - "rows": 5, - "queryFields": "label", - "type": "class", - "fieldList": "label" - } - - r = requests.get(base_url, params=arguments) - - data = [] - - if not r.status_code == 200: - return data - - res = r.json() - if res['response']['docs']: - data = [term['label'] for term in res['response']['docs']] - - return data - + query.set_graphs([onto_graph]) + return query.autocomplete_local_ontology(ontology_uri, query_term) elif ontology_type == "ols": base_url = "https://www.ebi.ac.uk/ols/api/search" arguments = { "q": query_term, - "ontology": onto_short_name, + "ontology": quote(onto_short_name.lower()), "rows": 5, "queryFields": "label", "type": "class", diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index 41565a1e..dd73b902 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -523,7 +523,7 @@ def get_uri_parameters(self, uri, endpoints): return formated_data - def autocomplete_local_ontology(self, uri, query, endpoints): + def autocomplete_local_ontology(self, uri, query): """Get results for a specific query Parameters @@ -558,7 +558,7 @@ def autocomplete_local_ontology(self, uri, query, endpoints): sparql = self.format_query(raw_query, limit=5, replace_froms=True, federated=False) - query_launcher = SparqlQueryLauncher(self.app, self.session, get_result_query=True, federated=False, endpoints=endpoints) + query_launcher = SparqlQueryLauncher(self.app, self.session, get_result_query=True, federated=False) _, data = query_launcher.process_query(sparql) formated_data = [] diff --git a/askomics/react/src/routes/admin/ontologies.jsx b/askomics/react/src/routes/admin/ontologies.jsx index 50632410..f2d6957b 100644 --- a/askomics/react/src/routes/admin/ontologies.jsx +++ b/askomics/react/src/routes/admin/ontologies.jsx @@ -21,10 +21,12 @@ export default class Ontologies extends Component { newontologyError: false, newontologyErrorMessage: '', ontologies: [], + datasets: [], name: "", uri: "", shortName: "", type: "local", + datasetId: "", ontologiesSelected: [] } this.handleChangeValue = this.handleChangeValue.bind(this) @@ -64,7 +66,8 @@ export default class Ontologies extends Component { return ( this.state.name.length > 0 && this.state.uri.length > 0 && - this.state.shortName.length > 0 + this.state.shortName.length > 0 && + this.state.datasetId.length > 0 ) } @@ -93,6 +96,16 @@ export default class Ontologies extends Component { } } + getDatasetName (datasetId) { + return this.state.datasets.map(dataset => { + if (dataset.id == datasetId) { + return dataset.name + } else { + return null + } + }).reduce(name=> name) + } + handleAddOntology(event) { let requestUrl = "/api/admin/addontology" @@ -100,7 +113,8 @@ export default class Ontologies extends Component { name: this.state.name, uri: this.state.uri, shortName: this.state.shortName, - type: this.state.type + type: this.state.type, + datasetId: this.state.datasetId } axios.post(requestUrl, data, { baseURL: this.props.config.proxyPath, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) @@ -131,6 +145,7 @@ export default class Ontologies extends Component { componentDidMount () { if (!this.props.waitForStart) { this.loadOntologies() + this.loadDatasets() } } @@ -154,6 +169,22 @@ export default class Ontologies extends Component { }) } + loadDatasets() { + let requestUrl = '/api/datasets' + axios.get(requestUrl, { baseURL: this.props.config.proxyPath, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) + .then(response => { + this.setState({ + datasets: response.data.datasets + }) + }) + .catch(error => { + console.log(error, error.response.data.errorMessage) + this.setState({ + success: !error.response.data.error + }) + }) + } + componentWillUnmount () { if (!this.props.waitForStart) { this.cancelRequest() @@ -188,6 +219,12 @@ export default class Ontologies extends Component { dataField: 'uri', text: 'Uri', sort: true + }, { + editable: false, + dataField: 'dataset_id', + text: 'Dataset', + formatter: (cell, row) => { return this.getDatasetName(cell)}, + sort: true }, { editable: false, dataField: 'type', @@ -223,35 +260,49 @@ export default class Ontologies extends Component {
- + - + - + - - - - - - - - - - + + + + + + {this.state.datasets.map(dataset => { + if (dataset.public == 1){ + return + } + })} + + + + + + + + + + + + + + diff --git a/tests/conftest.py b/tests/conftest.py index b3c94001..5782629e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -583,8 +583,9 @@ def create_prefix(self): def create_ontology(self): """Create ontology""" + self.upload_and_integrate() om = OntologyManager(self.app, self.session) - om.add_ontology("Open Biological and Biomedical Ontology", "http://purl.obolibrary.org/obo/agro.owl", "OBO") + om.add_ontology("Open Biological and Biomedical Ontology", "http://purl.obolibrary.org/obo/agro.owl", "OBO", 1, "mygraph", "local") @staticmethod def get_random_string(number): diff --git a/tests/test_api_admin.py b/tests/test_api_admin.py index 5b435d92..b845d7ba 100644 --- a/tests/test_api_admin.py +++ b/tests/test_api_admin.py @@ -612,7 +612,9 @@ def test_view_ontologies(self, client): "name": "Open Biological and Biomedical Ontology", "uri": "http://purl.obolibrary.org/obo/agro.owl", "short_name": "OBO", - "type": "local" + "type": "local", + "dataset_id": 1, + "graph": "mygraph" }] } @@ -624,7 +626,7 @@ def test_add_ontology(self, client): client.create_two_users() client.log_user("jsmith") - data = {"shortName": "OBO", "uri": "http://purl.obolibrary.org/obo/agro.owl", "name": "Open Biological and Biomedical Ontology", "type": "local"} + data = {"shortName": "OBO", "uri": "http://purl.obolibrary.org/obo/agro.owl", "name": "Open Biological and Biomedical Ontology", "type": "local", "datasetId": 1} response = client.client.post('/api/admin/addontology', json=data) assert response.status_code == 401 @@ -641,7 +643,9 @@ def test_add_ontology(self, client): "name": "Open Biological and Biomedical Ontology", "uri": "http://purl.obolibrary.org/obo/agro.owl", "short_name": "OBO", - "type": "local" + "type": "local", + "dataset_id": 1, + "graph": "mygraph" }] } From c10f149ce4c3d06a96f898782bc150c30fef0234 Mon Sep 17 00:00:00 2001 From: mboudet Date: Wed, 8 Dec 2021 10:12:12 +0100 Subject: [PATCH 048/113] Fix tests --- askomics/api/admin.py | 13 ++++-- .../react/src/routes/admin/ontologies.jsx | 46 +++++++++---------- tests/test_api_admin.py | 2 + 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/askomics/api/admin.py b/askomics/api/admin.py index 49a911c7..927d7a51 100644 --- a/askomics/api/admin.py +++ b/askomics/api/admin.py @@ -719,8 +719,15 @@ def add_ontology(): datasets_info = [{'id': dataset_id}] - datasets_handler = DatasetsHandler(current_app, session, datasets_info=datasets_info) - datasets_handler.handle_datasets() + try: + datasets_handler = DatasetsHandler(current_app, session, datasets_info=datasets_info) + datasets_handler.handle_datasets() + except IndexError: + return jsonify({ + 'ontologies': [], + 'error': True, + 'errorMessage': "Dataset {} not found".format(dataset_id) + }), 400 if not len(datasets_handler.datasets) == 1 or not datasets_handler.datasets[0]['public']: return jsonify({ @@ -781,7 +788,7 @@ def delete_ontologies(): om = OntologyManager(current_app, session) ontologies = om.list_full_ontologies() - onto_to_delete = [{"id": ontology.id, "dataset_id": ontology.dataset_id} for ontology in ontologies if ontology.id in data.get("ontologiesIdToDelete")] + onto_to_delete = [{"id": ontology['id'], "dataset_id": ontology['dataset_id']} for ontology in ontologies if ontology['id'] in data.get("ontologiesIdToDelete")] try: om.remove_ontologies(onto_to_delete) diff --git a/askomics/react/src/routes/admin/ontologies.jsx b/askomics/react/src/routes/admin/ontologies.jsx index f2d6957b..0e11c642 100644 --- a/askomics/react/src/routes/admin/ontologies.jsx +++ b/askomics/react/src/routes/admin/ontologies.jsx @@ -280,29 +280,29 @@ export default class Ontologies extends Component { - - - - - {this.state.datasets.map(dataset => { - if (dataset.public == 1){ - return - } - })} - - - - - - - - - - - - - - + + + + + {this.state.datasets.map(dataset => { + if (dataset.public == 1){ + return + } + })} + + + + + + + + + + + + + + diff --git a/tests/test_api_admin.py b/tests/test_api_admin.py index b845d7ba..2f16bff8 100644 --- a/tests/test_api_admin.py +++ b/tests/test_api_admin.py @@ -628,6 +628,8 @@ def test_add_ontology(self, client): data = {"shortName": "OBO", "uri": "http://purl.obolibrary.org/obo/agro.owl", "name": "Open Biological and Biomedical Ontology", "type": "local", "datasetId": 1} + client.upload_and_integrate() + response = client.client.post('/api/admin/addontology', json=data) assert response.status_code == 401 From a9831845b30bec14d08c8fb1c21891fbbd882bb7 Mon Sep 17 00:00:00 2001 From: mboudet Date: Wed, 8 Dec 2021 10:59:45 +0100 Subject: [PATCH 049/113] Fix tests 2 --- askomics/libaskomics/OntologyManager.py | 2 +- tests/conftest.py | 7 +++++++ tests/test_api_admin.py | 7 +++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/askomics/libaskomics/OntologyManager.py b/askomics/libaskomics/OntologyManager.py index f9e5ff4e..a5eea007 100644 --- a/askomics/libaskomics/OntologyManager.py +++ b/askomics/libaskomics/OntologyManager.py @@ -177,7 +177,7 @@ def remove_ontologies(self, ontology_ids): ontos_to_delete = [ontology['id'] for ontology in ontology_ids] for onto in ontologies: - datasets[onto.dataset_id].add(onto.id) + datasets[onto['dataset_id']].add(onto['id']) for key, values in datasets.items(): if values.issubset(ontos_to_delete): diff --git a/tests/conftest.py b/tests/conftest.py index 5782629e..65c6f84d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -498,6 +498,13 @@ def create_result(self, has_form=False): "size": file_size } + def publicize_dataset(self, dataset_id, public=True): + """Publicize a result""" + + dataset_info = {"id": dataset_id} + result = Dataset(self.app, self.session, dataset_info) + result.toggle_public(public) + def publicize_result(self, result_id, public=True): """Publicize a result""" diff --git a/tests/test_api_admin.py b/tests/test_api_admin.py index 2f16bff8..44a421d9 100644 --- a/tests/test_api_admin.py +++ b/tests/test_api_admin.py @@ -637,6 +637,13 @@ def test_add_ontology(self, client): response = client.client.post('/api/admin/addontology', json=data) + # Dataset is not public + assert response.status_code == 400 + assert response.json['errorMessage'] == "Invalid dataset id" + + self.publicize_dataset(1, True) + response = client.client.post('/api/admin/delete_ontologies', json=data) + expected = { "error": False, "errorMessage": "", From 090f2fe184b9d07c5ce5acdace316ecb5914a3f4 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 8 Dec 2021 14:38:22 +0000 Subject: [PATCH 050/113] Fixes, updates & tests --- Pipfile | 2 +- Pipfile.lock | 610 ++++++++++++++++++++------------ askomics/api/admin.py | 4 +- askomics/libaskomics/Dataset.py | 37 +- tests/conftest.py | 16 +- tests/test_api_admin.py | 16 +- 6 files changed, 425 insertions(+), 260 deletions(-) diff --git a/Pipfile b/Pipfile index cd5a56d0..6509b8eb 100644 --- a/Pipfile +++ b/Pipfile @@ -13,7 +13,7 @@ python-magic = "*" rdflib = "*" sparqlwrapper = "*" requests = "*" -celery = "*" +celery = "==5.0.5" redis = "*" watchdog = "*" gitpython = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 56c2411a..c1942d7c 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d398feea9b583f305001058caf3d00d225702ce354469889db8ec34953b40471" + "sha256": "5d768dd0f5c397f0380f7a594b66aed5e23d923674edfa3c62b4e47f5ce3e81e" }, "pipfile-spec": 6, "requires": {}, @@ -32,10 +32,10 @@ }, "bcbio-gff": { "hashes": [ - "sha256:6e6f70639149612272a3b298a93ac50bba6f9ecece934f2a0ea86d4abde975da" + "sha256:34dfa970e14f4533dc63c0a5512b7b5221e4a06449e6aaa344162ed5fdd7a1de" ], "index": "pypi", - "version": "==0.6.7" + "version": "==0.6.9" }, "billiard": { "hashes": [ @@ -96,13 +96,21 @@ ], "version": "==2.49.0" }, + "cached-property": { + "hashes": [ + "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130", + "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0" + ], + "markers": "python_version < '3.8'", + "version": "==1.5.2" + }, "celery": { "hashes": [ - "sha256:8d9a3de9162965e97f8e8cc584c67aad83b3f7a267584fa47701ed11c3e0d4b0", - "sha256:9dab2170b4038f7bf10ef2861dbf486ddf1d20592290a1040f7b7a1259705d42" + "sha256:5e8d364e058554e83bbb116e8377d90c79be254785f357cb2cec026e79febe13", + "sha256:f4efebe6f8629b0da2b8e529424de376494f5b7a743c321c8a2ddc2b1414921c" ], "index": "pypi", - "version": "==5.1.2" + "version": "==5.0.5" }, "certifi": { "hashes": [ @@ -113,11 +121,11 @@ }, "charset-normalizer": { "hashes": [ - "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0", - "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b" + "sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721", + "sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c" ], "markers": "python_version >= '3'", - "version": "==2.0.7" + "version": "==2.0.9" }, "click": { "hashes": [ @@ -132,7 +140,7 @@ "sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667", "sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035" ], - "markers": "python_version < '4' and python_full_version >= '3.6.2'", + "markers": "python_full_version >= '3.6.2' and python_full_version < '4.0.0'", "version": "==0.3.0" }, "click-plugins": { @@ -151,11 +159,11 @@ }, "configparser": { "hashes": [ - "sha256:85d5de102cfe6d14a5172676f09d19c465ce63d6019cf0a4ef13385fc535e828", - "sha256:af59f2cdd7efbdd5d111c1976ecd0b82db9066653362f0962d7bf1d3ab89a1fa" + "sha256:1b35798fdf1713f1c3139016cfcbc461f09edbf099d1fb658d4b7479fcaa3daa", + "sha256:e8b39238fb6f0153a069aa253d349467c3c4737934f253ef6abac5fe0eca1e5d" ], "index": "pypi", - "version": "==5.0.2" + "version": "==5.2.0" }, "deepdiff": { "hashes": [ @@ -165,6 +173,14 @@ "index": "pypi", "version": "==5.6.0" }, + "deprecated": { + "hashes": [ + "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d", + "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.2.13" + }, "flask": { "hashes": [ "sha256:0fbeb6180d383a9186d0d6ed954e0042ad9f18e0e8de088b2b419d526927d196", @@ -183,11 +199,11 @@ }, "gitdb": { "hashes": [ - "sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0", - "sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005" + "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd", + "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa" ], - "markers": "python_version >= '3.4'", - "version": "==4.0.7" + "markers": "python_version >= '3.6'", + "version": "==4.0.9" }, "gitpython": { "hashes": [ @@ -213,6 +229,14 @@ "markers": "python_version >= '3'", "version": "==3.3" }, + "importlib-metadata": { + "hashes": [ + "sha256:53ccfd5c134223e497627b9815d5030edf77d2ed573922f7a0b8f8bb81a1c100", + "sha256:75bdec14c397f528724c1bfd9709d660b33a4d2e77387a3358f20b848bb5e5fb" + ], + "markers": "python_version < '3.8'", + "version": "==4.8.2" + }, "isodate": { "hashes": [ "sha256:2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8", @@ -238,17 +262,18 @@ }, "kombu": { "hashes": [ - "sha256:01481d99f4606f6939cdc9b637264ed353ee9e3e4f62cfb582324142c41a572d", - "sha256:e2dedd8a86c9077c350555153825a31e456a0dc20c15d5751f00137ec9c75f0a" + "sha256:0f5d0763fb916808f617b886697b2be28e6bc35026f08e679697fc814b48a608", + "sha256:d36f0cde6a18d9eb7b6b3aa62a59bfdff7f5724689850e447eca5be8efc9d501" ], - "markers": "python_version >= '3.6'", - "version": "==5.1.0" + "markers": "python_version >= '3.7'", + "version": "==5.2.2" }, "markupsafe": { "hashes": [ "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298", "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64", "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b", + "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194", "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567", "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff", "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724", @@ -256,6 +281,7 @@ "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646", "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35", "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6", + "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a", "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6", "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad", "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26", @@ -263,27 +289,36 @@ "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac", "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7", "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6", + "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047", "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75", "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f", + "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b", "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135", "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8", "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a", "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a", + "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1", "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9", "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864", "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914", + "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee", + "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f", "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18", "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8", "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2", "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d", "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b", "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b", + "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86", + "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6", "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f", "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb", "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833", "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28", + "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e", "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415", "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902", + "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f", "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d", "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9", "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d", @@ -291,10 +326,14 @@ "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066", "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c", "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1", + "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a", + "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207", "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f", "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53", + "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd", "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134", "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85", + "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9", "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5", "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94", "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509", @@ -306,39 +345,39 @@ }, "numpy": { "hashes": [ - "sha256:09858463db6dd9f78b2a1a05c93f3b33d4f65975771e90d2cf7aadb7c2f66edf", - "sha256:209666ce9d4a817e8a4597cd475b71b4878a85fa4b8db41d79fdb4fdee01dde2", - "sha256:298156f4d3d46815eaf0fcf0a03f9625fc7631692bd1ad851517ab93c3168fc6", - "sha256:30fc68307c0155d2a75ad19844224be0f2c6f06572d958db4e2053f816b859ad", - "sha256:423216d8afc5923b15df86037c6053bf030d15cc9e3224206ef868c2d63dd6dc", - "sha256:426a00b68b0d21f2deb2ace3c6d677e611ad5a612d2c76494e24a562a930c254", - "sha256:466e682264b14982012887e90346d33435c984b7fead7b85e634903795c8fdb0", - "sha256:51a7b9db0a2941434cd930dacaafe0fc9da8f3d6157f9d12f761bbde93f46218", - "sha256:52a664323273c08f3b473548bf87c8145b7513afd63e4ebba8496ecd3853df13", - "sha256:550564024dc5ceee9421a86fc0fb378aa9d222d4d0f858f6669eff7410c89bef", - "sha256:5de64950137f3a50b76ce93556db392e8f1f954c2d8207f78a92d1f79aa9f737", - "sha256:640c1ccfd56724f2955c237b6ccce2e5b8607c3bc1cc51d3933b8c48d1da3723", - "sha256:7fdc7689daf3b845934d67cb221ba8d250fdca20ac0334fea32f7091b93f00d3", - "sha256:805459ad8baaf815883d0d6f86e45b3b0b67d823a8f3fa39b1ed9c45eaf5edf1", - "sha256:92a0ab128b07799dd5b9077a9af075a63467d03ebac6f8a93e6440abfea4120d", - "sha256:9f2dc79c093f6c5113718d3d90c283f11463d77daa4e83aeeac088ec6a0bda52", - "sha256:a5109345f5ce7ddb3840f5970de71c34a0ff7fceb133c9441283bb8250f532a3", - "sha256:a55e4d81c4260386f71d22294795c87609164e22b28ba0d435850fbdf82fc0c5", - "sha256:a9da45b748caad72ea4a4ed57e9cd382089f33c5ec330a804eb420a496fa760f", - "sha256:b160b9a99ecc6559d9e6d461b95c8eec21461b332f80267ad2c10394b9503496", - "sha256:b342064e647d099ca765f19672696ad50c953cac95b566af1492fd142283580f", - "sha256:b5e8590b9245803c849e09bae070a8e1ff444f45e3f0bed558dd722119eea724", - "sha256:bf75d5825ef47aa51d669b03ce635ecb84d69311e05eccea083f31c7570c9931", - "sha256:c01b59b33c7c3ba90744f2c695be571a3bd40ab2ba7f3d169ffa6db3cfba614f", - "sha256:d96a6a7d74af56feb11e9a443150216578ea07b7450f7c05df40eec90af7f4a7", - "sha256:dd0e3651d210068d13e18503d75aaa45656eef51ef0b261f891788589db2cc38", - "sha256:e167b9805de54367dcb2043519382be541117503ce99e3291cc9b41ca0a83557", - "sha256:e42029e184008a5fd3d819323345e25e2337b0ac7f5c135b7623308530209d57", - "sha256:f545c082eeb09ae678dd451a1b1dbf17babd8a0d7adea02897a76e639afca310", - "sha256:fde50062d67d805bc96f1a9ecc0d37bfc2a8f02b937d2c50824d186aa91f2419" + "sha256:0b78ecfa070460104934e2caf51694ccd00f37d5e5dbe76f021b1b0b0d221823", + "sha256:1247ef28387b7bb7f21caf2dbe4767f4f4175df44d30604d42ad9bd701ebb31f", + "sha256:1403b4e2181fc72664737d848b60e65150f272fe5a1c1cbc16145ed43884065a", + "sha256:170b2a0805c6891ca78c1d96ee72e4c3ed1ae0a992c75444b6ab20ff038ba2cd", + "sha256:2e4ed57f45f0aa38beca2a03b6532e70e548faf2debbeb3291cfc9b315d9be8f", + "sha256:32fe5b12061f6446adcbb32cf4060a14741f9c21e15aaee59a207b6ce6423469", + "sha256:34f3456f530ae8b44231c63082c8899fe9c983fd9b108c997c4b1c8c2d435333", + "sha256:4c9c23158b87ed0e70d9a50c67e5c0b3f75bcf2581a8e34668d4e9d7474d76c6", + "sha256:5d95668e727c75b3f5088ec7700e260f90ec83f488e4c0aaccb941148b2cd377", + "sha256:615d4e328af7204c13ae3d4df7615a13ff60a49cb0d9106fde07f541207883ca", + "sha256:69077388c5a4b997442b843dbdc3a85b420fb693ec8e33020bb24d647c164fa5", + "sha256:74b85a17528ca60cf98381a5e779fc0264b4a88b46025e6bcbe9621f46bb3e63", + "sha256:81225e58ef5fce7f1d80399575576fc5febec79a8a2742e8ef86d7b03beef49f", + "sha256:8890b3360f345e8360133bc078d2dacc2843b6ee6059b568781b15b97acbe39f", + "sha256:92aafa03da8658609f59f18722b88f0a73a249101169e28415b4fa148caf7e41", + "sha256:9864424631775b0c052f3bd98bc2712d131b3e2cd95d1c0c68b91709170890b0", + "sha256:9e6f5f50d1eff2f2f752b3089a118aee1ea0da63d56c44f3865681009b0af162", + "sha256:a3deb31bc84f2b42584b8c4001c85d1934dbfb4030827110bc36bfd11509b7bf", + "sha256:ad010846cdffe7ec27e3f933397f8a8d6c801a48634f419e3d075db27acf5880", + "sha256:b1e2312f5b8843a3e4e8224b2b48fe16119617b8fc0a54df8f50098721b5bed2", + "sha256:bc988afcea53e6156546e5b2885b7efab089570783d9d82caf1cfd323b0bb3dd", + "sha256:c449eb870616a7b62e097982c622d2577b3dbc800aaf8689254ec6e0197cbf1e", + "sha256:c74c699b122918a6c4611285cc2cad4a3aafdb135c22a16ec483340ef97d573c", + "sha256:c885bfc07f77e8fee3dc879152ba993732601f1f11de248d4f357f0ffea6a6d4", + "sha256:e3c3e990274444031482a31280bf48674441e0a5b55ddb168f3a6db3e0c38ec8", + "sha256:e4799be6a2d7d3c33699a6f77201836ac975b2e1b98c2a07f66a38f499cb50ce", + "sha256:e6c76a87633aa3fa16614b61ccedfae45b91df2767cf097aa9c933932a7ed1e0", + "sha256:e89717274b41ebd568cd7943fc9418eeb49b1785b66031bc8a7f6300463c5898", + "sha256:f5162ec777ba7138906c9c274353ece5603646c6965570d82905546579573f73", + "sha256:fde96af889262e85aa033f8ee1d3241e32bf36228318a61f1ace579df4e8170d" ], "markers": "python_version < '3.11' and python_version >= '3.7'", - "version": "==1.21.2" + "version": "==1.21.4" }, "ordered-set": { "hashes": [ @@ -349,11 +388,11 @@ }, "prompt-toolkit": { "hashes": [ - "sha256:6076e46efae19b1e0ca1ec003ed37a933dc94b4d20f486235d436e64771dcd5c", - "sha256:eb71d5a6b72ce6db177af4a7d4d7085b99756bf656d98ffcc4fecd36850eea6c" + "sha256:5f29d62cb7a0ecacfa3d8ceea05a63cd22500543472d64298fc06ddda906b25d", + "sha256:7053aba00895473cb357819358ef33f11aa97e4ac83d38efb123e5649ceeecaf" ], "markers": "python_full_version >= '3.6.2'", - "version": "==3.0.20" + "version": "==3.0.23" }, "pyasn1": { "hashes": [ @@ -400,30 +439,33 @@ }, "pyparsing": { "hashes": [ - "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", - "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4", + "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.4.7" + "markers": "python_version >= '3.6'", + "version": "==3.0.6" }, "pysam": { "hashes": [ - "sha256:08f88fa4472e140e39c9ec3c01643563587a28e49c115ef9077295b0452342ac", - "sha256:097eedc82095ff52e020b6e0e099a171313e60083c68ad861ac474f325e2b7d0", - "sha256:18478ac02511d6171bce1891b503031eaf5e22b7a38cf62e00f3a070a4a37da2", - "sha256:2fd8fa6205ad893082ec0aa1992446e3d9c63bfe9f7a3e59d81a956780e5cc2a", - "sha256:4032361c424fb3b27a7da7ddeba0de8acb6c548b025bdc03c8ffc6b306d7ee9a", - "sha256:4d0a35dec9194bacbde0647ddf32ebf805a6724b0192758039c031fce849f01f", - "sha256:4f5188fda840fe51144b8c56c45af014174295c3f539a40333f9e270ca0d5e01", - "sha256:5d140da81ca42f474006f5cc0fd66647f1b08d559af7026bbe9f01fab029bffd", - "sha256:6f802073d625a6a9b47aaaed6ff6b08e90ec4ad79df271073452fa8c7a62c529", - "sha256:7caada03fbec2e18b8eb2c80aabf8834ea2ceb12b2aa1c0fa6d4ba42b60d83fa", - "sha256:c4a8099cf474067eaf5e270b5e71433a9ea0e97af90262143a655528eb756cd9", - "sha256:cb2c3ca9ff3b5c9694846254fea9f8697406a35769b11767a312e97ad5c8cedd", - "sha256:eb35399530188efe48ef136a043ba3acf9f538f1b11e946f3aaea9fd09c8cbde" + "sha256:0cfa16f76ed3c3119c7b3c8dfdcba9e010fbcdcf87eaa165351bb369da5a6bf1", + "sha256:1d6d49a0b3c626fae410a93d4c80583a8b5ddaacc9b46a080b250dbcebd30a59", + "sha256:2717509556fecddf7c73966fa62066c6a59a7d39b755d8972afa8d143a1d5aa5", + "sha256:493988420db16e6ee03393518e4d272df05f0a35780248c08c61da7411e520e7", + "sha256:7a8a25fceaaa96e5b4c8b0a7fd6bb0b20b6c262dc4cc867c6d1467ac990f1d77", + "sha256:7ea2e019294e4bf25e4892b5de69c43f54fb6ac42b681265268aa322e1f36f5b", + "sha256:7f6a4ec58ad7995b791a71bf35f673ea794e734c587ea7329fca5cce9c53a7af", + "sha256:9422c2d0b581c3d24f247c15bb8981569e636003c4d6cad39ccd1bf205a79f2c", + "sha256:a88f875114bd3d8efb7fade80e0640094383ec5043861aa575175fa9a56edf90", + "sha256:c90341434e7a99439174aa64ca5406f63528be4217d4401fb30ec4ea4629c559", + "sha256:ca0c9289dfdc5e1a81bccdb8305192cd14cf9730bd21320ceca949fde071a572", + "sha256:cfb162358c5284b31b2b88b10947e0f1013da2d85ba0fd0b5723dd142c15329e", + "sha256:cfffad99cf3968cf85aadb70a8a02303f9172ea21abe02d587c44f808c504f52", + "sha256:e13e496da3a432db24f424439834b0ab5f40700a3db6e610d06f8bd639d9fd2d", + "sha256:ef5d8ad01cac8974cd09832c226cbb63a3f7c5bd63727d8e59447021ee16a186", + "sha256:f5a23a5dcf32f01c66d44e89113fa8f7522997ea43fbc0f98e5250a907911a5f" ], "index": "pypi", - "version": "==0.17.0" + "version": "==0.18.0" }, "python-dateutil": { "hashes": [ @@ -435,10 +477,10 @@ }, "python-ldap": { "hashes": [ - "sha256:4711cacf013e298754abd70058ccc995758177fb425f1c2d30e71adfc1d00aa5" + "sha256:60464c8fc25e71e0fd40449a24eae482dcd0fb7fcf823e7de627a6525b3e0d12" ], "index": "pypi", - "version": "==3.3.1" + "version": "==3.4.0" }, "python-magic": { "hashes": [ @@ -457,38 +499,42 @@ }, "pyyaml": { "hashes": [ - "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf", - "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696", - "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393", - "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77", - "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922", - "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5", - "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8", - "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10", - "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc", - "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018", - "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e", - "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253", - "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347", - "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183", - "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541", - "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb", - "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185", - "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc", - "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db", - "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa", - "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46", - "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122", - "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b", - "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63", - "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df", - "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc", - "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247", - "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6", - "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==5.4.1" + "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", + "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", + "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", + "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", + "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", + "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", + "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", + "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", + "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", + "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", + "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", + "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", + "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", + "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", + "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", + "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", + "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", + "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", + "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", + "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", + "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", + "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", + "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", + "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", + "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", + "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", + "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", + "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", + "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", + "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", + "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", + "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", + "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" + ], + "markers": "python_version >= '3.6'", + "version": "==6.0" }, "rdflib": { "hashes": [ @@ -500,11 +546,11 @@ }, "redis": { "hashes": [ - "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2", - "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24" + "sha256:c8481cf414474e3497ec7971a1ba9b998c8efad0f0d289a009a5bbef040894f9", + "sha256:ccf692811f2c1fc7a92b466aa2599e4a6d2d73d5f736a2c70be600657c0da34a" ], "index": "pypi", - "version": "==3.5.3" + "version": "==4.0.2" }, "requests": { "hashes": [ @@ -526,11 +572,19 @@ "flask" ], "hashes": [ - "sha256:b9844751e40710e84a457c5bc29b21c383ccb2b63d76eeaad72f7f1c808c8828", - "sha256:c091cc7115ff25fe3a0e410dbecd7a996f81a3f6137d2272daef32d6c3cfa6dc" + "sha256:0db297ab32e095705c20f742c3a5dac62fe15c4318681884053d0898e5abb2f6", + "sha256:789a11a87ca02491896e121efdd64e8fd93327b69e8f2f7d42f03e2569648e88" ], "index": "pypi", - "version": "==1.4.3" + "version": "==1.5.0" + }, + "setuptools": { + "hashes": [ + "sha256:6d10741ff20b89cd8c6a536ee9dc90d3002dec0226c78fb98605bfb9ef8a7adf", + "sha256:d144f85102f999444d06f9c0e8c737fd0194f10f2f7e5fdb77573f6e2fa4fad0" + ], + "markers": "python_version >= '3.6'", + "version": "==59.5.0" }, "six": { "hashes": [ @@ -542,11 +596,11 @@ }, "smmap": { "hashes": [ - "sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182", - "sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2" + "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94", + "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936" ], - "markers": "python_version >= '3.5'", - "version": "==4.0.0" + "markers": "python_version >= '3.6'", + "version": "==5.0.0" }, "sparqlwrapper": { "hashes": [ @@ -574,19 +628,18 @@ }, "typing-extensions": { "hashes": [ - "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e", - "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7", - "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34" + "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e", + "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b" ], "markers": "python_version < '3.10'", - "version": "==3.10.0.2" + "version": "==4.0.1" }, "urllib3": { "hashes": [ "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece", "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_full_version < '4.0.0'", "version": "==1.26.7" }, "validate-email": { @@ -647,6 +700,71 @@ ], "index": "pypi", "version": "==0.16.1" + }, + "wrapt": { + "hashes": [ + "sha256:086218a72ec7d986a3eddb7707c8c4526d677c7b35e355875a0fe2918b059179", + "sha256:0877fe981fd76b183711d767500e6b3111378ed2043c145e21816ee589d91096", + "sha256:0a017a667d1f7411816e4bf214646d0ad5b1da2c1ea13dec6c162736ff25a374", + "sha256:0cb23d36ed03bf46b894cfec777eec754146d68429c30431c99ef28482b5c1df", + "sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185", + "sha256:220a869982ea9023e163ba915077816ca439489de6d2c09089b219f4e11b6785", + "sha256:25b1b1d5df495d82be1c9d2fad408f7ce5ca8a38085e2da41bb63c914baadff7", + "sha256:2dded5496e8f1592ec27079b28b6ad2a1ef0b9296d270f77b8e4a3a796cf6909", + "sha256:2ebdde19cd3c8cdf8df3fc165bc7827334bc4e353465048b36f7deeae8ee0918", + "sha256:43e69ffe47e3609a6aec0fe723001c60c65305784d964f5007d5b4fb1bc6bf33", + "sha256:46f7f3af321a573fc0c3586612db4decb7eb37172af1bc6173d81f5b66c2e068", + "sha256:47f0a183743e7f71f29e4e21574ad3fa95676136f45b91afcf83f6a050914829", + "sha256:498e6217523111d07cd67e87a791f5e9ee769f9241fcf8a379696e25806965af", + "sha256:4b9c458732450ec42578b5642ac53e312092acf8c0bfce140ada5ca1ac556f79", + "sha256:51799ca950cfee9396a87f4a1240622ac38973b6df5ef7a41e7f0b98797099ce", + "sha256:5601f44a0f38fed36cc07db004f0eedeaadbdcec90e4e90509480e7e6060a5bc", + "sha256:5f223101f21cfd41deec8ce3889dc59f88a59b409db028c469c9b20cfeefbe36", + "sha256:610f5f83dd1e0ad40254c306f4764fcdc846641f120c3cf424ff57a19d5f7ade", + "sha256:6a03d9917aee887690aa3f1747ce634e610f6db6f6b332b35c2dd89412912bca", + "sha256:705e2af1f7be4707e49ced9153f8d72131090e52be9278b5dbb1498c749a1e32", + "sha256:766b32c762e07e26f50d8a3468e3b4228b3736c805018e4b0ec8cc01ecd88125", + "sha256:77416e6b17926d953b5c666a3cb718d5945df63ecf922af0ee576206d7033b5e", + "sha256:778fd096ee96890c10ce96187c76b3e99b2da44e08c9e24d5652f356873f6709", + "sha256:78dea98c81915bbf510eb6a3c9c24915e4660302937b9ae05a0947164248020f", + "sha256:7dd215e4e8514004c8d810a73e342c536547038fb130205ec4bba9f5de35d45b", + "sha256:7dde79d007cd6dfa65afe404766057c2409316135cb892be4b1c768e3f3a11cb", + "sha256:81bd7c90d28a4b2e1df135bfbd7c23aee3050078ca6441bead44c42483f9ebfb", + "sha256:85148f4225287b6a0665eef08a178c15097366d46b210574a658c1ff5b377489", + "sha256:865c0b50003616f05858b22174c40ffc27a38e67359fa1495605f96125f76640", + "sha256:87883690cae293541e08ba2da22cacaae0a092e0ed56bbba8d018cc486fbafbb", + "sha256:8aab36778fa9bba1a8f06a4919556f9f8c7b33102bd71b3ab307bb3fecb21851", + "sha256:8c73c1a2ec7c98d7eaded149f6d225a692caa1bd7b2401a14125446e9e90410d", + "sha256:936503cb0a6ed28dbfa87e8fcd0a56458822144e9d11a49ccee6d9a8adb2ac44", + "sha256:944b180f61f5e36c0634d3202ba8509b986b5fbaf57db3e94df11abee244ba13", + "sha256:96b81ae75591a795d8c90edc0bfaab44d3d41ffc1aae4d994c5aa21d9b8e19a2", + "sha256:981da26722bebb9247a0601e2922cedf8bb7a600e89c852d063313102de6f2cb", + "sha256:ae9de71eb60940e58207f8e71fe113c639da42adb02fb2bcbcaccc1ccecd092b", + "sha256:b73d4b78807bd299b38e4598b8e7bd34ed55d480160d2e7fdaabd9931afa65f9", + "sha256:d4a5f6146cfa5c7ba0134249665acd322a70d1ea61732723c7d3e8cc0fa80755", + "sha256:dd91006848eb55af2159375134d724032a2d1d13bcc6f81cd8d3ed9f2b8e846c", + "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a", + "sha256:e6906d6f48437dfd80464f7d7af1740eadc572b9f7a4301e7dd3d65db285cacf", + "sha256:e92d0d4fa68ea0c02d39f1e2f9cb5bc4b4a71e8c442207433d8db47ee79d7aa3", + "sha256:e94b7d9deaa4cc7bac9198a58a7240aaf87fe56c6277ee25fa5b3aa1edebd229", + "sha256:ea3e746e29d4000cd98d572f3ee2a6050a4f784bb536f4ac1f035987fc1ed83e", + "sha256:ec7e20258ecc5174029a0f391e1b948bf2906cd64c198a9b8b281b811cbc04de", + "sha256:ec9465dd69d5657b5d2fa6133b3e1e989ae27d29471a672416fd729b429eb554", + "sha256:f122ccd12fdc69628786d0c947bdd9cb2733be8f800d88b5a37c57f1f1d73c10", + "sha256:f99c0489258086308aad4ae57da9e8ecf9e1f3f30fa35d5e170b4d4896554d80", + "sha256:f9c51d9af9abb899bd34ace878fbec8bf357b3194a10c4e8e0a25512826ef056", + "sha256:fd76c47f20984b43d93de9a82011bb6e5f8325df6c9ed4d8310029a55fa361ea" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.13.3" + }, + "zipp": { + "hashes": [ + "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832", + "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc" + ], + "markers": "python_version >= '3.6'", + "version": "==3.6.0" } }, "develop": { @@ -667,11 +785,11 @@ }, "charset-normalizer": { "hashes": [ - "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0", - "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b" + "sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721", + "sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c" ], "markers": "python_version >= '3'", - "version": "==2.0.7" + "version": "==2.0.9" }, "click": { "hashes": [ @@ -686,69 +804,64 @@ "toml" ], "hashes": [ - "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c", - "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6", - "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45", - "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a", - "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03", - "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529", - "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a", - "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a", - "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2", - "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6", - "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759", - "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53", - "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a", - "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4", - "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff", - "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502", - "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793", - "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb", - "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905", - "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821", - "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b", - "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81", - "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0", - "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b", - "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3", - "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184", - "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701", - "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a", - "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82", - "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638", - "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5", - "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083", - "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6", - "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90", - "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465", - "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a", - "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3", - "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e", - "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066", - "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf", - "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b", - "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae", - "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669", - "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873", - "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b", - "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6", - "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb", - "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160", - "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c", - "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079", - "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d", - "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", - "version": "==5.5" + "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0", + "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd", + "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884", + "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48", + "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76", + "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0", + "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64", + "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685", + "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47", + "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d", + "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840", + "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f", + "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971", + "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c", + "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a", + "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de", + "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17", + "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4", + "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521", + "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57", + "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b", + "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282", + "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644", + "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475", + "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d", + "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da", + "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953", + "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2", + "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e", + "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c", + "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc", + "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64", + "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74", + "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617", + "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3", + "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d", + "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa", + "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739", + "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8", + "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8", + "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781", + "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58", + "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9", + "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c", + "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd", + "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e", + "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49" + ], + "markers": "python_version >= '3.6'", + "version": "==6.2" }, "coveralls": { "hashes": [ - "sha256:15a987d9df877fff44cd81948c5806ffb6eafb757b3443f737888358e96156ee", - "sha256:aedfcc5296b788ebaf8ace8029376e5f102f67c53d1373f2e821515c15b36527" + "sha256:b32a8bb5d2df585207c119d6c01567b81fba690c9c10a753bfe27a335bfc43ea", + "sha256:f42015f31d386b351d4226389b387ae173207058832fbf5c8ec4b40e27b16026" ], "index": "pypi", - "version": "==3.2.0" + "version": "==3.3.1" }, "docopt": { "hashes": [ @@ -781,11 +894,11 @@ }, "importlib-metadata": { "hashes": [ - "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15", - "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1" + "sha256:53ccfd5c134223e497627b9815d5030edf77d2ed573922f7a0b8f8bb81a1c100", + "sha256:75bdec14c397f528724c1bfd9709d660b33a4d2e77387a3358f20b848bb5e5fb" ], - "markers": "python_version >= '3.6'", - "version": "==4.8.1" + "markers": "python_version < '3.8'", + "version": "==4.8.2" }, "iniconfig": { "hashes": [ @@ -815,6 +928,7 @@ "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298", "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64", "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b", + "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194", "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567", "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff", "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724", @@ -822,6 +936,7 @@ "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646", "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35", "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6", + "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a", "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6", "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad", "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26", @@ -829,27 +944,36 @@ "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac", "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7", "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6", + "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047", "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75", "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f", + "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b", "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135", "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8", "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a", "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a", + "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1", "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9", "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864", "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914", + "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee", + "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f", "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18", "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8", "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2", "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d", "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b", "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b", + "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86", + "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6", "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f", "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb", "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833", "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28", + "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e", "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415", "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902", + "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f", "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d", "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9", "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d", @@ -857,10 +981,14 @@ "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066", "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c", "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1", + "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a", + "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207", "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f", "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53", + "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd", "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134", "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85", + "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9", "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5", "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94", "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509", @@ -895,11 +1023,11 @@ }, "packaging": { "hashes": [ - "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7", - "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14" + "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", + "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" ], "markers": "python_version >= '3.6'", - "version": "==21.0" + "version": "==21.3" }, "pluggy": { "hashes": [ @@ -911,11 +1039,11 @@ }, "py": { "hashes": [ - "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", - "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" + "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", + "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.10.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.11.0" }, "pycodestyle": { "hashes": [ @@ -935,11 +1063,11 @@ }, "pyparsing": { "hashes": [ - "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", - "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4", + "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.4.7" + "markers": "python_version >= '3.6'", + "version": "==3.0.6" }, "pytest": { "hashes": [ @@ -967,38 +1095,42 @@ }, "pyyaml": { "hashes": [ - "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf", - "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696", - "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393", - "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77", - "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922", - "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5", - "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8", - "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10", - "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc", - "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018", - "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e", - "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253", - "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347", - "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183", - "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541", - "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb", - "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185", - "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc", - "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db", - "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa", - "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46", - "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122", - "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b", - "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63", - "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df", - "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc", - "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247", - "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6", - "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==5.4.1" + "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", + "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", + "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", + "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", + "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", + "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", + "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", + "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", + "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", + "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", + "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", + "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", + "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", + "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", + "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", + "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", + "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", + "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", + "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", + "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", + "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", + "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", + "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", + "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", + "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", + "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", + "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", + "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", + "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", + "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", + "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", + "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", + "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" + ], + "markers": "python_version >= '3.6'", + "version": "==6.0" }, "pyyaml-env-tag": { "hashes": [ @@ -1029,14 +1161,30 @@ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.10.2" }, + "tomli": { + "hashes": [ + "sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee", + "sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade" + ], + "version": "==1.2.2" + }, + "typing-extensions": { + "hashes": [ + "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e", + "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b" + ], + "markers": "python_version < '3.10'", + "version": "==4.0.1" + }, "urllib3": { "hashes": [ "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece", "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_full_version < '4.0.0'", "version": "==1.26.7" }, "watchdog": { diff --git a/askomics/api/admin.py b/askomics/api/admin.py index 927d7a51..4bc4e799 100644 --- a/askomics/api/admin.py +++ b/askomics/api/admin.py @@ -729,7 +729,7 @@ def add_ontology(): 'errorMessage': "Dataset {} not found".format(dataset_id) }), 400 - if not len(datasets_handler.datasets) == 1 or not datasets_handler.datasets[0]['public']: + if not len(datasets_handler.datasets) == 1 or not datasets_handler.datasets[0].public: return jsonify({ 'ontologies': [], 'error': True, @@ -746,7 +746,7 @@ def add_ontology(): }), 400 try: - om.add_ontology(name, uri, short_name, dataset['id'], dataset['graph_name'], type) + om.add_ontology(name, uri, short_name, dataset.id, dataset.graph_name, type) ontologies = om.list_full_ontologies() except Exception as e: traceback.print_exc(file=sys.stdout) diff --git a/askomics/libaskomics/Dataset.py b/askomics/libaskomics/Dataset.py index 7a7c7b81..fe8691f4 100644 --- a/askomics/libaskomics/Dataset.py +++ b/askomics/libaskomics/Dataset.py @@ -74,10 +74,32 @@ def set_info_from_db(self, admin=False): self.start = rows[0][5] self.end = rows[0][6] - def save_in_db(self): + def save_in_db(self, set_graph=False): """Save the dataset into the database""" database = Database(self.app, self.session) + subquery = "NULL" + args = ( + self.session["user"]["id"], + self.celery_id, + self.file_id, + self.name, + self.public, + 0 + ) + + if set_graph: + subquery = "?" + args = ( + self.session["user"]["id"], + self.celery_id, + self.file_id, + self.name, + self.graph_name, + self.public, + 0 + ) + query = ''' INSERT INTO datasets VALUES( NULL, @@ -85,7 +107,7 @@ def save_in_db(self): ?, ?, ?, - NULL, + {}, ?, "queued", strftime('%s', 'now'), @@ -96,16 +118,9 @@ def save_in_db(self): NULL, 0 ) - ''' + '''.format(subquery) - self.id = database.execute_sql_query(query, ( - self.session["user"]["id"], - self.celery_id, - self.file_id, - self.name, - self.public, - 0 - ), get_id=True) + self.id = database.execute_sql_query(query, args, get_id=True) def toggle_public(self, new_status, admin=False): """Change public status of a dataset (triplestore and db) diff --git a/tests/conftest.py b/tests/conftest.py index 65c6f84d..06a18da6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -293,7 +293,7 @@ def upload_file_url(self, file_url): files.download_url(file_url, "1") return files.date - def integrate_file(self, info, public=False): + def integrate_file(self, info, public=False, set_graph=False): """Summary Parameters @@ -320,7 +320,7 @@ def integrate_file(self, info, public=False): } dataset = Dataset(self.app, self.session, dataset_info) - dataset.save_in_db() + dataset.save_in_db(set_graph=set_graph) if file.type == "csv/tsv": file.integrate(dataset.id, info["columns_type"], public=public) @@ -371,7 +371,7 @@ def upload(self): } } - def upload_and_integrate(self): + def upload_and_integrate(self, set_graph=False): """Summary Returns @@ -390,27 +390,27 @@ def upload_and_integrate(self): int_transcripts = self.integrate_file({ "id": 1, "columns_type": ["start_entity", "label", "category", "text", "reference", "start", "end", "category", "strand", "text", "text", "date"] - }) + }, set_graph=set_graph) int_de = self.integrate_file({ "id": 2, "columns_type": ["start_entity", "directed", "numeric", "numeric", "numeric", "text", "numeric", "numeric", "numeric", "numeric"] - }) + }, set_graph=set_graph) int_qtl = self.integrate_file({ "id": 3, "columns_type": ["start_entity", "ref", "start", "end"] - }) + }, set_graph=set_graph) int_gff = self.integrate_file({ "id": 4, "entities": ["gene", "transcript"] - }) + }, set_graph=set_graph) int_bed = self.integrate_file({ "id": 5, "entity_name": "gene" - }) + }, set_graph=set_graph) return { "transcripts": { diff --git a/tests/test_api_admin.py b/tests/test_api_admin.py index 44a421d9..9df06cea 100644 --- a/tests/test_api_admin.py +++ b/tests/test_api_admin.py @@ -628,12 +628,11 @@ def test_add_ontology(self, client): data = {"shortName": "OBO", "uri": "http://purl.obolibrary.org/obo/agro.owl", "name": "Open Biological and Biomedical Ontology", "type": "local", "datasetId": 1} - client.upload_and_integrate() - response = client.client.post('/api/admin/addontology', json=data) assert response.status_code == 401 client.log_user("jdoe") + client.upload_and_integrate(set_graph=True) response = client.client.post('/api/admin/addontology', json=data) @@ -641,8 +640,8 @@ def test_add_ontology(self, client): assert response.status_code == 400 assert response.json['errorMessage'] == "Invalid dataset id" - self.publicize_dataset(1, True) - response = client.client.post('/api/admin/delete_ontologies', json=data) + client.publicize_dataset(1, True) + response = client.client.post('/api/admin/addontology', json=data) expected = { "error": False, @@ -653,13 +652,16 @@ def test_add_ontology(self, client): "uri": "http://purl.obolibrary.org/obo/agro.owl", "short_name": "OBO", "type": "local", - "dataset_id": 1, - "graph": "mygraph" + "dataset_id": 1 }] } + # Graph name is random + res = response.json + res['ontologies'][0].pop('graph') + assert response.status_code == 200 - assert response.json == expected + assert res == expected def test_delete_ontologies(self, client): """test /api/admin/delete_ontologies route""" From d7e286de56179397e3b68c6a7d6edfe90e576d62 Mon Sep 17 00:00:00 2001 From: mboudet Date: Wed, 8 Dec 2021 15:54:16 +0100 Subject: [PATCH 051/113] Final fix --- askomics/libaskomics/Dataset.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/askomics/libaskomics/Dataset.py b/askomics/libaskomics/Dataset.py index fe8691f4..be00aadb 100644 --- a/askomics/libaskomics/Dataset.py +++ b/askomics/libaskomics/Dataset.py @@ -91,14 +91,14 @@ def save_in_db(self, set_graph=False): if set_graph: subquery = "?" args = ( - self.session["user"]["id"], - self.celery_id, - self.file_id, - self.name, - self.graph_name, - self.public, - 0 - ) + self.session["user"]["id"], + self.celery_id, + self.file_id, + self.name, + self.graph_name, + self.public, + 0 + ) query = ''' INSERT INTO datasets VALUES( From d90d0c5b56445a648e089b5cc6c8ebe588c3884e Mon Sep 17 00:00:00 2001 From: mboudet Date: Wed, 8 Dec 2021 16:29:21 +0000 Subject: [PATCH 052/113] Fix autocomplete display & ols --- askomics/api/ontology.py | 7 +++++++ askomics/libaskomics/OntologyManager.py | 15 ++++++--------- askomics/react/src/components/autocomplete.jsx | 3 +-- askomics/react/src/routes/admin/ontologies.jsx | 5 +++-- askomics/react/src/routes/query/attribute.jsx | 2 +- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/askomics/api/ontology.py b/askomics/api/ontology.py index 5f17043a..49d4bf85 100644 --- a/askomics/api/ontology.py +++ b/askomics/api/ontology.py @@ -30,6 +30,13 @@ def autocomplete(short_ontology): "results": [] }), 404 + if ontology['type'] == "none": + return jsonify({ + "error": True, + "errorMessage": "Ontology {} does not have autocompletion".format(short_ontology), + "results": [] + }), 404 + results = om.autocomplete(ontology["uri"], ontology["type"], request.args.get("q"), short_ontology, ontology["graph"]) except Exception as e: diff --git a/askomics/libaskomics/OntologyManager.py b/askomics/libaskomics/OntologyManager.py index a5eea007..65fcaeb5 100644 --- a/askomics/libaskomics/OntologyManager.py +++ b/askomics/libaskomics/OntologyManager.py @@ -1,7 +1,7 @@ import requests from collections import defaultdict -from urllib.parse import quote +from urllib.parse import quote_plus from askomics.libaskomics.Database import Database @@ -202,7 +202,7 @@ def remove_ontologies(self, ontology_ids): database.execute_sql_query(dataset_query, (ontology['dataset_id'],)) def test_ols_ontology(self, shortname): - base_url = "https://www.ebi.ac.uk/ols/api/ontologies/" + quote(shortname.lower()) + base_url = "https://www.ebi.ac.uk/ols/api/ontologies/" + quote_plus(shortname.lower()) r = requests.get(base_url) return r.status_code == 200 @@ -221,14 +221,11 @@ def autocomplete(self, ontology_uri, ontology_type, query_term, onto_short_name, query.set_graphs([onto_graph]) return query.autocomplete_local_ontology(ontology_uri, query_term) elif ontology_type == "ols": - base_url = "https://www.ebi.ac.uk/ols/api/search" + base_url = "https://www.ebi.ac.uk/ols/api/suggest" arguments = { "q": query_term, - "ontology": quote(onto_short_name.lower()), - "rows": 5, - "queryFields": "label", - "type": "class", - "fieldList": "label" + "ontology": quote_plus(onto_short_name.lower()), + "rows": 5 } r = requests.get(base_url, params=arguments) @@ -240,6 +237,6 @@ def autocomplete(self, ontology_uri, ontology_type, query_term, onto_short_name, res = r.json() if res['response']['docs']: - data = [term['label'] for term in res['response']['docs']] + data = [term['autosuggest'] for term in res['response']['docs']] return data diff --git a/askomics/react/src/components/autocomplete.jsx b/askomics/react/src/components/autocomplete.jsx index d13f29e4..cd6724e0 100644 --- a/askomics/react/src/components/autocomplete.jsx +++ b/askomics/react/src/components/autocomplete.jsx @@ -57,11 +57,10 @@ export default class Autocomplete extends Component { this.autocompleteOntology(event.target.value) } - renderAutocomplete () { let input = (
- this.handleOntoValue({target: {value: e, id: this.props.attributeId}})} id={this.props.attributeId} value={this.props.filterValue}/> + this.handleOntoValue({target: {value: e, id: this.props.attributeId}})} id={this.props.attributeId} value={this.props.filterValue}/>
) return input diff --git a/askomics/react/src/routes/admin/ontologies.jsx b/askomics/react/src/routes/admin/ontologies.jsx index 0e11c642..c225fee0 100644 --- a/askomics/react/src/routes/admin/ontologies.jsx +++ b/askomics/react/src/routes/admin/ontologies.jsx @@ -284,8 +284,9 @@ export default class Ontologies extends Component { + {this.state.datasets.map(dataset => { - if (dataset.public == 1){ + if (dataset.public == 1 && dataset.status == "success"){ return } })} @@ -295,7 +296,7 @@ export default class Ontologies extends Component { - + diff --git a/askomics/react/src/routes/query/attribute.jsx b/askomics/react/src/routes/query/attribute.jsx index fab02271..866cac8d 100644 --- a/askomics/react/src/routes/query/attribute.jsx +++ b/askomics/react/src/routes/query/attribute.jsx @@ -143,7 +143,7 @@ export default class AttributeBox extends Component {
) - if (this.props.attribute.uri == "rdfs:label"){ + if (this.isRegisteredOnto() && this.props.attribute.uri == "rdfs:label"){ input = ( this.handleFilterValue(p)}/> ) From 0f6ec8ad6e48862e625959bb8c0701d1e76cbb62 Mon Sep 17 00:00:00 2001 From: mboudet Date: Thu, 9 Dec 2021 13:00:12 +0000 Subject: [PATCH 053/113] Add timer to autocomplete --- askomics/react/src/components/autocomplete.jsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/askomics/react/src/components/autocomplete.jsx b/askomics/react/src/components/autocomplete.jsx index cd6724e0..52fba74e 100644 --- a/askomics/react/src/components/autocomplete.jsx +++ b/askomics/react/src/components/autocomplete.jsx @@ -16,6 +16,8 @@ export default class Autocomplete extends Component { this.autocompleteOntology = this.autocompleteOntology.bind(this) this.cancelRequest this.handleOntoValue = this.handleOntoValue.bind(this) + this.WAIT_INTERVAL = 500 + this.timerID } getAutoComplete () { @@ -54,7 +56,10 @@ export default class Autocomplete extends Component { handleOntoValue (event) { this.handleFilterValue(event) - this.autocompleteOntology(event.target.value) + clearTimeout(this.timerID) + this.timerID = setTimeout(() => { + this.autocompleteOntology(event.target.value) + }, this.WAIT_INTERVAL) } renderAutocomplete () { From 4fd0947af608f06d1da5a01dbacf9cfb28b3bb0c Mon Sep 17 00:00:00 2001 From: mboudet Date: Thu, 9 Dec 2021 16:59:13 +0100 Subject: [PATCH 054/113] Restrict unpublicize for ontology-linked datasets --- askomics/api/admin.py | 6 ++++ askomics/api/datasets.py | 6 ++++ askomics/libaskomics/Dataset.py | 4 ++- askomics/libaskomics/DatasetsHandler.py | 10 ++++--- .../react/src/routes/admin/datasetstable.jsx | 2 +- .../src/routes/datasets/datasetstable.jsx | 4 +-- tests/test_api_admin.py | 15 ++++++---- tests/test_api_datasets.py | 30 ++++++++++++------- 8 files changed, 54 insertions(+), 23 deletions(-) diff --git a/askomics/api/admin.py b/askomics/api/admin.py index 4bc4e799..16fd7315 100644 --- a/askomics/api/admin.py +++ b/askomics/api/admin.py @@ -258,6 +258,12 @@ def toogle_public_dataset(): datasets_handler.handle_datasets(admin=True) for dataset in datasets_handler.datasets: + if (not data.get("newStatus", False) and dataset.ontology): + return jsonify({ + 'datasets': [], + 'error': True, + 'errorMessage': "Cannot unpublicize a dataset linked to an ontology" + }), 400 current_app.logger.debug(data["newStatus"]) dataset.toggle_public(data["newStatus"], admin=True) diff --git a/askomics/api/datasets.py b/askomics/api/datasets.py index bceb125e..7c39a01f 100644 --- a/askomics/api/datasets.py +++ b/askomics/api/datasets.py @@ -135,6 +135,12 @@ def toogle_public(): datasets_handler.handle_datasets() for dataset in datasets_handler.datasets: + if (not data.get("newStatus", False) and dataset.ontology): + return jsonify({ + 'datasets': [], + 'error': True, + 'errorMessage': "Cannot unpublicize a dataset linked to an ontology" + }), 400 current_app.logger.debug(data.get("newStatus", False)) dataset.toggle_public(data.get("newStatus", False)) diff --git a/askomics/libaskomics/Dataset.py b/askomics/libaskomics/Dataset.py index be00aadb..fa0b5e89 100644 --- a/askomics/libaskomics/Dataset.py +++ b/askomics/libaskomics/Dataset.py @@ -45,6 +45,7 @@ def __init__(self, app, session, dataset_info={}): self.public = dataset_info["public"] if "public" in dataset_info else False self.start = dataset_info["start"] if "start" in dataset_info else None self.end = dataset_info["end"] if "end" in dataset_info else None + self.ontology = dataset_info["ontology"] if "ontology" in dataset_info else False def set_info_from_db(self, admin=False): """Set the info in from the database""" @@ -58,7 +59,7 @@ def set_info_from_db(self, admin=False): where_query = "AND user_id = ?" query = ''' - SELECT celery_id, file_id, name, graph_name, public, start, end + SELECT celery_id, file_id, name, graph_name, public, start, end, ontology FROM datasets WHERE id = ? {} @@ -73,6 +74,7 @@ def set_info_from_db(self, admin=False): self.public = rows[0][4] self.start = rows[0][5] self.end = rows[0][6] + self.ontology = rows[0][7] def save_in_db(self, set_graph=False): """Save the dataset into the database""" diff --git a/askomics/libaskomics/DatasetsHandler.py b/askomics/libaskomics/DatasetsHandler.py index 332dfc80..cdd1d172 100644 --- a/askomics/libaskomics/DatasetsHandler.py +++ b/askomics/libaskomics/DatasetsHandler.py @@ -51,7 +51,7 @@ def get_datasets(self): database = Database(self.app, self.session) query = ''' - SELECT id, name, public, status, start, end, ntriples, error_message, traceback, percent + SELECT id, name, public, status, start, end, ntriples, error_message, traceback, percent, ontology FROM datasets WHERE user_id = ? ''' @@ -76,7 +76,8 @@ def get_datasets(self): 'ntriples': row[6], 'error_message': row[7], 'traceback': row[8], - 'percent': row[9] + 'percent': row[9], + 'ontology': row[10] } datasets.append(dataset) @@ -97,7 +98,7 @@ def get_all_datasets(self): database = Database(self.app, self.session) query = ''' - SELECT datasets.id, datasets.name, datasets.public, datasets.status, datasets.start, datasets.end, datasets.ntriples, datasets.error_message, datasets.traceback, datasets.percent, users.username + SELECT datasets.id, datasets.name, datasets.public, datasets.status, datasets.start, datasets.end, datasets.ntriples, datasets.error_message, datasets.traceback, datasets.percent, users.username, datasets.ontology FROM datasets INNER JOIN users ON datasets.user_id=users.user_id ''' @@ -123,7 +124,8 @@ def get_all_datasets(self): 'error_message': row[7], 'traceback': row[8], 'percent': row[9], - 'user': row[10] + 'user': row[10], + 'ontology': row[11] } datasets.append(dataset) diff --git a/askomics/react/src/routes/admin/datasetstable.jsx b/askomics/react/src/routes/admin/datasetstable.jsx index e961b6b4..dd199566 100644 --- a/askomics/react/src/routes/admin/datasetstable.jsx +++ b/askomics/react/src/routes/admin/datasetstable.jsx @@ -99,7 +99,7 @@ render () { return (
- +
) diff --git a/askomics/react/src/routes/datasets/datasetstable.jsx b/askomics/react/src/routes/datasets/datasetstable.jsx index 23cec291..ae1f8723 100644 --- a/askomics/react/src/routes/datasets/datasetstable.jsx +++ b/askomics/react/src/routes/datasets/datasetstable.jsx @@ -118,7 +118,7 @@ export default class DatasetsTable extends Component { return (
- +
) @@ -209,4 +209,4 @@ DatasetsTable.propTypes = { waiting: PropTypes.bool, datasets: PropTypes.object, config: PropTypes.object -} \ No newline at end of file +} diff --git a/tests/test_api_admin.py b/tests/test_api_admin.py index 9df06cea..a6d04125 100644 --- a/tests/test_api_admin.py +++ b/tests/test_api_admin.py @@ -139,7 +139,8 @@ def test_get_datasets(self, client): 'traceback': None, 'percent': 100.0, 'exec_time': info["transcripts"]["end"] - info["transcripts"]["start"], - 'user': 'jsmith' + 'user': 'jsmith', + 'ontology': False }, { 'end': info["de"]["end"], 'error_message': '', @@ -152,7 +153,8 @@ def test_get_datasets(self, client): 'traceback': None, 'percent': 100.0, 'exec_time': info["de"]["end"] - info["de"]["start"], - 'user': 'jsmith' + 'user': 'jsmith', + 'ontology': False }, { 'end': info["qtl"]["end"], 'error_message': '', @@ -165,7 +167,8 @@ def test_get_datasets(self, client): 'traceback': None, 'percent': 100.0, 'exec_time': info["qtl"]["end"] - info["qtl"]["start"], - 'user': 'jsmith' + 'user': 'jsmith', + 'ontology': False }, { 'end': info["gff"]["end"], 'error_message': '', @@ -178,7 +181,8 @@ def test_get_datasets(self, client): 'traceback': None, 'percent': 100.0, 'exec_time': info["gff"]["end"] - info["gff"]["start"], - 'user': 'jsmith' + 'user': 'jsmith', + 'ontology': False }, { 'end': info["bed"]["end"], 'error_message': '', @@ -191,7 +195,8 @@ def test_get_datasets(self, client): 'traceback': None, 'percent': 100.0, 'exec_time': info["bed"]["end"] - info["bed"]["start"], - 'user': 'jsmith' + 'user': 'jsmith', + 'ontology': False }], 'error': False, 'errorMessage': '' diff --git a/tests/test_api_datasets.py b/tests/test_api_datasets.py index 3e35faed..ba5dba74 100644 --- a/tests/test_api_datasets.py +++ b/tests/test_api_datasets.py @@ -24,7 +24,8 @@ def test_get_datasets(self, client): 'status': 'success', 'traceback': None, 'percent': 100.0, - 'exec_time': info["transcripts"]["end"] - info["transcripts"]["start"] + 'exec_time': info["transcripts"]["end"] - info["transcripts"]["start"], + 'ontology': False }, { 'end': info["de"]["end"], 'error_message': '', @@ -36,7 +37,8 @@ def test_get_datasets(self, client): 'status': 'success', 'traceback': None, 'percent': 100.0, - 'exec_time': info["de"]["end"] - info["de"]["start"] + 'exec_time': info["de"]["end"] - info["de"]["start"], + 'ontology': False }, { 'end': info["qtl"]["end"], 'error_message': '', @@ -48,7 +50,8 @@ def test_get_datasets(self, client): 'status': 'success', 'traceback': None, 'percent': 100.0, - 'exec_time': info["qtl"]["end"] - info["qtl"]["start"] + 'exec_time': info["qtl"]["end"] - info["qtl"]["start"], + 'ontology': False }, { 'end': info["gff"]["end"], 'error_message': '', @@ -60,7 +63,8 @@ def test_get_datasets(self, client): 'status': 'success', 'traceback': None, 'percent': 100.0, - 'exec_time': info["gff"]["end"] - info["gff"]["start"] + 'exec_time': info["gff"]["end"] - info["gff"]["start"], + 'ontology': False }, { 'end': info["bed"]["end"], 'error_message': '', @@ -72,7 +76,8 @@ def test_get_datasets(self, client): 'status': 'success', 'traceback': None, 'percent': 100.0, - 'exec_time': info["bed"]["end"] - info["bed"]["start"] + 'exec_time': info["bed"]["end"] - info["bed"]["start"], + 'ontology': False }], 'error': False, 'errorMessage': '' @@ -118,7 +123,8 @@ def test_toggle_public(self, client): 'status': 'success', 'traceback': None, 'percent': 100.0, - 'exec_time': info["transcripts"]["end"] - info["transcripts"]["start"] + 'exec_time': info["transcripts"]["end"] - info["transcripts"]["start"], + 'ontology': False }, { 'end': info["de"]["end"], 'error_message': '', @@ -130,7 +136,8 @@ def test_toggle_public(self, client): 'status': 'success', 'traceback': None, 'percent': 100.0, - 'exec_time': info["de"]["end"] - info["de"]["start"] + 'exec_time': info["de"]["end"] - info["de"]["start"], + 'ontology': False }, { 'end': info["qtl"]["end"], 'error_message': '', @@ -142,7 +149,8 @@ def test_toggle_public(self, client): 'status': 'success', 'traceback': None, 'percent': 100.0, - 'exec_time': info["qtl"]["end"] - info["qtl"]["start"] + 'exec_time': info["qtl"]["end"] - info["qtl"]["start"], + 'ontology': False }, { 'end': info["gff"]["end"], 'error_message': '', @@ -154,7 +162,8 @@ def test_toggle_public(self, client): 'status': 'success', 'traceback': None, 'percent': 100.0, - 'exec_time': info["gff"]["end"] - info["gff"]["start"] + 'exec_time': info["gff"]["end"] - info["gff"]["start"], + 'ontology': False }, { 'end': info["bed"]["end"], 'error_message': '', @@ -166,7 +175,8 @@ def test_toggle_public(self, client): 'status': 'success', 'traceback': None, 'percent': 100.0, - 'exec_time': info["bed"]["end"] - info["bed"]["start"] + 'exec_time': info["bed"]["end"] - info["bed"]["start"], + 'ontology': False }], 'error': False, 'errorMessage': '' From c591172702d5ee26a985d59c966c1e98dc556e0f Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 10 Dec 2021 10:31:48 +0100 Subject: [PATCH 055/113] Change return of listallontologies --- askomics/libaskomics/OntologyManager.py | 6 ++++-- askomics/react/src/routes/admin/ontologies.jsx | 13 +------------ tests/test_api_admin.py | 4 +++- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/askomics/libaskomics/OntologyManager.py b/askomics/libaskomics/OntologyManager.py index 65fcaeb5..74b47c95 100644 --- a/askomics/libaskomics/OntologyManager.py +++ b/askomics/libaskomics/OntologyManager.py @@ -77,8 +77,9 @@ def list_full_ontologies(self): database = Database(self.app, self.session) query = ''' - SELECT id, name, uri, short_name, type, dataset_id, graph + SELECT ontologies.id, ontologies.name, ontologies.uri, ontologies.short_name, ontologies.type, datasets.id, datasets.name, ontologies.graph FROM ontologies + INNER JOIN datasets ON datasets.id=ontologies.dataset_id ''' rows = database.execute_sql_query(query) @@ -92,7 +93,8 @@ def list_full_ontologies(self): 'short_name': row[3], 'type': row[4], 'dataset_id': row[5], - 'graph': row[6] + 'dataset_name': row[6], + 'graph': row[7] } ontologies.append(prefix) diff --git a/askomics/react/src/routes/admin/ontologies.jsx b/askomics/react/src/routes/admin/ontologies.jsx index c225fee0..2832d974 100644 --- a/askomics/react/src/routes/admin/ontologies.jsx +++ b/askomics/react/src/routes/admin/ontologies.jsx @@ -96,16 +96,6 @@ export default class Ontologies extends Component { } } - getDatasetName (datasetId) { - return this.state.datasets.map(dataset => { - if (dataset.id == datasetId) { - return dataset.name - } else { - return null - } - }).reduce(name=> name) - } - handleAddOntology(event) { let requestUrl = "/api/admin/addontology" @@ -221,9 +211,8 @@ export default class Ontologies extends Component { sort: true }, { editable: false, - dataField: 'dataset_id', + dataField: 'dataset_name', text: 'Dataset', - formatter: (cell, row) => { return this.getDatasetName(cell)}, sort: true }, { editable: false, diff --git a/tests/test_api_admin.py b/tests/test_api_admin.py index a6d04125..fae15f3a 100644 --- a/tests/test_api_admin.py +++ b/tests/test_api_admin.py @@ -619,6 +619,7 @@ def test_view_ontologies(self, client): "short_name": "OBO", "type": "local", "dataset_id": 1, + "dataset_name": "transcripts.tsv", "graph": "mygraph" }] } @@ -657,7 +658,8 @@ def test_add_ontology(self, client): "uri": "http://purl.obolibrary.org/obo/agro.owl", "short_name": "OBO", "type": "local", - "dataset_id": 1 + "dataset_id": 1, + "dataset_name": "transcripts.tsv" }] } From 1bbc724f0661ffc69bf5b9491458387da46edcd9 Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 10 Dec 2021 09:57:07 +0000 Subject: [PATCH 056/113] Fix error div --- askomics/react/src/routes/admin/ontologies.jsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/askomics/react/src/routes/admin/ontologies.jsx b/askomics/react/src/routes/admin/ontologies.jsx index 2832d974..afbfe9a9 100644 --- a/askomics/react/src/routes/admin/ontologies.jsx +++ b/askomics/react/src/routes/admin/ontologies.jsx @@ -109,10 +109,9 @@ export default class Ontologies extends Component { axios.post(requestUrl, data, { baseURL: this.props.config.proxyPath, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) .then(response => { - console.log(requestUrl, response.data) this.setState({ newontologyError: response.data.error, - newontologyErrorMessage: response.data.errorMessage, + newontologyErrorMessage: [response.data.errorMessage], ontologies: response.data.ontologies, newontologyStatus: response.status, name: "", @@ -125,7 +124,7 @@ export default class Ontologies extends Component { console.log(error, error.response.data.errorMessage) this.setState({ newontologyError: true, - newontologyErrorMessage: error.response.data.errorMessage, + newontologyErrorMessage: [error.response.data.errorMessage], newontologyStatus: error.response.status, }) }) @@ -295,8 +294,8 @@ export default class Ontologies extends Component { -
+

From 272efcdcfad9f555f584f54fe4a417f1274ada68 Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 10 Dec 2021 13:41:07 +0100 Subject: [PATCH 057/113] Autocomplete for all ontologies? --- askomics/react/src/routes/query/query.jsx | 25 ++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/askomics/react/src/routes/query/query.jsx b/askomics/react/src/routes/query/query.jsx index b5d20391..2e8c0328 100644 --- a/askomics/react/src/routes/query/query.jsx +++ b/askomics/react/src/routes/query/query.jsx @@ -214,19 +214,30 @@ export default class Query extends Component { }) } - isOntoRelation (currentUri, targetUri) { - if (! currentUri == targetUri){ + isRemoteOnto (currentUri, targetUri) { + + let node = this.state.abstraction.entities.find(entity => { + return entity.uri == currentUri + }) + + if (! node){ return false } - return this.state.abstraction.entities.some(entity => { - return (entity.uri == currentUri && entity.ontology) + + return node.ontology ? currentUri == targetUri ? "endNode" : "node" : false + } + + isOntoNode (currentId) { + + return this.graphState.nodes.some(node => { + return (node.id == currentId && node.ontology) }) } isOntoEndNode (currentId) { return this.graphState.nodes.some(node => { - return (node.id == currentId && node.ontology) + return (node.id == currentId && node.ontology == "endNode") }) } @@ -505,7 +516,7 @@ export default class Query extends Component { } this.state.abstraction.relations.map(relation => { - let isOnto = this.isOntoRelation(relation.source, relation.target) + let isOnto = this.isRemoteOnto(relation.source, relation.target) if (relation.source == node.uri) { if (this.entityExist(relation.target)) { targetId = this.getId() @@ -1527,7 +1538,7 @@ export default class Query extends Component { if (!this.state.waiting) { // attribute boxes (right view) only for node if (this.currentSelected) { - isOnto = this.isOntoEndNode(this.currentSelected.id) + isOnto = this.isOntoNode(this.currentSelected.id) AttributeBoxes = this.state.graphState.attr.map(attribute => { if (attribute.nodeId == this.currentSelected.id && this.currentSelected.type == "node") { return ( From d9b5071d15903287003846d0d2454fd64009aaae Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 10 Dec 2021 12:47:47 +0000 Subject: [PATCH 058/113] Fix autocomplete again --- askomics/react/src/routes/query/query.jsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/askomics/react/src/routes/query/query.jsx b/askomics/react/src/routes/query/query.jsx index 2e8c0328..25a580c5 100644 --- a/askomics/react/src/routes/query/query.jsx +++ b/askomics/react/src/routes/query/query.jsx @@ -217,13 +217,16 @@ export default class Query extends Component { isRemoteOnto (currentUri, targetUri) { let node = this.state.abstraction.entities.find(entity => { - return entity.uri == currentUri + return entity.uri == targetUri }) if (! node){ return false } + console.log(node) + console.log(node.ontology ? currentUri == targetUri ? "endNode" : "node" : false) + return node.ontology ? currentUri == targetUri ? "endNode" : "node" : false } @@ -546,12 +549,12 @@ export default class Query extends Component { // push suggested link this.graphState.links.push({ uri: relation.uri, - type: isOnto ? "ontoLink" : "link", + type: isOnto == "endNode" ? "ontoLink" : "link", sameStrand: this.nodeHaveStrand(node.uri) && this.nodeHaveStrand(relation.target), sameRef: this.nodeHaveRef(node.uri) && this.nodeHaveRef(relation.target), strict: true, id: linkId, - label: isOnto ? this.getOntoLabel(relation.uri) : relation.label, + label: isOnto == "endNode" ? this.getOntoLabel(relation.uri) : relation.label, source: node.id, target: targetId, selected: false, From c16ad557ee7bde96271006cac8728f4a690a1012 Mon Sep 17 00:00:00 2001 From: mboudet Date: Mon, 21 Feb 2022 17:27:02 +0100 Subject: [PATCH 059/113] Merge pull request #4 from askomics/dev_onto (#305) From 8c7d1c9d5968d11065d02ea1502871a04794c8b2 Mon Sep 17 00:00:00 2001 From: mboudet Date: Tue, 10 May 2022 09:14:24 +0200 Subject: [PATCH 060/113] typo merge --- tests/test_api.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index 02412d8e..ea66cc70 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -43,11 +43,8 @@ def test_start(self, client): "proxyPath": "/", "user": {}, "logged": False, -<<<<<<< HEAD - "ontologies": [] -======= + "ontologies": [], "singleTenant": False ->>>>>>> dev } response = client.client.get('/api/start') assert response.status_code == 200 From 78b63335fe3f7918355f832cadb855729ea79b24 Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 13 May 2022 16:29:44 +0200 Subject: [PATCH 061/113] Dev onto (#333) Dev onto --- CHANGELOG.md | 12 ++++- askomics/api/data.py | 2 +- askomics/api/datasets.py | 1 + askomics/api/file.py | 2 +- askomics/api/query.py | 13 +---- askomics/api/results.py | 4 +- askomics/api/sparql.py | 6 +-- askomics/api/start.py | 3 +- askomics/libaskomics/BedFile.py | 18 +++++-- askomics/libaskomics/CsvFile.py | 22 +++++--- askomics/libaskomics/FilesHandler.py | 2 +- askomics/libaskomics/GffFile.py | 19 ++++--- askomics/libaskomics/RdfFile.py | 8 ++- askomics/libaskomics/Result.py | 29 ++++++++++- askomics/libaskomics/SparqlQuery.py | 31 +++++++---- askomics/libaskomics/TriplestoreExplorer.py | 51 ++++++++++-------- askomics/react/src/routes.jsx | 3 +- .../src/routes/integration/bedpreview.jsx | 9 ++-- .../react/src/routes/integration/csvtable.jsx | 9 ++-- .../src/routes/integration/gffpreview.jsx | 8 ++- .../src/routes/integration/rdfpreview.jsx | 9 ++-- askomics/tasks.py | 16 +++++- config/askomics.ini.template | 3 ++ config/askomics.test.ini | 2 + docker/Dockerfile | 4 +- docker/DockerfileAll | 4 +- docker/DockerfileCelery | 4 +- docs/requirements.txt | 2 +- package-lock.json | 30 +++++------ package.json | 2 +- tests/results/abstraction.json | 52 +++---------------- tests/test_api.py | 3 +- 32 files changed, 228 insertions(+), 155 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef6d8420..8dd0e971 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,14 +23,19 @@ This changelog was started for release 4.2.0. - Remote upload is now sent in a Celery task - Added 'Status' for files (for celery upload, and later for better file management) - Added tooltips to buttons in the query form (and other forms) +- Added owl integration +- Add better error management for RDF files +- Added 'single tenant' mode ### Changed - Changed "Query builder" to "Form editor" in form editing interface - Changed abstraction building method for relations. (Please refer to #248 and #268) +- Changed abstraction building method for attributes. (Please refer to #321 and #324) - Changed abstraction building method for 'strand': only add the required strand type, and not all three types (#277) - Updated documentation - Changed the sparql endpoint: now use the authenticated SPARQL endpoint instead of public endpoint. Write permissions are not required anymore +- Reverted base docker image to alpine-13 to solve a docker issue ### Removed @@ -39,11 +44,16 @@ This changelog was started for release 4.2.0. ### Security -- Bump prismjs from 1.23.0 to 1.25.0 - Bump axios from 0.21.1 to 0.21.2 - Bump tar from 6.1.0 to 6.1.11 - Bump @npmcli/git from 2.0.6 to 2.1.0 - Bump path-parse from 1.0.6 to 1.0.7 +- Bump prismjs from 1.23.0 to 1.27.0 +- Bump simple-get from 2.8.1 to 2.8.2 +- Bump ssri from 6.0.1 to 6.0.2 +- Bump follow-redirects from 1.14.4 to 1.14.8 +- Bump mkdocs from 1.0.4 to 1.2.3 in /docs +- Bump python-ldap from 3.3.1 to 3.4.0 ## [4.3.1] - 2021-06-16 diff --git a/askomics/api/data.py b/askomics/api/data.py index 4710b18b..24a26cdd 100644 --- a/askomics/api/data.py +++ b/askomics/api/data.py @@ -25,7 +25,7 @@ def get_data(uri): """ try: - query = SparqlQuery(current_app, session) + query = SparqlQuery(current_app, session, get_graphs=True) graphs, endpoints = query.get_graphs_and_endpoints(all_selected=True) endpoints = [val['uri'] for val in endpoints.values()] diff --git a/askomics/api/datasets.py b/askomics/api/datasets.py index 7c39a01f..665a0490 100644 --- a/askomics/api/datasets.py +++ b/askomics/api/datasets.py @@ -119,6 +119,7 @@ def toogle_public(): error: True if error, else False errorMessage: the error message of error, else an empty string """ + data = request.get_json() if not (data and data.get("id")): return jsonify({ diff --git a/askomics/api/file.py b/askomics/api/file.py index 6addb781..8ef6b4bf 100644 --- a/askomics/api/file.py +++ b/askomics/api/file.py @@ -340,7 +340,7 @@ def integrate(): "file_id": file.id, "name": file.human_name, "graph_name": file.file_graph, - "public": data.get("public") if session["user"]["admin"] else False + "public": (data.get("public", False) if session["user"]["admin"] else False) or current_app.iniconfig.getboolean("askomics", "single_tenant", fallback=False) } dataset = Dataset(current_app, session, dataset_info) diff --git a/askomics/api/query.py b/askomics/api/query.py index 1de59ddd..45dd8c6d 100644 --- a/askomics/api/query.py +++ b/askomics/api/query.py @@ -125,7 +125,7 @@ def get_preview(): 'errorMessage': "Missing graphState parameter" }), 400 - query = SparqlQuery(current_app, session, data["graphState"], get_graphs=False) + query = SparqlQuery(current_app, session, data["graphState"]) query.build_query_from_json(preview=True, for_editor=False) endpoints = query.endpoints @@ -186,18 +186,9 @@ def save_result(): 'errorMessage': "Missing graphState parameter" }), 400 - query = SparqlQuery(current_app, session, data["graphState"], get_graphs=False) - query.build_query_from_json(preview=False, for_editor=False) - federated = query.is_federated() - info = { "graph_state": data["graphState"], - "query": query.sparql, - "graphs": query.graphs, - "endpoints": query.endpoints, - "federated": federated, - "celery_id": None, - "selects": query.selects, + "celery_id": None } result = Result(current_app, session, info) diff --git a/askomics/api/results.py b/askomics/api/results.py index 97c394b3..c0092f32 100644 --- a/askomics/api/results.py +++ b/askomics/api/results.py @@ -164,7 +164,7 @@ def get_graph_and_sparql_query(): graphs = result.graphs endpoints = result.endpoints # Get all graphs and endpoint, and mark as selected the used one - query = SparqlQuery(current_app, session) + query = SparqlQuery(current_app, session, get_graphs=True) graphs, endpoints = query.get_graphs_and_endpoints(selected_graphs=graphs, selected_endpoints=endpoints) console_enabled = can_access(session['user']) @@ -361,7 +361,7 @@ def get_sparql_query(): 'error': True, 'errorMessage': "You do not have access to this result" }), 401 - query = SparqlQuery(current_app, session) + query = SparqlQuery(current_app, session, get_graphs=True) sparql = result.get_sparql_query() diff --git a/askomics/api/sparql.py b/askomics/api/sparql.py index 8a75fcb1..043897c8 100644 --- a/askomics/api/sparql.py +++ b/askomics/api/sparql.py @@ -32,7 +32,7 @@ def init(): disk_space = files_utils.get_size_occupied_by_user() if "user" in session else None # Get graphs and endpoints - query = SparqlQuery(current_app, session) + query = SparqlQuery(current_app, session, get_graphs=True) graphs, endpoints = query.get_graphs_and_endpoints(all_selected=True) # Default query @@ -116,7 +116,7 @@ def query(): }), 400 try: - query = SparqlQuery(current_app, session, get_graphs=False) + query = SparqlQuery(current_app, session) query.set_graphs_and_endpoints(graphs=graphs, endpoints=endpoints) @@ -209,7 +209,7 @@ def save_query(): }), 400 # Is query federated? - query = SparqlQuery(current_app, session, get_graphs=False) + query = SparqlQuery(current_app, session) query.set_graphs_and_endpoints(graphs=graphs, endpoints=endpoints) federated = query.is_federated() diff --git a/askomics/api/start.py b/askomics/api/start.py index 15564c9e..93ae4578 100644 --- a/askomics/api/start.py +++ b/askomics/api/start.py @@ -84,7 +84,8 @@ def start(): "proxyPath": proxy_path, "user": {}, "logged": False, - "ontologies": ontologies + "ontologies": ontologies, + "singleTenant": current_app.iniconfig.getboolean('askomics', 'single_tenant', fallback=False) } json = { diff --git a/askomics/libaskomics/BedFile.py b/askomics/libaskomics/BedFile.py index 88440756..24ebf6da 100644 --- a/askomics/libaskomics/BedFile.py +++ b/askomics/libaskomics/BedFile.py @@ -94,13 +94,19 @@ def set_rdf_abstraction_domain_knowledge(self): self.graph_abstraction_dk.add((self.namespace_data[self.format_uri(self.entity_name, remove_space=True)], rdflib.RDF.type, rdflib.OWL["Class"])) self.graph_abstraction_dk.add((self.namespace_data[self.format_uri(self.entity_name, remove_space=True)], rdflib.RDFS.label, rdflib.Literal(self.entity_name))) + attribute_blanks = {} + for attribute in self.attribute_abstraction: + blank = BNode() + for attr_type in attribute["type"]: - self.graph_abstraction_dk.add((attribute["uri"], rdflib.RDF.type, attr_type)) - self.graph_abstraction_dk.add((attribute["uri"], rdflib.RDFS.label, attribute["label"])) - self.graph_abstraction_dk.add((attribute["uri"], rdflib.RDFS.domain, attribute["domain"])) - self.graph_abstraction_dk.add((attribute["uri"], rdflib.RDFS.range, attribute["range"])) + self.graph_abstraction_dk.add((blank, rdflib.RDF.type, attr_type)) + self.graph_abstraction_dk.add((blank, self.namespace_internal["uri"], attribute["uri"])) + self.graph_abstraction_dk.add((blank, rdflib.RDFS.label, attribute["label"])) + self.graph_abstraction_dk.add((blank, rdflib.RDFS.domain, attribute["domain"])) + self.graph_abstraction_dk.add((blank, rdflib.RDFS.range, attribute["range"])) + attribute_blanks[attribute["uri"]] = blank # Domain Knowledge if "values" in attribute.keys(): for value in attribute["values"]: @@ -115,7 +121,9 @@ def set_rdf_abstraction_domain_knowledge(self): if self.faldo_entity: for key, value in self.faldo_abstraction.items(): if value: - self.graph_abstraction_dk.add((value, rdflib.RDF.type, self.faldo_abstraction_eq[key])) + blank = attribute_blanks[value] + self.graph_abstraction_dk.add((blank, rdflib.RDF.type, self.faldo_abstraction_eq[key])) + self.graph_abstraction_dk.add((blank, self.namespace_internal["uri"], value)) def generate_rdf_content(self): """Generate RDF content of the BED file diff --git a/askomics/libaskomics/CsvFile.py b/askomics/libaskomics/CsvFile.py index 7356a5fe..1a86f7b9 100644 --- a/askomics/libaskomics/CsvFile.py +++ b/askomics/libaskomics/CsvFile.py @@ -400,6 +400,7 @@ def set_rdf_abstraction(self): available_ontologies = {} for ontology in OntologyManager(self.app, self.session).list_ontologies(): available_ontologies[ontology['short_name']] = ontology['uri'] + attribute_blanks = {} # Attributes and relations for index, attribute_name in enumerate(self.header): @@ -414,6 +415,7 @@ def set_rdf_abstraction(self): if self.columns_type[index] == "label" and index == 1: continue + blank = BNode() # Relation if self.columns_type[index] in ('general_relation', 'symetric_relation'): symetric_relation = True if self.columns_type[index] == 'symetric_relation' else False @@ -425,7 +427,7 @@ def set_rdf_abstraction(self): rdf_type = rdflib.OWL.ObjectProperty # New way of storing relations (starting from 4.4.0) - blank = BNode() + endpoint = rdflib.Literal(self.external_endpoint) if self.external_endpoint else rdflib.Literal(self.settings.get('triplestore', 'endpoint')) self.graph_abstraction_dk.add((blank, rdflib.RDF.type, rdflib.OWL.ObjectProperty)) self.graph_abstraction_dk.add((blank, rdflib.RDF.type, self.namespace_internal["AskomicsRelation"])) @@ -469,7 +471,7 @@ def set_rdf_abstraction(self): label = rdflib.Literal(attribute_name) rdf_range = self.namespace_data["{}Category".format(self.format_uri(attribute_name, remove_space=True))] rdf_type = rdflib.OWL.ObjectProperty - self.graph_abstraction_dk.add((attribute, rdflib.RDF.type, self.namespace_internal["AskomicsCategory"])) + self.graph_abstraction_dk.add((blank, rdflib.RDF.type, self.namespace_internal["AskomicsCategory"])) # Numeric elif self.columns_type[index] in ('numeric', 'start', 'end'): @@ -499,16 +501,22 @@ def set_rdf_abstraction(self): rdf_range = rdflib.XSD.string rdf_type = rdflib.OWL.DatatypeProperty - self.graph_abstraction_dk.add((attribute, rdflib.RDF.type, rdf_type)) - self.graph_abstraction_dk.add((attribute, rdflib.RDFS.label, label)) - self.graph_abstraction_dk.add((attribute, rdflib.RDFS.domain, entity)) - self.graph_abstraction_dk.add((attribute, rdflib.RDFS.range, rdf_range)) + attribute_blanks[attribute] = blank + + # New way of storing attributes (starting from 4.4.0) + self.graph_abstraction_dk.add((blank, rdflib.RDF.type, rdf_type)) + self.graph_abstraction_dk.add((blank, self.namespace_internal["uri"], attribute)) + self.graph_abstraction_dk.add((blank, rdflib.RDFS.label, label)) + self.graph_abstraction_dk.add((blank, rdflib.RDFS.domain, entity)) + self.graph_abstraction_dk.add((blank, rdflib.RDFS.range, rdf_range)) # Faldo: if self.faldo_entity: for key, value in self.faldo_abstraction.items(): if value: - self.graph_abstraction_dk.add((value, rdflib.RDF.type, self.faldo_abstraction_eq[key])) + blank = attribute_blanks[value] + self.graph_abstraction_dk.add((blank, rdflib.RDF.type, self.faldo_abstraction_eq[key])) + self.graph_abstraction_dk.add((blank, self.namespace_internal["uri"], value)) def generate_rdf_content(self): """Generator of the rdf content diff --git a/askomics/libaskomics/FilesHandler.py b/askomics/libaskomics/FilesHandler.py index ae5e8bb8..d3d1adfe 100644 --- a/askomics/libaskomics/FilesHandler.py +++ b/askomics/libaskomics/FilesHandler.py @@ -247,7 +247,7 @@ def store_file_info_in_db(self, name, filetype, file_name, size, status="availab filetype = 'csv/tsv' elif filetype in ('text/turtle', 'ttl'): filetype = 'rdf/ttl' - elif filetype == "text/xml": + elif filetype in ["text/xml", "application/rdf+xml"]: filetype = "rdf/xml" elif filetype == "application/n-triples": filetype = "rdf/nt" diff --git a/askomics/libaskomics/GffFile.py b/askomics/libaskomics/GffFile.py index d807f6b0..28341bab 100644 --- a/askomics/libaskomics/GffFile.py +++ b/askomics/libaskomics/GffFile.py @@ -108,10 +108,12 @@ def set_rdf_abstraction_domain_knowledge(self): self.graph_abstraction_dk.add((self.namespace_data[self.format_uri(entity, remove_space=True)], rdflib.RDF.type, rdflib.OWL["Class"])) self.graph_abstraction_dk.add((self.namespace_data[self.format_uri(entity, remove_space=True)], rdflib.RDFS.label, rdflib.Literal(entity))) + attribute_blanks = {} + for attribute in self.attribute_abstraction: + blank = BNode() # New way of storing relations (starting from 4.4.0) if attribute.get("relation"): - blank = BNode() endpoint = rdflib.Literal(self.external_endpoint) if self.external_endpoint else rdflib.Literal(self.settings.get('triplestore', 'endpoint')) for attr_type in attribute["type"]: self.graph_abstraction_dk.add((blank, rdflib.RDF.type, attr_type)) @@ -123,12 +125,15 @@ def set_rdf_abstraction_domain_knowledge(self): self.graph_abstraction_dk.add((blank, rdflib.DCAT.dataset, rdflib.Literal(self.name))) else: + # New way of storing attributes (starting from 4.4.0) for attr_type in attribute["type"]: - self.graph_abstraction_dk.add((attribute["uri"], rdflib.RDF.type, attr_type)) - self.graph_abstraction_dk.add((attribute["uri"], rdflib.RDFS.label, attribute["label"])) - self.graph_abstraction_dk.add((attribute["uri"], rdflib.RDFS.domain, attribute["domain"])) - self.graph_abstraction_dk.add((attribute["uri"], rdflib.RDFS.range, attribute["range"])) + self.graph_abstraction_dk.add((blank, rdflib.RDF.type, attr_type)) + self.graph_abstraction_dk.add((blank, self.namespace_internal["uri"], attribute["uri"])) + self.graph_abstraction_dk.add((blank, rdflib.RDFS.label, attribute["label"])) + self.graph_abstraction_dk.add((blank, rdflib.RDFS.domain, attribute["domain"])) + self.graph_abstraction_dk.add((blank, rdflib.RDFS.range, attribute["range"])) + attribute_blanks[attribute["uri"]] = blank # Domain Knowledge if "values" in attribute.keys(): for value in attribute["values"]: @@ -143,7 +148,9 @@ def set_rdf_abstraction_domain_knowledge(self): if self.faldo_entity: for key, value in self.faldo_abstraction.items(): if value: - self.graph_abstraction_dk.add((value, rdflib.RDF.type, self.faldo_abstraction_eq[key])) + blank = attribute_blanks[value] + self.graph_abstraction_dk.add((blank, rdflib.RDF.type, self.faldo_abstraction_eq[key])) + self.graph_abstraction_dk.add((blank, self.namespace_internal["uri"], value)) def format_gff_entity(self, entity): """Format a gff entity name by removing type (type:entity --> entity) diff --git a/askomics/libaskomics/RdfFile.py b/askomics/libaskomics/RdfFile.py index 0954d91f..b1252d01 100644 --- a/askomics/libaskomics/RdfFile.py +++ b/askomics/libaskomics/RdfFile.py @@ -69,6 +69,12 @@ def get_preview(self): for x in range(1, 100): head += ttl_file.readline() + location = None + try: + location = self.get_location() + except Exception as e: + self.error_message = str(e) + return { 'type': self.type, 'id': self.id, @@ -77,7 +83,7 @@ def get_preview(self): 'error_message': self.error_message, 'data': { 'preview': head, - 'location': self.get_location() + 'location': location } } diff --git a/askomics/libaskomics/Result.py b/askomics/libaskomics/Result.py index 07e74ca5..d48f7725 100644 --- a/askomics/libaskomics/Result.py +++ b/askomics/libaskomics/Result.py @@ -389,6 +389,33 @@ def save_in_db(self): return self.id + def populate_db(self, graphs, endpoints): + """Update status of results in db + + Parameters + ---------- + query : bool, optional + True if error during integration + error_message : bool, optional + Error string if error is True + """ + + database = Database(self.app, self.session) + + query = ''' + UPDATE results SET + graphs_and_endpoints=? + WHERE user_id=? AND id=? + ''' + + variables = [ + json.dumps({"graphs": graphs, "endpoints": endpoints}), + self.session["user"]["id"], + self.id + ] + + database.execute_sql_query(query, tuple(variables)) + def update_public_status(self, public): """Change public status @@ -471,7 +498,7 @@ def update_db_status(self, status, size=None, update_celery=False, update_date=F def rollback(self): """Delete file""" - self.delete_file_from_filesystem(self) + self.delete_file_from_filesystem() def delete_result(self): """Remove results from db and filesystem""" diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index dd73b902..2369feb5 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -18,7 +18,7 @@ class SparqlQuery(Params): all public graph """ - def __init__(self, app, session, json_query=None, get_graphs=True): + def __init__(self, app, session, json_query=None, get_graphs=False): """init Parameters @@ -400,12 +400,10 @@ def set_graphs_and_endpoints(self, entities=None, graphs=None, endpoints=None): entities : list, optional list of entity uri """ - substrlst = [] filter_entity_string = '' if entities: - for entity in entities: - substrlst.append("?entity_uri = <{}>".format(entity)) - filter_entity_string = 'FILTER (' + ' || '.join(substrlst) + ')' + substr = ",".join(["<{}>".format(entity) for entity in entities]) + filter_entity_string = 'FILTER (?entity_uri IN( ' + substr + ' ))' filter_public_string = 'FILTER (?public = )' if 'user' in self.session: @@ -465,7 +463,7 @@ def get_uri_parameters(self, uri, endpoints): The corresponding parameters """ raw_query = ''' - SELECT DISTINCT ?predicate ?object ?faldo_value ?faldo_uri + SELECT DISTINCT ?predicate ?object ?faldo_value ?faldo_relation WHERE {{ ?URI ?predicate ?object . ?URI a ?entitytype . @@ -498,9 +496,14 @@ def get_uri_parameters(self, uri, endpoints): ?faldo_uri rdf:type askomics:faldoStrand . }} + OPTIONAL {{ + ?faldo_uri askomics:uri ?node_uri + }} + VALUES ?predicate {{faldo:location}} }} VALUES ?URI {{{}}} + BIND(IF(isBlank(?faldo_uri), ?node_uri ,?faldo_uri) as ?faldo_relation) }} '''.format(uri) @@ -516,9 +519,19 @@ def get_uri_parameters(self, uri, endpoints): formated_data = [] for row in data: + + predicate = row['predicate'] + object = row['object'] + + if row.get('faldo_relation'): + predicate = row.get("faldo_relation") + + if row.get('faldo_value'): + object = row.get('faldo_value') + formated_data.append({ - 'predicate': row['faldo_uri'] if row.get('faldo_uri') else row['predicate'], - 'object': row['faldo_value'] if row.get('faldo_value') else row['object'], + 'predicate': predicate, + 'object': object, }) return formated_data @@ -1435,7 +1448,7 @@ def build_query_from_json(self, preview=False, for_editor=False): )) var_to_replace.append((category_value_uri, var_2)) - from_string = self.get_froms_from_graphs(self.graphs) + from_string = "" if self.settings.getboolean("askomics", "single_tenant", fallback=False) else self.get_froms_from_graphs(self.graphs) federated_from_string = self.get_federated_froms_from_graphs(self.graphs) endpoints_string = self.get_endpoints_string() diff --git a/askomics/libaskomics/TriplestoreExplorer.py b/askomics/libaskomics/TriplestoreExplorer.py index b941a88e..492ee4ad 100644 --- a/askomics/libaskomics/TriplestoreExplorer.py +++ b/askomics/libaskomics/TriplestoreExplorer.py @@ -208,12 +208,14 @@ def get_abstraction(self): """ insert, abstraction = self.get_cached_asbtraction() + single_tenant = self.settings.getboolean("askomics", "single_tenant", fallback=False) + # No abstraction entry in database, create it if not abstraction: abstraction = { - "entities": self.get_abstraction_entities(), - "attributes": self.get_abstraction_attributes(), - "relations": self.get_abstraction_relations() + "entities": self.get_abstraction_entities(single_tenant), + "attributes": self.get_abstraction_attributes(single_tenant), + "relations": self.get_abstraction_relations(single_tenant) } # Cache abstraction in DB, only for logged users @@ -305,7 +307,7 @@ def uncache_abstraction(self, public=True, force=False): database.execute_sql_query(query, sql_var) - def get_abstraction_entities(self): + def get_abstraction_entities(self, single_tenant=False): """Get abstraction entities Returns @@ -379,7 +381,7 @@ def get_abstraction_entities(self): return entities - def get_abstraction_attributes(self): + def get_abstraction_attributes(self, single_tenant=False): """Get user abstraction attributes from the triplestore Returns @@ -408,13 +410,16 @@ def get_abstraction_attributes(self): ?graph askomics:public ?public . ?graph dc:creator ?creator . GRAPH ?graph {{ - ?attribute_uri a ?attribute_type . + ?node a ?attribute_type . VALUES ?attribute_type {{ owl:DatatypeProperty askomics:AskomicsCategory }} - ?attribute_uri rdfs:label ?attribute_label . - ?attribute_uri rdfs:range ?attribute_range . + ?node rdfs:label ?attribute_label . + ?node rdfs:range ?attribute_range . + # Retrocompatibility + OPTIONAL {{?node askomics:uri ?attribute_uri}} + BIND( IF(isBlank(?node),?attribute_uri, ?node) as ?attribute_uri ) # Faldo OPTIONAL {{ - ?attribute_uri a ?attribute_faldo . + ?node a ?attribute_faldo . VALUES ?attribute_faldo {{ askomics:faldoStart askomics:faldoEnd askomics:faldoStrand askomics:faldoReference }} }} # Categories (DK) @@ -425,10 +430,10 @@ def get_abstraction_attributes(self): }} # Attribute of entity (or motherclass of entity) {{ - ?attribute_uri rdfs:domain ?mother . + ?node rdfs:domain ?mother . ?entity_uri rdfs:subClassOf ?mother . }} UNION {{ - ?attribute_uri rdfs:domain ?entity_uri . + ?node rdfs:domain ?entity_uri . }} FILTER ( ?public = {} @@ -442,13 +447,13 @@ def get_abstraction_attributes(self): attributes = [] for result in data: - # Attributes - if "attribute_uri" in result and "attribute_label" in result and result["attribute_type"] != "{}AskomicsCategory".format(self.settings.get("triplestore", "namespace_internal")) and result["attribute_range"] in litterals: - attr_tpl = (result["attribute_uri"], result["entity_uri"]) + attribute_uri = result.get("attribute_uri") + if attribute_uri and "attribute_label" in result and result["attribute_type"] != "{}AskomicsCategory".format(self.settings.get("triplestore", "namespace_internal")) and result["attribute_range"] in litterals: + attr_tpl = (attribute_uri, result["entity_uri"]) if attr_tpl not in attributes_list: attributes_list.append(attr_tpl) attribute = { - "uri": result["attribute_uri"], + "uri": attribute_uri, "label": result["attribute_label"], "graphs": [result["graph"], ], "entityUri": result["entity_uri"], @@ -466,12 +471,12 @@ def get_abstraction_attributes(self): index_attribute = attributes_list.index(attr_tpl) # Categories - if "attribute_uri" in result and result["attribute_type"] == "{}AskomicsCategory".format(self.settings.get("triplestore", "namespace_internal")) and "category_value_uri" in result: - attr_tpl = (result["attribute_uri"], result["entity_uri"]) + if attribute_uri and result["attribute_type"] == "{}AskomicsCategory".format(self.settings.get("triplestore", "namespace_internal")) and "category_value_uri" in result: + attr_tpl = (attribute_uri, result["entity_uri"]) if attr_tpl not in attributes_list: attributes_list.append(attr_tpl) attribute = { - "uri": result["attribute_uri"], + "uri": attribute_uri, "label": result["attribute_label"], "graphs": [result["graph"], ], "entityUri": result["entity_uri"], @@ -498,7 +503,7 @@ def get_abstraction_attributes(self): return attributes - def get_abstraction_relations(self): + def get_abstraction_relations(self, single_tenant=False): """Get user abstraction relations from the triplestore Returns @@ -514,7 +519,7 @@ def get_abstraction_relations(self): query_builder = SparqlQuery(self.app, self.session) query = ''' - SELECT DISTINCT ?graph ?entity_uri ?entity_faldo ?entity_label ?node ?node_type ?attribute_uri ?attribute_faldo ?attribute_label ?attribute_range ?property_uri ?property_faldo ?property_label ?range_uri ?category_value_uri ?category_value_label + SELECT DISTINCT ?graph ?entity_uri ?entity_faldo ?entity_label ?attribute_uri ?attribute_faldo ?attribute_label ?attribute_range ?property_uri ?property_faldo ?property_label ?range_uri ?category_value_uri ?category_value_label WHERE {{ # Graphs ?graph askomics:public ?public . @@ -527,6 +532,7 @@ def get_abstraction_relations(self): ?node rdfs:range ?range_uri . # Retrocompatibility OPTIONAL {{?node askomics:uri ?property_uri}} + BIND( IF(isBlank(?node), ?property_uri, ?node) as ?property_uri) }} # Relation of entity (or motherclass of entity) {{ @@ -547,9 +553,8 @@ def get_abstraction_relations(self): relations = [] for result in data: # Relation - if "node" in result: - # Retrocompatibility - property_uri = result.get("property_uri", result["node"]) + if "property_uri" in result: + property_uri = result.get("property_uri") rel_tpl = (property_uri, result["entity_uri"], result["range_uri"]) if rel_tpl not in relations_list: relations_list.append(rel_tpl) diff --git a/askomics/react/src/routes.jsx b/askomics/react/src/routes.jsx index 6b910055..f0b7356b 100644 --- a/askomics/react/src/routes.jsx +++ b/askomics/react/src/routes.jsx @@ -47,7 +47,8 @@ export default class Routes extends Component { disableIntegration: null, namespaceData: null, namespaceInternal: null, - ontologies: [] + ontologies: [], + singleTenant: false } } this.cancelRequest diff --git a/askomics/react/src/routes/integration/bedpreview.jsx b/askomics/react/src/routes/integration/bedpreview.jsx index acbad90e..90e72d19 100644 --- a/askomics/react/src/routes/integration/bedpreview.jsx +++ b/askomics/react/src/routes/integration/bedpreview.jsx @@ -91,9 +91,12 @@ export default class BedPreview extends Component { if (this.state.publicTick) { publicIcon = } - + let privateButton + if (this.props.config.user.admin || !this.props.config.singleTenant){ + privateButton = + } let publicButton - if (this.props.config.user.admin) { + if (this.props.config.user.admin || this.props.config.singleTenant) { publicButton = } @@ -124,7 +127,7 @@ export default class BedPreview extends Component {
- + {privateButton} {publicButton}
diff --git a/askomics/react/src/routes/integration/csvtable.jsx b/askomics/react/src/routes/integration/csvtable.jsx index b1d3e047..c9a003a9 100644 --- a/askomics/react/src/routes/integration/csvtable.jsx +++ b/askomics/react/src/routes/integration/csvtable.jsx @@ -254,9 +254,12 @@ export default class CsvTable extends Component { if (this.state.publicTick) { publicIcon = } - + let privateButton + if (this.props.config.user.admin || !this.props.config.singleTenant){ + privateButton = + } let publicButton - if (this.props.config.user.admin) { + if (this.props.config.user.admin || this.props.config.singleTenant) { publicButton = } @@ -290,7 +293,7 @@ export default class CsvTable extends Component {
- + {privateButton} {publicButton}
diff --git a/askomics/react/src/routes/integration/gffpreview.jsx b/askomics/react/src/routes/integration/gffpreview.jsx index 7a3bdddb..0fafbbef 100644 --- a/askomics/react/src/routes/integration/gffpreview.jsx +++ b/askomics/react/src/routes/integration/gffpreview.jsx @@ -100,8 +100,12 @@ export default class GffPreview extends Component { if (this.state.publicTick) { publicIcon = } + let privateButton + if (this.props.config.user.admin || !this.props.config.singleTenant){ + privateButton = + } let publicButton - if (this.props.config.user.admin) { + if (this.props.config.user.admin || this.props.config.singleTenant) { publicButton = } @@ -130,7 +134,7 @@ export default class GffPreview extends Component {
- + {privateButton} {publicButton}
diff --git a/askomics/react/src/routes/integration/rdfpreview.jsx b/askomics/react/src/routes/integration/rdfpreview.jsx index 4d62c6e5..5dbbd4b9 100644 --- a/askomics/react/src/routes/integration/rdfpreview.jsx +++ b/askomics/react/src/routes/integration/rdfpreview.jsx @@ -107,9 +107,12 @@ export default class RdfPreview extends Component { if (this.state.publicTick) { publicIcon = } - + let privateButton + if (this.props.config.user.admin || !this.props.config.singleTenant){ + privateButton = + } let publicButton - if (this.props.config.user.admin) { + if (this.props.config.user.admin || this.props.config.singleTenant) { publicButton = } @@ -144,7 +147,7 @@ export default class RdfPreview extends Component {
- + {privateButton} {publicButton}
diff --git a/askomics/tasks.py b/askomics/tasks.py index 9d2bad0e..285cd6a8 100644 --- a/askomics/tasks.py +++ b/askomics/tasks.py @@ -16,6 +16,7 @@ from askomics.libaskomics.FilesHandler import FilesHandler from askomics.libaskomics.LocalAuth import LocalAuth from askomics.libaskomics.Result import Result +from askomics.libaskomics.SparqlQuery import SparqlQuery from askomics.libaskomics.SparqlQueryLauncher import SparqlQueryLauncher @@ -46,7 +47,7 @@ def integrate(self, session, data, host_url): files_handler = FilesHandler(app, session, host_url=host_url, external_endpoint=data["externalEndpoint"], custom_uri=data["customUri"]) files_handler.handle_files([data["fileId"], ]) - public = data.get("public", False) if session["user"]["admin"] else False + public = (data.get("public", False) if session["user"]["admin"] else False) or app.iniconfig.getboolean("askomics", "single_tenant", fallback=False) for file in files_handler.files: @@ -150,6 +151,17 @@ def query(self, session, info): info["celery_id"] = self.request.id result = Result(app, session, info, force_no_db=True) + query = SparqlQuery(app, session, info["graph_state"]) + query.build_query_from_json(preview=False, for_editor=False) + federated = query.is_federated() + result.populate_db(query.graphs, query.endpoints) + + info["query"] = query.sparql + info["graphs"] = query.graphs + info["endpoints"] = query.endpoints + info["federated"] = federated + info["selects"] = query.selects + # Save job in database database result.set_celery_id(self.request.id) result.update_db_status("started", update_celery=True, update_date=True) @@ -158,7 +170,7 @@ def query(self, session, info): headers = info["selects"] results = [] - if info["graphs"]: + if info["graphs"] or app.iniconfig.getboolean("askomics", "single_tenant", fallback=False): query_launcher = SparqlQueryLauncher(app, session, get_result_query=True, federated=info["federated"], endpoints=info["endpoints"]) headers, results = query_launcher.process_query(info["query"], isql_api=True) diff --git a/config/askomics.ini.template b/config/askomics.ini.template index 9e8001ba..dd1070a9 100644 --- a/config/askomics.ini.template +++ b/config/askomics.ini.template @@ -124,6 +124,9 @@ preview_limit = 25 # Triplestore max rows limit # result_set_max_rows = 10000 +# Single tenant means all graphs are public +single_tenant=False + [federation] # Query engine can be corese or fedx #query_engine = corese diff --git a/config/askomics.test.ini b/config/askomics.test.ini index 6165a1cd..f2afac84 100644 --- a/config/askomics.test.ini +++ b/config/askomics.test.ini @@ -118,6 +118,8 @@ preview_limit = 25 # Triplestore max rows limit result_set_max_rows = 10000 +single_tenant=False + [federation] # Query engine can be corese or fedx query_engine = corese diff --git a/docker/Dockerfile b/docker/Dockerfile index e3f38ef5..10c4902e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM quay.io/askomics/flaskomics-base:4.0.0-alpine3.14 AS builder +FROM quay.io/askomics/flaskomics-base:4.0.0-alpine3.13 AS builder MAINTAINER "Xavier Garnier " COPY . /askomics @@ -7,7 +7,7 @@ WORKDIR /askomics RUN make clean-config fast-install build # Final image -FROM alpine:3.14 +FROM alpine:3.13 WORKDIR /askomics RUN apk add --no-cache make python3 bash git libc-dev libstdc++ nodejs-current npm openldap-dev diff --git a/docker/DockerfileAll b/docker/DockerfileAll index 402d80bf..65224511 100644 --- a/docker/DockerfileAll +++ b/docker/DockerfileAll @@ -1,5 +1,5 @@ # Build AskOmics -FROM quay.io/askomics/flaskomics-base:4.0.0-alpine3.14 AS askomics_builder +FROM quay.io/askomics/flaskomics-base:4.0.0-alpine3.13 AS askomics_builder MAINTAINER "Xavier Garnier " COPY . /askomics @@ -14,7 +14,7 @@ FROM xgaia/corese:20.6.11 AS corese_builder FROM askomics/virtuoso:7.2.5.1 AS virtuoso_builder # Final image -FROM alpine:3.14 +FROM alpine:3.13 ENV MODE="prod" \ NTASKS="5" \ diff --git a/docker/DockerfileCelery b/docker/DockerfileCelery index bd11c62e..db08157a 100644 --- a/docker/DockerfileCelery +++ b/docker/DockerfileCelery @@ -1,4 +1,4 @@ -FROM quay.io/askomics/flaskomics-base:4.0.0-alpine3.14 AS builder +FROM quay.io/askomics/flaskomics-base:4.0.0-alpine3.13 AS builder MAINTAINER "Xavier Garnier " COPY . /askomics @@ -7,7 +7,7 @@ WORKDIR /askomics RUN make clean-config fast-install # Final image -FROM alpine:3.14 +FROM alpine:3.13 WORKDIR /askomics RUN apk add --no-cache make python3 bash git libc-dev libstdc++ openldap-dev diff --git a/docs/requirements.txt b/docs/requirements.txt index 2dfb00fb..100e7c46 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -mkdocs==1.0.4 +mkdocs==1.2.3 markdown-captions==2 diff --git a/package-lock.json b/package-lock.json index f1b8dfe5..0b75625b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5341,9 +5341,9 @@ } }, "follow-redirects": { - "version": "1.14.7", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", - "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==" + "version": "1.14.8", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", + "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==" }, "for-in": { "version": "1.0.2", @@ -7113,9 +7113,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "minipass": { "version": "3.1.3", @@ -8326,9 +8326,9 @@ "integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==" }, "prismjs": { - "version": "1.25.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.25.0.tgz", - "integrity": "sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg==" + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", + "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==" }, "private": { "version": "0.1.8", @@ -9477,9 +9477,9 @@ "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" }, "simple-get": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.1.tgz", - "integrity": "sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw==", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.2.tgz", + "integrity": "sha512-Ijd/rV5o+mSBBs4F/x9oDPtTx9Zb6X9brmnXvMW4J7IR15ngi9q5xxqWBKU744jTZiaXtxaPL7uHG6vtN8kUkw==", "requires": { "decompress-response": "^3.3.0", "once": "^1.3.1", @@ -11102,9 +11102,9 @@ } }, "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", "dev": true, "requires": { "figgy-pudding": "^3.5.1" diff --git a/package.json b/package.json index a2b8d346..9f088555 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "immutability-helper": "^3.1.1", "js-file-download": "^0.4.12", "pretty-time": "^1.1.0", - "prismjs": "^1.25.0", + "prismjs": "^1.27.0", "qs": "^6.9.4", "react": "^16.13.1", "react-ace": "^9.1.3", diff --git a/tests/results/abstraction.json b/tests/results/abstraction.json index 79f44a17..470c1c59 100644 --- a/tests/results/abstraction.json +++ b/tests/results/abstraction.json @@ -27,18 +27,6 @@ }, { "categories": [ - { - "label": "minus", - "uri": "http://askomics.org/test/data/minus" - }, - { - "label": "plus", - "uri": "http://askomics.org/test/data/plus" - }, - { - "label": "unknown/both", - "uri": "http://askomics.org/test/data/unknown/both" - }, { "label": "+", "uri": "http://askomics.org/test/data/%2B" @@ -55,7 +43,6 @@ "entityUri": "http://askomics.org/test/data/gene", "faldo": "http://askomics.org/test/internal/faldoStrand", "graphs": [ - "urn:sparql:askomics_test:1_jdoe:transcripts.tsv_###TRANSCRIPTS_TIMESTAMP###", "urn:sparql:askomics_test:1_jdoe:gene.bed_###BED_TIMESTAMP###", "urn:sparql:askomics_test:1_jdoe:gene.gff3_###GFF_TIMESTAMP###" ], @@ -73,7 +60,7 @@ "label": "plus", "uri": "http://askomics.org/test/data/plus" }, - { + { "label": "unknown/both", "uri": "http://askomics.org/test/data/unknown/both" }, @@ -94,8 +81,7 @@ "faldo": "http://askomics.org/test/internal/faldoStrand", "graphs": [ "urn:sparql:askomics_test:1_jdoe:transcripts.tsv_###TRANSCRIPTS_TIMESTAMP###", - "urn:sparql:askomics_test:1_jdoe:gene.gff3_###GFF_TIMESTAMP###", - "urn:sparql:askomics_test:1_jdoe:gene.bed_###BED_TIMESTAMP###" + "urn:sparql:askomics_test:1_jdoe:gene.gff3_###GFF_TIMESTAMP###" ], "label": "strand", "type": "http://askomics.org/test/internal/AskomicsCategory", @@ -127,17 +113,12 @@ { "label": "1", "uri": "http://askomics.org/test/data/1" - }, - { - "label": "7", - "uri": "http://askomics.org/test/data/7" } ], "entityUri": "http://askomics.org/test/data/transcript", "faldo": "http://askomics.org/test/internal/faldoReference", "graphs": [ - "urn:sparql:askomics_test:1_jdoe:gene.gff3_###GFF_TIMESTAMP###", - "urn:sparql:askomics_test:1_jdoe:gene.bed_###BED_TIMESTAMP###" + "urn:sparql:askomics_test:1_jdoe:gene.gff3_###GFF_TIMESTAMP###" ], "label": "reference", "type": "http://askomics.org/test/internal/AskomicsCategory", @@ -148,8 +129,6 @@ "entityUri": "http://askomics.org/test/data/gene", "faldo": "http://askomics.org/test/internal/faldoStart", "graphs": [ - "urn:sparql:askomics_test:1_jdoe:transcripts.tsv_###TRANSCRIPTS_TIMESTAMP###", - "urn:sparql:askomics_test:1_jdoe:qtl.tsv_###QTL_TIMESTAMP###", "urn:sparql:askomics_test:1_jdoe:gene.gff3_###GFF_TIMESTAMP###", "urn:sparql:askomics_test:1_jdoe:gene.bed_###BED_TIMESTAMP###" ], @@ -162,10 +141,7 @@ "entityUri": "http://askomics.org/test/data/QTL", "faldo": "http://askomics.org/test/internal/faldoStart", "graphs": [ - "urn:sparql:askomics_test:1_jdoe:transcripts.tsv_###TRANSCRIPTS_TIMESTAMP###", - "urn:sparql:askomics_test:1_jdoe:qtl.tsv_###QTL_TIMESTAMP###", - "urn:sparql:askomics_test:1_jdoe:gene.gff3_###GFF_TIMESTAMP###", - "urn:sparql:askomics_test:1_jdoe:gene.bed_###BED_TIMESTAMP###" + "urn:sparql:askomics_test:1_jdoe:qtl.tsv_###QTL_TIMESTAMP###" ], "label": "start", "type": "http://www.w3.org/2001/XMLSchema#decimal", @@ -177,9 +153,7 @@ "faldo": "http://askomics.org/test/internal/faldoStart", "graphs": [ "urn:sparql:askomics_test:1_jdoe:transcripts.tsv_###TRANSCRIPTS_TIMESTAMP###", - "urn:sparql:askomics_test:1_jdoe:qtl.tsv_###QTL_TIMESTAMP###", - "urn:sparql:askomics_test:1_jdoe:gene.gff3_###GFF_TIMESTAMP###", - "urn:sparql:askomics_test:1_jdoe:gene.bed_###BED_TIMESTAMP###" + "urn:sparql:askomics_test:1_jdoe:gene.gff3_###GFF_TIMESTAMP###" ], "label": "start", "type": "http://www.w3.org/2001/XMLSchema#decimal", @@ -191,9 +165,7 @@ "faldo": "http://askomics.org/test/internal/faldoEnd", "graphs": [ "urn:sparql:askomics_test:1_jdoe:transcripts.tsv_###TRANSCRIPTS_TIMESTAMP###", - "urn:sparql:askomics_test:1_jdoe:qtl.tsv_###QTL_TIMESTAMP###", - "urn:sparql:askomics_test:1_jdoe:gene.gff3_###GFF_TIMESTAMP###", - "urn:sparql:askomics_test:1_jdoe:gene.bed_###BED_TIMESTAMP###" + "urn:sparql:askomics_test:1_jdoe:gene.gff3_###GFF_TIMESTAMP###" ], "label": "end", "type": "http://www.w3.org/2001/XMLSchema#decimal", @@ -204,8 +176,6 @@ "entityUri": "http://askomics.org/test/data/gene", "faldo": "http://askomics.org/test/internal/faldoEnd", "graphs": [ - "urn:sparql:askomics_test:1_jdoe:transcripts.tsv_###TRANSCRIPTS_TIMESTAMP###", - "urn:sparql:askomics_test:1_jdoe:qtl.tsv_###QTL_TIMESTAMP###", "urn:sparql:askomics_test:1_jdoe:gene.gff3_###GFF_TIMESTAMP###", "urn:sparql:askomics_test:1_jdoe:gene.bed_###BED_TIMESTAMP###" ], @@ -218,10 +188,7 @@ "entityUri": "http://askomics.org/test/data/QTL", "faldo": "http://askomics.org/test/internal/faldoEnd", "graphs": [ - "urn:sparql:askomics_test:1_jdoe:transcripts.tsv_###TRANSCRIPTS_TIMESTAMP###", - "urn:sparql:askomics_test:1_jdoe:qtl.tsv_###QTL_TIMESTAMP###", - "urn:sparql:askomics_test:1_jdoe:gene.gff3_###GFF_TIMESTAMP###", - "urn:sparql:askomics_test:1_jdoe:gene.bed_###BED_TIMESTAMP###" + "urn:sparql:askomics_test:1_jdoe:qtl.tsv_###QTL_TIMESTAMP###" ], "label": "end", "type": "http://www.w3.org/2001/XMLSchema#decimal", @@ -284,7 +251,6 @@ "entityUri": "http://askomics.org/test/data/gene", "faldo": null, "graphs": [ - "urn:sparql:askomics_test:1_jdoe:transcripts.tsv_###TRANSCRIPTS_TIMESTAMP###", "urn:sparql:askomics_test:1_jdoe:gene.gff3_###GFF_TIMESTAMP###" ], "label": "biotype", @@ -296,8 +262,7 @@ "entityUri": "http://askomics.org/test/data/transcript", "faldo": null, "graphs": [ - "urn:sparql:askomics_test:1_jdoe:transcripts.tsv_###TRANSCRIPTS_TIMESTAMP###", - "urn:sparql:askomics_test:1_jdoe:gene.gff3_###GFF_TIMESTAMP###" + "urn:sparql:askomics_test:1_jdoe:transcripts.tsv_###TRANSCRIPTS_TIMESTAMP###" ], "label": "description", "type": "http://www.w3.org/2001/XMLSchema#string", @@ -308,7 +273,6 @@ "entityUri": "http://askomics.org/test/data/gene", "faldo": null, "graphs": [ - "urn:sparql:askomics_test:1_jdoe:transcripts.tsv_###TRANSCRIPTS_TIMESTAMP###", "urn:sparql:askomics_test:1_jdoe:gene.gff3_###GFF_TIMESTAMP###" ], "label": "description", diff --git a/tests/test_api.py b/tests/test_api.py index af30f8bd..ea66cc70 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -43,7 +43,8 @@ def test_start(self, client): "proxyPath": "/", "user": {}, "logged": False, - "ontologies": [] + "ontologies": [], + "singleTenant": False } response = client.client.get('/api/start') assert response.status_code == 200 From 2aabba1cfe4ff604182d0194acc5eb6d0fb14ca5 Mon Sep 17 00:00:00 2001 From: mboudet Date: Tue, 17 May 2022 11:48:34 +0200 Subject: [PATCH 062/113] Fix empty category column --- askomics/libaskomics/CsvFile.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/askomics/libaskomics/CsvFile.py b/askomics/libaskomics/CsvFile.py index 08f368eb..1b94c86b 100644 --- a/askomics/libaskomics/CsvFile.py +++ b/askomics/libaskomics/CsvFile.py @@ -361,8 +361,7 @@ def set_rdf_abstraction_domain_knowledge(self): def set_rdf_domain_knowledge(self): """Set the domain knowledge""" for index, attribute in enumerate(self.header): - - if self.columns_type[index] in ('category', 'reference', 'strand'): + if self.columns_type[index] in ('category', 'reference', 'strand') and self.header[index] in self.category_values: s = self.namespace_data["{}Category".format(self.format_uri(attribute, remove_space=True))] p = self.namespace_internal["category"] for value in self.category_values[self.header[index]]: From 5919426397b0238a2cfa86e4fe36f34700b4eea8 Mon Sep 17 00:00:00 2001 From: mboudet Date: Tue, 17 May 2022 11:51:22 +0200 Subject: [PATCH 063/113] Update CHANGELOG.md --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dd0e971..ce78b3d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ This changelog was started for release 4.2.0. - Fixed an issue with forms (missing field and entity name for label & uri fields) (Issue #255) - Fixed an issue with the data endpoint for FALDO entities (Issue #279) +- Fixed an issue where integration would fail when setting a category type on a empty column (#334) ### Added @@ -25,7 +26,7 @@ This changelog was started for release 4.2.0. - Added tooltips to buttons in the query form (and other forms) - Added owl integration - Add better error management for RDF files -- Added 'single tenant' mode +- Added 'single tenant' mode: Send queries to all graphs to speed up ### Changed @@ -54,6 +55,7 @@ This changelog was started for release 4.2.0. - Bump follow-redirects from 1.14.4 to 1.14.8 - Bump mkdocs from 1.0.4 to 1.2.3 in /docs - Bump python-ldap from 3.3.1 to 3.4.0 +- Bump minimist from 1.2.5 to 1.2.6 ## [4.3.1] - 2021-06-16 From 5134414d4185479a0543aa37249598717dabdf8e Mon Sep 17 00:00:00 2001 From: mboudet Date: Wed, 18 May 2022 14:24:16 +0200 Subject: [PATCH 064/113] Dev onto (#336) Dev onto --- CHANGELOG.md | 5 ++++- askomics/libaskomics/CsvFile.py | 3 +-- askomics/libaskomics/TriplestoreExplorer.py | 8 ++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dd0e971..c532a3eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ This changelog was started for release 4.2.0. - Fixed an issue with forms (missing field and entity name for label & uri fields) (Issue #255) - Fixed an issue with the data endpoint for FALDO entities (Issue #279) +- Fixed an issue where integration would fail when setting a category type on a empty column (#334) ### Added @@ -25,7 +26,8 @@ This changelog was started for release 4.2.0. - Added tooltips to buttons in the query form (and other forms) - Added owl integration - Add better error management for RDF files -- Added 'single tenant' mode +- Added 'single tenant' mode: Send queries to all graphs to speed up + ### Changed @@ -54,6 +56,7 @@ This changelog was started for release 4.2.0. - Bump follow-redirects from 1.14.4 to 1.14.8 - Bump mkdocs from 1.0.4 to 1.2.3 in /docs - Bump python-ldap from 3.3.1 to 3.4.0 +- Bump minimist from 1.2.5 to 1.2.6 ## [4.3.1] - 2021-06-16 diff --git a/askomics/libaskomics/CsvFile.py b/askomics/libaskomics/CsvFile.py index 1a86f7b9..8c1bcbac 100644 --- a/askomics/libaskomics/CsvFile.py +++ b/askomics/libaskomics/CsvFile.py @@ -362,8 +362,7 @@ def set_rdf_abstraction_domain_knowledge(self): def set_rdf_domain_knowledge(self): """Set the domain knowledge""" for index, attribute in enumerate(self.header): - - if self.columns_type[index] in ('category', 'reference', 'strand'): + if self.columns_type[index] in ('category', 'reference', 'strand') and self.header[index] in self.category_values: s = self.namespace_data["{}Category".format(self.format_uri(attribute, remove_space=True))] p = self.namespace_internal["category"] for value in self.category_values[self.header[index]]: diff --git a/askomics/libaskomics/TriplestoreExplorer.py b/askomics/libaskomics/TriplestoreExplorer.py index 492ee4ad..ced8a54e 100644 --- a/askomics/libaskomics/TriplestoreExplorer.py +++ b/askomics/libaskomics/TriplestoreExplorer.py @@ -415,8 +415,8 @@ def get_abstraction_attributes(self, single_tenant=False): ?node rdfs:label ?attribute_label . ?node rdfs:range ?attribute_range . # Retrocompatibility - OPTIONAL {{?node askomics:uri ?attribute_uri}} - BIND( IF(isBlank(?node),?attribute_uri, ?node) as ?attribute_uri ) + OPTIONAL {{?node askomics:uri ?new_attribute_uri}} + BIND( IF(isBlank(?node),?new_attribute_uri, ?node) as ?attribute_uri ) # Faldo OPTIONAL {{ ?node a ?attribute_faldo . @@ -531,8 +531,8 @@ def get_abstraction_relations(self, single_tenant=False): ?node rdfs:label ?property_label . ?node rdfs:range ?range_uri . # Retrocompatibility - OPTIONAL {{?node askomics:uri ?property_uri}} - BIND( IF(isBlank(?node), ?property_uri, ?node) as ?property_uri) + OPTIONAL {{?node askomics:uri ?new_property_uri}} + BIND( IF(isBlank(?node), ?new_property_uri, ?node) as ?property_uri) }} # Relation of entity (or motherclass of entity) {{ From be76c52151395f679b8c09fd6b118daa9560953e Mon Sep 17 00:00:00 2001 From: mboudet Date: Mon, 23 May 2022 16:17:28 +0200 Subject: [PATCH 065/113] Force refresh ontologies + case insensitive query + fix single_tenant --- askomics/libaskomics/SparqlQuery.py | 8 +++---- .../react/src/routes/admin/ontologies.jsx | 22 +++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index 2369feb5..63ba91e5 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -276,7 +276,7 @@ def get_default_query_with_prefix(self): self.get_default_query() ) - def format_query(self, query, limit=30, replace_froms=True, federated=False): + def format_query(self, query, limit=30, replace_froms=True, federated=False, ignore_single_tenant=True): """Format the Sparql query - remove all FROM @@ -296,7 +296,7 @@ def format_query(self, query, limit=30, replace_froms=True, federated=False): formatted sparql query """ froms = '' - if replace_froms: + if replace_froms and (not self.settings.getboolean("askomics", "single_tenant", fallback=False) or ignore_single_tenant): froms = self.get_froms() if federated: @@ -555,7 +555,7 @@ def autocomplete_local_ontology(self, uri, query): subquery = "" if query: - subquery = 'FILTER(contains(?label, "{}"))'.format(query) + subquery = 'FILTER(contains(lcase(?label), "{}"))'.format(query.lower()) raw_query = ''' SELECT DISTINCT ?label WHERE {{ @@ -569,7 +569,7 @@ def autocomplete_local_ontology(self, uri, query): raw_query = self.prefix_query(raw_query) - sparql = self.format_query(raw_query, limit=5, replace_froms=True, federated=False) + sparql = self.format_query(raw_query, limit=5, replace_froms=True, federated=False, ignore_single_tenant=True) query_launcher = SparqlQueryLauncher(self.app, self.session, get_result_query=True, federated=False) _, data = query_launcher.process_query(sparql) diff --git a/askomics/react/src/routes/admin/ontologies.jsx b/askomics/react/src/routes/admin/ontologies.jsx index afbfe9a9..5eef58dd 100644 --- a/askomics/react/src/routes/admin/ontologies.jsx +++ b/askomics/react/src/routes/admin/ontologies.jsx @@ -41,11 +41,26 @@ export default class Ontologies extends Component { return this.state.ontologiesSelected.length == 0 } + cleanupOntologies (newOntologies) { + let cleanOntologies = [] + newOntologies.map(onto => { + cleanOntologies.push({ + id:onto.id, + name:onto.name, + uri:onto.uri, + short_name: onto.short_name, + type: onto.type + }) + }) + return cleanOntologies + } + deleteSelectedOntologies () { let requestUrl = '/api/admin/delete_ontologies' let data = { ontologiesIdToDelete: this.state.ontologiesSelected } + axios.post(requestUrl, data, { baseURL: this.props.config.proxyPath, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) .then(response => { console.log(requestUrl, response.data) @@ -53,6 +68,9 @@ export default class Ontologies extends Component { ontologies: response.data.ontologies, ontologiesSelected: [], }) + this.props.setStateNavbar({ + config: update(this.props.config, {ontologies: {$set: cleanupOntologies(response.data.ontologies)}}) + }) }) } @@ -119,6 +137,9 @@ export default class Ontologies extends Component { shortName: "", type: "local" }) + this.props.setStateNavbar({ + config: update(this.props.config, {ontologies: {$set: cleanupOntologies(response.data.ontologies)}}) + }) }) .catch(error => { console.log(error, error.response.data.errorMessage) @@ -322,6 +343,7 @@ export default class Ontologies extends Component { } Ontologies.propTypes = { + setStateNavbar: PropTypes.func, waitForStart: PropTypes.bool, config: PropTypes.object } From 26d267f11446ff5dbe109c562a0b6dd007b6471e Mon Sep 17 00:00:00 2001 From: mboudet Date: Tue, 24 May 2022 10:16:02 +0000 Subject: [PATCH 066/113] typo --- askomics/react/src/routes/admin/ontologies.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/askomics/react/src/routes/admin/ontologies.jsx b/askomics/react/src/routes/admin/ontologies.jsx index 5eef58dd..cf2a77c5 100644 --- a/askomics/react/src/routes/admin/ontologies.jsx +++ b/askomics/react/src/routes/admin/ontologies.jsx @@ -3,6 +3,7 @@ import axios from 'axios' import {Button, Form, FormGroup, Label, Input, Alert, Row, Col, CustomInput } from 'reactstrap' import BootstrapTable from 'react-bootstrap-table-next' import paginationFactory from 'react-bootstrap-table2-paginator' +import update from 'react-addons-update' import PropTypes from 'prop-types' import Utils from '../../classes/utils' import { Redirect } from 'react-router-dom' @@ -69,7 +70,7 @@ export default class Ontologies extends Component { ontologiesSelected: [], }) this.props.setStateNavbar({ - config: update(this.props.config, {ontologies: {$set: cleanupOntologies(response.data.ontologies)}}) + config: update(this.props.config, {ontologies: {$set: this.cleanupOntologies(response.data.ontologies)}}) }) }) } @@ -138,7 +139,7 @@ export default class Ontologies extends Component { type: "local" }) this.props.setStateNavbar({ - config: update(this.props.config, {ontologies: {$set: cleanupOntologies(response.data.ontologies)}}) + config: update(this.props.config, {ontologies: {$set: this.cleanupOntologies(response.data.ontologies)}}) }) }) .catch(error => { From dcf2ffc1bc2dc9eb7a18b3a568b1825513215435 Mon Sep 17 00:00:00 2001 From: mboudet Date: Tue, 24 May 2022 13:15:56 +0000 Subject: [PATCH 067/113] 10 results, & fix issues with multiple ontologies --- askomics/libaskomics/OntologyManager.py | 4 ++-- askomics/libaskomics/SparqlQuery.py | 6 ++---- askomics/react/src/components/autocomplete.jsx | 14 +++++++------- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/askomics/libaskomics/OntologyManager.py b/askomics/libaskomics/OntologyManager.py index 74b47c95..a2e12a4a 100644 --- a/askomics/libaskomics/OntologyManager.py +++ b/askomics/libaskomics/OntologyManager.py @@ -221,13 +221,13 @@ def autocomplete(self, ontology_uri, ontology_type, query_term, onto_short_name, query = SparqlQuery(self.app, self.session, get_graphs=False) # TODO: Actually store the graph in the ontology to quicken search query.set_graphs([onto_graph]) - return query.autocomplete_local_ontology(ontology_uri, query_term) + return query.autocomplete_local_ontology(ontology_uri, query_term, 10) elif ontology_type == "ols": base_url = "https://www.ebi.ac.uk/ols/api/suggest" arguments = { "q": query_term, "ontology": quote_plus(onto_short_name.lower()), - "rows": 5 + "rows": 10 } r = requests.get(base_url, params=arguments) diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index 63ba91e5..015341b1 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -536,7 +536,7 @@ def get_uri_parameters(self, uri, endpoints): return formated_data - def autocomplete_local_ontology(self, uri, query): + def autocomplete_local_ontology(self, uri, query, max_terms): """Get results for a specific query Parameters @@ -563,13 +563,11 @@ def autocomplete_local_ontology(self, uri, query): ?uri rdfs:label ?label . {} }} - - LIMIT 5 '''.format(uri, subquery) raw_query = self.prefix_query(raw_query) - sparql = self.format_query(raw_query, limit=5, replace_froms=True, federated=False, ignore_single_tenant=True) + sparql = self.format_query(raw_query, limit=max_terms, replace_froms=True, federated=False, ignore_single_tenant=True) query_launcher = SparqlQueryLauncher(self.app, self.session, get_result_query=True, federated=False) _, data = query_launcher.process_query(sparql) diff --git a/askomics/react/src/components/autocomplete.jsx b/askomics/react/src/components/autocomplete.jsx index 52fba74e..635881b1 100644 --- a/askomics/react/src/components/autocomplete.jsx +++ b/askomics/react/src/components/autocomplete.jsx @@ -21,16 +21,16 @@ export default class Autocomplete extends Component { } getAutoComplete () { - return this.props.config.ontologies.map(onto => { - if (onto.uri == this.props.entityUri) { - return onto.short_name - } else { - return null - } - }) + let selectedOnto = this.props.config.ontologies.find(onto => onto.uri == this.props.entityUri) + if (selectedOnto){ + return selectedOnto.short_name + } + return "" } autocompleteOntology (value) { + if (this.state.ontologyShort.length == 0){ return } + let userInput = value let requestUrl = '/api/ontology/' + this.state.ontologyShort + "/autocomplete" From 106d3ed44a6dfa84b9ac1f50c9fe9647e5f973a1 Mon Sep 17 00:00:00 2001 From: mboudet Date: Tue, 24 May 2022 15:44:13 +0200 Subject: [PATCH 068/113] Config value --- CHANGELOG.md | 1 + askomics/libaskomics/OntologyManager.py | 7 +++++-- config/askomics.ini.template | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c532a3eb..07e66363 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ This changelog was started for release 4.2.0. - Added owl integration - Add better error management for RDF files - Added 'single tenant' mode: Send queries to all graphs to speed up +- Added ontologies management ### Changed diff --git a/askomics/libaskomics/OntologyManager.py b/askomics/libaskomics/OntologyManager.py index a2e12a4a..acaea31e 100644 --- a/askomics/libaskomics/OntologyManager.py +++ b/askomics/libaskomics/OntologyManager.py @@ -217,17 +217,20 @@ def autocomplete(self, ontology_uri, ontology_type, query_term, onto_short_name, list of dict Results """ + + max_results = self.settings.getint("askomics", "autocomplete_max_results", fallback=10) + if ontology_type == "local": query = SparqlQuery(self.app, self.session, get_graphs=False) # TODO: Actually store the graph in the ontology to quicken search query.set_graphs([onto_graph]) - return query.autocomplete_local_ontology(ontology_uri, query_term, 10) + return query.autocomplete_local_ontology(ontology_uri, query_term, max_results) elif ontology_type == "ols": base_url = "https://www.ebi.ac.uk/ols/api/suggest" arguments = { "q": query_term, "ontology": quote_plus(onto_short_name.lower()), - "rows": 10 + "rows": max_results } r = requests.get(base_url, params=arguments) diff --git a/config/askomics.ini.template b/config/askomics.ini.template index dd1070a9..af284f2b 100644 --- a/config/askomics.ini.template +++ b/config/askomics.ini.template @@ -125,8 +125,12 @@ preview_limit = 25 # result_set_max_rows = 10000 # Single tenant means all graphs are public +# All queries are launched on all graphes (speedup queries) single_tenant=False +# Max results returned for autocompletion +autocomplete_max_results = 10 + [federation] # Query engine can be corese or fedx #query_engine = corese From 2997b83303f650a505411727e0174938617c3872 Mon Sep 17 00:00:00 2001 From: mboudet Date: Wed, 1 Jun 2022 16:42:15 +0200 Subject: [PATCH 069/113] Test new onto behaviour? --- askomics/libaskomics/SparqlQuery.py | 15 ++++++++++++--- askomics/react/src/routes/query/query.jsx | 8 ++++---- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index 015341b1..ef1daba9 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -410,7 +410,7 @@ def set_graphs_and_endpoints(self, entities=None, graphs=None, endpoints=None): filter_public_string = 'FILTER (?public = || ?creator = <{}>)'.format(self.session["user"]["username"]) query = ''' - SELECT DISTINCT ?graph ?endpoint + SELECT DISTINCT ?graph ?endpoint ?entity_uri WHERE {{ ?graph_abstraction askomics:public ?public . ?graph_abstraction dc:creator ?creator . @@ -422,7 +422,9 @@ def set_graphs_and_endpoints(self, entities=None, graphs=None, endpoints=None): VALUES ?askomics_type {{askomics:entity askomics:ontology}} }} GRAPH ?graph {{ - [] a ?entity_uri . + {{ [] a ?entity_uri . }} + UNION + {{ ?entity_uri a askomics:ontology . }} }} {} {} @@ -1186,13 +1188,20 @@ def build_query_from_json(self, preview=False, for_editor=False): subject = self.format_sparql_variable("{}{}_uri".format(attribute["entityLabel"], attribute["nodeId"])) predicate = attribute["uri"] obj = "<{}>".format(attribute["entityUris"][0]) - if not self.is_bnode(attribute["entityUris"][0], self.json["nodes"]): + if not (self.is_bnode(attribute["entityUris"][0], self.json["nodes"]) or attribute["ontology"] is True): self.store_triple({ "subject": subject, "predicate": predicate, "object": obj, "optional": False }, block_id, sblock_id, pblock_ids) + if attribute["ontology"] is True: + self.store_triple({ + "subject": subject, + "predicate": predicate, + "object": "owl:Class", + "optional": False + }, block_id, sblock_id, pblock_ids) if attribute["visible"]: self.selects.append(subject) diff --git a/askomics/react/src/routes/query/query.jsx b/askomics/react/src/routes/query/query.jsx index 25a580c5..aa0c48f6 100644 --- a/askomics/react/src/routes/query/query.jsx +++ b/askomics/react/src/routes/query/query.jsx @@ -224,9 +224,6 @@ export default class Query extends Component { return false } - console.log(node) - console.log(node.ontology ? currentUri == targetUri ? "endNode" : "node" : false) - return node.ontology ? currentUri == targetUri ? "endNode" : "node" : false } @@ -276,6 +273,8 @@ export default class Query extends Component { let nodeAttributes = [] let isBnode = this.isBnode(nodeId) + let isOnto = this.isOntoNode(nodeId) + // if bnode without uri, first attribute is visible let firstAttrVisibleForBnode = isBnode @@ -303,7 +302,8 @@ export default class Query extends Component { form: false, negative: false, linked: false, - linkedWith: null + linkedWith: null, + ontology: isOnto }) } From c0af6d3b68601f206af6595b6f36ec2e030ec57a Mon Sep 17 00:00:00 2001 From: mboudet Date: Wed, 1 Jun 2022 17:54:19 +0200 Subject: [PATCH 070/113] Fix before tests --- askomics/libaskomics/SparqlQuery.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index ef1daba9..4079f86f 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -1188,14 +1188,14 @@ def build_query_from_json(self, preview=False, for_editor=False): subject = self.format_sparql_variable("{}{}_uri".format(attribute["entityLabel"], attribute["nodeId"])) predicate = attribute["uri"] obj = "<{}>".format(attribute["entityUris"][0]) - if not (self.is_bnode(attribute["entityUris"][0], self.json["nodes"]) or attribute["ontology"] is True): + if not (self.is_bnode(attribute["entityUris"][0], self.json["nodes"]) or attribute.get("ontology", False) is True): self.store_triple({ "subject": subject, "predicate": predicate, "object": obj, "optional": False }, block_id, sblock_id, pblock_ids) - if attribute["ontology"] is True: + if attribute.get("ontology", False) is True: self.store_triple({ "subject": subject, "predicate": predicate, From 79f0c8b94ca23f73384e2abe1146b6b52f839c8f Mon Sep 17 00:00:00 2001 From: mboudet Date: Thu, 2 Jun 2022 16:03:21 +0200 Subject: [PATCH 071/113] Batter management of ontologies and labels --- askomics/api/admin.py | 10 ++++- askomics/api/ontology.py | 2 +- askomics/libaskomics/Database.py | 13 +++++- askomics/libaskomics/OntologyManager.py | 40 +++++++++++++------ askomics/libaskomics/PrefixManager.py | 3 +- askomics/libaskomics/SparqlQuery.py | 18 +++++++-- .../react/src/routes/admin/ontologies.jsx | 20 ++++++++-- 7 files changed, 83 insertions(+), 23 deletions(-) diff --git a/askomics/api/admin.py b/askomics/api/admin.py index 16fd7315..67df9c2f 100644 --- a/askomics/api/admin.py +++ b/askomics/api/admin.py @@ -703,6 +703,7 @@ def add_ontology(): short_name = data.get("shortName") type = data.get("type") dataset_id = data.get("datasetId") + label_uri = data.get("datasetId") om = OntologyManager(current_app, session) @@ -751,8 +752,15 @@ def add_ontology(): 'errorMessage': "Name and short name must be unique" }), 400 + if any([dataset_id == onto['dataset_id'] for onto in ontologies]): + return jsonify({ + 'ontologies': [], + 'error': True, + 'errorMessage': "Dataset is already linked to another ontology" + }), 400 + try: - om.add_ontology(name, uri, short_name, dataset.id, dataset.graph_name, type) + om.add_ontology(name, uri, short_name, dataset.id, dataset.graph_name, type, label_uri) ontologies = om.list_full_ontologies() except Exception as e: traceback.print_exc(file=sys.stdout) diff --git a/askomics/api/ontology.py b/askomics/api/ontology.py index 49d4bf85..224bb95b 100644 --- a/askomics/api/ontology.py +++ b/askomics/api/ontology.py @@ -22,7 +22,7 @@ def autocomplete(short_ontology): try: # Disk space om = OntologyManager(current_app, session) - ontology = om.get_ontology(short_ontology) + ontology = om.get_ontology(short_name=short_ontology) if not ontology: return jsonify({ "error": True, diff --git a/askomics/libaskomics/Database.py b/askomics/libaskomics/Database.py index bd81202f..c2261285 100644 --- a/askomics/libaskomics/Database.py +++ b/askomics/libaskomics/Database.py @@ -416,10 +416,21 @@ def create_ontologies_table(self): name text NOT NULL, uri text NOT NULL, short_name text NOT NULL, - type text DEFAULT 'local', + type text DEFAULT 'sparql', dataset_id INTEGER NOT NULL, graph text NOT NULL, FOREIGN KEY(dataset_id) REFERENCES datasets(id) ) ''' self.execute_sql_query(query) + + query = ''' + ALTER TABLE files + ADD label_uri text NOT NULL + DEFAULT('rdfs:label') + ''' + + try: + self.execute_sql_query(query) + except Exception: + pass diff --git a/askomics/libaskomics/OntologyManager.py b/askomics/libaskomics/OntologyManager.py index acaea31e..832e091a 100644 --- a/askomics/libaskomics/OntologyManager.py +++ b/askomics/libaskomics/OntologyManager.py @@ -77,7 +77,7 @@ def list_full_ontologies(self): database = Database(self.app, self.session) query = ''' - SELECT ontologies.id, ontologies.name, ontologies.uri, ontologies.short_name, ontologies.type, datasets.id, datasets.name, ontologies.graph + SELECT ontologies.id, ontologies.name, ontologies.uri, ontologies.short_name, ontologies.type, ontologies.label_uri, datasets.id, datasets.name, ontologies.graph FROM ontologies INNER JOIN datasets ON datasets.id=ontologies.dataset_id ''' @@ -92,16 +92,17 @@ def list_full_ontologies(self): 'uri': row[2], 'short_name': row[3], 'type': row[4], - 'dataset_id': row[5], - 'dataset_name': row[6], - 'graph': row[7] + 'label_uri': row[5], + 'dataset_id': row[6], + 'dataset_name': row[7], + 'graph': row[8] } ontologies.append(prefix) return ontologies - def get_ontology(self, short_name): - """Get a specific ontology based on short name + def get_ontology(self, short_name="", uri=""): + """Get a specific ontology based on short name or uri Returns ------- @@ -109,15 +110,26 @@ def get_ontology(self, short_name): ontology """ + if not (short_name or uri): + return None + + if short_name: + where_clause = "WHERE short_name = ?" + args = (short_name,) + + if uri: + where_clause = "WHERE uri = ?" + args = (uri,) + database = Database(self.app, self.session) query = ''' - SELECT id, name, uri, short_name, type, dataset_id, graph + SELECT id, name, uri, short_name, type, dataset_id, graph, label_uri FROM ontologies - WHERE short_name = ? - ''' + {} + '''.format(where_clause) - rows = database.execute_sql_query(query, (short_name,)) + rows = database.execute_sql_query(query, args) if not rows: return None @@ -130,10 +142,11 @@ def get_ontology(self, short_name): 'short_name': ontology[3], 'type': ontology[4], 'dataset_id': ontology[5], - 'graph': ontology[6] + 'graph': ontology[6], + 'label_uri': ontology[7] } - def add_ontology(self, name, uri, short_name, dataset_id, graph, type="local"): + def add_ontology(self, name, uri, short_name, dataset_id, graph, type="local", label_uri="rdfs:label"): """Create a new ontology Returns @@ -151,11 +164,12 @@ def add_ontology(self, name, uri, short_name, dataset_id, graph, type="local"): ?, ?, ?, + ?, ? ) ''' - database.execute_sql_query(query, (name, uri, short_name, type, dataset_id, graph)) + database.execute_sql_query(query, (name, uri, short_name, type, dataset_id, graph, label_uri)) query = ''' UPDATE datasets SET diff --git a/askomics/libaskomics/PrefixManager.py b/askomics/libaskomics/PrefixManager.py index b3ff0999..07c4f5f0 100644 --- a/askomics/libaskomics/PrefixManager.py +++ b/askomics/libaskomics/PrefixManager.py @@ -41,7 +41,8 @@ def __init__(self, app, session): 'rdf:': str(rdflib.RDF), 'rdfs:': str(rdflib.RDFS), 'owl:': str(rdflib.OWL), - 'xsd:': str(rdflib.XSD) + 'xsd:': str(rdflib.XSD), + 'skos': str(rdflib.SKOS) } def get_prefix(self): diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index 4079f86f..013eb863 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -1,6 +1,7 @@ import re import textwrap +from askomics.libaskomics.OntologyManager import OntologyManager from askomics.libaskomics.Params import Params from askomics.libaskomics.PrefixManager import PrefixManager from askomics.libaskomics.SparqlQueryLauncher import SparqlQueryLauncher @@ -392,7 +393,7 @@ def get_endpoints_string(self): return endpoints_string - def set_graphs_and_endpoints(self, entities=None, graphs=None, endpoints=None): + def set_graphs_and_endpoints(self, entities=None, graphs=None, endpoints=None, ontologies={}): """Get all public and private graphs containing the given entities Parameters @@ -437,7 +438,11 @@ def set_graphs_and_endpoints(self, entities=None, graphs=None, endpoints=None): self.endpoints = [] for res in results: if not graphs or res["graph"] in graphs: - self.graphs.append(res["graph"]) + # Override with onto graph if matching uri + if ontologies.get(res['uri']): + self.graphs.append(ontologies[res['uri']]['graph']) + else: + self.graphs.append(res["graph"]) # If local triplestore url is not accessible by federated query engine if res["endpoint"] == self.settings.get('triplestore', 'endpoint') and self.local_endpoint_f is not None: @@ -1026,14 +1031,19 @@ def build_query_from_json(self, preview=False, for_editor=False): var_to_replace = [] + ontologies = {} + om = OntologyManager(self.app, self.session) + # Browse attributes to get entities for attr in self.json["attr"]: entities = entities + attr["entityUris"] + if attr["type"] == "uri" and attr.get("ontology", False) is True and not attr["entityUris"][0] in ontologies: + ontologies[attr["entityUris"][0]] = om.get_ontology(uri=attr["entityUris"][0]) entities = list(set(entities)) # uniq list # Set graphs in function of entities needed - self.set_graphs_and_endpoints(entities=entities) + self.set_graphs_and_endpoints(entities=entities, ontologies=ontologies) # self.log.debug(self.json) @@ -1196,6 +1206,8 @@ def build_query_from_json(self, preview=False, for_editor=False): "optional": False }, block_id, sblock_id, pblock_ids) if attribute.get("ontology", False) is True: + if ontologies.get(attribute["entityUris"][0]): + predicate = ontologies[attribute["entityUris"][0]]["label_uri"] self.store_triple({ "subject": subject, "predicate": predicate, diff --git a/askomics/react/src/routes/admin/ontologies.jsx b/askomics/react/src/routes/admin/ontologies.jsx index cf2a77c5..0e42ce12 100644 --- a/askomics/react/src/routes/admin/ontologies.jsx +++ b/askomics/react/src/routes/admin/ontologies.jsx @@ -28,6 +28,7 @@ export default class Ontologies extends Component { shortName: "", type: "local", datasetId: "", + label_uri: "rdfs:label", ontologiesSelected: [] } this.handleChangeValue = this.handleChangeValue.bind(this) @@ -123,7 +124,8 @@ export default class Ontologies extends Component { uri: this.state.uri, shortName: this.state.shortName, type: this.state.type, - datasetId: this.state.datasetId + datasetId: this.state.datasetId, + label_uri: this.state.label_uri } axios.post(requestUrl, data, { baseURL: this.props.config.proxyPath, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) @@ -136,7 +138,8 @@ export default class Ontologies extends Component { name: "", uri: "", shortName: "", - type: "local" + type: "local", + label_uri: "rdfs:label" }) this.props.setStateNavbar({ config: update(this.props.config, {ontologies: {$set: this.cleanupOntologies(response.data.ontologies)}}) @@ -236,6 +239,11 @@ export default class Ontologies extends Component { text: 'Dataset', sort: true }, { + editable: false, + dataField: 'label_uri', + text: 'Label uri', + sort: true + }, { editable: false, dataField: 'type', text: 'Autocomplete type', @@ -303,7 +311,13 @@ export default class Ontologies extends Component { - + + + + + From f27120f4e1d5996bb081f14be8e22a12ae83e52e Mon Sep 17 00:00:00 2001 From: mboudet Date: Thu, 2 Jun 2022 17:04:03 +0200 Subject: [PATCH 072/113] Circular import? --- askomics/libaskomics/SparqlQuery.py | 5 +++-- askomics/react/src/routes/admin/ontologies.jsx | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index 013eb863..15e2c9d1 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -1,7 +1,8 @@ import re import textwrap -from askomics.libaskomics.OntologyManager import OntologyManager +import askomics.libaskomics.OntologyManager + from askomics.libaskomics.Params import Params from askomics.libaskomics.PrefixManager import PrefixManager from askomics.libaskomics.SparqlQueryLauncher import SparqlQueryLauncher @@ -1032,7 +1033,7 @@ def build_query_from_json(self, preview=False, for_editor=False): var_to_replace = [] ontologies = {} - om = OntologyManager(self.app, self.session) + om = askomics.libaskomics.OntologyManager.OntologyManager(self.app, self.session) # Browse attributes to get entities for attr in self.json["attr"]: diff --git a/askomics/react/src/routes/admin/ontologies.jsx b/askomics/react/src/routes/admin/ontologies.jsx index 0e42ce12..f4b4bc8d 100644 --- a/askomics/react/src/routes/admin/ontologies.jsx +++ b/askomics/react/src/routes/admin/ontologies.jsx @@ -313,7 +313,7 @@ export default class Ontologies extends Component { - From 52cd3676b290c66de0eb85f74ca7329374623e11 Mon Sep 17 00:00:00 2001 From: mboudet Date: Thu, 2 Jun 2022 17:14:38 +0200 Subject: [PATCH 073/113] Circular import 2 --- askomics/libaskomics/OntologyManager.py | 4 ++-- askomics/libaskomics/SparqlQuery.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/askomics/libaskomics/OntologyManager.py b/askomics/libaskomics/OntologyManager.py index 832e091a..e800a898 100644 --- a/askomics/libaskomics/OntologyManager.py +++ b/askomics/libaskomics/OntologyManager.py @@ -5,7 +5,6 @@ from askomics.libaskomics.Database import Database -from askomics.libaskomics.SparqlQuery import SparqlQuery from askomics.libaskomics.Params import Params @@ -231,7 +230,8 @@ def autocomplete(self, ontology_uri, ontology_type, query_term, onto_short_name, list of dict Results """ - + # Circular import + from askomics.libaskomics.SparqlQuery import SparqlQuery max_results = self.settings.getint("askomics", "autocomplete_max_results", fallback=10) if ontology_type == "local": diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index 15e2c9d1..b9266bff 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -1,8 +1,6 @@ import re import textwrap -import askomics.libaskomics.OntologyManager - from askomics.libaskomics.Params import Params from askomics.libaskomics.PrefixManager import PrefixManager from askomics.libaskomics.SparqlQueryLauncher import SparqlQueryLauncher @@ -1015,6 +1013,8 @@ def build_query_from_json(self, preview=False, for_editor=False): for_editor : bool, optional Remove FROMS and @federate """ + # Circular import + from askomics.libaskomics.OntologyManager import OntologyManager entities = [] attributes = {} linked_attributes = [] @@ -1033,7 +1033,7 @@ def build_query_from_json(self, preview=False, for_editor=False): var_to_replace = [] ontologies = {} - om = askomics.libaskomics.OntologyManager.OntologyManager(self.app, self.session) + om = OntologyManager(self.app, self.session) # Browse attributes to get entities for attr in self.json["attr"]: From adee02fd878fd39d96e1ff62c1fee31e22509e09 Mon Sep 17 00:00:00 2001 From: mboudet Date: Thu, 2 Jun 2022 17:27:30 +0200 Subject: [PATCH 074/113] Fixes --- askomics/api/admin.py | 2 +- askomics/libaskomics/Database.py | 2 +- askomics/react/src/routes/admin/ontologies.jsx | 11 ++++++----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/askomics/api/admin.py b/askomics/api/admin.py index 67df9c2f..8ae19015 100644 --- a/askomics/api/admin.py +++ b/askomics/api/admin.py @@ -703,7 +703,7 @@ def add_ontology(): short_name = data.get("shortName") type = data.get("type") dataset_id = data.get("datasetId") - label_uri = data.get("datasetId") + label_uri = data.get("labelUri", "rdfs:label") om = OntologyManager(current_app, session) diff --git a/askomics/libaskomics/Database.py b/askomics/libaskomics/Database.py index c2261285..52fff976 100644 --- a/askomics/libaskomics/Database.py +++ b/askomics/libaskomics/Database.py @@ -425,7 +425,7 @@ def create_ontologies_table(self): self.execute_sql_query(query) query = ''' - ALTER TABLE files + ALTER TABLE ontologies ADD label_uri text NOT NULL DEFAULT('rdfs:label') ''' diff --git a/askomics/react/src/routes/admin/ontologies.jsx b/askomics/react/src/routes/admin/ontologies.jsx index f4b4bc8d..d7b97511 100644 --- a/askomics/react/src/routes/admin/ontologies.jsx +++ b/askomics/react/src/routes/admin/ontologies.jsx @@ -28,7 +28,7 @@ export default class Ontologies extends Component { shortName: "", type: "local", datasetId: "", - label_uri: "rdfs:label", + labelUri: "rdfs:label", ontologiesSelected: [] } this.handleChangeValue = this.handleChangeValue.bind(this) @@ -87,7 +87,8 @@ export default class Ontologies extends Component { this.state.name.length > 0 && this.state.uri.length > 0 && this.state.shortName.length > 0 && - this.state.datasetId.length > 0 + this.state.datasetId.length > 0 && + this.state.labelUri.length > 0 ) } @@ -125,7 +126,7 @@ export default class Ontologies extends Component { shortName: this.state.shortName, type: this.state.type, datasetId: this.state.datasetId, - label_uri: this.state.label_uri + labelUri: this.state.labelUri } axios.post(requestUrl, data, { baseURL: this.props.config.proxyPath, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) @@ -139,7 +140,7 @@ export default class Ontologies extends Component { uri: "", shortName: "", type: "local", - label_uri: "rdfs:label" + labelUri: "rdfs:label" }) this.props.setStateNavbar({ config: update(this.props.config, {ontologies: {$set: this.cleanupOntologies(response.data.ontologies)}}) @@ -314,7 +315,7 @@ export default class Ontologies extends Component { - + From 5a6137bcff3e6c1b52b8827467952b5e71a1145b Mon Sep 17 00:00:00 2001 From: mboudet Date: Thu, 2 Jun 2022 22:03:55 +0200 Subject: [PATCH 075/113] Update PrefixManager.py --- askomics/libaskomics/PrefixManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/askomics/libaskomics/PrefixManager.py b/askomics/libaskomics/PrefixManager.py index 07c4f5f0..67499f1b 100644 --- a/askomics/libaskomics/PrefixManager.py +++ b/askomics/libaskomics/PrefixManager.py @@ -42,7 +42,7 @@ def __init__(self, app, session): 'rdfs:': str(rdflib.RDFS), 'owl:': str(rdflib.OWL), 'xsd:': str(rdflib.XSD), - 'skos': str(rdflib.SKOS) + 'skos:': str(rdflib.SKOS) } def get_prefix(self): From ab34cebf7b16c785e7a9bdd3da7ada57a9e42da1 Mon Sep 17 00:00:00 2001 From: mboudet Date: Thu, 2 Jun 2022 22:45:41 +0200 Subject: [PATCH 076/113] Update SparqlQuery.py --- askomics/libaskomics/SparqlQuery.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index b9266bff..8e8def29 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -438,8 +438,8 @@ def set_graphs_and_endpoints(self, entities=None, graphs=None, endpoints=None, o for res in results: if not graphs or res["graph"] in graphs: # Override with onto graph if matching uri - if ontologies.get(res['uri']): - self.graphs.append(ontologies[res['uri']]['graph']) + if ontologies.get(res['entity_uri']): + self.graphs.append(ontologies[res['entity_uri']]['graph']) else: self.graphs.append(res["graph"]) From 8d7f4b24e1001b63218a9d9c32b2a1183dcb8227 Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 3 Jun 2022 09:50:26 +0200 Subject: [PATCH 077/113] Fix tests --- tests/results/init.json | 2 +- tests/results/sparql_query.json | 2 +- tests/test_api_admin.py | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/results/init.json b/tests/results/init.json index d557420b..2dcd8e37 100644 --- a/tests/results/init.json +++ b/tests/results/init.json @@ -1,5 +1,5 @@ { - "defaultQuery": "PREFIX : \nPREFIX askomics: \nPREFIX dc: \nPREFIX faldo: \nPREFIX owl: \nPREFIX prov: \nPREFIX rdf: \nPREFIX rdfs: \nPREFIX xsd: \n\nSELECT DISTINCT ?s ?p ?o\nWHERE {\n ?s ?p ?o\n}\n", + "defaultQuery": "PREFIX : \nPREFIX askomics: \nPREFIX dc: \nPREFIX faldo: \nPREFIX owl: \nPREFIX prov: \nPREFIX rdf: \nPREFIX rdfs: \nPREFIX xsd: \nskos: \n\nSELECT DISTINCT ?s ?p ?o\nWHERE {\n ?s ?p ?o\n}\n", "diskSpace": ###SIZE###, "console_enabled": true, "endpoints": { diff --git a/tests/results/sparql_query.json b/tests/results/sparql_query.json index d815b914..1da74702 100644 --- a/tests/results/sparql_query.json +++ b/tests/results/sparql_query.json @@ -37,5 +37,5 @@ "uri": "urn:sparql:askomics_test:1_jdoe:gene.gff3_###GFF_TIMESTAMP###" } }, - "query": "PREFIX : \nPREFIX askomics: \nPREFIX dc: \nPREFIX faldo: \nPREFIX owl: \nPREFIX prov: \nPREFIX rdf: \nPREFIX rdfs: \nPREFIX xsd: \n\nSELECT DISTINCT ?transcript1_Label\nWHERE {\n ?transcript1_uri rdf:type .\n ?transcript1_uri rdfs:label ?transcript1_Label .\n\n\n\n}\n" + "query": "PREFIX : \nPREFIX askomics: \nPREFIX dc: \nPREFIX faldo: \nPREFIX owl: \nPREFIX prov: \nPREFIX rdf: \nPREFIX rdfs: \nPREFIX xsd: \nPREFIX skos: \n\nSELECT DISTINCT ?transcript1_Label\nWHERE {\n ?transcript1_uri rdf:type .\n ?transcript1_uri rdfs:label ?transcript1_Label .\n\n\n\n}\n" } diff --git a/tests/test_api_admin.py b/tests/test_api_admin.py index fae15f3a..56b7d103 100644 --- a/tests/test_api_admin.py +++ b/tests/test_api_admin.py @@ -620,7 +620,8 @@ def test_view_ontologies(self, client): "type": "local", "dataset_id": 1, "dataset_name": "transcripts.tsv", - "graph": "mygraph" + "graph": "mygraph", + "label_uri": "rdfs:label" }] } @@ -659,7 +660,8 @@ def test_add_ontology(self, client): "short_name": "OBO", "type": "local", "dataset_id": 1, - "dataset_name": "transcripts.tsv" + "dataset_name": "transcripts.tsv", + "label_uri": "rdfs:label" }] } From 1c0d547ee4961c8caa72c6102129ec4a1e8d21ad Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 3 Jun 2022 10:25:29 +0200 Subject: [PATCH 078/113] Prefix + order --- tests/results/init.json | 2 +- tests/results/sparql_query.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/results/init.json b/tests/results/init.json index 2dcd8e37..8eea5f0e 100644 --- a/tests/results/init.json +++ b/tests/results/init.json @@ -1,5 +1,5 @@ { - "defaultQuery": "PREFIX : \nPREFIX askomics: \nPREFIX dc: \nPREFIX faldo: \nPREFIX owl: \nPREFIX prov: \nPREFIX rdf: \nPREFIX rdfs: \nPREFIX xsd: \nskos: \n\nSELECT DISTINCT ?s ?p ?o\nWHERE {\n ?s ?p ?o\n}\n", + "defaultQuery": "PREFIX : \nPREFIX askomics: \nPREFIX dc: \nPREFIX faldo: \nPREFIX owl: \nPREFIX prov: \nPREFIX rdf: \nPREFIX rdfs: \nPREFIX skos: \nPREFIX xsd: \n\nSELECT DISTINCT ?s ?p ?o\nWHERE {\n ?s ?p ?o\n}\n", "diskSpace": ###SIZE###, "console_enabled": true, "endpoints": { diff --git a/tests/results/sparql_query.json b/tests/results/sparql_query.json index 1da74702..aae08c25 100644 --- a/tests/results/sparql_query.json +++ b/tests/results/sparql_query.json @@ -37,5 +37,5 @@ "uri": "urn:sparql:askomics_test:1_jdoe:gene.gff3_###GFF_TIMESTAMP###" } }, - "query": "PREFIX : \nPREFIX askomics: \nPREFIX dc: \nPREFIX faldo: \nPREFIX owl: \nPREFIX prov: \nPREFIX rdf: \nPREFIX rdfs: \nPREFIX xsd: \nPREFIX skos: \n\nSELECT DISTINCT ?transcript1_Label\nWHERE {\n ?transcript1_uri rdf:type .\n ?transcript1_uri rdfs:label ?transcript1_Label .\n\n\n\n}\n" + "query": "PREFIX : \nPREFIX askomics: \nPREFIX dc: \nPREFIX faldo: \nPREFIX owl: \nPREFIX prov: \nPREFIX rdf: \nPREFIX rdfs: \nPREFIX skos: \nPREFIX xsd: \n\nSELECT DISTINCT ?transcript1_Label\nWHERE {\n ?transcript1_uri rdf:type .\n ?transcript1_uri rdfs:label ?transcript1_Label .\n\n\n\n}\n" } From c0682d2d81d69259cd6eda870f995cc453601185 Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 3 Jun 2022 15:08:49 +0000 Subject: [PATCH 079/113] Fix some stuff --- askomics/libaskomics/SparqlQuery.py | 4 ++-- askomics/libaskomics/SparqlQueryLauncher.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index 8e8def29..05c7f362 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -419,13 +419,13 @@ def set_graphs_and_endpoints(self, entities=None, graphs=None, endpoints=None, o GRAPH ?graph_abstraction {{ ?graph_abstraction prov:atLocation ?endpoint . ?entity_uri a ?askomics_type . - VALUES ?askomics_type {{askomics:entity askomics:ontology}} }} GRAPH ?graph {{ {{ [] a ?entity_uri . }} UNION - {{ ?entity_uri a askomics:ontology . }} + {{ ?entity_uri a ?askomics_type . }} }} + VALUES ?askomics_type {{askomics:entity askomics:ontology}} {} {} }} diff --git a/askomics/libaskomics/SparqlQueryLauncher.py b/askomics/libaskomics/SparqlQueryLauncher.py index 6c250619..a17bdc82 100644 --- a/askomics/libaskomics/SparqlQueryLauncher.py +++ b/askomics/libaskomics/SparqlQueryLauncher.py @@ -310,8 +310,8 @@ def execute_query(self, query, disable_log=False, isql_api=False): if self.endpoint.isSparqlUpdateRequest(): self.endpoint.setMethod('POST') # Virtuoso hack - if self.triplestore == 'virtuoso': - self.endpoint.queryType = "SELECT" + #if self.triplestore == 'virtuoso': + # self.endpoint.queryType = "SELECT" results = self.endpoint.query() # Select From da9d938a26b56446fef84615124e7ee3745c22fa Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 3 Jun 2022 17:30:08 +0200 Subject: [PATCH 080/113] Fix label --- askomics/libaskomics/SparqlQuery.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index 8e8def29..cefd4dc9 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -1207,8 +1207,6 @@ def build_query_from_json(self, preview=False, for_editor=False): "optional": False }, block_id, sblock_id, pblock_ids) if attribute.get("ontology", False) is True: - if ontologies.get(attribute["entityUris"][0]): - predicate = ontologies[attribute["entityUris"][0]]["label_uri"] self.store_triple({ "subject": subject, "predicate": predicate, @@ -1282,6 +1280,8 @@ def build_query_from_json(self, preview=False, for_editor=False): subject = self.format_sparql_variable("{}{}_uri".format(attribute["entityLabel"], attribute["nodeId"])) if attribute["uri"] == "rdfs:label": predicate = attribute["uri"] + if ontologies.get(attribute["entityUris"][0]): + predicate = ontologies[attribute["entityUris"][0]]["label_uri"] else: predicate = "<{}>".format(attribute["uri"]) From f1e1e360ac18323094b3bb2c0b9c63ac3d92cee9 Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 3 Jun 2022 17:54:13 +0200 Subject: [PATCH 081/113] lint --- askomics/libaskomics/SparqlQueryLauncher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/askomics/libaskomics/SparqlQueryLauncher.py b/askomics/libaskomics/SparqlQueryLauncher.py index a17bdc82..64fe2802 100644 --- a/askomics/libaskomics/SparqlQueryLauncher.py +++ b/askomics/libaskomics/SparqlQueryLauncher.py @@ -310,7 +310,7 @@ def execute_query(self, query, disable_log=False, isql_api=False): if self.endpoint.isSparqlUpdateRequest(): self.endpoint.setMethod('POST') # Virtuoso hack - #if self.triplestore == 'virtuoso': + # if self.triplestore == 'virtuoso': # self.endpoint.queryType = "SELECT" results = self.endpoint.query() From 44ead791b8ef080fe9df56b255b434647e16bace Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 3 Jun 2022 18:18:04 +0200 Subject: [PATCH 082/113] Stuff (1/?) --- askomics/api/admin.py | 2 +- askomics/api/file.py | 4 +++- askomics/api/ontology.py | 2 +- askomics/libaskomics/Database.py | 13 +++++++++++++ askomics/libaskomics/Dataset.py | 14 +++++++++----- askomics/libaskomics/OntologyManager.py | 10 +++++++--- askomics/libaskomics/SparqlQuery.py | 6 ++++-- 7 files changed, 38 insertions(+), 13 deletions(-) diff --git a/askomics/api/admin.py b/askomics/api/admin.py index 8ae19015..97e9a2ef 100644 --- a/askomics/api/admin.py +++ b/askomics/api/admin.py @@ -760,7 +760,7 @@ def add_ontology(): }), 400 try: - om.add_ontology(name, uri, short_name, dataset.id, dataset.graph_name, type, label_uri) + om.add_ontology(name, uri, short_name, dataset.id, dataset.graph_name, type, label_uri, dataset.endpoint) ontologies = om.list_full_ontologies() except Exception as e: traceback.print_exc(file=sys.stdout) diff --git a/askomics/api/file.py b/askomics/api/file.py index 8ef6b4bf..95e1d84f 100644 --- a/askomics/api/file.py +++ b/askomics/api/file.py @@ -343,8 +343,10 @@ def integrate(): "public": (data.get("public", False) if session["user"]["admin"] else False) or current_app.iniconfig.getboolean("askomics", "single_tenant", fallback=False) } + endpoint = data["externalEndpoint"] or current_app.iniconfig.get('triplestore', 'endpoint') + dataset = Dataset(current_app, session, dataset_info) - dataset.save_in_db() + dataset.save_in_db(endpoint) data["dataset_id"] = dataset.id dataset_ids.append(dataset.id) task = current_app.celery.send_task('integrate', (session_dict, data, request.host_url)) diff --git a/askomics/api/ontology.py b/askomics/api/ontology.py index 224bb95b..fb9ce736 100644 --- a/askomics/api/ontology.py +++ b/askomics/api/ontology.py @@ -37,7 +37,7 @@ def autocomplete(short_ontology): "results": [] }), 404 - results = om.autocomplete(ontology["uri"], ontology["type"], request.args.get("q"), short_ontology, ontology["graph"]) + results = om.autocomplete(ontology["uri"], ontology["type"], request.args.get("q"), short_ontology, ontology["graph"], ontology["endpoint"]) except Exception as e: traceback.print_exc(file=sys.stdout) diff --git a/askomics/libaskomics/Database.py b/askomics/libaskomics/Database.py index 52fff976..df96feb6 100644 --- a/askomics/libaskomics/Database.py +++ b/askomics/libaskomics/Database.py @@ -167,6 +167,7 @@ def create_datasets_table(self): end int, ntriples int, error_message text, + endpoint text, FOREIGN KEY(user_id) REFERENCES users(user_id), FOREIGN KEY(file_id) REFERENCES files(id) ) @@ -209,6 +210,17 @@ def update_datasets_table(self): except Exception: pass + query = ''' + ALTER TABLE datasets + ADD endpoint text NULL + DEFAULT(0) + ''' + + try: + self.execute_sql_query(query) + except Exception: + pass + def create_integration_table(self): """Create the integration table""" query = ''' @@ -428,6 +440,7 @@ def create_ontologies_table(self): ALTER TABLE ontologies ADD label_uri text NOT NULL DEFAULT('rdfs:label') + ADD endpoint text NOT NULL ''' try: diff --git a/askomics/libaskomics/Dataset.py b/askomics/libaskomics/Dataset.py index fa0b5e89..87bae1ba 100644 --- a/askomics/libaskomics/Dataset.py +++ b/askomics/libaskomics/Dataset.py @@ -59,7 +59,7 @@ def set_info_from_db(self, admin=False): where_query = "AND user_id = ?" query = ''' - SELECT celery_id, file_id, name, graph_name, public, start, end, ontology + SELECT celery_id, file_id, name, graph_name, public, start, end, ontology, endpoint FROM datasets WHERE id = ? {} @@ -75,8 +75,9 @@ def set_info_from_db(self, admin=False): self.start = rows[0][5] self.end = rows[0][6] self.ontology = rows[0][7] + self.endpoint = rows[0][8] - def save_in_db(self, set_graph=False): + def save_in_db(self, endpoint, set_graph=False): """Save the dataset into the database""" database = Database(self.app, self.session) @@ -87,7 +88,8 @@ def save_in_db(self, set_graph=False): self.file_id, self.name, self.public, - 0 + 0, + endpoint ) if set_graph: @@ -99,7 +101,8 @@ def save_in_db(self, set_graph=False): self.name, self.graph_name, self.public, - 0 + 0, + endpoint ) query = ''' @@ -118,7 +121,8 @@ def save_in_db(self, set_graph=False): NULL, NULL, NULL, - 0 + 0, + ? ) '''.format(subquery) diff --git a/askomics/libaskomics/OntologyManager.py b/askomics/libaskomics/OntologyManager.py index e800a898..7a8a1e3f 100644 --- a/askomics/libaskomics/OntologyManager.py +++ b/askomics/libaskomics/OntologyManager.py @@ -145,7 +145,7 @@ def get_ontology(self, short_name="", uri=""): 'label_uri': ontology[7] } - def add_ontology(self, name, uri, short_name, dataset_id, graph, type="local", label_uri="rdfs:label"): + def add_ontology(self, name, uri, short_name, dataset_id, graph, endpoint, type="local", label_uri="rdfs:label"): """Create a new ontology Returns @@ -154,6 +154,8 @@ def add_ontology(self, name, uri, short_name, dataset_id, graph, type="local", l Prefixes information """ database = Database(self.app, self.session) + if not endpoint: + endpoint = self.settings.get('triplestore', 'endpoint') query = ''' INSERT INTO ontologies VALUES( @@ -164,11 +166,12 @@ def add_ontology(self, name, uri, short_name, dataset_id, graph, type="local", l ?, ?, ?, + ?, ? ) ''' - database.execute_sql_query(query, (name, uri, short_name, type, dataset_id, graph, label_uri)) + database.execute_sql_query(query, (name, uri, short_name, type, dataset_id, graph, label_uri, endpoint)) query = ''' UPDATE datasets SET @@ -222,7 +225,7 @@ def test_ols_ontology(self, shortname): r = requests.get(base_url) return r.status_code == 200 - def autocomplete(self, ontology_uri, ontology_type, query_term, onto_short_name, onto_graph): + def autocomplete(self, ontology_uri, ontology_type, query_term, onto_short_name, onto_graph, onto_endpoint): """Search in ontology Returns @@ -238,6 +241,7 @@ def autocomplete(self, ontology_uri, ontology_type, query_term, onto_short_name, query = SparqlQuery(self.app, self.session, get_graphs=False) # TODO: Actually store the graph in the ontology to quicken search query.set_graphs([onto_graph]) + query.set_endpoints(set([self.settings.get('triplestore', 'endpoint'), onto_endpoint])) return query.autocomplete_local_ontology(ontology_uri, query_term, max_results) elif ontology_type == "ols": base_url = "https://www.ebi.ac.uk/ols/api/suggest" diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index b92c592c..ec462d6a 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -573,9 +573,11 @@ def autocomplete_local_ontology(self, uri, query, max_terms): raw_query = self.prefix_query(raw_query) - sparql = self.format_query(raw_query, limit=max_terms, replace_froms=True, federated=False, ignore_single_tenant=True) + is_federated = self.is_federated() - query_launcher = SparqlQueryLauncher(self.app, self.session, get_result_query=True, federated=False) + sparql = self.format_query(raw_query, limit=max_terms, replace_froms=True, federated=is_federated, ignore_single_tenant=True) + + query_launcher = SparqlQueryLauncher(self.app, self.session, get_result_query=True, federated=is_federated) _, data = query_launcher.process_query(sparql) formated_data = [] From 96acaece02b74a4bd29d97151525ccb7ed8811d8 Mon Sep 17 00:00:00 2001 From: mboudet Date: Tue, 7 Jun 2022 15:57:46 +0200 Subject: [PATCH 083/113] Remote graph --- askomics/libaskomics/PrefixManager.py | 3 +- askomics/libaskomics/SparqlQuery.py | 45 +++++++++++++++++++++------ tests/results/init.json | 2 +- tests/results/sparql_query.json | 2 +- 4 files changed, 40 insertions(+), 12 deletions(-) diff --git a/askomics/libaskomics/PrefixManager.py b/askomics/libaskomics/PrefixManager.py index 67499f1b..493fab53 100644 --- a/askomics/libaskomics/PrefixManager.py +++ b/askomics/libaskomics/PrefixManager.py @@ -42,7 +42,8 @@ def __init__(self, app, session): 'rdfs:': str(rdflib.RDFS), 'owl:': str(rdflib.OWL), 'xsd:': str(rdflib.XSD), - 'skos:': str(rdflib.SKOS) + 'skos:': str(rdflib.SKOS), + 'dcat:': str(rdflib.DCAT) } def get_prefix(self): diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index ec462d6a..bd07e9e7 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -6,6 +6,8 @@ from askomics.libaskomics.SparqlQueryLauncher import SparqlQueryLauncher from askomics.libaskomics.Utils import Utils +from collections import defaultdict + class SparqlQuery(Params): """Format a sparql query @@ -33,8 +35,9 @@ def __init__(self, app, session, json_query=None, get_graphs=False): self.json = json_query self.sparql = None - self.graphs = [] - self.endpoints = [] + self.graphs = set() + self.endpoints = set() + self.remote_graphs = defaultdict(set) self.selects = [] self.federated = False @@ -378,6 +381,23 @@ def get_federated_froms_from_graphs(self, graphs): return from_string + def get_federated_remote_from_graphs(self): + """Get @from string fir the federated query engine + + Returns + ------- + string + The from string + """ + from_string = "" + + for endpoint in self.endpoints: + remote_graphs = self.remote_graphs.get(endpoint) + if len(remote_graphs) == 1: + from_string += "\n@graph <{}> <{}>".format(endpoint, remote_graphs[0]) + + return from_string + def get_endpoints_string(self): """get endpoint strngs for the federated query engine @@ -410,7 +430,7 @@ def set_graphs_and_endpoints(self, entities=None, graphs=None, endpoints=None, o filter_public_string = 'FILTER (?public = || ?creator = <{}>)'.format(self.session["user"]["username"]) query = ''' - SELECT DISTINCT ?graph ?endpoint ?entity_uri + SELECT DISTINCT ?graph ?endpoint ?entity_uri ?remote_graph WHERE {{ ?graph_abstraction askomics:public ?public . ?graph_abstraction dc:creator ?creator . @@ -418,6 +438,7 @@ def set_graphs_and_endpoints(self, entities=None, graphs=None, endpoints=None, o ?graph dc:creator ?creator . GRAPH ?graph_abstraction {{ ?graph_abstraction prov:atLocation ?endpoint . + OPTIONAL {{?graph_abstraction dcat:Dataset ?remote_graph .}} ?entity_uri a ?askomics_type . }} GRAPH ?graph {{ @@ -433,15 +454,17 @@ def set_graphs_and_endpoints(self, entities=None, graphs=None, endpoints=None, o query_launcher = SparqlQueryLauncher(self.app, self.session) header, results = query_launcher.process_query(self.prefix_query(query)) - self.graphs = [] - self.endpoints = [] + self.graphs = set() + self.endpoints = set() + self.remote_graphs = defaultdict(set) for res in results: if not graphs or res["graph"] in graphs: # Override with onto graph if matching uri if ontologies.get(res['entity_uri']): - self.graphs.append(ontologies[res['entity_uri']]['graph']) + graph = ontologies[res['entity_uri']]['graph'] else: - self.graphs.append(res["graph"]) + graph = res["graph"] + self.graphs.add(graph) # If local triplestore url is not accessible by federated query engine if res["endpoint"] == self.settings.get('triplestore', 'endpoint') and self.local_endpoint_f is not None: @@ -450,9 +473,10 @@ def set_graphs_and_endpoints(self, entities=None, graphs=None, endpoints=None, o endpoint = res["endpoint"] if not endpoints or endpoint in endpoints: - self.endpoints.append(endpoint) + self.endpoints.add(endpoint) + if res.get("remote_graph"): + self.remote_graphs[endpoint] = res.get("remote_graph") - self.endpoints = Utils.unique(self.endpoints) self.federated = len(self.endpoints) > 1 def get_uri_parameters(self, uri, endpoints): @@ -1473,6 +1497,7 @@ def build_query_from_json(self, preview=False, for_editor=False): from_string = "" if self.settings.getboolean("askomics", "single_tenant", fallback=False) else self.get_froms_from_graphs(self.graphs) federated_from_string = self.get_federated_froms_from_graphs(self.graphs) endpoints_string = self.get_endpoints_string() + federated_graphs_string = self.get_federated_remote_from_graphs() # Linked attributes: replace SPARQL variable target by source self.replace_variables_in_blocks(var_to_replace) @@ -1502,6 +1527,7 @@ def build_query_from_json(self, preview=False, for_editor=False): query = """ {endpoints} {federated} +{remote_graphs} SELECT DISTINCT {selects} WHERE {{ @@ -1513,6 +1539,7 @@ def build_query_from_json(self, preview=False, for_editor=False): """.format( endpoints=endpoints_string, federated=federated_from_string, + remote_graphs=federated_graphs_string, selects=' '.join(self.selects), triples='\n '.join([self.triple_dict_to_string(triple_dict) for triple_dict in self.triples]), blocks='\n '.join([self.triple_block_to_string(triple_block) for triple_block in self.triples_blocks]), diff --git a/tests/results/init.json b/tests/results/init.json index 8eea5f0e..0f3054bf 100644 --- a/tests/results/init.json +++ b/tests/results/init.json @@ -1,5 +1,5 @@ { - "defaultQuery": "PREFIX : \nPREFIX askomics: \nPREFIX dc: \nPREFIX faldo: \nPREFIX owl: \nPREFIX prov: \nPREFIX rdf: \nPREFIX rdfs: \nPREFIX skos: \nPREFIX xsd: \n\nSELECT DISTINCT ?s ?p ?o\nWHERE {\n ?s ?p ?o\n}\n", + "defaultQuery": "PREFIX : \nPREFIX askomics: \nPREFIX dc: \nPREFIX dcat: \nPREFIX faldo: \nPREFIX owl: \nPREFIX prov: \nPREFIX rdf: \nPREFIX rdfs: \nPREFIX skos: \nPREFIX xsd: \n\nSELECT DISTINCT ?s ?p ?o\nWHERE {\n ?s ?p ?o\n}\n", "diskSpace": ###SIZE###, "console_enabled": true, "endpoints": { diff --git a/tests/results/sparql_query.json b/tests/results/sparql_query.json index aae08c25..0bfe4a08 100644 --- a/tests/results/sparql_query.json +++ b/tests/results/sparql_query.json @@ -37,5 +37,5 @@ "uri": "urn:sparql:askomics_test:1_jdoe:gene.gff3_###GFF_TIMESTAMP###" } }, - "query": "PREFIX : \nPREFIX askomics: \nPREFIX dc: \nPREFIX faldo: \nPREFIX owl: \nPREFIX prov: \nPREFIX rdf: \nPREFIX rdfs: \nPREFIX skos: \nPREFIX xsd: \n\nSELECT DISTINCT ?transcript1_Label\nWHERE {\n ?transcript1_uri rdf:type .\n ?transcript1_uri rdfs:label ?transcript1_Label .\n\n\n\n}\n" + "query": "PREFIX : \nPREFIX askomics: \nPREFIX dc: \nPREFIX dcat: \nPREFIX faldo: \nPREFIX owl: \nPREFIX prov: \nPREFIX rdf: \nPREFIX rdfs: \nPREFIX skos: \nPREFIX xsd: \n\nSELECT DISTINCT ?transcript1_Label\nWHERE {\n ?transcript1_uri rdf:type .\n ?transcript1_uri rdfs:label ?transcript1_Label .\n\n\n\n}\n" } From 234a6223afa4ca7f1cc2fff31b97ab3f135969ab Mon Sep 17 00:00:00 2001 From: mboudet Date: Tue, 7 Jun 2022 16:17:19 +0200 Subject: [PATCH 084/113] Test --- tests/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 06a18da6..18aabdc0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -320,7 +320,7 @@ def integrate_file(self, info, public=False, set_graph=False): } dataset = Dataset(self.app, self.session, dataset_info) - dataset.save_in_db(set_graph=set_graph) + dataset.save_in_db("http://virtuoso:8890/sparql", set_graph=set_graph) if file.type == "csv/tsv": file.integrate(dataset.id, info["columns_type"], public=public) @@ -481,7 +481,7 @@ def create_result(self, has_form=False): # Save job in database database result = Result(self.app, self.session, info) - result.save_in_db() + result.save_in_db("http://virtuoso:8890/sparql") # Execute query and write result to file headers, results = query_launcher.process_query(query.sparql) From 93c9ae74206905ecd92a4eff7eb024de7b7f7caa Mon Sep 17 00:00:00 2001 From: mboudet Date: Tue, 7 Jun 2022 16:51:27 +0200 Subject: [PATCH 085/113] Fix table order --- askomics/libaskomics/Database.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/askomics/libaskomics/Database.py b/askomics/libaskomics/Database.py index df96feb6..b60a72e7 100644 --- a/askomics/libaskomics/Database.py +++ b/askomics/libaskomics/Database.py @@ -167,6 +167,7 @@ def create_datasets_table(self): end int, ntriples int, error_message text, + ontology boolean, endpoint text, FOREIGN KEY(user_id) REFERENCES users(user_id), FOREIGN KEY(file_id) REFERENCES files(id) @@ -213,7 +214,6 @@ def update_datasets_table(self): query = ''' ALTER TABLE datasets ADD endpoint text NULL - DEFAULT(0) ''' try: From a7fbaf15f3aab25972dfb2e8de9bc9f361d6fb93 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 7 Jun 2022 16:29:27 +0000 Subject: [PATCH 086/113] tests --- askomics/api/admin.py | 6 +++--- askomics/libaskomics/Database.py | 14 +++++++++++--- askomics/libaskomics/SparqlQuery.py | 15 ++++++++------- tests/conftest.py | 2 +- tests/test_api_admin.py | 2 +- 5 files changed, 24 insertions(+), 15 deletions(-) diff --git a/askomics/api/admin.py b/askomics/api/admin.py index 97e9a2ef..631b8b1a 100644 --- a/askomics/api/admin.py +++ b/askomics/api/admin.py @@ -691,7 +691,7 @@ def add_ontology(): """ data = request.get_json() - if not data or not (data.get("name") and data.get("uri") and data.get("shortName") and data.get("type") and data.get("datasetId")): + if not data or not (data.get("name") and data.get("uri") and data.get("shortName") and data.get("type") and data.get("datasetId") and data.get("labelUri")): return jsonify({ 'ontologies': [], 'error': True, @@ -703,7 +703,7 @@ def add_ontology(): short_name = data.get("shortName") type = data.get("type") dataset_id = data.get("datasetId") - label_uri = data.get("labelUri", "rdfs:label") + label_uri = data.get("labelUri") om = OntologyManager(current_app, session) @@ -760,7 +760,7 @@ def add_ontology(): }), 400 try: - om.add_ontology(name, uri, short_name, dataset.id, dataset.graph_name, type, label_uri, dataset.endpoint) + om.add_ontology(name, uri, short_name, dataset.id, dataset.graph_name, dataset.endpoint, type, label_uri) ontologies = om.list_full_ontologies() except Exception as e: traceback.print_exc(file=sys.stdout) diff --git a/askomics/libaskomics/Database.py b/askomics/libaskomics/Database.py index b60a72e7..8a3c7e90 100644 --- a/askomics/libaskomics/Database.py +++ b/askomics/libaskomics/Database.py @@ -167,8 +167,6 @@ def create_datasets_table(self): end int, ntriples int, error_message text, - ontology boolean, - endpoint text, FOREIGN KEY(user_id) REFERENCES users(user_id), FOREIGN KEY(file_id) REFERENCES files(id) ) @@ -214,6 +212,7 @@ def update_datasets_table(self): query = ''' ALTER TABLE datasets ADD endpoint text NULL + DEFAULT(null) ''' try: @@ -440,7 +439,16 @@ def create_ontologies_table(self): ALTER TABLE ontologies ADD label_uri text NOT NULL DEFAULT('rdfs:label') - ADD endpoint text NOT NULL + ''' + + try: + self.execute_sql_query(query) + except Exception: + pass + + query = ''' + ALTER TABLE ontologies + ADD endpoint text NULL ''' try: diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index bd07e9e7..3bbcf1e1 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -35,8 +35,8 @@ def __init__(self, app, session, json_query=None, get_graphs=False): self.json = json_query self.sparql = None - self.graphs = set() - self.endpoints = set() + self.graphs = [] + self.endpoints = [] self.remote_graphs = defaultdict(set) self.selects = [] self.federated = False @@ -392,7 +392,7 @@ def get_federated_remote_from_graphs(self): from_string = "" for endpoint in self.endpoints: - remote_graphs = self.remote_graphs.get(endpoint) + remote_graphs = self.remote_graphs.get(endpoint, []) if len(remote_graphs) == 1: from_string += "\n@graph <{}> <{}>".format(endpoint, remote_graphs[0]) @@ -454,8 +454,8 @@ def set_graphs_and_endpoints(self, entities=None, graphs=None, endpoints=None, o query_launcher = SparqlQueryLauncher(self.app, self.session) header, results = query_launcher.process_query(self.prefix_query(query)) - self.graphs = set() - self.endpoints = set() + self.graphs = [] + self.endpoints = [] self.remote_graphs = defaultdict(set) for res in results: if not graphs or res["graph"] in graphs: @@ -464,7 +464,7 @@ def set_graphs_and_endpoints(self, entities=None, graphs=None, endpoints=None, o graph = ontologies[res['entity_uri']]['graph'] else: graph = res["graph"] - self.graphs.add(graph) + self.graphs.append(graph) # If local triplestore url is not accessible by federated query engine if res["endpoint"] == self.settings.get('triplestore', 'endpoint') and self.local_endpoint_f is not None: @@ -473,10 +473,11 @@ def set_graphs_and_endpoints(self, entities=None, graphs=None, endpoints=None, o endpoint = res["endpoint"] if not endpoints or endpoint in endpoints: - self.endpoints.add(endpoint) + self.endpoints.append(endpoint) if res.get("remote_graph"): self.remote_graphs[endpoint] = res.get("remote_graph") + self.endpoints = Utils.unique(self.endpoints) self.federated = len(self.endpoints) > 1 def get_uri_parameters(self, uri, endpoints): diff --git a/tests/conftest.py b/tests/conftest.py index 18aabdc0..84d08eed 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -481,7 +481,7 @@ def create_result(self, has_form=False): # Save job in database database result = Result(self.app, self.session, info) - result.save_in_db("http://virtuoso:8890/sparql") + result.save_in_db() # Execute query and write result to file headers, results = query_launcher.process_query(query.sparql) diff --git a/tests/test_api_admin.py b/tests/test_api_admin.py index 56b7d103..d3b74f6d 100644 --- a/tests/test_api_admin.py +++ b/tests/test_api_admin.py @@ -633,7 +633,7 @@ def test_add_ontology(self, client): client.create_two_users() client.log_user("jsmith") - data = {"shortName": "OBO", "uri": "http://purl.obolibrary.org/obo/agro.owl", "name": "Open Biological and Biomedical Ontology", "type": "local", "datasetId": 1} + data = {"shortName": "OBO", "uri": "http://purl.obolibrary.org/obo/agro.owl", "name": "Open Biological and Biomedical Ontology", "type": "local", "datasetId": 1, "labelUri": "rdfs:label"} response = client.client.post('/api/admin/addontology', json=data) assert response.status_code == 401 From 5b734d33b705ab3cf0f341e46907f5598f62084c Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 10 Jun 2022 15:44:05 +0000 Subject: [PATCH 087/113] custom external graph --- askomics/api/file.py | 1 + askomics/libaskomics/BedFile.py | 4 +-- askomics/libaskomics/CsvFile.py | 4 +-- askomics/libaskomics/File.py | 6 +++- askomics/libaskomics/FilesHandler.py | 11 +++---- askomics/libaskomics/GffFile.py | 4 +-- askomics/libaskomics/RdfFile.py | 29 +++++++++++++------ askomics/libaskomics/RdfGraph.py | 1 + .../src/routes/integration/rdfpreview.jsx | 14 ++++++++- askomics/tasks.py | 2 +- 10 files changed, 53 insertions(+), 23 deletions(-) diff --git a/askomics/api/file.py b/askomics/api/file.py index 95e1d84f..7348323f 100644 --- a/askomics/api/file.py +++ b/askomics/api/file.py @@ -333,6 +333,7 @@ def integrate(): for file in files_handler.files: data["externalEndpoint"] = data["externalEndpoint"] if (data.get("externalEndpoint") and isinstance(file, RdfFile)) else None + data["externalGraph"] = data["externalGraph"] if (data.get("externalGraph") and isinstance(file, RdfFile)) else None data["customUri"] = data["customUri"] if (data.get("customUri") and not isinstance(file, RdfFile)) else None dataset_info = { diff --git a/askomics/libaskomics/BedFile.py b/askomics/libaskomics/BedFile.py index 24ebf6da..98802ada 100644 --- a/askomics/libaskomics/BedFile.py +++ b/askomics/libaskomics/BedFile.py @@ -17,7 +17,7 @@ class BedFile(File): Public or private dataset """ - def __init__(self, app, session, file_info, host_url=None, external_endpoint=None, custom_uri=None): + def __init__(self, app, session, file_info, host_url=None, external_endpoint=None, custom_uri=None, external_graph=None): """init Parameters @@ -31,7 +31,7 @@ def __init__(self, app, session, file_info, host_url=None, external_endpoint=Non host_url : None, optional AskOmics url """ - File.__init__(self, app, session, file_info, host_url, external_endpoint=external_endpoint, custom_uri=custom_uri) + File.__init__(self, app, session, file_info, host_url, external_endpoint=external_endpoint, custom_uri=custom_uri, external_graph=external_graph) self.entity_name = '' self.category_values = {} diff --git a/askomics/libaskomics/CsvFile.py b/askomics/libaskomics/CsvFile.py index 8c1bcbac..821f00dd 100644 --- a/askomics/libaskomics/CsvFile.py +++ b/askomics/libaskomics/CsvFile.py @@ -29,7 +29,7 @@ class CsvFile(File): Public """ - def __init__(self, app, session, file_info, host_url=None, external_endpoint=None, custom_uri=None): + def __init__(self, app, session, file_info, host_url=None, external_endpoint=None, custom_uri=None, external_graph=None): """init Parameters @@ -43,7 +43,7 @@ def __init__(self, app, session, file_info, host_url=None, external_endpoint=Non host_url : None, optional AskOmics url """ - File.__init__(self, app, session, file_info, host_url, external_endpoint=external_endpoint, custom_uri=custom_uri) + File.__init__(self, app, session, file_info, host_url, external_endpoint=external_endpoint, custom_uri=custom_uri, external_graph=external_graph) self.preview_limit = 30 try: self.preview_limit = self.settings.getint("askomics", "npreview") diff --git a/askomics/libaskomics/File.py b/askomics/libaskomics/File.py index b5cd4879..05389c3d 100644 --- a/askomics/libaskomics/File.py +++ b/askomics/libaskomics/File.py @@ -69,7 +69,7 @@ class File(Params): User graph """ - def __init__(self, app, session, file_info, host_url=None, external_endpoint=None, custom_uri=None): + def __init__(self, app, session, file_info, host_url=None, external_endpoint=None, custom_uri=None, external_graph=None): """init Parameters @@ -97,6 +97,7 @@ def __init__(self, app, session, file_info, host_url=None, external_endpoint=Non self.ntriples = 0 self.timestamp = int(time.time()) self.external_endpoint = external_endpoint + self.external_graph = external_graph self.default_graph = "{}".format(self.settings.get('triplestore', 'default_graph')) self.user_graph = "{}:{}_{}".format( @@ -130,6 +131,7 @@ def __init__(self, app, session, file_info, host_url=None, external_endpoint=Non self.faldo = Namespace('http://biohackathon.org/resource/faldo/') self.prov = Namespace('http://www.w3.org/ns/prov#') self.dc = Namespace('http://purl.org/dc/elements/1.1/') + self.dcat = Namespace('http://www.w3.org/ns/dcat#') self.faldo_entity = False self.faldo_abstraction = { @@ -277,6 +279,8 @@ def set_metadata(self): self.graph_metadata.add((rdflib.Literal(self.file_graph), self.prov.wasDerivedFrom, rdflib.Literal(self.name))) self.graph_metadata.add((rdflib.Literal(self.file_graph), self.dc.hasVersion, rdflib.Literal(get_distribution('askomics').version))) self.graph_metadata.add((rdflib.Literal(self.file_graph), self.prov.describesService, rdflib.Literal(os.uname()[1]))) + if self.external_graph: + self.graph_metadata.add((rdflib.Literal(self.file_graph), self.dcat.Dataset, self.external_graph)) if self.public: self.graph_metadata.add((rdflib.Literal(self.file_graph), self.namespace_internal['public'], rdflib.Literal(True))) diff --git a/askomics/libaskomics/FilesHandler.py b/askomics/libaskomics/FilesHandler.py index d3d1adfe..04254028 100644 --- a/askomics/libaskomics/FilesHandler.py +++ b/askomics/libaskomics/FilesHandler.py @@ -24,7 +24,7 @@ class FilesHandler(FilesUtils): Upload path """ - def __init__(self, app, session, host_url=None, external_endpoint=None, custom_uri=None): + def __init__(self, app, session, host_url=None, external_endpoint=None, custom_uri=None, external_graph=None): """init Parameters @@ -47,6 +47,7 @@ def __init__(self, app, session, host_url=None, external_endpoint=None, custom_u self.date = None self.external_endpoint = external_endpoint self.custom_uri = custom_uri + self.external_graph = external_graph def handle_files(self, files_id): """Handle file @@ -60,13 +61,13 @@ def handle_files(self, files_id): for file in files_infos: if file['type'] == 'csv/tsv': - self.files.append(CsvFile(self.app, self.session, file, host_url=self.host_url, external_endpoint=self.external_endpoint, custom_uri=self.custom_uri)) + self.files.append(CsvFile(self.app, self.session, file, host_url=self.host_url, external_endpoint=self.external_endpoint, custom_uri=self.custom_uri, external_graph=self.external_graph)) elif file['type'] == 'gff/gff3': - self.files.append(GffFile(self.app, self.session, file, host_url=self.host_url, external_endpoint=self.external_endpoint, custom_uri=self.custom_uri)) + self.files.append(GffFile(self.app, self.session, file, host_url=self.host_url, external_endpoint=self.external_endpoint, custom_uri=self.custom_uri, external_graph=self.external_graph)) elif file['type'] in ('rdf/ttl', 'rdf/xml', 'rdf/nt'): - self.files.append(RdfFile(self.app, self.session, file, host_url=self.host_url, external_endpoint=self.external_endpoint, custom_uri=self.custom_uri)) + self.files.append(RdfFile(self.app, self.session, file, host_url=self.host_url, external_endpoint=self.external_endpoint, custom_uri=self.custom_uri, external_graph=self.external_graph)) elif file['type'] == 'bed': - self.files.append(BedFile(self.app, self.session, file, host_url=self.host_url, external_endpoint=self.external_endpoint, custom_uri=self.custom_uri)) + self.files.append(BedFile(self.app, self.session, file, host_url=self.host_url, external_endpoint=self.external_endpoint, custom_uri=self.custom_uri, external_graph=self.external_graph)) def get_files_infos(self, files_id=None, files_path=None, return_path=False): """Get files info diff --git a/askomics/libaskomics/GffFile.py b/askomics/libaskomics/GffFile.py index 28341bab..7c413de7 100644 --- a/askomics/libaskomics/GffFile.py +++ b/askomics/libaskomics/GffFile.py @@ -18,7 +18,7 @@ class GffFile(File): Public or private dataset """ - def __init__(self, app, session, file_info, host_url=None, external_endpoint=None, custom_uri=None): + def __init__(self, app, session, file_info, host_url=None, external_endpoint=None, custom_uri=None, external_graph=None): """init Parameters @@ -32,7 +32,7 @@ def __init__(self, app, session, file_info, host_url=None, external_endpoint=Non host_url : None, optional AskOmics url """ - File.__init__(self, app, session, file_info, host_url, external_endpoint=external_endpoint, custom_uri=custom_uri) + File.__init__(self, app, session, file_info, host_url, external_endpoint=external_endpoint, custom_uri=custom_uri, external_graph=external_graph) self.entities = [] self.entities_to_integrate = [] diff --git a/askomics/libaskomics/RdfFile.py b/askomics/libaskomics/RdfFile.py index b1252d01..008bbd3b 100644 --- a/askomics/libaskomics/RdfFile.py +++ b/askomics/libaskomics/RdfFile.py @@ -14,7 +14,7 @@ class RdfFile(File): Public or private dataset """ - def __init__(self, app, session, file_info, host_url=None, external_endpoint=None, custom_uri=None): + def __init__(self, app, session, file_info, host_url=None, external_endpoint=None, custom_uri=None, external_graph=None): """init Parameters @@ -28,7 +28,7 @@ def __init__(self, app, session, file_info, host_url=None, external_endpoint=Non host_url : None, optional AskOmics url """ - File.__init__(self, app, session, file_info, host_url, external_endpoint=external_endpoint, custom_uri=custom_uri) + File.__init__(self, app, session, file_info, host_url, external_endpoint=external_endpoint, custom_uri=custom_uri, external_graph=external_graph) self.type_dict = { "rdf/ttl": "turtle", @@ -40,7 +40,7 @@ def set_preview(self): """Summary""" pass - def get_location(self): + def get_location_and_remote_graph(self): """Get location of data if specified Returns @@ -50,10 +50,19 @@ def get_location(self): """ graph = RdfGraph(self.app, self.session) graph.parse(self.path, format=self.type_dict[self.type]) - triple = (None, self.prov.atLocation, None) - for s, p, o in graph.graph.triples(triple): - return str(o) - return None + triple_loc = (None, self.prov.atLocation, None) + triple_graph = (None, self.dcat.Dataset, None) + loc = None + remote_graph = None + for s, p, o in graph.graph.triples(triple_loc): + loc = str(o) + break + + for s, p, o in graph.graph.triples(triple_graph): + remote_graph = str(o) + break + + return loc, remote_graph def get_preview(self): """Get a preview of the frist 100 lines of a ttl file @@ -71,7 +80,7 @@ def get_preview(self): location = None try: - location = self.get_location() + location, remote_graph = self.get_location_and_remote_graph() except Exception as e: self.error_message = str(e) @@ -83,13 +92,15 @@ def get_preview(self): 'error_message': self.error_message, 'data': { 'preview': head, - 'location': location + 'location': location, + 'remote_graph': remote_graph } } def delete_metadata_location(self): """Delete metadata from data""" self.graph_chunk.remove((None, self.prov.atLocation, None)) + self.graph_chunk.remove((None, self.dcat.Dataset, None)) def integrate(self, public=False): """Integrate the file into the triplestore diff --git a/askomics/libaskomics/RdfGraph.py b/askomics/libaskomics/RdfGraph.py index 0bd40e0b..0024a3ed 100644 --- a/askomics/libaskomics/RdfGraph.py +++ b/askomics/libaskomics/RdfGraph.py @@ -40,6 +40,7 @@ def __init__(self, app, session): self.graph.bind('faldo', "http://biohackathon.org/resource/faldo/") self.graph.bind('dc', 'http://purl.org/dc/elements/1.1/') self.graph.bind('prov', 'http://www.w3.org/ns/prov#') + self.graph.bind('dcat', 'http://www.w3.org/ns/dcat#') self.ntriple = 0 self.percent = None diff --git a/askomics/react/src/routes/integration/rdfpreview.jsx b/askomics/react/src/routes/integration/rdfpreview.jsx index 5dbbd4b9..c21fc4be 100644 --- a/askomics/react/src/routes/integration/rdfpreview.jsx +++ b/askomics/react/src/routes/integration/rdfpreview.jsx @@ -24,6 +24,7 @@ export default class RdfPreview extends Component { privateTick: false, customUri: "", externalEndpoint: props.file.data.location ? props.file.data.location : "", + externalGraph: props.file.data.remote_graph ? props.file.data.remote_graph : "", error: false, errorMessage: null, status: null @@ -49,7 +50,8 @@ export default class RdfPreview extends Component { public: event.target.value == 'public', type: this.props.file.type, customUri: this.state.customUri, - externalEndpoint: this.state.externalEndpoint + externalEndpoint: this.state.externalEndpoint, + remoteGraph: this.state.remoteGraph } axios.post(requestUrl, data, { baseURL: this.props.config.proxyPath, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) .then(response => { @@ -85,6 +87,14 @@ export default class RdfPreview extends Component { }) } + handleChangeRemoteGraph (event) { + this.setState({ + remoteGraph: event.target.value, + publicTick: false, + privateTick: false + }) + } + guess_mode(type) { if (type == "rdf/ttl") { return "turtle" @@ -142,6 +152,8 @@ export default class RdfPreview extends Component { config={this.props.config} handleChangeEndpoint={p => this.handleChangeEndpoint(p)} externalEndpoint={this.state.externalEndpoint} + handleChangeExternalGraph={p => this.handleChangeExternalGraph(p)} + externalGraph={this.state.externalGraph} handleChangeUri={p => this.handleChangeUri(p)} />
diff --git a/askomics/tasks.py b/askomics/tasks.py index 285cd6a8..ee048d3e 100644 --- a/askomics/tasks.py +++ b/askomics/tasks.py @@ -44,7 +44,7 @@ def integrate(self, session, data, host_url): error: True if error, else False errorMessage: the error message of error, else an empty string """ - files_handler = FilesHandler(app, session, host_url=host_url, external_endpoint=data["externalEndpoint"], custom_uri=data["customUri"]) + files_handler = FilesHandler(app, session, host_url=host_url, external_endpoint=data["externalEndpoint"], custom_uri=data["customUri"], external_graph=data['externalGraph']) files_handler.handle_files([data["fileId"], ]) public = (data.get("public", False) if session["user"]["admin"] else False) or app.iniconfig.getboolean("askomics", "single_tenant", fallback=False) From 755b6becd08e9fa86dc3ebfdb5174ce4640b26cb Mon Sep 17 00:00:00 2001 From: mboudet Date: Mon, 13 Jun 2022 10:23:27 +0200 Subject: [PATCH 088/113] React --- .../routes/integration/advancedoptions.jsx | 11 ++++++++-- .../src/routes/integration/bedpreview.jsx | 5 ++++- .../react/src/routes/integration/csvtable.jsx | 13 +++++++++++- .../src/routes/integration/gffpreview.jsx | 21 ++++++++++++++++++- 4 files changed, 45 insertions(+), 5 deletions(-) diff --git a/askomics/react/src/routes/integration/advancedoptions.jsx b/askomics/react/src/routes/integration/advancedoptions.jsx index 4a6c8988..6c9ae84c 100644 --- a/askomics/react/src/routes/integration/advancedoptions.jsx +++ b/askomics/react/src/routes/integration/advancedoptions.jsx @@ -41,6 +41,12 @@ export default class AdvancedOptions extends Component {
+
@@ -55,5 +61,6 @@ AdvancedOptions.propTypes = { hideCustomUri: PropTypes.bool, customUri: PropTypes.string, hideDistantEndpoint: PropTypes.bool, - externalEndpoint: PropTypes.string -} + externalEndpoint: PropTypes.string, + handleChangeExternalGraph: PropTypes.function, + externalGraph: PropTypes.string diff --git a/askomics/react/src/routes/integration/bedpreview.jsx b/askomics/react/src/routes/integration/bedpreview.jsx index 90e72d19..33755d69 100644 --- a/askomics/react/src/routes/integration/bedpreview.jsx +++ b/askomics/react/src/routes/integration/bedpreview.jsx @@ -21,7 +21,8 @@ export default class BedPreview extends Component { externalEndpoint: "", error: false, errorMessage: null, - status: null + status: null, + externalGraph: "" } this.cancelRequest this.integrate = this.integrate.bind(this) @@ -122,6 +123,8 @@ export default class BedPreview extends Component { hideDistantEndpoint={true} handleChangeUri={p => this.handleChangeUri(p)} handleChangeEndpoint={p => this.handleChangeEndpoint(p)} + handleChangeExternalGraph={p => this.handleChangeExternalGraph(p)} + externalGraph={this.state.externalGraph} customUri={this.state.customUri} />
diff --git a/askomics/react/src/routes/integration/csvtable.jsx b/askomics/react/src/routes/integration/csvtable.jsx index c9a003a9..01837aa8 100644 --- a/askomics/react/src/routes/integration/csvtable.jsx +++ b/askomics/react/src/routes/integration/csvtable.jsx @@ -26,7 +26,8 @@ export default class CsvTable extends Component { externalEndpoint: "", error: false, errorMessage: null, - status: null + status: null, + externalGraph: "" } this.cancelRequest this.headerFormatter = this.headerFormatter.bind(this) @@ -213,6 +214,14 @@ export default class CsvTable extends Component { }) } + handleChangeRemoteGraph (event) { + this.setState({ + remoteGraph: event.target.value, + publicTick: false, + privateTick: false + }) + } + toggleHeaderForm(event) { this.setState({ header: update(this.state.header, { [event.target.id]: { input: { $set: true } } }) @@ -288,6 +297,8 @@ export default class CsvTable extends Component { hideDistantEndpoint={true} handleChangeUri={p => this.handleChangeUri(p)} handleChangeEndpoint={p => this.handleChangeEndpoint(p)} + handleChangeExternalGraph={p => this.handleChangeExternalGraph(p)} + externalGraph={this.state.externalGraph} customUri={this.state.customUri} />
diff --git a/askomics/react/src/routes/integration/gffpreview.jsx b/askomics/react/src/routes/integration/gffpreview.jsx index 0fafbbef..2210ee86 100644 --- a/askomics/react/src/routes/integration/gffpreview.jsx +++ b/askomics/react/src/routes/integration/gffpreview.jsx @@ -18,7 +18,8 @@ export default class GffPreview extends Component { publicTick: false, privateTick: false, customUri: "", - externalEndpoint: "" + externalEndpoint: "", + externalGraph: "" } this.cancelRequest this.integrate = this.integrate.bind(this) @@ -90,6 +91,22 @@ export default class GffPreview extends Component { }) } + handleChangeRemoteGraph (event) { + this.setState({ + remoteGraph: event.target.value, + publicTick: false, + privateTick: false + }) + } + + handleChangeRemoteGraph (event) { + this.setState({ + remoteGraph: event.target.value, + publicTick: false, + privateTick: false + }) + } + render () { let privateIcon = @@ -129,6 +146,8 @@ export default class GffPreview extends Component { hideDistantEndpoint={true} handleChangeUri={p => this.handleChangeUri(p)} handleChangeEndpoint={p => this.handleChangeEndpoint(p)} + handleChangeExternalGraph={p => this.handleChangeExternalGraph(p)} + externalGraph={this.state.externalGraph} customUri={this.state.customUri} />
From d4b6cc9b5d2b782c34b3285ce25e59d46984dd92 Mon Sep 17 00:00:00 2001 From: mboudet Date: Mon, 13 Jun 2022 08:21:19 +0000 Subject: [PATCH 089/113] typo --- askomics/react/src/routes/integration/advancedoptions.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/askomics/react/src/routes/integration/advancedoptions.jsx b/askomics/react/src/routes/integration/advancedoptions.jsx index 6c9ae84c..d54375bf 100644 --- a/askomics/react/src/routes/integration/advancedoptions.jsx +++ b/askomics/react/src/routes/integration/advancedoptions.jsx @@ -64,3 +64,4 @@ AdvancedOptions.propTypes = { externalEndpoint: PropTypes.string, handleChangeExternalGraph: PropTypes.function, externalGraph: PropTypes.string +} From 412d91ee91e6ef15d0e99610fd8b5a20b510b6ba Mon Sep 17 00:00:00 2001 From: mboudet Date: Mon, 13 Jun 2022 09:39:35 +0000 Subject: [PATCH 090/113] fix --- askomics/libaskomics/File.py | 2 +- askomics/libaskomics/SparqlQuery.py | 6 +++--- askomics/react/src/routes/integration/bedpreview.jsx | 8 ++++++++ askomics/react/src/routes/integration/csvtable.jsx | 4 ++-- askomics/react/src/routes/integration/gffpreview.jsx | 4 ++-- askomics/react/src/routes/integration/rdfpreview.jsx | 6 +++--- 6 files changed, 19 insertions(+), 11 deletions(-) diff --git a/askomics/libaskomics/File.py b/askomics/libaskomics/File.py index 05389c3d..030f30e3 100644 --- a/askomics/libaskomics/File.py +++ b/askomics/libaskomics/File.py @@ -280,7 +280,7 @@ def set_metadata(self): self.graph_metadata.add((rdflib.Literal(self.file_graph), self.dc.hasVersion, rdflib.Literal(get_distribution('askomics').version))) self.graph_metadata.add((rdflib.Literal(self.file_graph), self.prov.describesService, rdflib.Literal(os.uname()[1]))) if self.external_graph: - self.graph_metadata.add((rdflib.Literal(self.file_graph), self.dcat.Dataset, self.external_graph)) + self.graph_metadata.add((rdflib.Literal(self.file_graph), self.dcat.Dataset, rdflib.Literal(self.external_graph))) if self.public: self.graph_metadata.add((rdflib.Literal(self.file_graph), self.namespace_internal['public'], rdflib.Literal(True))) diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index 3bbcf1e1..69945cfb 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -37,7 +37,7 @@ def __init__(self, app, session, json_query=None, get_graphs=False): self.graphs = [] self.endpoints = [] - self.remote_graphs = defaultdict(set) + self.remote_graphs = defaultdict(list) self.selects = [] self.federated = False @@ -456,7 +456,7 @@ def set_graphs_and_endpoints(self, entities=None, graphs=None, endpoints=None, o header, results = query_launcher.process_query(self.prefix_query(query)) self.graphs = [] self.endpoints = [] - self.remote_graphs = defaultdict(set) + self.remote_graphs = defaultdict(list) for res in results: if not graphs or res["graph"] in graphs: # Override with onto graph if matching uri @@ -475,7 +475,7 @@ def set_graphs_and_endpoints(self, entities=None, graphs=None, endpoints=None, o if not endpoints or endpoint in endpoints: self.endpoints.append(endpoint) if res.get("remote_graph"): - self.remote_graphs[endpoint] = res.get("remote_graph") + self.remote_graphs[endpoint].append(res.get("remote_graph")) self.endpoints = Utils.unique(self.endpoints) self.federated = len(self.endpoints) > 1 diff --git a/askomics/react/src/routes/integration/bedpreview.jsx b/askomics/react/src/routes/integration/bedpreview.jsx index 33755d69..ad3f7269 100644 --- a/askomics/react/src/routes/integration/bedpreview.jsx +++ b/askomics/react/src/routes/integration/bedpreview.jsx @@ -82,6 +82,14 @@ export default class BedPreview extends Component { }) } + handleChangeExternalGraph (event) { + this.setState({ + externalGraph: event.target.value, + publicTick: false, + privateTick: false + }) + } + render () { let privateIcon = diff --git a/askomics/react/src/routes/integration/csvtable.jsx b/askomics/react/src/routes/integration/csvtable.jsx index 01837aa8..dddd55ba 100644 --- a/askomics/react/src/routes/integration/csvtable.jsx +++ b/askomics/react/src/routes/integration/csvtable.jsx @@ -214,9 +214,9 @@ export default class CsvTable extends Component { }) } - handleChangeRemoteGraph (event) { + handleChangeExternalGraph (event) { this.setState({ - remoteGraph: event.target.value, + externalGraph: event.target.value, publicTick: false, privateTick: false }) diff --git a/askomics/react/src/routes/integration/gffpreview.jsx b/askomics/react/src/routes/integration/gffpreview.jsx index 2210ee86..ecff25c9 100644 --- a/askomics/react/src/routes/integration/gffpreview.jsx +++ b/askomics/react/src/routes/integration/gffpreview.jsx @@ -99,9 +99,9 @@ export default class GffPreview extends Component { }) } - handleChangeRemoteGraph (event) { + handleChangeExternalGraph (event) { this.setState({ - remoteGraph: event.target.value, + externalGraph: event.target.value, publicTick: false, privateTick: false }) diff --git a/askomics/react/src/routes/integration/rdfpreview.jsx b/askomics/react/src/routes/integration/rdfpreview.jsx index c21fc4be..654747b4 100644 --- a/askomics/react/src/routes/integration/rdfpreview.jsx +++ b/askomics/react/src/routes/integration/rdfpreview.jsx @@ -51,7 +51,7 @@ export default class RdfPreview extends Component { type: this.props.file.type, customUri: this.state.customUri, externalEndpoint: this.state.externalEndpoint, - remoteGraph: this.state.remoteGraph + externalGraph: this.state.externalGraph } axios.post(requestUrl, data, { baseURL: this.props.config.proxyPath, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) .then(response => { @@ -87,9 +87,9 @@ export default class RdfPreview extends Component { }) } - handleChangeRemoteGraph (event) { + handleChangeExternalGraph (event) { this.setState({ - remoteGraph: event.target.value, + externalGraph: event.target.value, publicTick: false, privateTick: false }) From f2dc04c51e0c1d5b1beabcfc7e8be48ed6e1210e Mon Sep 17 00:00:00 2001 From: mboudet Date: Wed, 22 Jun 2022 16:49:32 +0200 Subject: [PATCH 091/113] Tests & stuff --- askomics/libaskomics/OntologyManager.py | 10 ++-- test-data/agro_min.ttl | 70 +++++++++++++++++++++++++ tests/conftest.py | 39 ++++++++++++-- tests/test_api_admin.py | 31 +++++------ tests/test_api_ontology.py | 21 ++++++++ 5 files changed, 148 insertions(+), 23 deletions(-) create mode 100644 test-data/agro_min.ttl create mode 100644 tests/test_api_ontology.py diff --git a/askomics/libaskomics/OntologyManager.py b/askomics/libaskomics/OntologyManager.py index 7a8a1e3f..d180d13a 100644 --- a/askomics/libaskomics/OntologyManager.py +++ b/askomics/libaskomics/OntologyManager.py @@ -76,7 +76,7 @@ def list_full_ontologies(self): database = Database(self.app, self.session) query = ''' - SELECT ontologies.id, ontologies.name, ontologies.uri, ontologies.short_name, ontologies.type, ontologies.label_uri, datasets.id, datasets.name, ontologies.graph + SELECT ontologies.id, ontologies.name, ontologies.uri, ontologies.short_name, ontologies.type, ontologies.label_uri, datasets.id, datasets.name, ontologies.graph, ontologies.endpoint FROM ontologies INNER JOIN datasets ON datasets.id=ontologies.dataset_id ''' @@ -94,7 +94,8 @@ def list_full_ontologies(self): 'label_uri': row[5], 'dataset_id': row[6], 'dataset_name': row[7], - 'graph': row[8] + 'graph': row[8], + 'endpoint': row[9] } ontologies.append(prefix) @@ -123,7 +124,7 @@ def get_ontology(self, short_name="", uri=""): database = Database(self.app, self.session) query = ''' - SELECT id, name, uri, short_name, type, dataset_id, graph, label_uri + SELECT id, name, uri, short_name, type, dataset_id, graph, label_uri, endpoint FROM ontologies {} '''.format(where_clause) @@ -142,7 +143,8 @@ def get_ontology(self, short_name="", uri=""): 'type': ontology[4], 'dataset_id': ontology[5], 'graph': ontology[6], - 'label_uri': ontology[7] + 'label_uri': ontology[7], + 'endpoint': ontology[8] } def add_ontology(self, name, uri, short_name, dataset_id, graph, endpoint, type="local", label_uri="rdfs:label"): diff --git a/test-data/agro_min.ttl b/test-data/agro_min.ttl new file mode 100644 index 00000000..4f895879 --- /dev/null +++ b/test-data/agro_min.ttl @@ -0,0 +1,70 @@ +@prefix askomics: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix xml: . +@prefix xsd: . +@prefix ns1: . + + + a askomics:ontology ; + a owl:ontology ; + rdfs:label "AGRO". + +[] a owl:ObjectProperty ; + a askomics:AskomicsRelation ; + askomics:uri rdfs:subClassOf ; + rdfs:label "subClassOf" ; + rdfs:domain ; + rdfs:range . + + + a owl:Class ; + rdfs:label "desuckering" ; + rdfs:subClassOf . + + a owl:Class ; + rdfs:label "irrigation water source role" ; + rdfs:subClassOf . + + a owl:Class ; + rdfs:label "irrigation water quantity" ; + rdfs:subClassOf , + . + + a owl:Class ; + rdfs:label "reduced tillage process" ; + rdfs:subClassOf . + + a owl:Class ; + rdfs:label "laser land levelling process" ; + rdfs:subClassOf . + + a owl:Class ; + rdfs:label "chemical pest control process" ; + rdfs:subClassOf . + + a owl:Class ; + rdfs:label "no-till" ; + rdfs:subClassOf , + . + + a owl:Class ; + rdfs:label "puddling process" ; + rdfs:subClassOf . + + a owl:Class ; + rdfs:label "mulch-till" ; + rdfs:subClassOf . + + a owl:Class ; + rdfs:label "ridge-till" ; + rdfs:subClassOf . + + a owl:Class ; + rdfs:label "strip-till" ; + rdfs:subClassOf . + + a owl:Class ; + rdfs:label "aerial application" ; + rdfs:subClassOf . diff --git a/tests/conftest.py b/tests/conftest.py index 84d08eed..287c57c0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -328,7 +328,8 @@ def integrate_file(self, info, public=False, set_graph=False): file.integrate(dataset.id, info["entities"], public=public) elif file.type == "bed": file.integrate(dataset.id, info["entity_name"], public=public) - + elif file.type in ('rdf/ttl', 'rdf/xml', 'rdf/nt'): + file.integrate(public=public) # done dataset.update_in_db("success") dataset.set_info_from_db() @@ -336,7 +337,9 @@ def integrate_file(self, info, public=False, set_graph=False): return { "timestamp": file.timestamp, "start": dataset.start, - "end": dataset.end + "end": dataset.end, + "graph": dataset.graph_name, + "endpoint": dataset.endpoint } def upload(self): @@ -445,6 +448,33 @@ def upload_and_integrate(self, set_graph=False): } } + def upload_and_integrate_ontology(self): + """Summary + + Returns + ------- + TYPE + Description + """ + # upload + up_ontology = self.upload_file("test-data/agro_min.ttl") + + # integrate + int_ontology = self.integrate_file({ + "id": 1, + }) + + return { + "ontology": { + "upload": up_ontology, + "timestamp": int_ontology["timestamp"], + "start": int_ontology["start"], + "end": int_ontology["end"], + "graph": int_ontology["graph"], + "endpoint": int_ontology["endpoint"] + }, + } + def create_result(self, has_form=False): """Create a result entry in db @@ -590,9 +620,10 @@ def create_prefix(self): def create_ontology(self): """Create ontology""" - self.upload_and_integrate() + data = self.upload_and_integrate_ontology() om = OntologyManager(self.app, self.session) - om.add_ontology("Open Biological and Biomedical Ontology", "http://purl.obolibrary.org/obo/agro.owl", "OBO", 1, "mygraph", "local") + om.add_ontology("AgrO ontology", "http://purl.obolibrary.org/obo/agro.owl", "AGRO", 1, data["graph"], "local") + return data["graph"], data["endpoint"] @staticmethod def get_random_string(number): diff --git a/tests/test_api_admin.py b/tests/test_api_admin.py index d3b74f6d..0d178197 100644 --- a/tests/test_api_admin.py +++ b/tests/test_api_admin.py @@ -605,7 +605,7 @@ def test_view_ontologies(self, client): assert response.status_code == 200 assert response.json == expected_empty - client.create_ontology() + graph, endpoint = client.create_ontology() response = client.client.get('/api/admin/getontologies') @@ -614,13 +614,14 @@ def test_view_ontologies(self, client): "errorMessage": "", "ontologies": [{ "id": 1, - "name": "Open Biological and Biomedical Ontology", + "name": "AgrO ontology", "uri": "http://purl.obolibrary.org/obo/agro.owl", - "short_name": "OBO", + "short_name": "AGRO", "type": "local", "dataset_id": 1, - "dataset_name": "transcripts.tsv", - "graph": "mygraph", + "dataset_name": "agro_min.ttl", + "graph": graph, + "endpoint": endpoint, "label_uri": "rdfs:label" }] } @@ -633,13 +634,15 @@ def test_add_ontology(self, client): client.create_two_users() client.log_user("jsmith") - data = {"shortName": "OBO", "uri": "http://purl.obolibrary.org/obo/agro.owl", "name": "Open Biological and Biomedical Ontology", "type": "local", "datasetId": 1, "labelUri": "rdfs:label"} + data = {"shortName": "AGRO", "uri": "http://purl.obolibrary.org/obo/agro.owl", "name": "AgrO ontology", "type": "local", "datasetId": 1, "labelUri": "rdfs:label"} response = client.client.post('/api/admin/addontology', json=data) assert response.status_code == 401 client.log_user("jdoe") - client.upload_and_integrate(set_graph=True) + data = client.upload_and_integrate_ontology() + graph = data["graph"] + endpoint = data["endpoint"] response = client.client.post('/api/admin/addontology', json=data) @@ -655,22 +658,20 @@ def test_add_ontology(self, client): "errorMessage": "", "ontologies": [{ "id": 1, - "name": "Open Biological and Biomedical Ontology", + "name": "AgrO ontology", "uri": "http://purl.obolibrary.org/obo/agro.owl", "short_name": "OBO", "type": "local", "dataset_id": 1, - "dataset_name": "transcripts.tsv", - "label_uri": "rdfs:label" + "dataset_name": "agro_min.ttl", + "label_uri": "rdfs:label", + "graph": graph, + "endpoint": endpoint }] } - # Graph name is random - res = response.json - res['ontologies'][0].pop('graph') - assert response.status_code == 200 - assert res == expected + assert response.json == expected def test_delete_ontologies(self, client): """test /api/admin/delete_ontologies route""" diff --git a/tests/test_api_ontology.py b/tests/test_api_ontology.py new file mode 100644 index 00000000..76418512 --- /dev/null +++ b/tests/test_api_ontology.py @@ -0,0 +1,21 @@ +import json + +from . import AskomicsTestCase + + +class TestApiOntology(AskomicsTestCase): + """Test AskOmics API /api/ontology/""" + + def test_local_autocompletion(self, client): + """test /api/ontology/AGRO/autocomplete route""" + client.create_two_users() + client.log_user("jdoe") + + client.create_ontology() + + query = "" + response = client.client.get('/api/ontology/AGRO/autocomplete?q={}'.format(query)) + + print(response.json) + assert response.status_code == 200 + assert len(response.json["results"]) == 10 From 326ac8a4bee1b00d2d3e64c16a8d5b8681ed399e Mon Sep 17 00:00:00 2001 From: mboudet Date: Wed, 22 Jun 2022 14:40:51 +0000 Subject: [PATCH 092/113] Fix autocomplete --- askomics/api/ontology.py | 2 +- askomics/libaskomics/SparqlQuery.py | 4 ++-- tests/test_api_ontology.py | 33 ++++++++++++++++++++++++++++- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/askomics/api/ontology.py b/askomics/api/ontology.py index fb9ce736..d8ce1c8d 100644 --- a/askomics/api/ontology.py +++ b/askomics/api/ontology.py @@ -48,7 +48,7 @@ def autocomplete(short_ontology): }), 404 return jsonify({ - "error": True, + "error": False, "errorMessage": "", "results": results }), 200 diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index 69945cfb..a1c002cd 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -590,11 +590,11 @@ def autocomplete_local_ontology(self, uri, query, max_terms): raw_query = ''' SELECT DISTINCT ?label WHERE {{ - ?uri rdf:type <{}> . + ?uri rdf:type owl:Class . ?uri rdfs:label ?label . {} }} - '''.format(uri, subquery) + '''.format(subquery) raw_query = self.prefix_query(raw_query) diff --git a/tests/test_api_ontology.py b/tests/test_api_ontology.py index 76418512..c23edd42 100644 --- a/tests/test_api_ontology.py +++ b/tests/test_api_ontology.py @@ -13,9 +13,40 @@ def test_local_autocompletion(self, client): client.create_ontology() + query = "blabla" + response = client.client.get('/api/ontology/AGRO/autocomplete?q={}'.format(query)) + + assert response.status_code == 200 + assert len(response.json["results"]) == 0 + assert response.json["results"] == [] + query = "" response = client.client.get('/api/ontology/AGRO/autocomplete?q={}'.format(query)) - print(response.json) + expected = [ + "desuckering", + "irrigation water source role", + "irrigation water quantity", + "reduced tillage process", + "laser land levelling process", + "chemical pest control process", + "no-till","puddling process", + "mulch-till", + "ridge-till" + ] + assert response.status_code == 200 assert len(response.json["results"]) == 10 + assert response.json["results"] == expected + + query = "irrigation" + response = client.client.get('/api/ontology/AGRO/autocomplete?q={}'.format(query)) + + expected = [ + "irrigation water source role", + "irrigation water quantity" + ] + + assert response.status_code == 200 + assert len(response.json["results"]) == 2 + assert response.json["results"] == expected From 90ddd10561072c449a3afd7cb97bb2e3d4336b95 Mon Sep 17 00:00:00 2001 From: mboudet Date: Wed, 22 Jun 2022 15:17:18 +0000 Subject: [PATCH 093/113] lint --- tests/conftest.py | 3 ++- tests/test_api_ontology.py | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 287c57c0..0e1ac172 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -251,7 +251,8 @@ def upload_file(self, file_path): ".tsv": "text/tab-separated-values", ".csv": "text/tab-separated-values", ".gff3": "null", - ".bed": "null" + ".bed": "null", + ".ttl": "rdf/ttl" } with open(file_path, 'r') as file_content: diff --git a/tests/test_api_ontology.py b/tests/test_api_ontology.py index c23edd42..f5cfe248 100644 --- a/tests/test_api_ontology.py +++ b/tests/test_api_ontology.py @@ -1,5 +1,3 @@ -import json - from . import AskomicsTestCase @@ -30,7 +28,8 @@ def test_local_autocompletion(self, client): "reduced tillage process", "laser land levelling process", "chemical pest control process", - "no-till","puddling process", + "no-till", + "puddling process", "mulch-till", "ridge-till" ] From 0487ab5c072d25292e933e4cb58940765cc9f7c5 Mon Sep 17 00:00:00 2001 From: mboudet Date: Wed, 22 Jun 2022 20:48:38 +0200 Subject: [PATCH 094/113] typo --- tests/conftest.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 0e1ac172..486b1ccf 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -466,14 +466,12 @@ def upload_and_integrate_ontology(self): }) return { - "ontology": { - "upload": up_ontology, - "timestamp": int_ontology["timestamp"], - "start": int_ontology["start"], - "end": int_ontology["end"], - "graph": int_ontology["graph"], - "endpoint": int_ontology["endpoint"] - }, + "upload": up_ontology, + "timestamp": int_ontology["timestamp"], + "start": int_ontology["start"], + "end": int_ontology["end"], + "graph": int_ontology["graph"], + "endpoint": int_ontology["endpoint"] } def create_result(self, has_form=False): From a49bc15841bef4deda70160bc4dfac6a8914ea85 Mon Sep 17 00:00:00 2001 From: mboudet Date: Wed, 22 Jun 2022 21:12:39 +0200 Subject: [PATCH 095/113] graph save --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 486b1ccf..5895b7d3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -463,7 +463,7 @@ def upload_and_integrate_ontology(self): # integrate int_ontology = self.integrate_file({ "id": 1, - }) + }, set_graph=True) return { "upload": up_ontology, From 9af113e71903942944e8683224e6c3b4b7176f51 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 23 Jun 2022 11:59:58 +0000 Subject: [PATCH 096/113] Fix --- askomics/api/ontology.py | 2 +- tests/conftest.py | 11 +++++++---- tests/test_api_admin.py | 8 ++++---- tests/test_api_ontology.py | 13 +++++++++---- 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/askomics/api/ontology.py b/askomics/api/ontology.py index d8ce1c8d..7558930a 100644 --- a/askomics/api/ontology.py +++ b/askomics/api/ontology.py @@ -45,7 +45,7 @@ def autocomplete(short_ontology): "error": True, "errorMessage": str(e), "results": [] - }), 404 + }), 500 return jsonify({ "error": False, diff --git a/tests/conftest.py b/tests/conftest.py index 5895b7d3..50ca2e46 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -294,7 +294,7 @@ def upload_file_url(self, file_url): files.download_url(file_url, "1") return files.date - def integrate_file(self, info, public=False, set_graph=False): + def integrate_file(self, info, public=False, set_graph=False, graph=""): """Summary Parameters @@ -310,6 +310,9 @@ def integrate_file(self, info, public=False, set_graph=False): files_handler = FilesHandler(self.app, self.session) files_handler.handle_files([info["id"], ]) + # TODO: Fix this. Why do we need the virtuoso url? + graph = graph or "http://virtuoso:8890/sparql" + for file in files_handler.files: dataset_info = { @@ -321,7 +324,7 @@ def integrate_file(self, info, public=False, set_graph=False): } dataset = Dataset(self.app, self.session, dataset_info) - dataset.save_in_db("http://virtuoso:8890/sparql", set_graph=set_graph) + dataset.save_in_db(graph, set_graph=set_graph) if file.type == "csv/tsv": file.integrate(dataset.id, info["columns_type"], public=public) @@ -463,7 +466,7 @@ def upload_and_integrate_ontology(self): # integrate int_ontology = self.integrate_file({ "id": 1, - }, set_graph=True) + }, set_graph=True, graph="http://localhost:8891/sparql-auth") return { "upload": up_ontology, @@ -621,7 +624,7 @@ def create_ontology(self): """Create ontology""" data = self.upload_and_integrate_ontology() om = OntologyManager(self.app, self.session) - om.add_ontology("AgrO ontology", "http://purl.obolibrary.org/obo/agro.owl", "AGRO", 1, data["graph"], "local") + om.add_ontology("AgrO ontology", "http://purl.obolibrary.org/obo/agro.owl", "AGRO", 1, data["graph"], data['endpoint'], "local") return data["graph"], data["endpoint"] @staticmethod diff --git a/tests/test_api_admin.py b/tests/test_api_admin.py index 0d178197..d88b7431 100644 --- a/tests/test_api_admin.py +++ b/tests/test_api_admin.py @@ -640,9 +640,9 @@ def test_add_ontology(self, client): assert response.status_code == 401 client.log_user("jdoe") - data = client.upload_and_integrate_ontology() - graph = data["graph"] - endpoint = data["endpoint"] + graph_data = client.upload_and_integrate_ontology() + graph = graph_data["graph"] + endpoint = graph_data["endpoint"] response = client.client.post('/api/admin/addontology', json=data) @@ -660,7 +660,7 @@ def test_add_ontology(self, client): "id": 1, "name": "AgrO ontology", "uri": "http://purl.obolibrary.org/obo/agro.owl", - "short_name": "OBO", + "short_name": "AGRO", "type": "local", "dataset_id": 1, "dataset_name": "agro_min.ttl", diff --git a/tests/test_api_ontology.py b/tests/test_api_ontology.py index f5cfe248..9bf9fe84 100644 --- a/tests/test_api_ontology.py +++ b/tests/test_api_ontology.py @@ -31,12 +31,17 @@ def test_local_autocompletion(self, client): "no-till", "puddling process", "mulch-till", - "ridge-till" + "ridge-till", + "strip-till", + "aerial application" ] assert response.status_code == 200 - assert len(response.json["results"]) == 10 - assert response.json["results"] == expected + assert len(response.json["results"]) == 12 + + # SPARQL order is not reliable, so we make sure to return everything + # If it fails, skip this + assert self.equal_objects(response.json["results"], expected) query = "irrigation" response = client.client.get('/api/ontology/AGRO/autocomplete?q={}'.format(query)) @@ -48,4 +53,4 @@ def test_local_autocompletion(self, client): assert response.status_code == 200 assert len(response.json["results"]) == 2 - assert response.json["results"] == expected + assert self.equal_objects(response.json["results"], expected) From 546251ce8bbcdb1252c3d902fa2783e7ca0515b5 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 23 Jun 2022 13:04:13 +0000 Subject: [PATCH 097/113] Fix2 --- config/askomics.ini.template | 7 ++++--- config/askomics.test.ini | 3 +++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/config/askomics.ini.template b/config/askomics.ini.template index af284f2b..5e75e378 100644 --- a/config/askomics.ini.template +++ b/config/askomics.ini.template @@ -83,6 +83,10 @@ ldap_mail_attribute = mail #ldap_password_reset_link = #ldap_account_link = +# Max results returned for autocompletion +autocomplete_max_results = 10 + + [triplestore] # name of the triplestore, can be virtuoso or fuseki triplestore = virtuoso @@ -128,9 +132,6 @@ preview_limit = 25 # All queries are launched on all graphes (speedup queries) single_tenant=False -# Max results returned for autocompletion -autocomplete_max_results = 10 - [federation] # Query engine can be corese or fedx #query_engine = corese diff --git a/config/askomics.test.ini b/config/askomics.test.ini index f2afac84..6d9bd17c 100644 --- a/config/askomics.test.ini +++ b/config/askomics.test.ini @@ -76,6 +76,7 @@ ldap_surname_attribute = sn ldap_mail_attribute = mail #ldap_password_reset_link = #ldap_account_link = +autocomplete_max_results = 20 [triplestore] # name of the triplestore, can be virtuoso or fuseki @@ -132,3 +133,5 @@ local_endpoint=http://askomics-host:8891/sparql # Sentry dsn to report python and js errors in a sentry instance # server_dsn = https://00000000000000000000000000000000@exemple.org/1 # frontend_dsn = https://00000000000000000000000000000000@exemple.org/2 + +# Max results returned for autocompletion From c0a10ca309eb32804b3b089cf69eb63f0da4b253 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 23 Jun 2022 15:10:32 +0000 Subject: [PATCH 098/113] more test, and restrict autocomplete if protected --- askomics/api/ontology.py | 8 ++++++-- tests/test_api_ontology.py | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/askomics/api/ontology.py b/askomics/api/ontology.py index 7558930a..6b662d0a 100644 --- a/askomics/api/ontology.py +++ b/askomics/api/ontology.py @@ -10,7 +10,6 @@ @onto_bp.route("/api/ontology//autocomplete", methods=["GET"]) @api_auth -@login_required def autocomplete(short_ontology): """Get the default sparql query @@ -19,8 +18,13 @@ def autocomplete(short_ontology): json """ + if "user" not in session and current_app.iniconfig.getboolean("askomics", "protect_public"): + return jsonify({ + "error": True, + "errorMessage": "Ontology {} not found".format(short_ontology), + "results": [] + }), 401 try: - # Disk space om = OntologyManager(current_app, session) ontology = om.get_ontology(short_name=short_ontology) if not ontology: diff --git a/tests/test_api_ontology.py b/tests/test_api_ontology.py index 9bf9fe84..4f8a8bb0 100644 --- a/tests/test_api_ontology.py +++ b/tests/test_api_ontology.py @@ -4,6 +4,24 @@ class TestApiOntology(AskomicsTestCase): """Test AskOmics API /api/ontology/""" + def test_local_autocompletion_protected(self, client): + """ Test autocompletion on missing ontology""" + query = "blabla" + client.set_config("askomics", "protect_public", "true") + response = client.client.get('/api/ontology/AGRO/autocomplete?q={}'.format(query)) + + assert response.status_code == 401 + assert len(response.json["results"]) == 0 + + def test_local_autocompletion_missing_ontology(self, client): + """ Test autocompletion on missing ontology""" + query = "blabla" + response = client.client.get('/api/ontology/AGRO/autocomplete?q={}'.format(query)) + + assert response.status_code == 404 + assert len(response.json["results"]) == 0 + + def test_local_autocompletion(self, client): """test /api/ontology/AGRO/autocomplete route""" client.create_two_users() From 458e69dfbc1386e07e603950095e526e7c436fea Mon Sep 17 00:00:00 2001 From: root Date: Thu, 23 Jun 2022 15:25:25 +0000 Subject: [PATCH 099/113] lint --- askomics/api/ontology.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/askomics/api/ontology.py b/askomics/api/ontology.py index 6b662d0a..c4269e08 100644 --- a/askomics/api/ontology.py +++ b/askomics/api/ontology.py @@ -1,6 +1,6 @@ import traceback import sys -from askomics.api.auth import api_auth, login_required +from askomics.api.auth import api_auth from askomics.libaskomics.OntologyManager import OntologyManager from flask import (Blueprint, current_app, jsonify, request, session) @@ -20,10 +20,10 @@ def autocomplete(short_ontology): if "user" not in session and current_app.iniconfig.getboolean("askomics", "protect_public"): return jsonify({ - "error": True, - "errorMessage": "Ontology {} not found".format(short_ontology), - "results": [] - }), 401 + "error": True, + "errorMessage": "Ontology {} not found".format(short_ontology), + "results": [] + }), 401 try: om = OntologyManager(current_app, session) ontology = om.get_ontology(short_name=short_ontology) From 41480ad88127c2bc9ee0461592c66847c48edca1 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 23 Jun 2022 15:26:28 +0000 Subject: [PATCH 100/113] lint2 --- tests/test_api_ontology.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_api_ontology.py b/tests/test_api_ontology.py index 4f8a8bb0..c22a1821 100644 --- a/tests/test_api_ontology.py +++ b/tests/test_api_ontology.py @@ -21,7 +21,6 @@ def test_local_autocompletion_missing_ontology(self, client): assert response.status_code == 404 assert len(response.json["results"]) == 0 - def test_local_autocompletion(self, client): """test /api/ontology/AGRO/autocomplete route""" client.create_two_users() From b6006184853a7b3ab4e4fa2c97f12676e0c8c28c Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 24 Jun 2022 10:10:20 +0000 Subject: [PATCH 101/113] fix (1/?) --- askomics/api/ontology.py | 2 +- askomics/libaskomics/OntologyManager.py | 4 ++-- askomics/libaskomics/SparqlQuery.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/askomics/api/ontology.py b/askomics/api/ontology.py index d8ce1c8d..93b05992 100644 --- a/askomics/api/ontology.py +++ b/askomics/api/ontology.py @@ -37,7 +37,7 @@ def autocomplete(short_ontology): "results": [] }), 404 - results = om.autocomplete(ontology["uri"], ontology["type"], request.args.get("q"), short_ontology, ontology["graph"], ontology["endpoint"]) + results = om.autocomplete(ontology["uri"], ontology["type"], request.args.get("q"), short_ontology, ontology["graph"], ontology["endpoint"], ontology['label_uri']) except Exception as e: traceback.print_exc(file=sys.stdout) diff --git a/askomics/libaskomics/OntologyManager.py b/askomics/libaskomics/OntologyManager.py index d180d13a..efd038b7 100644 --- a/askomics/libaskomics/OntologyManager.py +++ b/askomics/libaskomics/OntologyManager.py @@ -227,7 +227,7 @@ def test_ols_ontology(self, shortname): r = requests.get(base_url) return r.status_code == 200 - def autocomplete(self, ontology_uri, ontology_type, query_term, onto_short_name, onto_graph, onto_endpoint): + def autocomplete(self, ontology_uri, ontology_type, query_term, onto_short_name, onto_graph, onto_endpoint, custom_label): """Search in ontology Returns @@ -244,7 +244,7 @@ def autocomplete(self, ontology_uri, ontology_type, query_term, onto_short_name, # TODO: Actually store the graph in the ontology to quicken search query.set_graphs([onto_graph]) query.set_endpoints(set([self.settings.get('triplestore', 'endpoint'), onto_endpoint])) - return query.autocomplete_local_ontology(ontology_uri, query_term, max_results) + return query.autocomplete_local_ontology(ontology_uri, query_term, max_results, custom_label) elif ontology_type == "ols": base_url = "https://www.ebi.ac.uk/ols/api/suggest" arguments = { diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index a1c002cd..3b87d9c9 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -567,7 +567,7 @@ def get_uri_parameters(self, uri, endpoints): return formated_data - def autocomplete_local_ontology(self, uri, query, max_terms): + def autocomplete_local_ontology(self, uri, query, max_terms, label): """Get results for a specific query Parameters @@ -591,10 +591,10 @@ def autocomplete_local_ontology(self, uri, query, max_terms): SELECT DISTINCT ?label WHERE {{ ?uri rdf:type owl:Class . - ?uri rdfs:label ?label . + ?uri {} ?label . {} }} - '''.format(subquery) + '''.format(label, subquery) raw_query = self.prefix_query(raw_query) From 58f579a8ba062666236c73b43d8fb76aadd3c180 Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 24 Jun 2022 15:56:55 +0200 Subject: [PATCH 102/113] Federated queries should not have 'from' clause --- askomics/libaskomics/SparqlQuery.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index 3b87d9c9..f3684921 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -279,7 +279,7 @@ def get_default_query_with_prefix(self): self.get_default_query() ) - def format_query(self, query, limit=30, replace_froms=True, federated=False, ignore_single_tenant=True): + def format_query(self, query, limit=30, replace_froms=True, federated=False): """Format the Sparql query - remove all FROM @@ -299,11 +299,12 @@ def format_query(self, query, limit=30, replace_froms=True, federated=False, ign formatted sparql query """ froms = '' - if replace_froms and (not self.settings.getboolean("askomics", "single_tenant", fallback=False) or ignore_single_tenant): - froms = self.get_froms() if federated: federated_line = "{}\n{}".format(self.get_federated_line(), self.get_federated_froms()) + else: + if replace_froms and (not self.settings.getboolean("askomics", "single_tenant", fallback=False)): + froms = self.get_froms() query_lines = query.split('\n') @@ -600,7 +601,7 @@ def autocomplete_local_ontology(self, uri, query, max_terms, label): is_federated = self.is_federated() - sparql = self.format_query(raw_query, limit=max_terms, replace_froms=True, federated=is_federated, ignore_single_tenant=True) + sparql = self.format_query(raw_query, limit=max_terms, replace_froms=True, federated=is_federated) query_launcher = SparqlQueryLauncher(self.app, self.session, get_result_query=True, federated=is_federated) _, data = query_launcher.process_query(sparql) From 06934202cb7cff22ede75e980ef6b7c8abd8e2e3 Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 24 Jun 2022 15:01:03 +0000 Subject: [PATCH 103/113] fix federation --- askomics/libaskomics/SparqlQuery.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index f3684921..f5660e3d 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -301,7 +301,8 @@ def format_query(self, query, limit=30, replace_froms=True, federated=False): froms = '' if federated: - federated_line = "{}\n{}".format(self.get_federated_line(), self.get_federated_froms()) + federated_line = "" if self.settings.getboolean("askomics", "single_tenant", fallback=False) else "{}\n{}".format(self.get_federated_line(), self.get_federated_froms()) + federated_graphs_string = self.get_federated_remote_from_graphs() else: if replace_froms and (not self.settings.getboolean("askomics", "single_tenant", fallback=False)): froms = self.get_froms() @@ -315,6 +316,7 @@ def format_query(self, query, limit=30, replace_froms=True, federated=False): if not line.upper().lstrip().startswith('FROM') and not line.upper().lstrip().startswith('LIMIT') and not line.upper().lstrip().startswith('@FEDERATE'): if line.upper().lstrip().startswith('SELECT') and federated: new_query += "\n{}\n".format(federated_line) + new_query += "\n{}\n".format(federated_graphs_string) new_query += '\n{}'.format(line) # Add new FROM if line.upper().lstrip().startswith('SELECT'): @@ -379,7 +381,6 @@ def get_federated_froms_from_graphs(self, graphs): from_string = "@from <{}>".format(self.local_endpoint_f) for graph in graphs: from_string += " <{}>".format(graph) - return from_string def get_federated_remote_from_graphs(self): From de1ae05ebcc323ad044b400e648165a0faac8c81 Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 24 Jun 2022 17:37:29 +0200 Subject: [PATCH 104/113] Federated autocompletion on remote graph --- askomics/api/admin.py | 2 +- askomics/api/file.py | 2 +- askomics/api/ontology.py | 2 +- askomics/libaskomics/Database.py | 21 +++++++++++++++++++++ askomics/libaskomics/Dataset.py | 12 ++++++++---- askomics/libaskomics/OntologyManager.py | 10 +++++++--- askomics/libaskomics/SparqlQuery.py | 10 ++++++++++ tests/conftest.py | 6 +++--- 8 files changed, 52 insertions(+), 13 deletions(-) diff --git a/askomics/api/admin.py b/askomics/api/admin.py index 631b8b1a..f396d2ae 100644 --- a/askomics/api/admin.py +++ b/askomics/api/admin.py @@ -760,7 +760,7 @@ def add_ontology(): }), 400 try: - om.add_ontology(name, uri, short_name, dataset.id, dataset.graph_name, dataset.endpoint, type, label_uri) + om.add_ontology(name, uri, short_name, dataset.id, dataset.graph_name, dataset.endpoint, data.remote_graph, type, label_uri) ontologies = om.list_full_ontologies() except Exception as e: traceback.print_exc(file=sys.stdout) diff --git a/askomics/api/file.py b/askomics/api/file.py index 7348323f..5d224030 100644 --- a/askomics/api/file.py +++ b/askomics/api/file.py @@ -347,7 +347,7 @@ def integrate(): endpoint = data["externalEndpoint"] or current_app.iniconfig.get('triplestore', 'endpoint') dataset = Dataset(current_app, session, dataset_info) - dataset.save_in_db(endpoint) + dataset.save_in_db(endpoint, data["externalGraph"]) data["dataset_id"] = dataset.id dataset_ids.append(dataset.id) task = current_app.celery.send_task('integrate', (session_dict, data, request.host_url)) diff --git a/askomics/api/ontology.py b/askomics/api/ontology.py index 36b62c99..05e66fd0 100644 --- a/askomics/api/ontology.py +++ b/askomics/api/ontology.py @@ -41,7 +41,7 @@ def autocomplete(short_ontology): "results": [] }), 404 - results = om.autocomplete(ontology["uri"], ontology["type"], request.args.get("q"), short_ontology, ontology["graph"], ontology["endpoint"], ontology['label_uri']) + results = om.autocomplete(ontology["uri"], ontology["type"], request.args.get("q"), short_ontology, ontology["graph"], ontology["endpoint"], ontology['label_uri'], ontology['remote_graph']) except Exception as e: traceback.print_exc(file=sys.stdout) diff --git a/askomics/libaskomics/Database.py b/askomics/libaskomics/Database.py index 8a3c7e90..9fc02acb 100644 --- a/askomics/libaskomics/Database.py +++ b/askomics/libaskomics/Database.py @@ -220,6 +220,17 @@ def update_datasets_table(self): except Exception: pass + query = ''' + ALTER TABLE datasets + ADD remote_graph text NULL + DEFAULT(null) + ''' + + try: + self.execute_sql_query(query) + except Exception: + pass + def create_integration_table(self): """Create the integration table""" query = ''' @@ -455,3 +466,13 @@ def create_ontologies_table(self): self.execute_sql_query(query) except Exception: pass + + query = ''' + ALTER TABLE ontologies + ADD remote_graph text NULL + ''' + + try: + self.execute_sql_query(query) + except Exception: + pass diff --git a/askomics/libaskomics/Dataset.py b/askomics/libaskomics/Dataset.py index 87bae1ba..e1aad0e9 100644 --- a/askomics/libaskomics/Dataset.py +++ b/askomics/libaskomics/Dataset.py @@ -59,7 +59,7 @@ def set_info_from_db(self, admin=False): where_query = "AND user_id = ?" query = ''' - SELECT celery_id, file_id, name, graph_name, public, start, end, ontology, endpoint + SELECT celery_id, file_id, name, graph_name, public, start, end, ontology, endpoint, remote_graph FROM datasets WHERE id = ? {} @@ -76,8 +76,9 @@ def set_info_from_db(self, admin=False): self.end = rows[0][6] self.ontology = rows[0][7] self.endpoint = rows[0][8] + self.remove_graph = rows[0][9] - def save_in_db(self, endpoint, set_graph=False): + def save_in_db(self, endpoint, remote_graph=None, set_graph=False): """Save the dataset into the database""" database = Database(self.app, self.session) @@ -89,7 +90,8 @@ def save_in_db(self, endpoint, set_graph=False): self.name, self.public, 0, - endpoint + endpoint, + remote_graph ) if set_graph: @@ -102,7 +104,8 @@ def save_in_db(self, endpoint, set_graph=False): self.graph_name, self.public, 0, - endpoint + endpoint, + remote_graph ) query = ''' @@ -122,6 +125,7 @@ def save_in_db(self, endpoint, set_graph=False): NULL, NULL, 0, + ?, ? ) '''.format(subquery) diff --git a/askomics/libaskomics/OntologyManager.py b/askomics/libaskomics/OntologyManager.py index efd038b7..2582654e 100644 --- a/askomics/libaskomics/OntologyManager.py +++ b/askomics/libaskomics/OntologyManager.py @@ -147,7 +147,7 @@ def get_ontology(self, short_name="", uri=""): 'endpoint': ontology[8] } - def add_ontology(self, name, uri, short_name, dataset_id, graph, endpoint, type="local", label_uri="rdfs:label"): + def add_ontology(self, name, uri, short_name, dataset_id, graph, endpoint, remote_graph=None, type="local", label_uri="rdfs:label"): """Create a new ontology Returns @@ -169,11 +169,12 @@ def add_ontology(self, name, uri, short_name, dataset_id, graph, endpoint, type= ?, ?, ?, + ?, ? ) ''' - database.execute_sql_query(query, (name, uri, short_name, type, dataset_id, graph, label_uri, endpoint)) + database.execute_sql_query(query, (name, uri, short_name, type, dataset_id, graph, label_uri, endpoint, remote_graph)) query = ''' UPDATE datasets SET @@ -227,7 +228,7 @@ def test_ols_ontology(self, shortname): r = requests.get(base_url) return r.status_code == 200 - def autocomplete(self, ontology_uri, ontology_type, query_term, onto_short_name, onto_graph, onto_endpoint, custom_label): + def autocomplete(self, ontology_uri, ontology_type, query_term, onto_short_name, onto_graph, onto_endpoint, custom_label, remote_graph): """Search in ontology Returns @@ -244,6 +245,9 @@ def autocomplete(self, ontology_uri, ontology_type, query_term, onto_short_name, # TODO: Actually store the graph in the ontology to quicken search query.set_graphs([onto_graph]) query.set_endpoints(set([self.settings.get('triplestore', 'endpoint'), onto_endpoint])) + if remote_graph: + query.set_remote_graph({onto_endpoint: [remote_graph]}) + return query.autocomplete_local_ontology(ontology_uri, query_term, max_results, custom_label) elif ontology_type == "ols": base_url = "https://www.ebi.ac.uk/ols/api/suggest" diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index f5660e3d..b281be19 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -71,6 +71,16 @@ def set_endpoints(self, endpoints): """ self.endpoints = endpoints + def set_remote_graph(self, remote_graphs): + """Set endpoints + + Parameters + ---------- + endpoints : list + Endpoints + """ + self.remote_graphs = remote_graphs + def is_federated(self): """Return True if there is more than 1 endpoint diff --git a/tests/conftest.py b/tests/conftest.py index 50ca2e46..2e4fd4bd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -294,7 +294,7 @@ def upload_file_url(self, file_url): files.download_url(file_url, "1") return files.date - def integrate_file(self, info, public=False, set_graph=False, graph=""): + def integrate_file(self, info, public=False, set_graph=False, endpoint=""): """Summary Parameters @@ -311,7 +311,7 @@ def integrate_file(self, info, public=False, set_graph=False, graph=""): files_handler.handle_files([info["id"], ]) # TODO: Fix this. Why do we need the virtuoso url? - graph = graph or "http://virtuoso:8890/sparql" + endpoint = endpoint or "http://virtuoso:8890/sparql" for file in files_handler.files: @@ -324,7 +324,7 @@ def integrate_file(self, info, public=False, set_graph=False, graph=""): } dataset = Dataset(self.app, self.session, dataset_info) - dataset.save_in_db(graph, set_graph=set_graph) + dataset.save_in_db(endpoint, set_graph=set_graph) if file.type == "csv/tsv": file.integrate(dataset.id, info["columns_type"], public=public) From 48fcb84f4e099a84ff33846249d8da26766fe6ac Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 24 Jun 2022 18:03:52 +0200 Subject: [PATCH 105/113] typo --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 2e4fd4bd..364084a0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -466,7 +466,7 @@ def upload_and_integrate_ontology(self): # integrate int_ontology = self.integrate_file({ "id": 1, - }, set_graph=True, graph="http://localhost:8891/sparql-auth") + }, set_graph=True, endpoint="http://localhost:8891/sparql-auth") return { "upload": up_ontology, From 6fc46019f42701c6283046aafcc27081a6476df6 Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 24 Jun 2022 19:15:01 +0200 Subject: [PATCH 106/113] typo2 --- askomics/libaskomics/OntologyManager.py | 10 ++++++---- tests/test_api_admin.py | 4 +++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/askomics/libaskomics/OntologyManager.py b/askomics/libaskomics/OntologyManager.py index 2582654e..45d2c8ec 100644 --- a/askomics/libaskomics/OntologyManager.py +++ b/askomics/libaskomics/OntologyManager.py @@ -76,7 +76,7 @@ def list_full_ontologies(self): database = Database(self.app, self.session) query = ''' - SELECT ontologies.id, ontologies.name, ontologies.uri, ontologies.short_name, ontologies.type, ontologies.label_uri, datasets.id, datasets.name, ontologies.graph, ontologies.endpoint + SELECT ontologies.id, ontologies.name, ontologies.uri, ontologies.short_name, ontologies.type, ontologies.label_uri, datasets.id, datasets.name, ontologies.graph, ontologies.endpoint, ontologies.remote_graph FROM ontologies INNER JOIN datasets ON datasets.id=ontologies.dataset_id ''' @@ -95,7 +95,8 @@ def list_full_ontologies(self): 'dataset_id': row[6], 'dataset_name': row[7], 'graph': row[8], - 'endpoint': row[9] + 'endpoint': row[9], + 'remote_graph': row[10] } ontologies.append(prefix) @@ -124,7 +125,7 @@ def get_ontology(self, short_name="", uri=""): database = Database(self.app, self.session) query = ''' - SELECT id, name, uri, short_name, type, dataset_id, graph, label_uri, endpoint + SELECT id, name, uri, short_name, type, dataset_id, graph, label_uri, endpoint, remote_graph FROM ontologies {} '''.format(where_clause) @@ -144,7 +145,8 @@ def get_ontology(self, short_name="", uri=""): 'dataset_id': ontology[5], 'graph': ontology[6], 'label_uri': ontology[7], - 'endpoint': ontology[8] + 'endpoint': ontology[8], + 'remote_graph': ontology[9] } def add_ontology(self, name, uri, short_name, dataset_id, graph, endpoint, remote_graph=None, type="local", label_uri="rdfs:label"): diff --git a/tests/test_api_admin.py b/tests/test_api_admin.py index d88b7431..30b6cbba 100644 --- a/tests/test_api_admin.py +++ b/tests/test_api_admin.py @@ -622,6 +622,7 @@ def test_view_ontologies(self, client): "dataset_name": "agro_min.ttl", "graph": graph, "endpoint": endpoint, + "remote_graph": None, "label_uri": "rdfs:label" }] } @@ -666,7 +667,8 @@ def test_add_ontology(self, client): "dataset_name": "agro_min.ttl", "label_uri": "rdfs:label", "graph": graph, - "endpoint": endpoint + "endpoint": endpoint, + "remote_graph": None }] } From e3000c05cb3497cdb19ca44291e4ab1f2e602bac Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 24 Jun 2022 19:32:51 +0200 Subject: [PATCH 107/113] Fix3 --- askomics/api/admin.py | 2 +- tests/conftest.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/askomics/api/admin.py b/askomics/api/admin.py index f396d2ae..4fe15951 100644 --- a/askomics/api/admin.py +++ b/askomics/api/admin.py @@ -760,7 +760,7 @@ def add_ontology(): }), 400 try: - om.add_ontology(name, uri, short_name, dataset.id, dataset.graph_name, dataset.endpoint, data.remote_graph, type, label_uri) + om.add_ontology(name, uri, short_name, dataset.id, dataset.graph_name, dataset.endpoint, remote_graph=dataset.remote_graph, type=type, label_uri=label_uri) ontologies = om.list_full_ontologies() except Exception as e: traceback.print_exc(file=sys.stdout) diff --git a/tests/conftest.py b/tests/conftest.py index 364084a0..999d0248 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -624,7 +624,7 @@ def create_ontology(self): """Create ontology""" data = self.upload_and_integrate_ontology() om = OntologyManager(self.app, self.session) - om.add_ontology("AgrO ontology", "http://purl.obolibrary.org/obo/agro.owl", "AGRO", 1, data["graph"], data['endpoint'], "local") + om.add_ontology("AgrO ontology", "http://purl.obolibrary.org/obo/agro.owl", "AGRO", 1, data["graph"], data['endpoint'], type="local") return data["graph"], data["endpoint"] @staticmethod From af4e6d3f704018f4449368e4bf10d5d28cc70948 Mon Sep 17 00:00:00 2001 From: mboudet Date: Fri, 24 Jun 2022 19:49:14 +0200 Subject: [PATCH 108/113] typo4 --- askomics/libaskomics/Dataset.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/askomics/libaskomics/Dataset.py b/askomics/libaskomics/Dataset.py index e1aad0e9..c3fd7b1b 100644 --- a/askomics/libaskomics/Dataset.py +++ b/askomics/libaskomics/Dataset.py @@ -46,6 +46,8 @@ def __init__(self, app, session, dataset_info={}): self.start = dataset_info["start"] if "start" in dataset_info else None self.end = dataset_info["end"] if "end" in dataset_info else None self.ontology = dataset_info["ontology"] if "ontology" in dataset_info else False + self.endpoint = dataset_info["endpoint"] if "endpoint" in dataset_info else False + self.remote_graph = dataset_info["remote_graph"] if "remote_graph" in dataset_info else False def set_info_from_db(self, admin=False): """Set the info in from the database""" @@ -76,7 +78,7 @@ def set_info_from_db(self, admin=False): self.end = rows[0][6] self.ontology = rows[0][7] self.endpoint = rows[0][8] - self.remove_graph = rows[0][9] + self.remote_graph = rows[0][9] def save_in_db(self, endpoint, remote_graph=None, set_graph=False): """Save the dataset into the database""" From 3926a0827b9cd5721aaabf04d423ac664bca1287 Mon Sep 17 00:00:00 2001 From: mboudet Date: Mon, 27 Jun 2022 08:54:17 +0000 Subject: [PATCH 109/113] better remote autocomplete --- askomics/api/start.py | 3 ++- askomics/libaskomics/OntologyManager.py | 8 +++++--- askomics/react/src/components/autocomplete.jsx | 3 ++- askomics/react/src/routes.jsx | 3 ++- tests/test_api.py | 3 ++- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/askomics/api/start.py b/askomics/api/start.py index 93ae4578..910af044 100644 --- a/askomics/api/start.py +++ b/askomics/api/start.py @@ -85,7 +85,8 @@ def start(): "user": {}, "logged": False, "ontologies": ontologies, - "singleTenant": current_app.iniconfig.getboolean('askomics', 'single_tenant', fallback=False) + "singleTenant": current_app.iniconfig.getboolean('askomics', 'single_tenant', fallback=False), + "autocompleteMaxResults": current_app.iniconfig.getint("askomics", "autocomplete_max_results", fallback=10) } json = { diff --git a/askomics/libaskomics/OntologyManager.py b/askomics/libaskomics/OntologyManager.py index 45d2c8ec..c988f959 100644 --- a/askomics/libaskomics/OntologyManager.py +++ b/askomics/libaskomics/OntologyManager.py @@ -252,11 +252,13 @@ def autocomplete(self, ontology_uri, ontology_type, query_term, onto_short_name, return query.autocomplete_local_ontology(ontology_uri, query_term, max_results, custom_label) elif ontology_type == "ols": - base_url = "https://www.ebi.ac.uk/ols/api/suggest" + base_url = "https://www.ebi.ac.uk/ols/api/select" arguments = { "q": query_term, "ontology": quote_plus(onto_short_name.lower()), - "rows": max_results + "rows": max_results, + "type": "class", + "fieldList": "label" } r = requests.get(base_url, params=arguments) @@ -268,6 +270,6 @@ def autocomplete(self, ontology_uri, ontology_type, query_term, onto_short_name, res = r.json() if res['response']['docs']: - data = [term['autosuggest'] for term in res['response']['docs']] + data = [term['label'] for term in res['response']['docs']] return data diff --git a/askomics/react/src/components/autocomplete.jsx b/askomics/react/src/components/autocomplete.jsx index 635881b1..41a2dfd2 100644 --- a/askomics/react/src/components/autocomplete.jsx +++ b/askomics/react/src/components/autocomplete.jsx @@ -9,6 +9,7 @@ export default class Autocomplete extends Component { super(props) this.state = { ontologyShort: this.getAutoComplete(), + maxResults: this.props.config.autocompleteMaxResults, options: [] } @@ -65,7 +66,7 @@ export default class Autocomplete extends Component { renderAutocomplete () { let input = (
- this.handleOntoValue({target: {value: e, id: this.props.attributeId}})} id={this.props.attributeId} value={this.props.filterValue}/> + this.handleOntoValue({target: {value: e, id: this.props.attributeId}})} id={this.props.attributeId} value={this.props.filterValue} maxOptions={this.state.maxResults}/>
) return input diff --git a/askomics/react/src/routes.jsx b/askomics/react/src/routes.jsx index f0b7356b..94e92c7e 100644 --- a/askomics/react/src/routes.jsx +++ b/askomics/react/src/routes.jsx @@ -48,7 +48,8 @@ export default class Routes extends Component { namespaceData: null, namespaceInternal: null, ontologies: [], - singleTenant: false + singleTenant: false, + autocompleteMaxResults: 10 } } this.cancelRequest diff --git a/tests/test_api.py b/tests/test_api.py index ea66cc70..70199589 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -44,7 +44,8 @@ def test_start(self, client): "user": {}, "logged": False, "ontologies": [], - "singleTenant": False + "singleTenant": False, + "autocompleteMaxResults": 10 } response = client.client.get('/api/start') assert response.status_code == 200 From b92ed00c550f642f97d15782491319d13091fab1 Mon Sep 17 00:00:00 2001 From: mboudet Date: Mon, 27 Jun 2022 09:14:50 +0000 Subject: [PATCH 110/113] Woops --- tests/test_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_api.py b/tests/test_api.py index 70199589..81a8bb23 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -45,7 +45,7 @@ def test_start(self, client): "logged": False, "ontologies": [], "singleTenant": False, - "autocompleteMaxResults": 10 + "autocompleteMaxResults": 20 } response = client.client.get('/api/start') assert response.status_code == 200 From 9137cdeca3e7ee3cf5bb75be649248aec56f70f7 Mon Sep 17 00:00:00 2001 From: mboudet Date: Mon, 27 Jun 2022 15:29:02 +0200 Subject: [PATCH 111/113] failed merge --- askomics/api/ontology.py | 1 - askomics/libaskomics/SparqlQuery.py | 17 ----------------- 2 files changed, 18 deletions(-) diff --git a/askomics/api/ontology.py b/askomics/api/ontology.py index 775c5f56..fc1efe37 100644 --- a/askomics/api/ontology.py +++ b/askomics/api/ontology.py @@ -42,7 +42,6 @@ def autocomplete(short_ontology): "results": [] }), 404 - results = om.autocomplete(ontology["uri"], ontology["type"], request.args.get("q"), short_ontology, ontology["graph"], ontology["endpoint"], ontology['label_uri'], ontology['remote_graph']) except Exception as e: diff --git a/askomics/libaskomics/SparqlQuery.py b/askomics/libaskomics/SparqlQuery.py index f910ac5a..57d2c29a 100644 --- a/askomics/libaskomics/SparqlQuery.py +++ b/askomics/libaskomics/SparqlQuery.py @@ -410,23 +410,6 @@ def get_federated_remote_from_graphs(self): return from_string - def get_federated_remote_from_graphs(self): - """Get @from string fir the federated query engine - - Returns - ------- - string - The from string - """ - from_string = "" - - for endpoint in self.endpoints: - remote_graphs = self.remote_graphs.get(endpoint, []) - if len(remote_graphs) == 1: - from_string += "\n@graph <{}> <{}>".format(endpoint, remote_graphs[0]) - - return from_string - def get_endpoints_string(self): """get endpoint strngs for the federated query engine From 53cbeb3007b1302c0a357a4cff66a6b710603ed3 Mon Sep 17 00:00:00 2001 From: mboudet Date: Tue, 28 Jun 2022 14:52:29 +0000 Subject: [PATCH 112/113] better autocomplete --- .../react/src/components/autocomplete.jsx | 87 ++++++++++++++++--- askomics/static/css/askomics.css | 36 +++++++- package-lock.json | 52 +++++++++++ package.json | 2 +- 4 files changed, 159 insertions(+), 18 deletions(-) diff --git a/askomics/react/src/components/autocomplete.jsx b/askomics/react/src/components/autocomplete.jsx index 41a2dfd2..452e9575 100644 --- a/askomics/react/src/components/autocomplete.jsx +++ b/askomics/react/src/components/autocomplete.jsx @@ -1,8 +1,9 @@ import React, { Component} from 'react' import axios from 'axios' import PropTypes from 'prop-types' -import TextInput from 'react-autocomplete-input'; -import 'react-autocomplete-input/dist/bundle.css'; +import { Input } from 'reactstrap' +import Autosuggest from 'react-autosuggest'; + export default class Autocomplete extends Component { constructor (props) { @@ -15,6 +16,8 @@ export default class Autocomplete extends Component { this.handleFilterValue = this.props.handleFilterValue.bind(this) this.autocompleteOntology = this.autocompleteOntology.bind(this) + this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(this) + this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(this) this.cancelRequest this.handleOntoValue = this.handleOntoValue.bind(this) this.WAIT_INTERVAL = 500 @@ -39,7 +42,6 @@ export default class Autocomplete extends Component { axios.get(requestUrl, {baseURL: this.props.config.proxyPath, params:{q: userInput}, cancelToken: new axios.CancelToken((c) => { this.cancelRequest = c }) }) .then(response => { - // set state of resultsPreview this.setState({ options: response.data.results }) @@ -55,25 +57,84 @@ export default class Autocomplete extends Component { }) } - handleOntoValue (event) { - this.handleFilterValue(event) + + handleOntoValue (event, value) { + this.handleFilterValue({target:{value: value.newValue, id: this.props.attributeId}}) + } + + + renderSuggestion (suggestion, {query, isHighlighted}) { + let textArray = suggestion.split(RegExp(query, "gi")); + let match = suggestion.match(RegExp(query, "gi")); + + return ( + + {textArray.map((item, index) => ( + + {item} + {index !== textArray.length - 1 && match && ( + {match[index]} + )} + + ))} + + ); + } + + onSuggestionsClearRequested () { + this.setState({ + options: [] + }) + } + + getSuggestionValue (suggestion) { + return suggestion + }; + + onSuggestionsFetchRequested ( value ){ clearTimeout(this.timerID) this.timerID = setTimeout(() => { - this.autocompleteOntology(event.target.value) + this.autocompleteOntology(value.value) }, this.WAIT_INTERVAL) + }; + + + renderInputComponent (inputProps){ + return( +
+ +
+ ) } - renderAutocomplete () { - - let input = (
- this.handleOntoValue({target: {value: e, id: this.props.attributeId}})} id={this.props.attributeId} value={this.props.filterValue} maxOptions={this.state.maxResults}/> -
) - return input + shouldRenderSuggestions(value, reason){ + return value.trim().length > 2; } render () { - return this.renderAutocomplete() + + let value = this.props.filterValue + + let inputProps = { + placeholder: '', + value, + onChange: this.handleOntoValue + }; + + return ( + + ) + } } diff --git a/askomics/static/css/askomics.css b/askomics/static/css/askomics.css index c6525b2a..2d6a4065 100644 --- a/askomics/static/css/askomics.css +++ b/askomics/static/css/askomics.css @@ -276,9 +276,37 @@ button.input-with-icon { display: block; } -.react-autocomplete-input { - left: auto !important; - top: auto !important; +/***********************************************************************/ + + +.react-autosuggest__suggestions-container--open { + background-clip: padding-box; + background-color: #fff; + border: 1px solid rgba(0,0,0,0.15); + bottom: auto; + box-shadow: 0 6px 12px rgba(0,0,0,0.175); + display: block; + font-size: 14px; + list-style: none; + padding: 1px; + position: absolute; + text-align: left; + z-index: 20000; } -/***********************************************************************/ +.react-autosuggest__suggestions-list { + margin: 0; + padding: 0; + list-style-type: none; +} + +.react-autosuggest__suggestion { + cursor: pointer; + padding: 10px; + min-width: 100px; +} + +.react-autosuggest__suggestion--highlighted { + background-color: #0356fc; + color: #fff; +} diff --git a/package-lock.json b/package-lock.json index 0b75625b..04f514fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4547,6 +4547,11 @@ "is-symbol": "^1.0.2" } }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -5513,6 +5518,11 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-input-selection": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/get-input-selection/-/get-input-selection-1.1.4.tgz", + "integrity": "sha512-o3rv95OOpoHznujIEwZljNhUM9efW/gZsIKCQtTrjRU4PkneVpDvxNBmC7kXC4519lZYT95DKcdj0A5f9GZkKg==" + }, "get-intrinsic": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", @@ -8625,6 +8635,18 @@ "object-assign": "^4.1.0" } }, + "react-autosuggest": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-autosuggest/-/react-autosuggest-10.1.0.tgz", + "integrity": "sha512-/azBHmc6z/31s/lBf6irxPf/7eejQdR0IqnZUzjdSibtlS8+Rw/R79pgDAo6Ft5QqCUTyEQ+f0FhL+1olDQ8OA==", + "requires": { + "es6-promise": "^4.2.8", + "prop-types": "^15.7.2", + "react-themeable": "^1.1.0", + "section-iterator": "^2.0.0", + "shallow-equal": "^1.2.1" + } + }, "react-bootstrap-table-next": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/react-bootstrap-table-next/-/react-bootstrap-table-next-4.0.3.tgz", @@ -8817,6 +8839,21 @@ "refractor": "^3.2.0" } }, + "react-themeable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/react-themeable/-/react-themeable-1.1.0.tgz", + "integrity": "sha512-kl5tQ8K+r9IdQXZd8WLa+xxYN04lLnJXRVhHfdgwsUJr/SlKJxIejoc9z9obEkx1mdqbTw1ry43fxEUwyD9u7w==", + "requires": { + "object-assign": "^3.0.0" + }, + "dependencies": { + "object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha512-jHP15vXVGeVh1HuaA2wY6lxk+whK/x4KBG88VXeRma7CCun7iGD5qPc4eYykQ9sdQvg8jkwFKsSxHln2ybW3xQ==" + } + } + }, "react-tooltip": { "version": "4.2.21", "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-4.2.21.tgz", @@ -9352,6 +9389,11 @@ "ajv-keywords": "^3.5.2" } }, + "section-iterator": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/section-iterator/-/section-iterator-2.0.0.tgz", + "integrity": "sha512-xvTNwcbeDayXotnV32zLb3duQsP+4XosHpb/F+tu6VzEZFmIjzPdNk6/O+QOOx5XTh08KL2ufdXeCO33p380pQ==" + }, "select": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", @@ -9440,6 +9482,11 @@ "safe-buffer": "^5.0.1" } }, + "shallow-equal": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz", + "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==" + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -10118,6 +10165,11 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "textarea-caret": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/textarea-caret/-/textarea-caret-3.1.0.tgz", + "integrity": "sha512-cXAvzO9pP5CGa6NKx0WYHl+8CHKZs8byMkt3PCJBCmq2a34YA9pO1NrQET5pzeqnBjBdToF5No4rrmkDUgQC2Q==" + }, "three": { "version": "0.126.1", "resolved": "https://registry.npmjs.org/three/-/three-0.126.1.tgz", diff --git a/package.json b/package.json index 9f088555..fb733689 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "qs": "^6.9.4", "react": "^16.13.1", "react-ace": "^9.1.3", - "react-autocomplete-input": "1.0.18", + "react-autosuggest": "^10.1.0", "react-addons-update": "^15.6.3", "react-bootstrap-table-next": "^4.0.3", "react-bootstrap-table2-editor": "^1.4.0", From 6c05bfd7847485aec29e26bcaece0b9fb0093cef Mon Sep 17 00:00:00 2001 From: mboudet Date: Tue, 28 Jun 2022 17:31:55 +0200 Subject: [PATCH 113/113] cleanup --- askomics/api/admin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/askomics/api/admin.py b/askomics/api/admin.py index 4fe15951..5200ebb5 100644 --- a/askomics/api/admin.py +++ b/askomics/api/admin.py @@ -698,12 +698,12 @@ def add_ontology(): 'errorMessage': "Missing parameter" }), 400 - name = data.get("name") - uri = data.get("uri") + name = data.get("name").strip() + uri = data.get("uri").strip() short_name = data.get("shortName") - type = data.get("type") + type = data.get("type").strip() dataset_id = data.get("datasetId") - label_uri = data.get("labelUri") + label_uri = data.get("labelUri").strip() om = OntologyManager(current_app, session)