Attention une mise à jour du serveur va être effectuée le vendredi 16 avril entre 12h et 12h30. Cette mise à jour va générer une interruption du service de quelques minutes.

Commit d084f99f authored by Mathieu Giraud's avatar Mathieu Giraud

Merge branch 'feature-s/2951-my-account-stats' into 'dev'

My Account

Closes #2952, #4469, #4484, #2951, #3880, #2606, and #2605

See merge request !837
parents 28f3270d 8db12a88
Pipeline #187805 passed with stages
in 8 minutes and 27 seconds
...@@ -2304,3 +2304,30 @@ form .form_label { ...@@ -2304,3 +2304,30 @@ form .form_label {
.list_lock_on { .list_lock_on {
color: #839496; color: #839496;
} }
.set_group {
display: inline-flex;
flex-wrap: wrap;
flex-direction: row;
margin-bottom: 25px;
margin-right: 15px;
flex-flow: column;
width: 48%;
}
.set_group_header {
width: 100%;
}
.set_cell {
word-break: break-all;
}
.set_data {
width: 100%;
}
.analysis_button {
display: inline-flex;
}
.margined-top {
margin-top: 10px;
}
.margined-bottom {
margin-bottom: 10px;
}
...@@ -2304,3 +2304,30 @@ form .form_label { ...@@ -2304,3 +2304,30 @@ form .form_label {
.list_lock_on { .list_lock_on {
color: #657b83; color: #657b83;
} }
.set_group {
display: inline-flex;
flex-wrap: wrap;
flex-direction: row;
margin-bottom: 25px;
margin-right: 15px;
flex-flow: column;
width: 48%;
}
.set_group_header {
width: 100%;
}
.set_cell {
word-break: break-all;
}
.set_data {
width: 100%;
}
.analysis_button {
display: inline-flex;
}
.margined-top {
margin-top: 10px;
}
.margined-bottom {
margin-bottom: 10px;
}
...@@ -2304,6 +2304,33 @@ form .form_label { ...@@ -2304,6 +2304,33 @@ form .form_label {
.list_lock_on { .list_lock_on {
color: #657b83; color: #657b83;
} }
.set_group {
display: inline-flex;
flex-wrap: wrap;
flex-direction: row;
margin-bottom: 25px;
margin-right: 15px;
flex-flow: column;
width: 48%;
}
.set_group_header {
width: 100%;
}
.set_cell {
word-break: break-all;
}
.set_data {
width: 100%;
}
.analysis_button {
display: inline-flex;
}
.margined-top {
margin-top: 10px;
}
.margined-bottom {
margin-bottom: 10px;
}
body { body {
line-height: 20px; line-height: 20px;
} }
......
...@@ -2303,6 +2303,33 @@ form .form_label { ...@@ -2303,6 +2303,33 @@ form .form_label {
.list_lock_on { .list_lock_on {
color: #777777; color: #777777;
} }
.set_group {
display: inline-flex;
flex-wrap: wrap;
flex-direction: row;
margin-bottom: 25px;
margin-right: 15px;
flex-flow: column;
width: 48%;
}
.set_group_header {
width: 100%;
}
.set_cell {
word-break: break-all;
}
.set_data {
width: 100%;
}
.analysis_button {
display: inline-flex;
}
.margined-top {
margin-top: 10px;
}
.margined-bottom {
margin-bottom: 10px;
}
#resolution5 { #resolution5 {
opacity: 0; opacity: 0;
} }
......
...@@ -2304,6 +2304,33 @@ form .form_label { ...@@ -2304,6 +2304,33 @@ form .form_label {
.list_lock_on { .list_lock_on {
color: #657b83; color: #657b83;
} }
.set_group {
display: inline-flex;
flex-wrap: wrap;
flex-direction: row;
margin-bottom: 25px;
margin-right: 15px;
flex-flow: column;
width: 48%;
}
.set_group_header {
width: 100%;
}
.set_cell {
word-break: break-all;
}
.set_data {
width: 100%;
}
.analysis_button {
display: inline-flex;
}
.margined-top {
margin-top: 10px;
}
.margined-bottom {
margin-bottom: 10px;
}
#mid-container { #mid-container {
font-size: 125%; font-size: 125%;
} }
......
...@@ -2651,4 +2651,38 @@ form { ...@@ -2651,4 +2651,38 @@ form {
} }
.list_lock_on { .list_lock_on {
color: @default; color: @default;
} }
\ No newline at end of file
.set_group {
display: inline-flex;
flex-wrap: wrap;
flex-direction: row;
margin-bottom: 25px;
margin-right: 15px;
flex-flow: column;
width: 48%;
}
.set_group_header {
width: 100%;
}
.set_cell {
word-break: break-all;
}
.set_data {
width: 100%;
}
.analysis_button {
display: inline-flex;
}
.margined-top {
margin-top: 10px;
}
.margined-bottom {
margin-bottom: 10px;
}
...@@ -291,6 +291,34 @@ Database.prototype = { ...@@ -291,6 +291,34 @@ Database.prototype = {
}); });
}, },
callUrlJson : function(url, args) {
var self=this;
$.ajax({
type: "POST",
crossDomain: true,
url: url,
data: {'data': JSON.stringify(args)},
timeout: DB_TIMEOUT_CALL,
xhrFields: {withCredentials: true},
success: function (result) {
self.display_result(result, url, args)
self.connected = true;
},
error: function (request, status, error) {
self.connected = false;
if (status === "timeout") {
console.log({"type": "flash", "default" : "database_timeout", "priority": 2});
} else {
self.check_cert()
}
self.warn("callUrlJson: " + status + " - " + url.replace(self.db_address, '') + "?" + self.argsToStr(args))
}
});
},
callLinkable: function (linkable) { callLinkable: function (linkable) {
var href = linkable.attr('href'); var href = linkable.attr('href');
var type = linkable.data('linkable-type'); var type = linkable.data('linkable-type');
...@@ -1228,6 +1256,29 @@ Database.prototype = { ...@@ -1228,6 +1256,29 @@ Database.prototype = {
this.updateStatsButton(); this.updateStatsButton();
}, },
callGroupStats: function() {
var group_ids = [];
$('[name^="group_ids"]:checked').each(function() {
group_ids.push($(this).val());
});
this.callUrlJson(DB_ADDRESS + 'my_account/index', {'group_ids': group_ids});
},
callJobStats: function() {
var group_ids = [];
$('[name^="group_ids"]:checked').each(function() {
group_ids.push($(this).val());
});
this.callUrlJson(DB_ADDRESS + 'my_account/jobs', {'group_ids': group_ids});
},
stopGroupPropagate: function(e) {
if(!e) {
e = window.event
}
e.stopPropagation();
},
// Log functions, to server // Log functions, to server
// 'quiet' is set to true to avoid infinite loops with timeouts // 'quiet' is set to true to avoid infinite loops with timeouts
log : function (lvl, msg) { log : function (lvl, msg) {
......
...@@ -373,6 +373,11 @@ class VidjilBrowser < Watir::Browser ...@@ -373,6 +373,11 @@ class VidjilBrowser < Watir::Browser
return tag_selector.button(:text => 'ok') return tag_selector.button(:text => 'ok')
end end
# Enter in dev mode
def devel_mode
$b.execute_script('$(".devel-mode").show();')
end
# Unselect everything, both on clones, and move away from menus (as if we click on the back) # Unselect everything, both on clones, and move away from menus (as if we click on the back)
def unselect def unselect
$b.execute_script('m.unselectAll()') $b.execute_script('m.unselectAll()')
......
...@@ -177,7 +177,80 @@ def info(): ...@@ -177,7 +177,80 @@ def info():
log.info("access user list", extra={'user_id': auth.user.id, log.info("access user list", extra={'user_id': auth.user.id,
'record_id': request.vars["id"], 'record_id': request.vars["id"],
'table_name': "group"}) 'table_name': "group"})
return dict(message=T('group info'))
group = db.auth_group[request.vars["id"]]
base_query = ((db.auth_user.id == db.auth_membership.user_id)
& (db.auth_membership.group_id == request.vars["id"]))
parent_group = db(db.group_assoc.second_group_id == request.vars["id"]).select()
group_ids = [request.vars["id"]] + [r.first_group_id for r in parent_group]
base_left = [db.auth_permission.on(
(db.auth_permission.group_id.belongs(group_ids)) &
(db.auth_permission.name == 'access') &
(db.auth_permission.table_name == 'sample_set') &
(db.auth_permission.record_id > 0)),
db.sample_set_membership.on(
db.sample_set_membership.sample_set_id == db.auth_permission.record_id)]
query = db(base_query).select(
db.auth_user.id,
db.auth_user.first_name,
db.auth_user.last_name,
db.auth_user.email,
db.sequence_file.id.count(True).with_alias('file_count'),
db.sequence_file.size_file.coalesce_zero().sum().with_alias('size'),
left=base_left + [db.sequence_file.on(
(db.sequence_file.provider == db.auth_user.id) &
(db.sequence_file.id == db.sample_set_membership.sequence_file_id))
],
groupby=(db.auth_user.id)
)
sset_count = db(base_query).select(
db.auth_user.id,
db.patient.id.count(True).with_alias('patient_count'),
db.run.id.count(True).with_alias('run_count'),
db.generic.id.count(True).with_alias('generic_count'),
left=base_left + [db.patient.on(
(db.patient.creator == db.auth_user.id) &
(db.patient.sample_set_id == db.sample_set_membership.sample_set_id)),
db.run.on(
(db.run.creator == db.auth_user.id) &
(db.run.sample_set_id == db.sample_set_membership.sample_set_id)),
db.generic.on(
(db.generic.creator == db.auth_user.id) &
(db.generic.sample_set_id == db.sample_set_membership.sample_set_id))
],
groupby=(db.auth_user.id)
)
logins = db(base_query).select(
db.auth_user.id,
db.auth_event.time_stamp.max().with_alias('last_login'),
left=[db.auth_event.on(
(db.auth_event.user_id == db.auth_user.id) &
(db.auth_event.origin == 'auth') &
(db.auth_event.description.like('%Logged-in')))
],
groupby=(db.auth_user.id, db.auth_event.description)
)
result = {}
for row in query:
result[row.auth_user.id] = row
for row in sset_count:
result[row.auth_user.id]['count'] = row.patient_count + row.run_count + row.generic_count
for row in logins:
result[row.auth_user.id]['last_login'] = row.last_login
return dict(message=T('group info'),
result=result,
group=group)
return error_message(ACCESS_DENIED) return error_message(ACCESS_DENIED)
......
# coding: utf8
from datetime import datetime, timedelta
import time
import types
if request.env.http_origin:
response.headers['Access-Control-Allow-Origin'] = request.env.http_origin
response.headers['Access-Control-Allow-Credentials'] = 'true'
response.headers['Access-Control-Max-Age'] = 86400
ACCESS_DENIED = "access denied"
def generic_get_group(element_type):
group = {}
separator = "|| ';' ||"
fields = ["config.name",
"%s_file.sample_set_id" % element_type,
"%s_file.config_id" % element_type,
"sample_set.sample_type"
]
group_concat = "GROUP_CONCAT(DISTINCT " + separator.join(fields)
group['patient'] = group_concat + separator + "patient.first_name || ' ' || patient.last_name || '#')"
group['run'] = group_concat + separator + "run.name || '#')"
group['set'] = group_concat + separator + "generic.name || '#')"
return group
def get_group_fuses():
return generic_get_group('fused')
def get_group_analyses():
group = {}
separator = "|| ';' ||"
fields = ["analysis_file.sample_set_id",
"sample_set.sample_type"
]
group_concat = "GROUP_CONCAT(DISTINCT " + separator.join(fields)
group['patient'] = group_concat + separator + "patient.first_name || ' ' || patient.last_name || '#')"
group['run'] = group_concat + separator + "run.name || '#')"
group['set'] = group_concat + separator + "generic.name || '#')"
return group
def group_permissions():
return "GROUP_CONCAT(DISTINCT auth_permission.name)"
def base_query(group_list):
return (db.auth_group.id.belongs(group_list) &
(db.auth_permission.group_id == db.auth_group.id))
def access_query(group_list):
return (base_query(group_list) &
(db.auth_permission.table_name == 'sample_set') &
(db.auth_permission.name == 'access') &
(db.auth_permission.record_id > 0))
def filter_by_tags(tags):
return ((db.tag.name.upper().belongs([t.upper() for t in tags])) &
(db.tag_ref.tag_id == db.tag.id) &
(db.tag_ref.table_name == 'sequence_file') &
(db.tag_ref.record_id == db.sample_set_membership.sequence_file_id)
)
def base_left():
return [db.sample_set.on(
db.sample_set.id == db.auth_permission.record_id),
db.sample_set_membership.on(
db.sample_set_membership.sample_set_id == db.sample_set.id)]
def get_permissions(group_list):
return db(db.auth_group.id.belongs(group_list)
).select(
db.auth_group.role,
group_permissions(),
left=(db.auth_permission.on(
(db.auth_permission.group_id == db.auth_group.id) &
(db.auth_permission.table_name == 'sample_set') &
(db.auth_permission.record_id == 0))),
groupby=db.auth_group.role
)
def get_most_used_tags(group_list):
left = [
db.sample_set_membership.on(
db.sample_set_membership.sample_set_id == db.auth_permission.record_id),
db.tag_ref.on(
(db.tag_ref.table_name == 'sequence_file') &
(db.tag_ref.record_id == db.sample_set_membership.sequence_file_id)),
db.tag.on(
db.tag_ref.tag_id == db.tag.id)
]
return db(base_query(group_list)).select(
db.auth_group.role,
db.tag_ref.id.count().with_alias('count'),
db.tag.name,
left=left,
groupby=(db.auth_group.role, db.tag.name),
orderby=~db.tag_ref.id.count(),
limitby=(0,100)
)
def index():
start = time.time()
since = datetime.today() - timedelta(days=30)
if auth.is_admin() and 'data' in request.vars:
import json
group_list = json.loads(request.vars['data'])['group_ids']
else:
group_list = [int(g.id) for g in auth.get_user_groups() + auth.get_user_group_parents()]
log.debug("group_list: %s" % group_list)
if "filter" not in request.vars :
request.vars["filter"] = ""
search, tags = parse_search(request.vars["filter"])
result = {}
perm_query = get_permissions(group_list)
for r in perm_query:
if(r.auth_group.role not in result):
result[r.auth_group.role] = {}
for set_type in ['patient', 'run', 'set']:
result[r.auth_group.role][set_type] = {'count': {'num_sets': 0, 'num_samples': 0, 'sample_type': 'generic' if set_type == 'set' else set_type}}
result[r.auth_group.role][set_type]['tags'] = []
result[r.auth_group.role][set_type]['statuses'] = ""
result[r.auth_group.role][set_type]['num_jobs'] = 0
result[r.auth_group.role]['fuses'] = []
result[r.auth_group.role]['analyses'] = []
result[r.auth_group.role]['tags'] = []
result[r.auth_group.role]['permissions'] = "" if r._extra[group_permissions()] is None else r._extra[group_permissions()]
query = access_query(group_list)
if (tags is not None and len(tags) > 0):
query = (query & filter_by_tags(tags))
group_statuses = "GROUP_CONCAT(DISTINCT scheduler_task.id || ';' || scheduler_task.status || '#')"
group_fuses = get_group_fuses()
group_analyses = get_group_analyses()
left = base_left() + [
db.sequence_file.on(
db.sequence_file.id == db.sample_set_membership.sequence_file_id),
db.results_file.on(
db.sample_set_membership.sequence_file_id == db.results_file.sequence_file_id),
db.scheduler_task.on(
(db.scheduler_task.id == db.results_file.scheduler_task_id) &
(db.scheduler_task.start_time >= since)),
db.fused_file.on(
(db.fused_file.sample_set_id == db.sample_set.id) &
(db.fused_file.fuse_date >= since)),
db.config.on(
db.config.id == db.fused_file.config_id),
db.analysis_file.on(
(db.analysis_file.sample_set_id == db.sample_set.id) &
(db.analysis_file.analyze_date > since))
]
select = [
db.auth_group.id,
db.auth_group.role,
db.sample_set.sample_type.with_alias('sample_type'),
db.sample_set.id.count(distinct=True).with_alias('num_sets'),
db.sample_set_membership.sequence_file_id.count(distinct=True).with_alias('num_samples'),
group_statuses,
]
queries = {}
for set_type in ['patient', 'run', 'generic']:
key = 'set' if set_type == 'generic' else set_type
set_query = (query &
(db[set_type].sample_set_id == db.auth_permission.record_id))
queries[key] = db(set_query).select(
group_fuses[key],
group_analyses[key],
*select,
left=left,
groupby=(db.auth_group.role, db.sample_set.sample_type),