Skip to content

Commit

Permalink
Merge pull request #170 from mboudet/dev_uri
Browse files Browse the repository at this point in the history
Add a route to avoid 404 in URIs
  • Loading branch information
abretaud committed Mar 2, 2021
2 parents 7ed8bf3 + 35b0fa3 commit 1e2ec30
Show file tree
Hide file tree
Showing 13 changed files with 558 additions and 2 deletions.
14 changes: 14 additions & 0 deletions Makefile
Expand Up @@ -55,6 +55,8 @@ help:
@echo ' make serve [MODE=dev] [HOST=0.0.0.0] [PORT=5000] [NTASKS=1] Serve AskOmics at $(HOST):$(PORT)'
@echo ' make test Lint and test javascript and python code'
@echo ' make serve-doc [DOCPORT=8000] Serve documentation at localhost:$(DOCPORT)'
@echo ' make update-base-url Update all graphs from an old base_url to a new base_url'
@echo ' make clear-cache Clear abstraction cache'
@echo ''
@echo 'Examples:'
@echo ' make clean install build serve NTASKS=10 Clean install and serve AskOmics in production mode, 10 celery tasks in parallel'
Expand Down Expand Up @@ -133,6 +135,18 @@ create-user:
bash cli/set_user.sh
@echo 'Done'

update-base-url: check-venv
@echo 'Updating base url...'
. $(ACTIVATE)
bash cli/update_base_url.sh
@echo 'Done'

clear-cache: check-venv
@echo 'Clearing abstraction cache...'
. $(ACTIVATE)
bash cli/clear_cache.sh
@echo 'Done'

build: build-js

build-js: check-node-modules
Expand Down
61 changes: 61 additions & 0 deletions askomics/api/data.py
@@ -0,0 +1,61 @@
"""Api routes"""
import sys
import traceback

from askomics.libaskomics.SparqlQuery import SparqlQuery
from askomics.libaskomics.SparqlQueryLauncher import SparqlQueryLauncher

from flask import (Blueprint, current_app, jsonify, session)


data_bp = Blueprint('data', __name__, url_prefix='/')


@data_bp.route('/api/data/<string:uri>', methods=['GET'])
def get_data(uri):
"""Get information about uri
Returns
-------
json
error: True if error, else False
errorMessage: the error message of error, else an empty string
"""

try:
query = SparqlQuery(current_app, session)
graphs, endpoints = query.get_graphs_and_endpoints(all_selected=True)

endpoints = [val['uri'] for val in endpoints.values()]

data = []

# If the user do not have access to any endpoint (no viewable graph), skip
if endpoints:

base_uri = current_app.iniconfig.get('triplestore', 'namespace_data')
full_uri = "<%s%s>" % (base_uri, uri)

raw_query = "SELECT DISTINCT ?predicat ?object\nWHERE {\n%s ?predicat ?object\n}" % (full_uri)
federated = query.is_federated()
replace_froms = query.replace_froms()

sparql = query.format_query(raw_query, replace_froms=replace_froms, federated=federated)

query_launcher = SparqlQueryLauncher(current_app, session, get_result_query=True, federated=federated, endpoints=endpoints)
header, data = query_launcher.process_query(sparql)

except Exception as e:
current_app.logger.error(str(e).replace('\\n', '\n'))
traceback.print_exc(file=sys.stdout)
return jsonify({
'error': True,
'errorMessage': str(e).replace('\\n', '\n'),
'data': []
}), 500

return jsonify({
'data': data,
'error': False,
'errorMessage': ""
})
2 changes: 2 additions & 0 deletions askomics/app.py
Expand Up @@ -10,6 +10,7 @@

from askomics.api.admin import admin_bp
from askomics.api.auth import auth_bp
from askomics.api.data import data_bp
from askomics.api.datasets import datasets_bp
from askomics.api.file import file_bp
from askomics.api.sparql import sparql_bp
Expand Down Expand Up @@ -41,6 +42,7 @@
auth_bp,
admin_bp,
file_bp,
data_bp,
datasets_bp,
query_bp,
results_bp,
Expand Down
22 changes: 22 additions & 0 deletions askomics/libaskomics/LocalAuth.py
Expand Up @@ -1303,3 +1303,25 @@ def delete_user_rdf(self, username):
graphs = tse.get_graph_of_user(username)
for graph in graphs:
Utils.redo_if_failure(self.log, 3, 1, query_launcher.drop_dataset, graph)

def update_base_url(self, old_url, new_url):
"""Update base url for all graphs
Parameters
----------
old_url : string
Previous base url
new_url : string
New base url
"""
tse = TriplestoreExplorer(self.app, self.session)
graphs = tse.get_all_graphs()

for graph in graphs:
tse.update_base_url(graph, old_url, new_url)

def clear_abstraction_cache(self):
"""Clear cache for all users"""

tse = TriplestoreExplorer(self.app, self.session)
tse.uncache_abstraction(public=True, force=True)
72 changes: 70 additions & 2 deletions askomics/libaskomics/TriplestoreExplorer.py
Expand Up @@ -53,6 +53,71 @@ def get_graph_of_user(self, username):
graphs.append(result["graph"])
return graphs

def get_all_graphs(self):
"""get all graph of a user
Returns
-------
list
List of graphs
"""

query_launcher = SparqlQueryLauncher(self.app, self.session)
query_builder = SparqlQuery(self.app, self.session)

