Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 1558 hide unauthorized cells in dynamic table #1620

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions app/assets/javascripts/single_page/dynamic_table.js.erb
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,14 @@ const handleSelect = (e) => {
if (c.multi_link) {
data = data && Array.isArray(data) ? data : [data];
data = data[0]?.id ? data : [];
const existingOptions = data.map((e) => `<option selected="selected" title="ID: ${e.id}" value="${e.id}">${e.title}</option>`).join("");
const existingOptions = data.map((e) => {
isHiddenInput = (e.title == '#HIDDEN')
if (isHiddenInput) {
return `<option selected="selected" title="ID: hidden" value="hidden">hidden</option>`
} else {
return `<option selected="selected" title="ID: ${e.id}" value="${e.id}">${e.title}</option>`
}
}).join("");
if (options.readonly) {
return data.map((e) => `<span title="ID: ${e.id}" class="badge">${e.title}</span>`).join(" ");
} else {
Expand All @@ -61,15 +68,15 @@ const handleSelect = (e) => {
const linkedSamples = retrieveLinkedSamples(url);
const linkedSampleIds = linkedSamples.map((ls) => ls.id);
const unLinkedSamples = data.reduce(function(filtered, sample) {
if(!linkedSampleIds.includes(parseInt(sample.id))){
if(!linkedSampleIds.includes(parseInt(sample.id)) && sample.title != '#HIDDEN'){
filtered.push(sample);
}
return filtered;
}, []);
const hasUnlinkedSamples = unLinkedSamples.length > 0 ? true : false;

const extraClass = hasUnlinkedSamples ? 'select2__error' : '';
const titleText = hasUnlinkedSamples ? `Sample(s) '${unLinkedSamples.map(uls => uls.title).join(', ')}' not recognised as sources. Please correct this issue!` : '';
const titleText = hasUnlinkedSamples ? `Sample(s) '${unLinkedSamples.map(uls => uls.title).join(', ')}' not recognised as input. Please correct this issue!` : '';

return objectInputTemp
.replace(/_NAME_/g, objectInputName)
Expand Down
111 changes: 64 additions & 47 deletions app/assets/javascripts/single_page/index.js.erb
Original file line number Diff line number Diff line change
Expand Up @@ -207,61 +207,78 @@ async function batchDeleteSample(sampleTypes) {
// Posts the content of the dynamic table to the single_pages_controller,
// which generates an excel workbook based on a template.
async function exportToExcel(tableName, studyId, assayId, sampleTypeId) {
const headerRow = $j(`table[id=${tableName}] thead tr`)["0"];
const headerRow = $j(`table[id=${tableName}] thead tr`)["0"];

// Cells from the table body => Each row = array
// For some reason they contain null values, which should be filtered out
let bodyCells = $j(`table[id=${tableName}]`).DataTable().data().toArray().map(subarray => subarray.filter(function(el){return el != null}));
// Cells from the table body => Each row = array
// For some reason they contain null values, which should be filtered out
let bodyCells = $j(`table[id=${tableName}]`).DataTable().data().toArray().map(subarray => subarray.filter(function(el){return el != null}));

// Return array of table header cells from headerRow
let headerCells = [];
$j.each(headerRow.cells, function (i, v) {
headerCells.push(v.textContent);
});
// Checks whether the dynamic table has errors
// The excel export will be aborted as long as the dynamic table has errors
hasErrorcells = $j(`table[id=${tableName}]`).find('select.select2__error').size() > 0
if (hasErrorcells) {
alert('It appears this sample table has some errors. Please correct the errors in the current sample table and try downloading again.');
return;
}

// Construct the 'struct' of the table data
let data = [];
for (let i = 0; i < bodyCells.length; i++) {
const row = bodyCells[i];
// If a row has been checked / unchecked,
// the datatable body gets one extra hidden cell at index 1,
// compared to the table header. This has to be removed.
if (row.length !== headerCells.length)row.splice(1,1);
let obj = {};
for (let j = 0; j < headerCells.length; j++) {
const attr = headerCells[j];
const val = row[j];
// Return array of table header cells from headerRow
let headerCells = [];
$j.each(headerRow.cells, function (i, v) {
headerCells.push(v.textContent);
});

// The source inputs are wrapped in an object and must be unwrapped first
// And then packaged as a single element
if(j === 0) {
obj["selected"] = (val === "") ? false : val;
} else {
obj[attr] = val;
}
if(obj.selected) data.push(obj);
// Construct the 'struct' of the table data
let data = [];
for (let i = 0; i < bodyCells.length; i++) {
const row = bodyCells[i];
// If a row has been checked / unchecked,
// the datatable body gets one extra hidden cell at index 1,
// compared to the table header. This has to be removed.
if (row.length !== headerCells.length)row.splice(1,1);
let obj = {};
let hasHiddenInputs = false;
for (let j = 0; j < headerCells.length; j++) {
const attr = headerCells[j];
const val = row[j];

// Check whether sample has one or more hidden Inputs or if sample is completely hidden
if (attr.toLowerCase().includes('input')) {
if (Array.isArray(val)){
hasHiddenInputs = val.some(input => input.title === '#HIDDEN'); // Sample has hidden inputs
} else {
hasHiddenInputs = val === '#HIDDEN'; // Sample is completely hidden
}
}

// Use ajax for POST request with table data in the body
// If succes => Redirect to download path for retrieving excel file
$j.ajax({
type: 'POST',
url: '<%= export_to_excel_single_pages_path() %>',
data: { sample_data: JSON.stringify(data),
sample_type_id: JSON.stringify(sampleTypeId),
study_id: JSON.stringify(studyId),
assay_id: JSON.stringify(assayId),
},
success: function(response) {
downloadUrl = `<%= download_samples_excel_single_pages_path() %>?uuid=${response.uuid}`;
window.location.href = downloadUrl;
},
error: function(response) {
alert(`Failed to export through excel!\nStatus: ${response.status}\nError: ${JSON.stringify(response.error().statusText)}`);
}
});
// The source inputs are wrapped in an object and must be unwrapped first
// And then packaged as a single element
if(j === 0) {
obj["selected"] = (val === "") ? false : val;
} else {
obj[attr] = val;
}
}
if (!hasHiddenInputs) data.push(obj);
}

// Use ajax for POST request with table data in the body
// If succes => Redirect to download path for retrieving excel file
$j.ajax({
type: 'POST',
url: '<%= export_to_excel_single_pages_path() %>',
data: { sample_data: JSON.stringify(data),
sample_type_id: JSON.stringify(sampleTypeId),
study_id: JSON.stringify(studyId),
assay_id: JSON.stringify(assayId),
},
success: function(response) {
downloadUrl = `<%= download_samples_excel_single_pages_path() %>?uuid=${response.uuid}`;
window.location.href = downloadUrl;
},
error: function(response) {
alert(`Failed to export through excel!\nStatus: ${response.status}\nError: ${JSON.stringify(response.error().statusText)}`);
}
});
}

async function batchUpdateSample(sampleTypes) {
Expand Down
6 changes: 4 additions & 2 deletions app/controllers/single_pages_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ def download_samples_excel
@project = @study.projects.first
@samples = Sample.where(id: sample_ids)&.authorized_for(:view)&.sort_by(&:id)

raise 'Nothing to export to Excel.' if @samples.nil? || @samples == [] || sample_type_id.nil?
if @samples.nil? || @samples == [] || sample_type_id.nil?
raise 'Nothing to export to Excel. Please select samples in the table and try downloading the table again.'
end

@sample_type = SampleType.find(sample_type_id)

Expand Down Expand Up @@ -114,7 +116,7 @@ def download_samples_excel
def export_to_excel
cache_uuid = UUID.new.generate
id_label = "#{Seek::Config.instance_name} id"
sample_ids = JSON.parse(params[:sample_data]).map { |sample| sample[id_label] unless sample[id_label] == '#HIDDEN' }
sample_ids = JSON.parse(params[:sample_data]).map { |sample| sample[id_label] }
sample_type_id = JSON.parse(params[:sample_type_id])
study_id = JSON.parse(params[:study_id])
assay_id = JSON.parse(params[:assay_id])
Expand Down
30 changes: 25 additions & 5 deletions app/helpers/dynamic_table_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,34 @@ def link_sequence(sample_type)

def dt_rows(sample_type)
sample_type.samples.map do |s|
if s.can_view?
['', s.id, s.uuid] +
JSON(s.json_metadata).values
else
['', '#HIDDEN', '#HIDDEN'] +
if s.can_view?
sanitized_json_metadata = hide_unauthorized_inputs(JSON(s.json_metadata))
['', s.id, s.uuid] +
sanitized_json_metadata.values
else
['', '#HIDDEN', '#HIDDEN'] +
Array.new(JSON(s.json_metadata).length, '#HIDDEN')
end
end
end

def hide_unauthorized_inputs(json_metadata)
input_key = json_metadata.keys.detect { |jmdk| jmdk.downcase.include? 'input' }

unless input_key.nil?
json_metadata[input_key] = json_metadata[input_key].map do |input|
input_exists = Sample.where(id: input['id']).any?
if !input_exists
input
elsif Sample.find(input['id']).can_view?
input
else
{ 'id' => input['id'], 'type' => input['type'], 'title' => '#HIDDEN' }
end
end
end

json_metadata
end

def dt_cols(sample_type)
Expand Down
38 changes: 23 additions & 15 deletions app/views/isa_studies/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

<div id="investigation_collection" class="hidden">
<%= render :partial=>"studies/investigation_list",:locals=>{study:@isa_study.study, :investigations=>Investigation.all.select {|i|current_user.person.member_of? i.projects}} -%>
</div>
</div>

<div class="form-group">
<%= study_fields.label "Study position" -%><br/>
Expand All @@ -42,48 +42,56 @@
<%= render partial: 'projects/implicit_project_selector', locals: { action: action,
select_id: '#study_investigation_id',
parents: Investigation.authorized_for('edit') } %>

<%= folding_panel("Define #{t(:sample_type)} for Source") do %>
<%= render partial: 'sample_types_form', locals: {f: f, sample_type: @isa_study.source, id_suffix: "_source_sample_type", action: action } if @isa_study.source %>
<% end %>

<%= folding_panel(t(:sop).pluralize) do %>
<p class="help-block">The following <%= t(:sop).pluralize %> are associated with this <%= t(:study) %>:</p>

<%= study_fields.fancy_multiselect :sops, { other_projects_checkbox: true, name: "isa_study[study][sop_ids]" } %>

<% end %>

<%= folding_panel("Define #{t(:sample_type)} for Sample") do %>
<%= render partial: 'sample_types_form', locals: {f: f, sample_type: @isa_study.sample_collection, id_suffix: "_sample_collection_sample_type", action: action } if @isa_study.sample_collection %>
<% end %>

<% end -%>
<% end -%>





<%= form_submit_buttons(@isa_study.study) %>

<script>
const templates = <%= load_templates().to_json.html_safe %>
const initTemplateModal = function(field_name) {
Templates.context.description_elem = `#isa_study_${field_name}_description`
Templates.context.suffix = "_" + field_name
Templates.context.field_name = field_name
showTemplateModal()
if (templates.length > 0) {
Templates.context.description_elem = `#isa_study_${field_name}_description`
Templates.context.suffix = "_" + field_name
Templates.context.field_name = field_name
showTemplateModal()
}else{
if (confirm("No templates available for this project. Click 'OK' to create a project specific template or 'cancel' to close this window.")){
window.open('/templates/new')
}
}
}

$j(document).ready(function () {
$j(document).ready(function () {
const urlSearchParams = new URLSearchParams(window.location.search);
const params = Object.fromEntries(urlSearchParams.entries());
const investigation_id = params["investigation_id"]
// Prevent setting the hidden field on redirect (query string parameters are missing)
if(investigation_id) {
$j("#isa_study_study_investigation_id").val(investigation_id);
$j("#isa_study_study_investigation_id").val(investigation_id);
$j("#study_investigation_id").val(investigation_id).change();
}
}

Templates.init($j('#template-attributes'));
});
if (templates.length > 0) {
Templates.init($j('#template-attributes'));
}
});
</script>
11 changes: 1 addition & 10 deletions app/views/isa_studies/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,11 @@
</span>
<h1>New <%=t('isa_study')%></h1>

<% if show_batch_miappe_button? %>
<h4>Upload studies from file:</h4>
<%= link_to "MIAPPE Study batch upload", batch_uploader_studies_path, class: "btn btn-primary" %>
<div>
<%= link_to "Download MIAPPE template here", asset_path('batch_upload_template/MIAPPE Study batch template.zip', skip_pipeline: true) %>
</div>
<h4>Or create a study with the form:</h4>
<% end %>

<%= render :partial => "templates/template_modal" -%>

<div class="show_basic">
<%= form_for @isa_study do |f| %>
<%= render :partial => "form", :locals => { :f => f, :action=>:new } -%>
<% end -%>
</div>
<% end -%>
<% end -%>
Loading