Skip to content

Commit

Permalink
Feature: add SPARQL endpoint UI (#427)
Browse files Browse the repository at this point in the history
* add yasgui dependency to have a SPARQL editor

* add sparql ednpoint proxy action

* add SPARQL section to the admin page

* add SPARQL section to the ontology viewer
  • Loading branch information
syphax-bouazzouni authored Dec 30, 2023
1 parent b498460 commit b5369a6
Show file tree
Hide file tree
Showing 14 changed files with 730 additions and 11 deletions.
7 changes: 7 additions & 0 deletions app/assets/stylesheets/admin.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@
}
}

.yasgui .autocompleteWrapper, .yasgui .tabContextButton{
display: none !important;
}
.yasqe .yasqe_buttons .yasqe_share{
display: none !important;
}

.alert-box span {
font-weight: bold;
text-transform: uppercase;
Expand Down
1 change: 1 addition & 0 deletions app/assets/stylesheets/application.css.scss.erb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
@import "tom-select/dist/scss/tom-select";
@import "tippy.js/dist/tippy";
@import 'tippy.js/themes/light-border';
@import '@triply/yasgui/build/yasgui.min';
@import "feedback";
@import "login";
@import "components/index";
Expand Down
16 changes: 15 additions & 1 deletion app/controllers/admin_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class AdminController < ApplicationController
include TurboHelper, HomeHelper
include TurboHelper, HomeHelper, SparqlHelper
layout :determine_layout
before_action :cache_setup

Expand All @@ -10,6 +10,20 @@ class AdminController < ApplicationController
PARSE_LOG_URL = lambda { |acronym| "#{ONTOLOGY_URL.call(acronym)}/log" }
REPORT_NEVER_GENERATED = "NEVER GENERATED"


def sparql_endpoint
graph = params["named-graph-uri"]
if !session[:user]&.admin? && !graph.blank?
acronym = graph.split('/')[-3]
@ontology = LinkedData::Client::Models::Ontology.find_by_acronym(acronym).first
render(inline: 'Query not permitted') && return if @ontology.nil? || @ontology.errors
end

response = helpers.ontology_sparql_query(params[:query], graph)

render inline: response
end

def index
@users = LinkedData::Client::Models::User.all
@ontology_visits = ontology_visits_data
Expand Down
15 changes: 13 additions & 2 deletions app/controllers/ontologies_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class OntologiesController < ApplicationController
include MappingStatistics
include OntologyUpdater
include TurboHelper
include SparqlHelper
include SubmissionFilter

require 'multi_json'
Expand All @@ -22,7 +23,7 @@ class OntologiesController < ApplicationController

before_action :authorize_and_redirect, :only => [:edit, :update, :create, :new]
before_action :submission_metadata, only: [:show]
KNOWN_PAGES = Set.new(["terms", "classes", "mappings", "notes", "widgets", "summary", "properties", "instances", "schemes", "collections"])
KNOWN_PAGES = Set.new(["terms", "classes", "mappings", "notes", "widgets", "summary", "properties", "instances", "schemes", "collections", "sparql"])
EXTERNAL_MAPPINGS_GRAPH = "http://data.bioontology.org/metadata/ExternalMappings"
INTERPORTAL_MAPPINGS_GRAPH = "http://data.bioontology.org/metadata/InterportalMappings"

Expand Down Expand Up @@ -208,6 +209,14 @@ def collections
end
end


def sparql
if request.xhr?
render partial: 'ontologies/sections/sparql', layout: false
else
render partial: 'ontologies/sections/sparql', layout: 'ontology_viewer'
end
end
# GET /ontologies/ACRONYM
# GET /ontologies/1.xml
def show
Expand Down Expand Up @@ -240,7 +249,7 @@ def show

# Note: find_by_acronym includes ontology views
@ontology = LinkedData::Client::Models::Ontology.find_by_acronym(params[:ontology]).first
ontology_not_found(params[:ontology]) if @ontology.nil?
ontology_not_found(params[:ontology]) if @ontology.nil? || @ontology.errors

# Handle the case where an ontology is converted to summary only.
# See: https://github.com/ncbo/bioportal_web_ui/issues/133.
Expand Down Expand Up @@ -288,6 +297,8 @@ def show
self.schemes
when 'collections'
self.collections
when 'sparql'
self.sparql
else
self.summary
end
Expand Down
2 changes: 1 addition & 1 deletion app/helpers/ontologies_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ def sections_to_show
sections += %w[properties]
sections += %w[schemes collections] if skos?
sections += %w[instances] unless skos?
sections += %w[notes mappings widgets]
sections += %w[notes mappings widgets sparql]
end
sections
end
Expand Down
67 changes: 67 additions & 0 deletions app/helpers/sparql_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
module SparqlHelper
def change_from_clause(query, graph)
unless graph.blank?
graph = graph.gsub($REST_URL, 'http://data.bioontology.org')

if query.match?(/FROM <[^>]+>/i)
# Use a regular expression to replace all instances of FROM <uri>
query = query.gsub(/FROM <[^>]+>/i, "")
else
query = query.gsub("WHERE", "FROM <#{graph}> WHERE")
end

query = query.gsub(/GRAPH <[^>]+>/i, "")
end
query
end
def ontology_sparql_query(query, graph = '')
query = change_from_clause(query, graph)
sparql_query(query)
end
def is_allowed_query?(sparql_query)
forbidden_operations = [
'INSERT DATA',
'DELETE DATA',
'DELETE/INSERT',
'DELETE',
'INSERT',
'DELETE WHERE',
'LOAD',
'CLEAR',
'CREATE',
'DROP',
'COPY',
'MOVE',
'ADD'
]

# Define a regular expression to match SELECT queries
select_query_regex = /\A\s*SELECT\b/m

# Check if the query contains any forbidden operations outside SELECT queries
return false if forbidden_operations.any? { |op| sparql_query.upcase.include?(op) && !sparql_query.match(select_query_regex) }

true
end

def sparql_query(query)
return 'No SPARQL endpoint configured' if $SPARQL_URL.blank?
return 'INSERT Queries not permitted' unless is_allowed_query?(query)
endpoint = $SPARQL_URL.gsub('test', 'sparql')
begin
conn = Faraday.new do |conn|
conn.options.timeout = 60
end
response = conn.get("#{endpoint}?query=#{encode_param(query)}")
response.body.force_encoding('ISO-8859-1').encode('UTF-8')
rescue
"Query timeout"
end
end
def sparql_query_container(graph: nil)
content_tag(:div, '', data: {controller: 'sparql',
'sparql-proxy-value': '/sparql_proxy/',
'sparql-graph-value': graph})
end

end
3 changes: 3 additions & 0 deletions app/javascript/controllers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ application.register("simple-tree", SimpleTreeController)
import SkosCollectionColorsController from "./skos_collection_colors_controller"
application.register("skos-collection-colors", SkosCollectionColorsController)

import SparqlController from "./sparql_controller"
application.register("sparql", SparqlController)

import TextTruncateController from "./text_truncate_controller"
application.register("text-truncate", TextTruncateController)

Expand Down
25 changes: 25 additions & 0 deletions app/javascript/controllers/sparql_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Controller } from '@hotwired/stimulus'
import { getYasgui } from '../mixins/useYasgui'