query = """
SELECT DISTINCT ?graph
WHERE {{
?graph dc:creator ?user .
}}
"""

header, data = query_launcher.process_query(query_builder.prefix_query(query))

graphs = []
for result in data:
graphs.append(result["graph"])
return graphs

def update_base_url(self, graph, old_url, new_url):
"""Update base url for a graph
Parameters
----------
graph : string
Graph to update
old_url : string
Old base_url
new_url : string
New base_url
Returns
-------
list
List of graphs
"""

query_launcher = SparqlQueryLauncher(self.app, self.session)
query_builder = SparqlQuery(self.app, self.session)

query = """
WITH <{0}>
DELETE{{
?s ?p ?o
}}
INSERT{{
?s2 ?p2 ?o2
}}
WHERE {{
?s ?p ?o
FILTER(REGEX(?s, '{1}', 'i') || REGEX(?p, '{1}', 'i') || REGEX(?o, '{1}', 'i')) .
BIND(IF (isURI(?o), URI(REPLACE(STR(?o), '{1}', '{2}')), ?o) AS ?o2) .
BIND(IF (isURI(?s), URI(REPLACE(STR(?s), '{1}', '{2}')), ?s) AS ?s2) .
BIND(IF (isURI(?p), URI(REPLACE(STR(?p), '{1}', '{2}')), ?p) AS ?p2) .
}}
""".format(graph, old_url, new_url)

header, data = query_launcher.process_query(query_builder.prefix_query(query))

def get_startpoints(self):
"""Get public and user startpoints
Expand Down Expand Up @@ -215,15 +280,18 @@ def cache_asbtraction(self, abstraction, insert):
"""
database.execute_sql_query(query, (json.dumps(abstraction), self.session["user"]["id"]))

def uncache_abstraction(self, public=True):
def uncache_abstraction(self, public=True, force=False):
"""Remove cached abstraction from database
Parameters
----------
public : bool, optional
Remove for all users if True, else, for logged user only
"""
if "user" in self.session:
if force:
public = True

if "user" in self.session or force:
database = Database(self.app, self.session)

sub_query = "WHERE user_id=?" if not public else ""
Expand Down
2 changes: 2 additions & 0 deletions askomics/react/src/routes.jsx
Expand Up @@ -6,6 +6,7 @@ import Ask from './routes/ask/ask'
import About from './routes/about/about'
import Upload from './routes/upload/upload'
import Integration from './routes/integration/integration'
import Data from './routes/data/data'
import Datasets from './routes/datasets/datasets'
import Signup from './routes/login/signup'
import Login from './routes/login/login'
Expand Down Expand Up @@ -112,6 +113,7 @@ export default class Routes extends Component {
<Route path="/query" exact component={Query} />
<Route path="/results" exact component={() => (<Results config={this.state.config} waitForStart={this.state.waiting} />)} />
<Route path="/sparql" render={(props) => <Sparql config={this.state.config} waitForStart={this.state.waiting} {...props}/>}/>
<Route path="/data/:uri" exact component={() => (<Data config={this.state.config} waitForStart={this.state.waiting} />)} />
{integrationRoutes}
</Switch>
<br />
Expand Down
118 changes: 118 additions & 0 deletions askomics/react/src/routes/data/data.jsx
@@ -0,0 +1,118 @@
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 cellEditFactory from 'react-bootstrap-table2-editor'
import update from 'react-addons-update'
import { withRouter } from "react-router-dom";
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'

class Data extends Component {
constructor (props) {
super(props)
this.utils = new Utils()
this.state = {
isLoading: true,
error: false,
errorMessage: '',
data: [],
}
}

componentDidMount () {
if (!this.props.waitForStart) {
this.loadData()
}
}

loadData() {
let uri = this.props.match.params.uri;
let requestUrl = '/api/data/' + uri
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({
isLoading: false,
data: response.data.data,
})
})
.catch(error => {
console.log(error, error.response.data.errorMessage)
this.setState({
error: true,
errorMessage: error.response.data.errorMessage,
success: !error.response.data.error
})
})
}

componentWillUnmount () {
if (!this.props.waitForStart) {
this.cancelRequest()
}
}

render () {
let uri = this.props.match.params.uri;

let columns = [{
dataField: 'predicat',
text: 'Property',
sort: true,
formatter: (cell, row) => {
if (this.utils.isUrl(cell)) {
return this.utils.splitUrl(cell)
}
return cell
}
},{
dataField: 'object',
text: 'Value',
sort: true,
formatter: (cell, row) => {
if (this.utils.isUrl(cell)) {
if (cell.startsWith(this.props.config.namespaceInternal)){
return this.utils.splitUrl(cell)
} else {
return <a href={cell}>{this.utils.splitUrl(cell)}</a>
}
}
return cell
}
}]


return (
<div className="container">
<h2>Information about uri {uri}</h2>
<br />
<div className="asko-table-height-div">
<BootstrapTable
classes="asko-table"
wrapperClasses="asko-table-wrapper"
tabIndexCell
bootstrap4
keyField='id'
data={this.state.data}
columns={columns}
pagination={paginationFactory()}
noDataIndication={'No results for this URI. You may not have access to any graph including it.'}
/>
</div>
</div>
)
}
}

Data.propTypes = {
waitForStart: PropTypes.bool,
config: PropTypes.object,
match: PropTypes.object
}

export default withRouter(Data)

0 comments on commit 1e2ec30

Please sign in to comment.