Skip to content

Commit

Permalink
Merge pull request #446 from NBISweden/feature/bioschema
Browse files Browse the repository at this point in the history
Feature/bioschema
  • Loading branch information
viklund committed Apr 4, 2018
2 parents 3bc47e9 + d431430 commit 9c0b289
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 15 deletions.
77 changes: 77 additions & 0 deletions backend/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import string
import uuid
import math
import re

import db
import handlers
Expand Down Expand Up @@ -40,6 +41,82 @@ def build_dataset_structure(dataset_version, user=None, dataset=None):
return r


class GetSchema(handlers.UnsafeHandler):
"""
Returns the schema.org, and bioschemas.org, annotation for a given
url.
This function behaves quite differently from the rest of the application as
the structured data testing tool had trouble catching the schema inject
when it went through AngularJS. The solution for now has been to make this
very general function that "re-parses" the 'url' request parameter to
figure out what information to return.
"""
def get(self):

dataset = None
version = None
try:
url = self.get_argument('url')
match = re.match(".*/dataset/([^/]+)(/version/([^/]+))?", url)
if match:
dataset = match.group(1)
version = match.group(3)
except tornado.web.MissingArgumentError:
pass

base = {"@context": "http://schema.org/",
"@type": "DataCatalog",
"name": "SweFreq",
"alternateName": [ "The Swedish Frequency resource for genomics" ],
"description": "The Swedish Frequency resource for genomics (SweFreq) is a website developed to make genomic datasets more findable and accessible in order to promote collaboration, new research and increase public benefit.",
"url": "https://swefreq.nbis.se/",
"provider": {
"@type": "Organization",
"name": "National Bioinformatics Infrastructure Sweden",
"alternateName": [ "NBIS",
"ELIXIR Sweden" ],
"logo": "http://nbis.se/assets/img/logos/nbislogo-green.svg",
"url": "https://nbis.se/"
},
"datePublished": "2016-12-23",
"dateModified": "2017-02-01",
"license": {
"@type": "CreativeWork",
"name": "GNU General Public License v3.0",
"url": "https://www.gnu.org/licenses/gpl-3.0.en.html"
}
}

if dataset:
dataset_schema = {'@type':"Dataset"}

try:
dataset_version = db.get_dataset_version(dataset, version)

if dataset_version.available_from > datetime.now():
# If it's not available yet, only return if user is admin.
if not (self.current_user and self.current_user.is_admin(version.dataset)):
self.send_error(status_code=403)

base_url = "%s://%s" % (self.request.protocol, self.request.host)
dataset_schema['url'] = base_url + "/dataset/" + dataset_version.dataset.short_name
dataset_schema['@id'] = dataset_schema['url']
dataset_schema['name'] = dataset_version.dataset.short_name
dataset_schema['description'] = dataset_version.description
dataset_schema['identifier'] = dataset_schema['name']
dataset_schema['citation'] = dataset_version.ref_doi

base["dataset"] = dataset_schema

except db.DatasetVersion.DoesNotExist as e:
logging.error("Dataset version does not exist: {}".format(e))
except db.DatasetVersionCurrent.DoesNotExist as e:
logging.error("Dataset does not exist: {}".format(e))

self.finish(base)


class ListDatasets(handlers.UnsafeHandler):
def get(self):
# List all datasets available to the current user, earliear than now OR
Expand Down
10 changes: 2 additions & 8 deletions backend/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,8 @@ def write(self, chunk):

def _convert_keys_to_hump_back(chunk):
"""
Converts keys given in snake_case to humbBack-case, while preserving the
Converts keys given in snake_case to humpBack-case, while preserving the
capitalization of the first letter.
This conversion will rewrite a name already in camel, or humpback,
i.e. thisIsAKey -> thisisakey.
If this is unwanted, the conversion can instead be written as:
new_key = k[0] + "".join([a[0].upper() + a[1:] for a in k.split("_")])[1:]
to preserve upper-case letters within words.
"""
if isinstance(chunk, list):
return [_convert_keys_to_hump_back(e) for e in chunk]
Expand All @@ -98,7 +92,7 @@ def _convert_keys_to_hump_back(chunk):
new_chunk = {}
for k, v in chunk.items():
# First character should be the same as in the original string
new_key = k[0] + k.title().replace("_", "")[1:]
new_key = k[0] + "".join([a[0].upper() + a[1:] for a in k.split("_")])[1:]
new_chunk[new_key] = _convert_keys_to_hump_back(v)
return new_chunk