// Connects to data-controller="sparql"
export default class extends Controller {
static values = {
proxy: String,
graph: String,
}
connect () {
localStorage.removeItem('yagui__config');
this.yasgui = getYasgui(this.element,
{
corsProxy: this.proxyValue,
copyEndpointOnNewTab: true,
requestConfig: {
endpoint: this.proxyValue,
acceptHeaderGraph: false,
acceptHeaderUpdate: false,
namedGraphs: [this.graphValue],
}
})

}
}
6 changes: 6 additions & 0 deletions app/javascript/mixins/useYasgui.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Yasgui from "@triply/yasgui";

export const getYasgui = (elem, config) => {
return new Yasgui(elem, config)
}

13 changes: 6 additions & 7 deletions app/views/admin/index.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@

%div
%div.mx-1
- sections = ['Analytics', 'Site Administration','Ontology Administration', 'Licensing', 'Users', 'Metadata Administration', 'Groups', 'Categories', 'Persons & Organizations']
- sections = ['Analytics', 'Site Administration','Ontology Administration', 'Licensing', 'Users', 'Metadata Administration', 'Groups', 'Categories', 'Persons & Organizations', 'SPARQL']
- selected = params[:section] || sections.first
= render TabsContainerComponent.new do |t|
- sections.each do |section_title|
- t.item(title: section_title,
path: '',
selected: section_title.eql?(sections.first),
selected: section_title.downcase.eql?(selected),
page_name: '')
- t.item_content do
= render 'analytics'
Expand Down Expand Up @@ -66,8 +67,6 @@
%div.ontologies_list_container.mt-3
%div.mx-auto.w-75
= render TurboFrameComponent.new(id: 'agents-list', src: '/agents', loading: 'lazy')

-# Groups tab
%div.tab-pane.fade{id: "groups", role: "tabpanel", aria: { labelledby: "groups-admin-tab" }}
%div.ontologies_list_container.mt-3
%table#adminGroups.zebra{:cellpadding => "0", :cellspacing => "0", :width => "100%"}
- t.item_content do
%div.container
= sparql_query_container
2 changes: 2 additions & 0 deletions app/views/ontologies/sections/_sparql.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
= render TurboFrameComponent.new(id: "sparql", data: {"turbo-frame-target": "frame"} ) do
= sparql_query_container(graph: @submission_latest.id )
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@

get '' => 'home#index'

match 'sparql_proxy', to: 'admin#sparql_endpoint', via: [:get, :post]

# Top-level pages
match '/feedback', to: 'home#feedback', via: [:get, :post]
get '/account' => 'home#account'
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"dependencies": {
"@hotwired/stimulus": "^3.0.1",
"@hotwired/turbo-rails": "^7.1.1",
"@triply/yasgui": "^4.2.28",
"chart.js": "^4.4.1",
"debounce": "^1.2.1",
"esbuild": "^0.14.41",
Expand Down
Loading

0 comments on commit b5369a6

Please sign in to comment.