Commit c6099ae7 authored by flothoni's avatar flothoni

Merge branch 'feature-sc/better_pagination_sampleset' of...

Merge branch 'feature-sc/better_pagination_sampleset' of gitlab.inria.fr:vidjil/vidjil into feature-sc/better_pagination_sampleset
parents 9a8ddf4e 32c97b7b
Pipeline #152373 passed with stages
in 22 minutes and 22 seconds
......@@ -48,3 +48,5 @@ germline/germline-59.tar.gz
germline/IMGT_RELEASE
vidjil-algo
server/web2py/gluon/packages/dal/
/*
This file is part of Vidjil-algo <http://www.vidjil.org>
Copyright (C) 2011-2019 by VidjilNet consortium and Bonsai bioinformatics
Copyright (C) 2011-2020 by VidjilNet consortium and Bonsai bioinformatics
at CRIStAL (UMR CNRS 9189, Université Lille) and Inria Lille
Contributors:
Mathieu Giraud <mathieu.giraud@vidjil.org>
......@@ -202,7 +202,7 @@ string string_NO_LIMIT(string s)
int main (int argc, char **argv)
{
cout << "# " << PROGNAME << " -- V(D)J recombinations analysis <http://www.vidjil.org/>" << endl
<< "# Copyright (C) 2011-2019 by the Vidjil team" << endl
<< "# Copyright (C) 2011-2020 by the Vidjil team" << endl
<< "# Bonsai bioinformatics at CRIStAL (UMR CNRS 9189, Université Lille) and Inria Lille" << endl
<< "# VidjilNet consortium" << endl
<< endl
......
......@@ -26,8 +26,9 @@
<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="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/animation.css" />
<link rel="stylesheet" href="css/vmi/vmi.css" />
......@@ -322,7 +323,6 @@
</div></div>
</div>
<div class="menu" id="alert"></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/";
requirejs.config({
baseUrl: 'js/lib',
baseUrl: './js/lib',
paths: {
app: '',
jquery: 'jquery-3.3.1.min',
......
......@@ -932,13 +932,23 @@ Clone.prototype = {
}
var s = ''
s += this.sequence.substring(0, this.seg['5'].stop+1)
s += '\n'
if (this.seg['5'].start != undefined && this.seg['5'].start != 0){
s += this.sequence.substring(0, this.seg['5'].start) + '\n'
s += this.sequence.substring(this.seg['5'].start, this.seg['5'].stop+1) + '\n'
} else {
s += this.sequence.substring(0, this.seg['5'].stop+1) + '\n'
}
if (this.seg['5'].stop+1 < this.seg['3'].start ) {
s += this.sequence.substring(this.seg['5'].stop+1, this.seg['3'].start)
s += '\n'
}
s += this.sequence.substring(this.seg['3'].start)
if (this.seg['3'].stop != undefined && this.seg['3'].stop != this.sequence.length){
s += this.sequence.substring(this.seg['3'].start, this.seg['3'].stop+1) +'\n'
s += this.sequence.substring(this.seg['3'].stop+1)
} else {
s += this.sequence.substring(this.seg['3'].start)
}
return s
},
......
......@@ -77,6 +77,7 @@ Model.prototype = {
build: function () {
var self =this;
this.waiting_screen_is_on = false;
this.waiting_screen = document.createElement("div");
this.waiting_screen.className = "waiting_screen";
......@@ -1304,10 +1305,15 @@ changeAlleleNotation: function(alleleNotation) {
* return true if a view has not finished an update
*/
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())
return true;
}
//check waiting screen
if (this.waiting_screen_is_on)
return true;
return false;
},
......@@ -2629,12 +2635,16 @@ changeAlleleNotation: function(alleleNotation) {
this.waiting_screen.style.display = "block";
this.waiting_msg.innerHTML= text;
if (typeof shortcut != 'undefined') shortcut.on = false;
this.waiting_screen_is_on = true;
this.updateIcon();
},
resume: function(){
this.waiting_screen.style.display = "none";
this.waiting_msg.removeAllChildren();
if (typeof shortcut != 'undefined') shortcut.on = true;
this.waiting_screen_is_on = false;
this.updateIcon();
},
......
......@@ -1570,20 +1570,25 @@ Sequence.prototype = Object.create(genSeq.prototype);
// We first put the end positions
highlights.push({'type':'N', 'color': "", 'start': vdjArray["5"].stop});
highlights.push({'type':'N', 'color': "", 'start': vdjArray["3"].start});
highlights.push({'type':'before5', 'color': "black", 'start': 0}); // from seq start to 5 start
highlights.push({'type':'after3', 'color': "black", 'start': vdjArray["3"].stop}); // from 3 stop to seq end
// TOOD: remove the two previous lines, see #2135
var key;
for (var i in SEGMENT_KEYS) {
key = SEGMENT_KEYS[i]
if (typeof vdjArray[key] != 'undefined' && typeof vdjArray[key].stop != 'undefined'){
highlights.push({'type':'N', 'color': "", 'start': vdjArray[key].stop});
}}
}
}
// We now put the start positions (that may override previous end positions)
for (var j in SEGMENT_KEYS) {
key = SEGMENT_KEYS[j]
if (typeof vdjArray[key] != 'undefined' && typeof vdjArray[key].start!= 'undefined'){
highlights.push({'type':'D', 'color': "", 'start': vdjArray[key].start});
}}
}
}
highlights.push({'type':'V', 'color': this.m.colorMethod == "V" ? clone.colorV : "", 'start': vdjArray["5"].start});
highlights.push({'type':'J', 'color': this.m.colorMethod == "J" ? clone.colorJ : "", 'start': vdjArray["3"].start});
......@@ -1615,10 +1620,10 @@ Sequence.prototype = Object.create(genSeq.prototype);
Sequence.prototype.getVdjStartEnd = function (clone) {
var vdjArray ={"5": {}, "3": {}} ;
vdjArray["5"].start = 0;
vdjArray["5"].stop = this.pos[clone.seg["5"].stop] + 1;
vdjArray["5"].start = (clone.seg["5"].start != undefined) ? this.pos[clone.seg["5"].start] : 0;
vdjArray["5"].stop = this.pos[clone.seg["5"].stop] + 1;
vdjArray["3"].start = this.pos[clone.seg["3"].start];
vdjArray["3"].stop = this.seq.length;
vdjArray["3"].stop = (clone.seg["3"].stop != undefined) ? this.pos[clone.seg["3"].stop] : this.seq.length;
for (var i in SEGMENT_KEYS)
{
......
function Url(model, win) {
View.call(this, model);
this.m = model;
this.window = (typeof win != "undefined") ? win : window
......@@ -33,9 +32,9 @@ Url.prototype= {
}
// get sample_set/patient/run, config...
var straight_params = this.getStraightParams();
for (var i = 0; i < straight_params.length; i++) {
var p = straight_params[i];
var positionnal_params = this.getPositionnalParams();
for (var i = 0; i < positionnal_params.length; i++) {
var p = positionnal_params[i];
if (typeof this.m[p] !== "undefined") {
params_dict[p] = this.m[p];
}
......@@ -62,6 +61,7 @@ Url.prototype= {
/**
* update(size/style/position) a list of selected clones <br>
* a slight function for operation who impact only a bunch of clones (merge/split/...)
* @abstract
* @param {integer[]} list - array of clone index
* */
updateElem : function (list) {
......@@ -71,6 +71,7 @@ Url.prototype= {
/**
* update(style only) a list of selected clones <br>
* a slight function for operation who impact only styles of clones (select/focus)
* @abstract
* @param {integer[]} list - array of clone index
* */
updateElemStyle : function () {
......@@ -79,10 +80,10 @@ Url.prototype= {
applyURL : function() {
var straight_params = this.getStraightParams();
for (var i = 0; i < straight_params.length; i++) {
if (typeof this.url_dict[straight_params[i]] !== "undefined") {
this.m[straight_params[i]] = this.url_dict[straight_params[i]];
var positionnal_params = this.getPositionnalParams();
for (var i = 0; i < positionnal_params.length; i++) {
if (typeof this.url_dict[positionnal_params[i]] !== "undefined") {
this.m[positionnal_params[i]] = this.url_dict[positionnal_params[i]];
}
}
......@@ -90,7 +91,7 @@ Url.prototype= {
var clones = this.url_dict.clone.split(',');
for (var j = 0; j < clones.length; j++) {
var c = this.m.clone(clones[j]);
if (typeof c !== "undefined" && c.isInteractable()) {
if (typeof c !== "undefined" && !c.isVirtual()) {
c.select = true;
}
}
......@@ -107,7 +108,7 @@ Url.prototype= {
},
parseUrlParams:function (urlparams) {
params={}
params={}
if (urlparams.length === 0) {
return params;
}
......@@ -117,7 +118,11 @@ Url.prototype= {
var p = params[tmparr[0]];
var key = this.encoder.decode(tmparr[0]);
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;
} else if (p.constructor === String){
params[key] = [];
......@@ -127,28 +132,42 @@ Url.prototype= {
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
},
generateParamsString: function(params_dict) {
var params_list = [];
var positionnal_params = [];
for (var key in params_dict){
if ((typeof key != "undefined" && key !== "") && (typeof params_dict[key]!= "undefined")) {
var encoded = this.encoder.encode(key);
if (params_dict[key].constructor !== Array && params_dict[key] !== '') {
params_list.push(encoded+"="+params_dict[key])
} else if (params_dict[key].constructor === Array) {
for (var i = 0; i < params_dict[key].length; i++) {
params_list.push(encoded+"="+params_dict[key][i]);
var val = params_dict[key];
if ((typeof key != "undefined" && key !== "") && (typeof val != "undefined")) {
var pos = this.getPosition(key);
if (pos >= 0) {
positionnal_params[pos] = val;
} else {
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) {
var new_url = "?" + params;
var new_url = params;
try {
this.window.history.pushState('plop', 'plop', new_url);
} catch(error) {
......@@ -156,10 +175,14 @@ Url.prototype= {
}
},
getStraightParams: function() {
getPositionnalParams: function() {
return ["sample_set_id", "config"];
},
getPosition: function(param) {
return this.getPositionnalParams().indexOf(param);
},
loadUrl: function(db, args, filename) {
this.url_dict = args;
var newParams = this.generateParamsString(args);
......@@ -176,12 +199,13 @@ Url.prototype= {
};
Url.prototype = $.extend(Object.create(View.prototype), Url.prototype);
function UrlEncoder() {
this.encoding = {
'sample_set_id': 'set',
'patient_id': 'patient',
'run_id': 'run'
'run_id': 'run',
'config': 'conf'
};
this.decoding = {};
......
......@@ -122,6 +122,42 @@ var json_clone6 = {
"normalized_reads" : [20,20,0,null],
}
// Cmone with seg start and stop position
var json_clone7 = {
"germline": "IGH",
"id": "id7",
"name": "clone with exact position",
"reads": [0,0,0,0],
"seg": {
"3": {
"delLeft": 1,
"delRight": 40,
"name": "IGHJ6*02",
"start": 16,
"stop": 25
},
"4": {
"delLeft": 4,
"delRight": 9,
"name": "IGHD3-3*01",
"start": 13,
"stop": 15
},
"5": {
"delLeft": 0,
"delRight": 2,
"name": "IGHV1-69*06",
"start": 6,
"stop": 10
},
"N1": 15,
"N2": 0,
},
"sequence": "bbbbbVVVVVnnDDDJJJJJJJJJJaaaaaa",
"top": 7
}
QUnit.test("name, informations, getHtmlInfo", function(assert) {
assert.equal(json_clone1.seg.junction.start, 10, "Start junction is 10 in JSON for clone 1");
......@@ -527,14 +563,21 @@ QUnit.test("export", function(assert) {
]
assert.deepEqual(c4.toCSV(), res4, "c4.toCSV() - no junctionAA")
m.select(0)
m.select(1)
m.merge()
assert.equal(c3.toCSV()[0], "0+1", ".toCSV(), merged clone")
});
// Fasta output for clone with preV and postJ
var c7 = new Clone(json_clone7, m, 1, c_attributes)
assert.equal(c7.getFasta(), ">clone with exact position 31 nt, 0 read \nbbbbb\nVVVVV\nnnDDD\nJJJJJJJJJJ\naaaaaa\n", "getFasta() with preV and postJ: Ok");
// Test if preV and postJ are empty (no empty line waited)
c7.seg["5"].start = 0
c7.seg["3"].stop = c7.sequence.length
assert.equal(c7.getFasta(), ">clone with exact position 31 nt, 0 read \nbbbbbVVVVV\nnnDDD\nJJJJJJJJJJaaaaaa\n", "getFasta() with preV and postJ: Ok");
})
QUnit.test("changeLocus/Segment", function(assert) {
......
......@@ -476,8 +476,9 @@ QUnit.test("getFasta", function(assert) {
m.select(1)
var fasta = m.getFasta()
console.log( fasta )
clone = m.clone(0)
fasta_to_get = ">hello 19 nt, 10 reads (5.000%, 10.00% of TRG)\naaaaaa\naaaattttt\ntttt\n\n>IGHV3-23*01 6/ACGTG/4 IGHD1-1*01 5/CCCACGTGGGGG/4 IGHJ5*02 11 nt, 10 reads (5.000%, 10.00% of IGH)\nAACGTACCAGG\n\n"
fasta_to_get = ">hello 19 nt, 10 reads (5.000%, 10.00% of TRG)\na\naaaaa\naaaattttt\ntttt\n\n>IGHV3-23*01 6/ACGTG/4 IGHD1-1*01 5/CCCACGTGGGGG/4 IGHJ5*02 11 nt, 10 reads (5.000%, 10.00% of IGH)\nAACGTACCAGG\n\n"
assert.equal(fasta, fasta_to_get, "getFasta return the correct content")
});
......
......@@ -363,3 +363,43 @@ QUnit.test("reset", function (assert) {
}, delay+=step)
})
QUnit.test("highlight", function (assert) {
var m = new Model();
m.parseJsonData(json_data, 100);
m.initClones();
var segment = new Segment("segment",m)
segment.init();
var clone = m.clone(3);
clone.seg["5"].start = 15 // 15 nt before V
clone.seg["3"].stop = 221 // 20 nt after J
m.select(3)
segment.addToSegmenter(3);
// "<span class='seq-marge'></span> // Marge
// <span></span>
// <span style=\"color: black;\" class=\"before5\">GGAAGGCCCCACAGC</span> // before V
// <span class=\"V\">GTCTTCTGTACTAT...TCTGGGGTCTATTACTGTGCCACCTGGGACAGG</span> // seg V
// <span class=\"N\">CTGA</span> // insert N1
// <span class=\"N\"></span> // insert N2
// <span class=\"J\">AGGATTGGATCAAGACGTTTGCAAAAGGGACTAGGCTC</span> // seg J
// <span style=\"color: black;\" class=\"after3\">ATAGTAACTTCGCCTGGTAA</span>" // after J
//
var str = segment.sequence[3].toString()
var spans = str.split("</span>")
assert.equal(spans.length, 9, "correct number of spans");
assert.includes(spans[0], "seq-marge", "")
assert.includes(spans[2], "GGAAGGCCCCACAGC", "segment highlight: seq before V")
assert.includes(spans[2], "class=\"before5\"", "segment highlight: color of seq before V")
assert.includes(spans[3], "GTCTTCTGTACTAT", "segment highlight: part seq V")
assert.includes(spans[3], "class=\"V\"", "segment highlight: class V")
assert.includes(spans[4], ">CTGA", "segment highlight: seq N1 (VJ)")
assert.includes(spans[4], "class=\"N\"", "segment highlight: class N")
assert.includes(spans[6], "AGGATTGGATCAAGACGTTTGCAAAAGGGACTAGGCTC", "segment highlight: seq J")
assert.includes(spans[6], "class=\"J\"", "segment highlight: class J")
assert.includes(spans[7], "ATAGTAACTTCGCCTGGTAA", "segment highlight: seq after J")
assert.includes(spans[7], "class=\"after3\"", "segment highlight: color of seq after J")
})
......@@ -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 = {
mocked: true,
location: {
search: {
toString: function() { var search = (current_url + '?').split('?')[1] ;
return (search == "" ? "" : '?' + search) }
}},
search: "",
pathname: ""
},
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
......@@ -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
# id 29 --> seg5; seg3 (IGHV1; IGHJ1)
def test_01_multiple_select_barmode
$b.menu_filter.click
$b.update_icon.wait_while(&:present?)
$b.send_keys 4
$b.update_icon.wait_while(&:present?)
# to verify correct selection, We will look in semgenter the presence if clone entrie
......@@ -57,6 +59,8 @@ class TestScatterplot < BrowserTest
end
def test_02_multiple_select_bubble
$b.menu_filter.click
$b.update_icon.wait_while(&:present?)
$b.send_keys 1
$b.update_icon.wait_while(&:present?)
# to verify correct selection, We will look in semgenter the presence if clone entrie
......
......@@ -242,7 +242,7 @@ These buttons open another window/tab.
- [`❯ Blast`](http://www.ensembl.org/Multi/Tools/Blast):
Nucleotide alignement against the Homo sapiens genome and other nucleotide collections
- [`❯ AssignSubsets`](http://tools.bat.infspire.org/arrest/assignsubsets/) (availaible for clones with IGH recombinations):
- [`❯ AssignSubsets`](https://station1.arrest.tools/subsets) (availaible for clones with IGH recombinations):
Assignment to the [19 known major subsets](https://www.ncbi.nlm.nih.gov/pubmed/22415752)
of stereotyped antigen receptor sequences for CLL
......
......@@ -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/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 /usr/share/vidjil/browser /usr/share/vidjil/b
copy ./scripts/nginx-entrypoint.sh /entrypoints/nginx-entrypoint.sh
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 {
keepalive_timeout 70;
uwsgi_max_temp_file_size 20480m;
location / {
include /etc/nginx/conf.d/web2py/uwsgi.conf;
proxy_read_timeout 600;
client_max_body_size 20G;
###
}