Commit df1fd249 authored by Ryan Herbert's avatar Ryan Herbert

Merge branch 'feature-s/file_set_autocomplete' into 'dev'

Feature s/file set autocomplete

Closes #2118 and #2702

See merge request !101
parents 431b261d 8283917b
......@@ -28,6 +28,7 @@ function VidjilAutoComplete(datasource) {
}
this.datasource = datasource;
this.dataUrls = {};
this.isLoadingData = {};
this.cachedData = {};
this.loadedGroups = [];
......@@ -64,24 +65,32 @@ VidjilAutoComplete.prototype = {
this.cachedData = {};
},
isLoaded : function(group_ids) {
// this.loadedGroups is sorted on assignment
if (this.loadedGroups.length !== group_ids.length) {
isLoaded : function(keys) {
// this.loadedData is sorted on assignment
if (this.loadedData.length !== keys.length) {
return false;
}
var sorted_groups = group_ids.sort();
for (var i = 0; i < this.loadedGroups.length; i++) {
if (this.loadedGroups[i] !== sorted_groups[i]) {
var sorted_keys = keys.sort();
for (var i = 0; i < this.loadedData.length; i++) {
if (this.loadedData[i] !== sorted_keys[i]) {
return false;
}
}
return true;
},
setupAtWho: function(input) {
setupTags: function(input) {
this.setupAtWho(input, this.atWhoTags);
},
setupSamples: function(input) {
this.setupAtWho(input, this.atWhoSamples);
},
setupAtWho: function(input, callback) {
var $input = $(input);
if ($input.data('needs-atwho')) {
$input.off('focus.setupAtWho').on('focus.setupAtWho', this.setupTags.bind(this, $input));
$input.off('focus.setupAtWho').on('focus.setupAtWho', callback.bind(this, $input));
$input.on('change.atwho', function() { input.dispatchEvent(new Event('input'))});
// This triggers at.js again
// Needed for quick actions with suffixes (ex: /label ~)
......@@ -94,10 +103,11 @@ VidjilAutoComplete.prototype = {
}
},
setupTags : function($input) {
atWhoTags : function($input) {
var self = this;
var at = '#';
this.initCache(at);
this.dataUrls[at] = this.datasource.db_address + 'tag/auto_complete';
$input.atwho({
at: at,
alias: 'tags',
......@@ -108,24 +118,61 @@ VidjilAutoComplete.prototype = {
});
},
atWhoSamples : function($input) {
var self = this;
var at = '';
this.initCache(at);
this.dataUrls[at] = this.datasource.db_address + 'sample_set/auto_complete'
var callbacks = self.getDefaultCallbacks()
callbacks.beforeSave = function(data) {
if (data.length == 1 && data[0] == VidjilAutoComplete.defaultLoadingData[0]) {
return data;
}
var res = $.map(data, function(i) {
return {
name: i.name,
search: i.name
};
});
return res;
};
callbacks.matcher = function(flag, subtext) {
var regex = new RegExp(`[\\s\\S]+`);
var match = regex.exec(subtext);
if (match) {
return match[0];
}
return null;
};
$input.atwho({
at: at,
alias: 'samples',
data: VidjilAutoComplete.defaultLoadingData,
callbacks: callbacks,
searchKey: 'search',
});
},
getDefaultCallbacks : function() {
var fetchData = this.fetchData.bind(this);
var isLoaded = this.isLoaded.bind(this);
var callbacks = {
filter : function(query, data, searchKey) {
var group_ids = this.$inputor.data('group-ids');
if (VidjilAutoComplete.isLoading(data) || !isLoaded(group_ids)) {
var keys = this.$inputor.data('keys');
if (VidjilAutoComplete.isLoading(data) || !isLoaded(keys)) {
this.$inputor.atwho('load', this.at, VidjilAutoComplete.defaultLoadingData);
fetchData(this.$inputor, this.at, group_ids);
fetchData(this.$inputor, this.at, keys);
return data;
}
return $.fn.atwho.default.callbacks.filter(query, data, searchKey);
},
beforeSave: function(tags) {
if (tags.length == 1 && tags[0] == VidjilAutoComplete.defaultLoadingData[0]) {
return tags;
beforeSave: function(data) {
if (data.length == 1 && data[0] == VidjilAutoComplete.defaultLoadingData[0]) {
return data;
}
return $.map(tags, function(i) {
return $.map(data, function(i) {
if (i.name === null) {
return i;
}
......@@ -150,17 +197,17 @@ VidjilAutoComplete.prototype = {
return callbacks;
},
fetchData : function($input, at, group_ids) {
fetchData : function($input, at, keys) {
var self = this;
if (this.isLoadingData[at]) return;
this.isLoadingData[at] = true;
var uncached = [];
for (var i = 0; i < group_ids.length; i++) {
var group_id = group_ids[i];
if (!this.cachedData[at][group_id]) {
uncached.push(group_id);
//this.loadData($input, at, this.cachedData[at][group_id], group_id);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (!this.cachedData[at][key]) {
uncached.push(key);
//this.loadData($input, at, this.cachedData[at][key], key);
}
}
......@@ -168,39 +215,39 @@ VidjilAutoComplete.prototype = {
$.ajax({
type: "GET",
data: {
group_ids: JSON.stringify(uncached)
keys: JSON.stringify(uncached)
},
timeout: 5000,
crossDomain: true,
url: self.datasource,
url: self.dataUrls[at],
success: function (data) {
var my_data = JSON.parse(data);
self.cacheData(at, my_data, uncached);
self.loadData($input, at, group_ids);
self.loadData($input, at, keys);
},
error: function (request, status, error) {
self.isLoadingData[at] = false;
}
});
} else {
this.loadData($input, at, group_ids);
this.loadData($input, at, keys);
}
},
cacheData : function(at, data, group_ids) {
for (var i = 0; i < group_ids.length; i++) {
var group_id = group_ids[i];
this.cachedData[at][group_id] = data[group_id] ? data[group_id] : [];
cacheData : function(at, data, keys) {
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
this.cachedData[at][key] = data[key] ? data[key] : [];
}
},
loadData : function($input, at, group_ids) {
loadData : function($input, at, keys) {
this.isLoadingData[at] = false;
this.loadedGroups = group_ids.sort();
this.loadedData = keys.sort();
var loaded_data = [];
for (var i = 0; i < this.loadedGroups.length; i++) {
loaded_data = loaded_data.concat(this.cachedData[at][this.loadedGroups[i]]);
for (var i = 0; i < this.loadedData.length; i++) {
loaded_data = loaded_data.concat(this.cachedData[at][this.loadedData[i]]);
}
// remove duplicates
......
......@@ -373,9 +373,6 @@ Database.prototype = {
//bind javascript
this.init_ajaxform()
//
this.build_suggest_box()
//
this.fixed_header()
......@@ -1023,79 +1020,6 @@ Database.prototype = {
this.call('group/rights', arg)
},
updateList: function(that) {
var lastValue = that.lastValue,
value = that.value,
array = [],
pos = value.indexOf('|'),
start = that.selectionStart,
end = that.selectionEnd,
options;
if (that.options) {
options = that.options;
} else {
options = Object.keys(that.list.options).map(function (option) {
return that.list.options[option].value;
});
that.options = options;
}
if (lastValue !== value && value.length>1) {
that.list.innerHTML = options.filter(function (a) {
if (a === undefined)
return 0
return a.toLowerCase().indexOf(value.toLowerCase()) != -1;
}).slice(0,10).map(function (a) {
return '<option value="' + value + '|' + a + '">' + a + '</option>';
}).join();
this.updateInput(that);
that.lastValue = value;
}
},
updateInput: function(that) {
var value = that.value,
pos = value.indexOf('|'),
start = that.selectionStart,
end = that.selectionEnd;
if (pos != -1) {
value = value.slice(pos + 1);
}
that.value = value;
if (that.lastValue !== value){
if (that.options.indexOf(value)!= -1) {
that.style.color = "green";
} else {
that.style.color = "red";
}
}
that.setSelectionRange(start, end);
},
build_suggest_box: function() {
var self = this
if (document.getElementById("patient_list")){
document.getElementById('patient_list').addEventListener('keyup', function (e) {
self.updateList(this);
});
document.getElementById('patient_list').addEventListener('input', function (e) {
self.updateInput(this);
});
document.getElementById('run_list').addEventListener('keyup', function (e) {
self.updateList(this);
});
document.getElementById('run_list').addEventListener('input', function (e) {
self.updateInput(this);
});
}
},
argsToStr : function (args) {
var str = ""
......
......@@ -318,10 +318,10 @@ Info.prototype = {
textarea.innerHTML = info ;
textarea.setAttribute('placeholder', placeholder);
$(textarea).data('group-ids', [this.m.group_id]);
$(textarea).data('keys', [this.m.group_id]);
$(textarea).data('needs-atwho', true);
$(textarea).on('focus', function() {
new VidjilAutoComplete().setupAtWho(this);
new VidjilAutoComplete().setupTags(this);
})
container.appendChild(textarea);
......
......@@ -17,29 +17,10 @@ if request.env.http_origin:
def extract_id(target, error):
try:
id = int(target.split('(')[-1][:-1])
id = int(target.strip().split('(')[-1][:-1])
return id
except:
raise ValueError('invalid input %s' % target)
def get_sample_set_list(type, reference):
query = db(
auth.vidjil_accessible_query(PermissionEnum.read.value, db[type])
).select(
db[type].ALL, # sub optimal, use helpers to reduce ?
orderby = ~db[type].id
)
ss_list = []
ref = ""
factory = ModelFactory()
helper = factory.get_instance(type=type)
for row in query :
tmp = helper.get_id_string(row)
ss_list.append(tmp)
if reference == row.id:
ref = tmp
return ss_list, ref
def add():
sample_set = db.sample_set[request.vars["id"]]
......@@ -54,19 +35,18 @@ def add():
message="The space in directory %s has passed below %d%%." % (defs.DIR_SEQUENCES, defs.FS_LOCK_THRESHHOLD))
return error_message("Uploads are temporarily disabled. System admins have been made aware of the situation.")
patient_id = None
run_id = None
generic_id = None
group_ids = []
if sample_set.sample_type == defs.SET_TYPE_GENERIC:
generic_id = db( db.generic.sample_set_id == request.vars["id"]).select()[0].id
group_ids.append(get_set_group(sample_set.sample_type, generic_id))
if sample_set.sample_type == defs.SET_TYPE_PATIENT:
patient_id = db( db.patient.sample_set_id == request.vars["id"]).select()[0].id
group_ids.append(get_set_group(sample_set.sample_type, patient_id))
if sample_set.sample_type == defs.SET_TYPE_RUN:
run_id = db( db.run.sample_set_id == request.vars["id"]).select()[0].id
group_ids.append(get_set_group(sample_set.sample_type, run_id))
sample_types = [defs.SET_TYPE_GENERIC, defs.SET_TYPE_PATIENT, defs.SET_TYPE_RUN]
id_strings = {}
factory = ModelFactory()
for sample_type in sample_types:
if sample_set.sample_type == sample_type:
row = db(db[sample_type].sample_set_id == request.vars["id"]).select().first()
group_ids.append(get_set_group(sample_type, row.id))
helper = factory.get_instance(type=sample_type)
id_strings[sample_type] = helper.get_id_string(row)
else:
id_strings[sample_type] = ""
group_ids = [int(gid) for gid in group_ids]
......@@ -89,20 +69,11 @@ def add():
info = row.info
))
generic_list, generic = get_sample_set_list(defs.SET_TYPE_GENERIC, generic_id)
patient_list, patient = get_sample_set_list(defs.SET_TYPE_PATIENT, patient_id)
run_list, run = get_sample_set_list(defs.SET_TYPE_RUN, run_id)
source_module_active = hasattr(defs, 'FILE_SOURCE') and hasattr(defs, 'FILE_TYPES')
return dict(message = T('add file'),
generic_list = generic_list,
patient_list = patient_list,
run_list = run_list,
pre_process_list = pre_process_list,
generic = generic,
patient = patient,
id_strings = id_strings,
sample_type = sample_set.sample_type,
run = run,
source_module_active = source_module_active,
group_ids = group_ids)
......@@ -279,26 +250,24 @@ def edit():
info = row.info
))
generic_list, generic = get_sample_set_list(defs.SET_TYPE_GENERIC, relevant_ids['generic'])
patient_list, patient = get_sample_set_list(defs.SET_TYPE_PATIENT, relevant_ids['patient'])
run_list, run = get_sample_set_list(defs.SET_TYPE_RUN, relevant_ids['run'])
group_ids = []
id_strings = {}
factory = ModelFactory()
for key in relevant_ids:
if (relevant_ids[key] is not None):
if relevant_ids[key] is not None:
group_ids.append(get_set_group(key, relevant_ids[key]))
helper = factory.get_instance(type=key)
row = db(db[key].id == relevant_ids[key]).select().first()
id_strings[key] = helper.get_id_string(row)
else:
id_strings[key] = ""
group_ids = [int(gid) for gid in group_ids]
source_module_active = hasattr(defs, 'FILE_SOURCE') and hasattr(defs, 'FILE_TYPES')
return dict(message = T('edit file'),
generic_list = generic_list,
patient_list = patient_list,
run_list = run_list,
patient = patient,
id_strings = id_strings,
pre_process_list = pre_process_list,
run = run,
generic = generic,
file = db.sequence_file[request.vars["id"]],
sample_type = request.vars['sample_type'],
source_module_active = source_module_active,
......
......@@ -2,6 +2,7 @@
import gluon.contrib.simplejson, datetime
import vidjil_utils
import time
import json
if request.env.http_origin:
response.headers['Access-Control-Allow-Origin'] = request.env.http_origin
......@@ -577,3 +578,30 @@ def change_permission():
res = {"message": ACCESS_DENIED}
log.error(res)
return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
def get_sample_set_list(type):
query = db(
auth.vidjil_accessible_query(PermissionEnum.admin.value, db[type])
).select(
db[type].ALL, # sub optimal, use helpers to reduce ?
orderby = ~db[type].id
)
ss_list = []
factory = ModelFactory()
helper = factory.get_instance(type=type)
for row in query :
tmp = helper.get_id_string(row)
ss_list.append({'name':tmp})
return ss_list
def auto_complete():
if "keys" not in request.vars:
return error_message("missing group ids")
sample_types = json.loads(request.vars['keys'])
result = {}
for sample_type in sample_types:
result[sample_type] = get_sample_set_list(sample_type)
return json.dumps(result)
......@@ -7,11 +7,11 @@ if request.env.http_origin:
response.headers['Access-Control-Max-Age'] = 86400
def auto_complete():
if "group_ids" not in request.vars:
if "keys" not in request.vars:
return error_message("missing group ids")
prefix = get_tag_prefix()
group_ids = json.loads(request.vars["group_ids"])
group_ids = json.loads(request.vars["keys"])
tags = get_tags(db, group_ids)
return tags_to_json(tags, group_ids)
......@@ -36,7 +36,7 @@ class Patient(SampleSet):
return db(db.patient.sample_set_id == sample_set_id).select()[0]
def get_id_string(self, data):
name = "%s %s" % (data.first_name, data.last_name)
name = self.get_name(data)
birth = "[%s]" % str(data.birth)
id = "(%d)" % data.id
return "%s %s %s" % (birth, name, id)
......@@ -53,35 +53,15 @@
</tr>
<tr class='db_table_split' ><td colspan="2"> patient and run </td></tr>
<tr>
<td><label for="generic_id" id="sample_set_id__label">set: </label></td>
<td><input list="generics" id="generic_list" name="generic_id" type="text" size="80" value="{{=generic}}"></td>
<datalist id="generics">
{{for row in generic_list :}}
<option value="{{=row}}">
{{pass}}
</datalist>
<td rowspan="2">* You must associate this sample with at least one patient, run or set.
<br/>You can also associate it with any combination of the three.</td>
<td colspan="2">You must associate this sample with at least one patient, run or set.
<br/>You can also associate it with any combination of the three.</td>
</tr>
{{ for key in id_strings: }}
<tr>
<td><label for="patient_id" id="patient_id__label">patient: </label></td>
<td><input list="patients" id="patient_list" name="patient_id" type="text" size="80" value="{{=patient}}"></td>
<datalist id="patients">
{{for row in patient_list :}}
<option value="{{=row}}">
{{pass}}
</datalist>
</tr>
<tr>
<td><label for="run_id" id="run_id__label">run: </label></td>
<td><input list="runs" id="run_list" name="run_id" type="text" size="80" value="{{=run}}"></td>
<datalist id="runs">
{{for row in run_list :}}
<option value="{{=row}}">
{{pass}}
</datalist>
<td></td>
<td><label for="{{=key}}_id" id="{{=key}}_id__label">{{=key if key != 'generic' else 'set'}}: </label></td>
<td><input onfocus="new VidjilAutoComplete().setupSamples(this);" data-needs-atwho="true" data-keys=["{{=key}}"] id="{{=key}}_list" name="{{=key}}_id" type="text" size="80" value="{{=id_strings[key]}}", autocomplete="off"></td>
</tr>
{{ pass }}
<tr class='db_table_split' ><td colspan="2"> sample information </td></tr>
<tr>
<td><label for="sampling_date" id="sampling_date__label">sampling date: </label></td>
......
......@@ -59,36 +59,15 @@ info = db.sequence_file[request.vars["id"]]
<tr class='db_table_split' ><td colspan="2"> patient and run </td></tr>
<tr>
<td><label for="generic_id" id="generic_id__label">set: </label></td>
<td><input list="generics" id="generic_list" name="generic_id" type="text" size="80" value="{{=generic}}"></td>
<datalist id="generics">
{{for row in generic_list :}}
<option value="{{=row}}">
{{pass}}
</datalist>
<td rowspan="2">* You must associate this sample with at least one patient, run or set.
<br/>You can also associate it with any combination of the three.</td>
<td colspan="2">You must associate this sample with at least one patient, run or set.
<br/>You can also associate it with any combination of the three.</td>
</tr>
{{ for key in id_strings: }}
<tr>
<td><label for="patient_id" id="patient_id__label">patient: </label></td>
<td><input list="patients" id="patient_list" name="patient_id" type="text" size="80" value="{{=patient}}"></td>
<datalist id="patients">
{{for row in patient_list :}}
<option value="{{=row}}">
{{pass}}
</datalist>
<td><label for="{{=key}}_id" id="{{=key}}_id__label">{{=key if key != 'generic' else 'set'}}: </label></td>
<td><input onfocus="new VidjilAutoComplete().setupSamples(this);" data-needs-atwho="true" data-keys=["{{=key}}"] id="{{=key}}_list" name="{{=key}}_id" type="text" size="80" value="{{=id_strings[key]}}" autocomplete="off"></td>
</tr>
<tr>
<td><label for="run_id" id="run_id__label">run: </label></td>
<td><input list="runs" id="run_list" name="run_id" type="text" size="80" value="{{=run}}"></td>
<datalist id="runs">
{{for row in run_list :}}
<option value="{{=row}}">
{{pass}}
</datalist>
<td></td>
</tr>
{{ pass }}
<tr class='db_table_split' ><td colspan="2"> sample information </td></tr>
<tr>
......
......@@ -28,7 +28,7 @@
<tr>
<td><label for="run_info" id="run_info__label">Info: </label></td>
<td><textarea onfocus="$(this).data('group-ids', [$('#group_select option:selected').val()]); new VidjilAutoComplete().setupAtWho(this);" data-needs-atwho="true" class="text" cols="40" id="run_info" name="info" rows="10"></textarea></td>
<td></td>
<td></td>
</tr>
<tr>
<td><label for="sequencer" id="sequencer__label">Sequencer: </label></td>
......
......@@ -9,8 +9,8 @@
<div class="db_block_left">
search
<input id="db_filter_input" type="text" value="{{=request.vars["filter"]}}" onchange="db.call('sample_set/all', {'type': '{{=helper.get_type()}}', 'filter' : this.value} )"
onfocus="new VidjilAutoComplete().setupAtWho(this);"
data-needs-atwho="true" data-group-ids="{{=group_ids}}">
onfocus="new VidjilAutoComplete().setupTags(this);"
data-needs-atwho="true" data-keys="{{=group_ids}}">
</div>
</div>
......
......@@ -21,8 +21,8 @@
onchange="db.call('sample_set/custom', {'config_id' : '{{=request.vars["config_id"]}}',
'filter' : this.value,
'custom_list' : db.getListInput('custom_result[]')} )"
onfocus="new VidjilAutoComplete().setupAtWho(this);"
data-needs-atwho="true" data-group-ids="{{=group_ids}}">
onfocus="new VidjilAutoComplete().setupTags(this);"
data-needs-atwho="true" data-keys="{{=group_ids}}">
</div>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment