Commit 32c97b7b authored by Mathieu Giraud's avatar Mathieu Giraud

Merge branch 'feature-sc/url_params' into 'dev'

Nicer URLs

Closes #2792

See merge request !720
parents 069f79a6 8f2c4cd6
Pipeline #152372 passed with stages
in 18 minutes and 25 seconds
...@@ -48,3 +48,5 @@ germline/germline-59.tar.gz ...@@ -48,3 +48,5 @@ germline/germline-59.tar.gz
germline/IMGT_RELEASE germline/IMGT_RELEASE
vidjil-algo vidjil-algo
server/web2py/gluon/packages/dal/
...@@ -26,8 +26,9 @@ ...@@ -26,8 +26,9 @@
<link rel="shortcut icon" href="./images/favicon-v.ico" type="image/x-icon"> <link rel="shortcut icon" href="./images/favicon-v.ico" type="image/x-icon">
<link rel="icon" href="./images/favicon-v.ico" type="image/x-icon"> <link rel="icon" href="./images/favicon-v.ico" type="image/x-icon">
<link rel="stylesheet" type="text/css" href="css/fonts/ubuntu/stylesheet.css" />
<link rel="stylesheet" type="text/css" href="css/fonts/ubuntu-mono/stylesheet.css" /> <link rel="stylesheet" type="text/css" href="css/fonts/ubuntu/stylesheet.css" />
<link rel="stylesheet" type="text/css" href="css/fonts/ubuntu-mono/stylesheet.css" />
<link rel="stylesheet" type="text/css" href="css/icons/fontello/css/fontello.css" /> <link rel="stylesheet" type="text/css" href="css/icons/fontello/css/fontello.css" />
<link rel="stylesheet" type="text/css" href="css/icons/fontello/css/animation.css" /> <link rel="stylesheet" type="text/css" href="css/icons/fontello/css/animation.css" />
<link rel="stylesheet" href="css/vmi/vmi.css" /> <link rel="stylesheet" href="css/vmi/vmi.css" />
...@@ -322,7 +323,6 @@ ...@@ -322,7 +323,6 @@
</div></div> </div></div>
</div> </div>
<div class="menu" id="alert"></div> <div class="menu" id="alert"></div>
<div id="logo" onclick="console.log({'type': 'popup', 'default':'welcome'})">Vidjil <span class='logo'>(beta)</span></div> <div id="logo" onclick="console.log({'type': 'popup', 'default':'welcome'})">Vidjil <span class='logo'>(beta)</span></div>
<!-- <!--
......
var DEFAULT_DB_ADDRESS="https://db.vidjil.org/vidjil/"; var DEFAULT_DB_ADDRESS="https://db.vidjil.org/vidjil/";
requirejs.config({ requirejs.config({
baseUrl: 'js/lib', baseUrl: './js/lib',
paths: { paths: {
app: '', app: '',
jquery: 'jquery-3.3.1.min', jquery: 'jquery-3.3.1.min',
......
...@@ -77,6 +77,7 @@ Model.prototype = { ...@@ -77,6 +77,7 @@ Model.prototype = {
build: function () { build: function () {
var self =this; var self =this;
this.waiting_screen_is_on = false;
this.waiting_screen = document.createElement("div"); this.waiting_screen = document.createElement("div");
this.waiting_screen.className = "waiting_screen"; this.waiting_screen.className = "waiting_screen";
...@@ -1304,10 +1305,15 @@ changeAlleleNotation: function(alleleNotation) { ...@@ -1304,10 +1305,15 @@ changeAlleleNotation: function(alleleNotation) {
* return true if a view has not finished an update * return true if a view has not finished an update
*/ */
updateIsPending:function(){ updateIsPending:function(){
for (var i = 0; i < this.view.length; i++) { //check if a view is waiting an update
for (var i = 0; i < this.view.length; i++)
if (this.view[i].updateIsPending()) if (this.view[i].updateIsPending())
return true; return true;
}
//check waiting screen
if (this.waiting_screen_is_on)
return true;
return false; return false;
}, },
...@@ -2629,12 +2635,16 @@ changeAlleleNotation: function(alleleNotation) { ...@@ -2629,12 +2635,16 @@ changeAlleleNotation: function(alleleNotation) {
this.waiting_screen.style.display = "block"; this.waiting_screen.style.display = "block";
this.waiting_msg.innerHTML= text; this.waiting_msg.innerHTML= text;
if (typeof shortcut != 'undefined') shortcut.on = false; if (typeof shortcut != 'undefined') shortcut.on = false;
this.waiting_screen_is_on = true;
this.updateIcon();
}, },
resume: function(){ resume: function(){
this.waiting_screen.style.display = "none"; this.waiting_screen.style.display = "none";
this.waiting_msg.removeAllChildren(); this.waiting_msg.removeAllChildren();
if (typeof shortcut != 'undefined') shortcut.on = true; if (typeof shortcut != 'undefined') shortcut.on = true;
this.waiting_screen_is_on = false;
this.updateIcon();
}, },
......
function Url(model, win) { function Url(model, win) {
View.call(this, model); View.call(this, model);
this.m = model; this.m = model;
this.window = (typeof win != "undefined") ? win : window this.window = (typeof win != "undefined") ? win : window
...@@ -33,9 +32,9 @@ Url.prototype= { ...@@ -33,9 +32,9 @@ Url.prototype= {
} }
// get sample_set/patient/run, config... // get sample_set/patient/run, config...
var straight_params = this.getStraightParams(); var positionnal_params = this.getPositionnalParams();
for (var i = 0; i < straight_params.length; i++) { for (var i = 0; i < positionnal_params.length; i++) {
var p = straight_params[i]; var p = positionnal_params[i];
if (typeof this.m[p] !== "undefined") { if (typeof this.m[p] !== "undefined") {
params_dict[p] = this.m[p]; params_dict[p] = this.m[p];
} }
...@@ -62,6 +61,7 @@ Url.prototype= { ...@@ -62,6 +61,7 @@ Url.prototype= {
/** /**
* update(size/style/position) a list of selected clones <br> * update(size/style/position) a list of selected clones <br>
* a slight function for operation who impact only a bunch of clones (merge/split/...) * a slight function for operation who impact only a bunch of clones (merge/split/...)
* @abstract
* @param {integer[]} list - array of clone index * @param {integer[]} list - array of clone index
* */ * */
updateElem : function (list) { updateElem : function (list) {
...@@ -71,6 +71,7 @@ Url.prototype= { ...@@ -71,6 +71,7 @@ Url.prototype= {
/** /**
* update(style only) a list of selected clones <br> * update(style only) a list of selected clones <br>
* a slight function for operation who impact only styles of clones (select/focus) * a slight function for operation who impact only styles of clones (select/focus)
* @abstract
* @param {integer[]} list - array of clone index * @param {integer[]} list - array of clone index
* */ * */
updateElemStyle : function () { updateElemStyle : function () {
...@@ -79,10 +80,10 @@ Url.prototype= { ...@@ -79,10 +80,10 @@ Url.prototype= {
applyURL : function() { applyURL : function() {
var straight_params = this.getStraightParams(); var positionnal_params = this.getPositionnalParams();
for (var i = 0; i < straight_params.length; i++) { for (var i = 0; i < positionnal_params.length; i++) {
if (typeof this.url_dict[straight_params[i]] !== "undefined") { if (typeof this.url_dict[positionnal_params[i]] !== "undefined") {
this.m[straight_params[i]] = this.url_dict[straight_params[i]]; this.m[positionnal_params[i]] = this.url_dict[positionnal_params[i]];
} }
} }
...@@ -90,7 +91,7 @@ Url.prototype= { ...@@ -90,7 +91,7 @@ Url.prototype= {
var clones = this.url_dict.clone.split(','); var clones = this.url_dict.clone.split(',');
for (var j = 0; j < clones.length; j++) { for (var j = 0; j < clones.length; j++) {
var c = this.m.clone(clones[j]); var c = this.m.clone(clones[j]);
if (typeof c !== "undefined" && c.isInteractable()) { if (typeof c !== "undefined" && !c.isVirtual()) {
c.select = true; c.select = true;
} }
} }
...@@ -107,7 +108,7 @@ Url.prototype= { ...@@ -107,7 +108,7 @@ Url.prototype= {
}, },
parseUrlParams:function (urlparams) { parseUrlParams:function (urlparams) {
params={} params={}
if (urlparams.length === 0) { if (urlparams.length === 0) {
return params; return params;
} }
...@@ -117,7 +118,11 @@ Url.prototype= { ...@@ -117,7 +118,11 @@ Url.prototype= {
var p = params[tmparr[0]]; var p = params[tmparr[0]];
var key = this.encoder.decode(tmparr[0]); var key = this.encoder.decode(tmparr[0]);
var val = tmparr[1]; var val = tmparr[1];
if (typeof p === "undefined") { if ((key == "") /*empty keys are due to the use of "//" in url, ignore them*/ ||
(typeof val == "undefined")) {
//do nothing
}
else if (typeof p === "undefined") {
params[key] = val; params[key] = val;
} else if (p.constructor === String){ } else if (p.constructor === String){
params[key] = []; params[key] = [];
...@@ -127,28 +132,42 @@ Url.prototype= { ...@@ -127,28 +132,42 @@ Url.prototype= {
params[key].push(val); params[key].push(val);
} }
} }
var url = this.window.location;
var positionnal_params = url.pathname.substr(1).split('-');
var pos_param_keys = this.getPositionnalParams();
if (positionnal_params.length > 1 && positionnal_params[0] != "index.html")
for (var j = 0; j < positionnal_params.length; j++)
params[pos_param_keys[j]] = positionnal_params[j];
return params return params
}, },
generateParamsString: function(params_dict) { generateParamsString: function(params_dict) {
var params_list = []; var params_list = [];
var positionnal_params = [];
for (var key in params_dict){ for (var key in params_dict){
if ((typeof key != "undefined" && key !== "") && (typeof params_dict[key]!= "undefined")) { var val = params_dict[key];
var encoded = this.encoder.encode(key); if ((typeof key != "undefined" && key !== "") && (typeof val != "undefined")) {
if (params_dict[key].constructor !== Array && params_dict[key] !== '') { var pos = this.getPosition(key);
params_list.push(encoded+"="+params_dict[key]) if (pos >= 0) {
} else if (params_dict[key].constructor === Array) { positionnal_params[pos] = val;
for (var i = 0; i < params_dict[key].length; i++) { } else {
params_list.push(encoded+"="+params_dict[key][i]); var encoded = this.encoder.encode(key);
if (val.constructor !== Array && val !== '') {
params_list.push(encoded+"="+val)
} else if (val.constructor === Array) {
for (var i = 0; i < val.length; i++) {
params_list.push(encoded+"="+val[i]);
}
} }
} }
} }
} }
return params_list.join("&"); return positionnal_params.join('-') + '?' + params_list.join("&");
}, },
pushUrl: function(params) { pushUrl: function(params) {
var new_url = "?" + params; var new_url = params;
try { try {
this.window.history.pushState('plop', 'plop', new_url); this.window.history.pushState('plop', 'plop', new_url);
} catch(error) { } catch(error) {
...@@ -156,10 +175,14 @@ Url.prototype= { ...@@ -156,10 +175,14 @@ Url.prototype= {
} }
}, },
getStraightParams: function() { getPositionnalParams: function() {
return ["sample_set_id", "config"]; return ["sample_set_id", "config"];
}, },
getPosition: function(param) {
return this.getPositionnalParams().indexOf(param);
},
loadUrl: function(db, args, filename) { loadUrl: function(db, args, filename) {
this.url_dict = args; this.url_dict = args;
var newParams = this.generateParamsString(args); var newParams = this.generateParamsString(args);
...@@ -176,12 +199,13 @@ Url.prototype= { ...@@ -176,12 +199,13 @@ Url.prototype= {
}; };
Url.prototype = $.extend(Object.create(View.prototype), Url.prototype); Url.prototype = $.extend(Object.create(View.prototype), Url.prototype);
function UrlEncoder() { function UrlEncoder() {
this.encoding = { this.encoding = {
'sample_set_id': 'set', 'sample_set_id': 'set',
'patient_id': 'patient', 'patient_id': 'patient',
'run_id': 'run' 'run_id': 'run',
'config': 'conf'
}; };
this.decoding = {}; this.decoding = {};
......
...@@ -3,16 +3,37 @@ QUnit.module("Url", { ...@@ -3,16 +3,37 @@ QUnit.module("Url", {
}); });
/* ------------------------------------ */ /* ------------------------------------ */
var current_url = "mock://" var initial_url = "mock://";
var current_url = initial_url;
function getSearch(url) {
var search = url.split('?')[1] ;
return (search == "" ? "" : '?' + search)
}
function getPathname(url) {
// remove protocol
var pathname = url.split('//');
pathname = pathname[pathname.length-1];
// remove search
pathname = pathname.split('?')[0];
// remove hostname
pathname = pathname.split('/');
return '/' + pathname.slice(1).join('/');
}
var windowMock = { var windowMock = {
mocked: true, mocked: true,
location: { location: {
search: { search: "",
toString: function() { var search = (current_url + '?').split('?')[1] ; pathname: ""
return (search == "" ? "" : '?' + search) } },
}},
history: { history: {
pushState: function(x, y, url) { current_url = url } pushState: function(x, y, url) {
current_url = url;
windowMock.location.search = getSearch(url);
windowMock.location.pathname = getPathname(url);
}
} }
}; };
windowMock.window = windowMock windowMock.window = windowMock
...@@ -113,4 +134,61 @@ QUnit.test("plot : modifyURL",function (assert) { with (windowMock) { ...@@ -113,4 +134,61 @@ QUnit.test("plot : modifyURL",function (assert) { with (windowMock) {
}}); }});
QUnit.test("url: parse", function(assert) { with (windowMock) {
windowMock.history.pushState('plop', 'plop', 'mock://foo.bar?param1=foo&param2=bar');
var m = new Model();
var url = new Url(m, windowMock);
var params = url.parseUrlParams('?param1=foo&param2=bar');
assert.deepEqual(params, {
"param1": "foo",
"param2": "bar"
}, "test url parse correct url");
windowMock.history.pushState('plop', 'plop', 'mock://foo.bar?fakeparam&realparam=real');
url = new Url(m, windowMock);
params = url.parseUrlParams('?fakeparam&realparam=real');
assert.deepEqual(params, {
'realparam': 'real'
});
}})
QUnit.test("url: generate", function(assert) { with (windowMock) {
var params = {
'param1': 'first',
'param2': 'second',
'param3': 'third'
};
var m = new Model();
var url = new Url(m, windowMock);
var param_string = url.generateParamsString(params);
assert.equal(param_string, "?param1=first&param2=second&param3=third");
}});
QUnit.test("url: positional parse", function(assert) { with (windowMock) {
var m = new Model();
windowMock.history.pushState('plop', 'plop', 'mock://foo.bar/1-3?param3=third');
var url = new Url(m, windowMock);
var params = url.parseUrlParams('?param3=third');
assert.deepEqual(params, {
'sample_set_id': '1',
'config': '3',
'param3': 'third'
});
}});
QUnit.test("url: positional generate", function(assert) { with (windowMock) {
var params = {
'sample_set_id': 1,
'config': 4,
'foobar': 'barfoo',
'param4': 'fourth'
};
var m = new Model();
var url = new Url(m, windowMock);
var param_string = url.generateParamsString(params);
assert.equal(param_string, '1-4?foobar=barfoo&param4=fourth');
}});
...@@ -32,6 +32,8 @@ class TestScatterplot < BrowserTest ...@@ -32,6 +32,8 @@ class TestScatterplot < BrowserTest
# id 29 --> seg5; seg3 (IGHV1; IGHJ1) # id 29 --> seg5; seg3 (IGHV1; IGHJ1)
def test_01_multiple_select_barmode def test_01_multiple_select_barmode
$b.menu_filter.click
$b.update_icon.wait_while(&:present?)
$b.send_keys 4 $b.send_keys 4
$b.update_icon.wait_while(&:present?) $b.update_icon.wait_while(&:present?)
# to verify correct selection, We will look in semgenter the presence if clone entrie # to verify correct selection, We will look in semgenter the presence if clone entrie
...@@ -57,6 +59,8 @@ class TestScatterplot < BrowserTest ...@@ -57,6 +59,8 @@ class TestScatterplot < BrowserTest
end end
def test_02_multiple_select_bubble def test_02_multiple_select_bubble
$b.menu_filter.click
$b.update_icon.wait_while(&:present?)
$b.send_keys 1 $b.send_keys 1
$b.update_icon.wait_while(&:present?) $b.update_icon.wait_while(&:present?)
# to verify correct selection, We will look in semgenter the presence if clone entrie # to verify correct selection, We will look in semgenter the presence if clone entrie
......
...@@ -31,6 +31,7 @@ run ln -s /etc/vidjil/nginx_gzip_static.conf /etc/nginx/conf.d/web2py/gzip_stati ...@@ -31,6 +31,7 @@ run ln -s /etc/vidjil/nginx_gzip_static.conf /etc/nginx/conf.d/web2py/gzip_stati
run ln -s /etc/vidjil/nginx_gzip.conf /etc/nginx/conf.d/web2py/gzip.conf run ln -s /etc/vidjil/nginx_gzip.conf /etc/nginx/conf.d/web2py/gzip.conf
run ln -s /etc/vidjil/uwsgi.conf /etc/nginx/conf.d/web2py/uwsgi.conf run ln -s /etc/vidjil/uwsgi.conf /etc/nginx/conf.d/web2py/uwsgi.conf
run ln -s /etc/vidjil/germline.js /usr/share/vidjil/browser/js/germline.js run ln -s /etc/vidjil/germline.js /usr/share/vidjil/browser/js/germline.js
run ln -s /usr/share/vidjil/browser /usr/share/vidjil/b
copy ./scripts/nginx-entrypoint.sh /entrypoints/nginx-entrypoint.sh copy ./scripts/nginx-entrypoint.sh /entrypoints/nginx-entrypoint.sh
run chown -R www-data:www-data /usr/share/vidjil run chown -R www-data:www-data /usr/share/vidjil
......
location / {
root /usr/share/vidjil/browser;
rewrite ^/([0-9]+)-([0-9]+)/(css|js|image|test)/(.*) /$3/$4 redirect;
rewrite ^/([0-9]+)-([0-9]+)/ /?set=$1&config=$2 redirect;
rewrite ^/([0-9]+)-([0-9]+) /?set=$1&config=$2 redirect;
expires 1h;
add_header Cache-Control must-revalidate;
error_page 405 = $uri;
}
location /browser {
root /usr/share/vidjil;
rewrite ^/browser/([0-9]+)-([0-9]+)/(css|js|image|test)/(.*) /browser/$3/$4 redirect;
rewrite ^/browser/([0-9]+)-([0-9]+)/ /browser/?set=$1&config=$2 redirect;
rewrite ^/browser/([0-9]+)-([0-9]+) /browser/?set=$1&config=$2 redirect;
expires 1h;
add_header Cache-Control must-revalidate;
error_page 405 = $uri;
}
location /germline {
root /usr/share/vidjil/;
expires 1h;
add_header Cache-Control must-revalidate;
error_page 405 = $uri;
}
...@@ -24,60 +24,8 @@ server { ...@@ -24,60 +24,8 @@ server {
keepalive_timeout 70; keepalive_timeout 70;
uwsgi_max_temp_file_size 20480m; uwsgi_max_temp_file_size 20480m;
location / {
include /etc/nginx/conf.d/web2py/uwsgi.conf;
proxy_read_timeout 600;
client_max_body_size 20G;
###
}
## if you serve static files through https, copy here the section
## from the previous server instance to manage static files
location /browser {
root /usr/share/vidjil;
expires 1h;
add_header Cache-Control must-revalidate;
error_page 405 = $uri;
}
location /germline {
root /usr/share/vidjil/;
expires 1h;
add_header Cache-Control must-revalidate;
error_page 405 = $uri;
}
###to enable correct use of response.static_version
#location ~* ^/(\w+)/static(?:/_[\d]+\.[\d]+\.[\d]+)?/(.*)$ {
# alias /usr/share/vidjil/server/web2py/applications/$1/static/$2;
# expires max;
#
#}
###
client_max_body_size 20G;
location /cgi/ {
gzip off;
root /usr/share/vidjil/browser/;
# Fastcgi socket
fastcgi_pass unix:/var/run/fcgiwrap.socket;
# Fastcgi parameters, include the standard ones
include /etc/nginx/fastcgi_params;
# Adjust non standard parameters (SCRIPT_FILENAME)
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location /vidjil/file/upload {
include /etc/nginx/conf.d/web2py/uwsgi.conf;
uwsgi_read_timeout 10m;
client_max_body_size 20G; client_max_body_size 20G;
}
include /etc/vidjil/server_location.conf;
include /etc/vidjil/client_location.conf;
} }
...@@ -7,60 +7,8 @@ server { ...@@ -7,60 +7,8 @@ server {
keepalive_timeout 70; keepalive_timeout 70;
uwsgi_max_temp_file_size 20480m; uwsgi_max_temp_file_size 20480m;
location / {
include /etc/nginx/conf.d/web2py/uwsgi.conf;
proxy_read_timeout 600;
client_max_body_size 20G;
###
}
## if you serve static files through https, copy here the section
## from the previous server instance to manage static files
location /browser {
root /usr/share/vidjil;
expires 1h;
add_header Cache-Control must-revalidate;
error_page 405 = $uri;
}
location /germline {
root /usr/share/vidjil/;
expires 1h;
add_header Cache-Control must-revalidate;
error_page 405 = $uri;
}
###to enable correct use of response.static_version
#location ~* ^/(\w+)/static(?:/_[\d]+\.[\d]+\.[\d]+)?/(.*)$ {
# alias /usr/share/vidjil/server/web2py/applications/$1/static/$2;
# expires max;
#
#}
###
client_max_body_size 20G;
location /cgi/ {
gzip off;
root /usr/share/vidjil/browser/;
# Fastcgi socket
fastcgi_pass unix:/var/run/fcgiwrap.socket;
# Fastcgi parameters, include the standard ones
include /etc/nginx/fastcgi_params;
# Adjust non standard parameters (SCRIPT_FILENAME)
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location /vidjil/file/upload {
include /etc/nginx/conf.d/web2py/uwsgi.conf;
uwsgi_read_timeout 10m;
client_max_body_size 20G; client_max_body_size 20G;
}
include /etc/vidjil/server_location.conf;
include /etc/vidjil/client_location.conf;
} }