Expand Down
1 change: 1 addition & 0 deletions backend/route.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def __init__(self, settings):
(r"/api/users/me", application.GetUser),
(r"/api/users/datasets", application.UserDatasetAccess),
(r"/api/users/sftp_access", application.SFTPAccess),
(r"/api/schema", application.GetSchema),
### Dataset Api
(r"/api/datasets", application.ListDatasets),
(r"/api/datasets/(?P<dataset>[^\/]+)", application.GetDataset),
Expand Down
14 changes: 14 additions & 0 deletions frontend/assets/js/schema-injector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* This function could not be included in angular, due to what we believe is
* a timing issue. When we had angular inject the schema.org annotation, it
* simply would not register in the testing tool - so now we inject it before
* loading the angular app.
*
* The function passes the current url to the backend, which uses it to
* the correct schema.org annotation from the database.
*/
(function() {
$.getJSON( "/api/schema?url=" + $(location).attr("href"), function(data) {
$("#ldJsonTarget").html( JSON.stringify(data, null, 2) );
});
})();
4 changes: 2 additions & 2 deletions frontend/src/js/app.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
.when("/", { templateUrl: "static/templates/ng-templates/home.html" })
.when("/login", { templateUrl: "static/templates/ng-templates/login.html" })
.when("/profile", { templateUrl: "static/templates/ng-templates/profile.html" })
.when("/error", { templateUrl: "static/templates/ng-templates/error.html" })
.when("/security_warning", { templateUrl: "static/templates/ng-templates/security_warning.html" })
.when("/error", { templateUrl: "static/templates/ng-templates/error.html" })
.when("/security_warning", { templateUrl: "static/templates/ng-templates/security_warning.html" })
.when("/dataset/:dataset", { templateUrl: "static/templates/ng-templates/dataset.html" })
.when("/dataset/:dataset/terms", { templateUrl: "static/templates/ng-templates/dataset-terms.html" })
.when("/dataset/:dataset/download", { templateUrl: "static/templates/ng-templates/dataset-download.html" })
Expand Down
13 changes: 12 additions & 1 deletion frontend/src/js/controller.mainController.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
(function() {
angular.module("App")
.controller("mainController", ["$location", "$cookies", "User", function($location, $cookies, User) {
.controller("mainController", ["$location", "$cookies", "$scope", "User",
function( $location, $cookies, $scope, User) {
var localThis = this;
localThis.url = function() { return $location.path(); };
localThis.loggedIn = false;
Expand All @@ -9,6 +10,16 @@
activate();

function activate() {
// This function is the same as is run from schema-injector.js.
// It re-runs every time the route changes to keep the schema.org
// annotation updated.
// It passes the new url to the backend to update the the schema tag
// based on what is currently being browsed.
$scope.$on("$routeChangeStart", function() {
$.getJSON( "/api/schema?url=" + $location.path(), function(data) {
$("#ldJsonTarget").html( JSON.stringify(data, null, 2) );
});
});
User.getUser().then(function(data) {
localThis.user = data;
localThis.loginType = data.loginType;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/js/controller.profileController.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
(function() {
angular.module("App")
.controller("profileController", ["User", "Countries", "SFTPAccess",
.controller("profileController", ["User", "Countries", "SFTPAccess",
function(User, Countries, SFTPAccess) {
var localThis = this;
localThis.sftp = {"user":"", "password":"", "expires":null};
Expand Down
4 changes: 4 additions & 0 deletions frontend/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<html ng-app="App">
<head>
<title>SweFreq</title>
<script type="application/ld+json" id="ldJsonTarget"></script>
<!-- SCROLLS -->
<meta charset="utf-8" />
<base href="/">
Expand Down Expand Up @@ -131,7 +132,10 @@
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular-route.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular-cookies.min.js"></script>

[% endif %]
<!-- Schema.org injector -->
<script src="/static/js/schema-injector.js"></script>
<!-- The application -->
<script src="/static/js/vendor.js"></script>
<script src="/static/js/app.js"></script>
Expand Down
6 changes: 3 additions & 3 deletions frontend/templates/ng-templates/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ <h1>Your account has been updated!</h1>
<h1>Login to swefreq is changing</h1>
<div class="panel-body">
<p>
To transfer your login credentials from a google account to an elixir account,
just use the buttons below to log in to both google and elixir, we will then
To transfer your login credentials from a google account to an elixir account,
just use the buttons below to log in to both google and elixir, we will then
give you the option to update your account with the elixir information.
</p>
<center>
Expand All @@ -36,7 +36,7 @@ <h1>Login to swefreq is changing</h1>
</button>
</a>
</center>

</div>
</div>
</div>
Expand Down

0 comments on commit 9c0b289

Please sign in to comment.