Update the Load protocol template to task modal [SCI-7591] (#4870)

* Update the Load protocol template to task modal [SCI-7591]

* Update the Load protocol template to task modal [SCI-7591]

* Load last protocol version from repository to task [SCI-7591]

* Fix find_by for load_table [SCI-7591]
This commit is contained in:
ajugo 2023-02-14 13:19:24 +01:00 committed by GitHub
parent ea9d2d8291
commit cb332f163f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 115 additions and 252 deletions

View file

@ -1,5 +1,5 @@
/* global TinyMCE I18n animateSpinner importProtocolFromFile */
/* global HelperModule GLOBAL_CONSTANTS */
/* global HelperModule DataTableHelpers GLOBAL_CONSTANTS */
/* eslint-disable no-use-before-define, no-alert, no-restricted-globals, no-underscore-dangle */
//= require my_modules
@ -151,19 +151,7 @@ function initLoadFromRepository() {
modal.modal('show');
// Init Datatable on recent tab
initLoadFromRepositoryTable(modalBody.find('#recent-tab'));
modalBody.find("a[data-toggle='tab']")
.on('hide.bs.tab', function(el) {
// Destroy Handsontable in to-be-hidden tab
var content = $($(el.target).attr('href'));
destroyLoadFromRepositoryTable(content);
})
.on('shown.bs.tab', function(el) {
// Initialize Handsontable in to-be-shown tab
var content = $($(el.target).attr('href'));
initLoadFromRepositoryTable(content);
});
initLoadFromRepositoryTable(modalBody.find('#load-protocols-datatable'));
loadBtn.on('click', function() {
loadFromRepository();
@ -171,13 +159,8 @@ function initLoadFromRepository() {
});
modal.on('hidden.bs.modal', function() {
// Destroy the current Datatable
destroyLoadFromRepositoryTable(modalBody.find('.tab-pane.active'));
modalBody.find("a[data-toggle='tab']")
.off('hide.bs.tab shown.bs.tab');
destroyLoadFromRepositoryTable(modalBody.find('#load-protocols-datatable'));
loadBtn.off('click');
modalBody.html('');
});
}
@ -185,14 +168,15 @@ function initLoadFromRepository() {
function initLoadFromRepositoryTable(content) {
var tableEl = content.find("[data-role='datatable']");
var datatable = tableEl.DataTable({
dom: "RBfl<'row'<'col-sm-12't>><'row'<'col-sm-7'i><'col-sm-5'p>>",
dom: "R<'main-actions'<'toolbar'><'protocol-filters'f>>t"
+ "<'pagination-row'<'pagination-info'li><'pagination-actions'p>>",
sScrollX: '100%',
sScrollXInner: '100%',
buttons: [],
processing: true,
serverSide: true,
responsive: true,
order: tableEl.data('default-order') || [[1, 'asc']],
order: [[5, 'desc']],
ajax: {
url: tableEl.data('source'),
type: 'POST'
@ -201,17 +185,16 @@ function initLoadFromRepositoryTable(content) {
fixedColumnsLeft: 1000000 // Disable reordering
},
columnDefs: [{
targets: 0,
searchable: false,
orderable: false,
sWidth: '1%',
render: function() {
return "<input type='radio'>";
}
}, {
targets: [1, 2, 3, 4, 5, 6],
targets: [0, 3, 4],
searchable: true,
orderable: true
}, {
targets: [1, 2, 5],
searchable: true,
orderable: true,
render: function(data) {
return `<div class="nowrap">${data}</div`;
}
}],
columns: [
{ data: '0' },
@ -219,8 +202,7 @@ function initLoadFromRepositoryTable(content) {
{ data: '2' },
{ data: '3' },
{ data: '4' },
{ data: '5' },
{ data: '6' }
{ data: '5' }
],
oLanguage: {
sSearch: I18n.t('general.filter')
@ -228,45 +210,34 @@ function initLoadFromRepositoryTable(content) {
rowCallback: function(row, data) {
// Get row ID
var rowId = data.DT_RowId;
$(row).attr('data-row-id', rowId);
// If row ID is in the list of selected row IDs
if (rowId === selectedRow) {
$(row).find("input[type='radio']").prop('checked', true);
$(row).addClass('selected');
}
},
fnDrawCallback: function() {
animateSpinner(this, false);
},
preDrawCallback: function() {
animateSpinner(this);
},
fnInitComplete: function(e) {
var dataTableWrapper = $(e.nTableWrapper);
DataTableHelpers.initLengthAppearance(dataTableWrapper);
DataTableHelpers.initSearchField(
dataTableWrapper,
I18n.t('my_modules.protocols.load_from_repository_modal.filter_protocols')
);
$('.toolbar').html(I18n.t('my_modules.protocols.load_from_repository_modal.text2'));
}
});
// Handle click on table cells with radio buttons
tableEl.find('tbody').on('click', 'td', function() {
$(this).parent().find("input[type='radio']").trigger('click');
});
// Handle clicks on radio buttons
tableEl.find('tbody').on('click', "input[type='radio']", function(e) {
// Get row ID
var row = $(this).closest('tr');
var data = datatable.row(row).data();
var rowId = data.DT_RowId;
// Uncheck all radio buttons
tableEl.find("tbody input[type='radio']")
.prop('checked', false)
.closest('tr')
.removeClass('selected');
// Handle clicks on row
tableEl.find('tbody').on('click', 'tr', function(e) {
// Uncheck all
tableEl.find('tbody tr.selected').removeClass('selected');
// Select the current row
row.find("input[type='radio']").prop('checked', true);
selectedRow = rowId;
row.addClass('selected');
selectedRow = datatable.row($(this)).data().DT_RowId;
$(this).addClass('selected');
// Enable load btn
content.closest('.modal')
@ -294,8 +265,7 @@ function destroyLoadFromRepositoryTable(content) {
// Unbind event listeners
tableEl.find('tbody').off('click', "a[data-action='filter']");
tableEl.find('tbody').off('click', "input[type='radio']");
tableEl.find('tbody').off('click', 'td');
tableEl.find('tbody').off('click', 'tr');
// Destroy datatable
tableEl.DataTable().destroy();
@ -323,6 +293,7 @@ function loadFromRepository() {
}
if (selectedRow !== null && confirm(confirmMessage)) {
modal.find(".modal-footer [data-action='submit']").prop('disabled', true);
// POST via ajax
$.ajax({
url: modal.attr('data-url'),

View file

@ -55,6 +55,10 @@
td {
padding-left: 10px;
.nowrap {
white-space: nowrap;
}
}
.dt-body-center {

View file

@ -26,6 +26,14 @@
#protocols-index,
#load-from-repository-modal {
tbody {
tr {
&:hover {
background-color: $color-concrete;
}
}
}
.nav-tabs > li {
text-transform: uppercase;

View file

@ -420,12 +420,6 @@ mark,.mark {
padding: 10px;
}
/** Settings */
.nav-settings {
margin-top: 15px;
margin-bottom: 0;
}
.tab-pane-settings {
background-color: $color-white;
padding: 15px;
@ -851,15 +845,6 @@ ul.content-activities {
}
}
.load-from-repository-protocols-datatable {
clear: right;
& .dataTables_wrapper .dataTables_filter {
float: right;
margin-top: 15px;
}
}
// Preview protocol modal
@media (min-width: 768px) {
#protocol-preview-modal .modal-dialog {

View file

@ -929,13 +929,11 @@ class ProtocolsController < ApplicationController
def load_from_repository_datatable
@protocol = Protocol.find_by_id(params[:id])
@type = (params[:type] || 'public').to_sym
respond_to do |format|
format.json do
render json: ::LoadFromRepositoryProtocolsDatatable.new(
view_context,
@protocol.team,
@type,
current_user
)
end
@ -1214,8 +1212,8 @@ class ProtocolsController < ApplicationController
end
def check_load_from_repository_permissions
@protocol = Protocol.find_by_id(params[:id])
@source = Protocol.find_by_id(params[:source_id])
@protocol = Protocol.find_by(id: params[:id])
@source = Protocol.find_by(id: params[:source_id])&.published_versions&.order(version_number: :desc)&.first
render_403 unless @protocol.present? && @source.present? &&
(can_manage_protocol_in_module?(@protocol) &&

View file

@ -3,53 +3,31 @@ class LoadFromRepositoryProtocolsDatatable < CustomDatatable
include ActiveRecord::Sanitization::ClassMethods
include InputSanitizeHelper
def initialize(view, team, type, user)
def initialize(view, team, user)
super(view)
@team = team
# :public or :private
@type = type
@user = user
end
def sortable_columns
@sortable_columns ||= [
"Protocol.name",
"protocol_keywords_str",
"Protocol.nr_of_linked_children",
"full_username_str",
timestamp_db_column,
"Protocol.updated_at"
'Protocol.name',
'nr_of_versions',
'Protocol.id',
'protocol_keywords_str',
'full_username_str',
'Protocol.published_on'
]
end
def searchable_columns
@searchable_columns ||= [
"Protocol.name",
timestamp_db_column,
"Protocol.updated_at"
'Protocol.name',
'Protocol.id',
'Protocol.published_on'
]
end
# A hack that overrides the new_search_contition method default behavior of the ajax-datatables-rails gem
# now the method checks if the column is the created_at or updated_at and generate a custom SQL to parse
# it back to the caller method
def new_search_condition(column, value)
model, column = column.split('.')
model = model.constantize
case column
when 'published_on'
casted_column = ::Arel::Nodes::NamedFunction.new('CAST',
[ Arel.sql("to_char( protocols.created_at, '#{ formated_date }' ) AS VARCHAR") ] )
when 'updated_at'
casted_column = ::Arel::Nodes::NamedFunction.new('CAST',
[ Arel.sql("to_char( protocols.updated_at, '#{ formated_date }' ) AS VARCHAR") ] )
else
casted_column = ::Arel::Nodes::NamedFunction.new('CAST',
[model.arel_table[column.to_sym].as(typecast)])
end
casted_column.matches("%#{value}%")
end
# This hack is needed to display a correct amount of
# searched entries (needed for pagination).
# This is needed because of usage of GROUP operator in SQL.
@ -70,12 +48,12 @@ class LoadFromRepositoryProtocolsDatatable < CustomDatatable
records.map do |record|
{
'DT_RowId': record.id,
'1': escape_input(record.name),
'2': keywords_html(record),
'3': record.nr_of_linked_children,
'0': escape_input(record.name),
'1': record.nr_of_versions,
'2': record.code,
'3': keywords_html(record),
'4': escape_input(record.full_username_str),
'5': timestamp_column_html(record),
'6': I18n.l(record.updated_at, format: :full)
'5': I18n.l(record.published_on, format: :full)
}
end
end
@ -84,31 +62,15 @@ class LoadFromRepositoryProtocolsDatatable < CustomDatatable
records =
Protocol
.where(team: @team)
.joins('LEFT OUTER JOIN "protocol_protocol_keywords" ON "protocol_protocol_keywords"."protocol_id" = "protocols"."id"')
.joins('LEFT OUTER JOIN "protocol_keywords" ON "protocol_protocol_keywords"."protocol_keyword_id" = "protocol_keywords"."id"')
if @type == :public
records =
records
.joins('LEFT OUTER JOIN users ON users.id = protocols.added_by_id')
.where('protocols.protocol_type = ?',
Protocol.protocol_types[:in_repository_public])
elsif @type == :private
records =
records
.joins('LEFT OUTER JOIN users ON users.id = protocols.added_by_id')
.where('protocols.protocol_type = ?',
Protocol.protocol_types[:in_repository_private])
.where(added_by: @user)
else
records =
records
.joins('LEFT OUTER JOIN users ON users.id = protocols.added_by_id')
.where('(protocols.protocol_type = ? OR (protocols.protocol_type = ? AND added_by_id = ?))',
Protocol.protocol_types[:in_repository_public],
Protocol.protocol_types[:in_repository_private],
@user.id)
end
.where(protocols: { protocol_type: Protocol.protocol_types[:in_repository_published_original] })
.joins("LEFT OUTER JOIN protocols protocol_versions "\
"ON protocol_versions.protocol_type = #{Protocol.protocol_types[:in_repository_published_version]} "\
"AND protocol_versions.parent_id = protocols.id")
.joins('LEFT OUTER JOIN "protocol_protocol_keywords" '\
'ON "protocol_protocol_keywords"."protocol_id" = "protocols"."id"')
.joins('LEFT OUTER JOIN "protocol_keywords"'\
'ON "protocol_protocol_keywords"."protocol_keyword_id" = "protocol_keywords"."id"')
.joins('LEFT OUTER JOIN users ON users.id = protocols.added_by_id').active
records.group('"protocols"."id"')
end
@ -118,36 +80,23 @@ class LoadFromRepositoryProtocolsDatatable < CustomDatatable
# will return json
def get_raw_records
get_raw_records_base
.select(
'"protocols"."id"',
'"protocols"."name"',
'"protocols"."protocol_type"',
'string_agg("protocol_keywords"."name", \', \') AS "protocol_keywords_str"',
'"protocols"."nr_of_linked_children"',
'max("users"."full_name") AS "full_username_str"', # "Hack" to get single username
'"protocols"."created_at"',
'"protocols"."updated_at"',
'"protocols"."published_on"'
)
.select(
'"protocols".*',
'STRING_AGG("protocol_keywords"."name", \', \') AS "protocol_keywords_str"',
'COUNT("protocol_versions"."id") + 1 AS "nr_of_versions"',
'MAX("users"."full_name") AS "full_username_str"'
)
end
# Various helper methods
def timestamp_db_column
if @type == :public
"Protocol.published_on"
else
"Protocol.created_at"
end
end
def keywords_html(record)
if record.protocol_keywords_str.blank?
"<i>#{I18n.t("protocols.no_keywords")}</i>"
"<i>#{I18n.t('protocols.no_keywords')}</i>"
else
kws = record.protocol_keywords_str.split(", ")
kws = record.protocol_keywords_str.split(', ')
res = []
kws.sort_by{ |word| word.downcase }.each do |kw|
kws.sort_by(&:downcase).each do |kw|
res << "<a href='#' data-action='filter' data-param='#{kw}'>#{kw}</a>"
end
sanitize_input(res.join(', '))
@ -179,10 +128,15 @@ class LoadFromRepositoryProtocolsDatatable < CustomDatatable
'CAST',
[::Arel::Nodes::SqlLiteral.new("string_agg(\"protocol_keywords\".\"name\", ' ') AS #{typecast}")]
).matches("%#{sanitize_sql_like(search_val)}%").to_sql +
" OR " +
' OR ' +
::Arel::Nodes::NamedFunction.new(
'CAST',
[::Arel::Nodes::SqlLiteral.new("max(\"users\".\"full_name\") AS #{typecast}")]
).matches("%#{sanitize_sql_like(search_val)}%").to_sql +
' OR ' +
::Arel::Nodes::NamedFunction.new(
'CAST',
[::Arel::Nodes::SqlLiteral.new("COUNT(\"protocol_versions\".\"id\") + 1 AS #{typecast}")]
).matches("%#{sanitize_sql_like(search_val)}%").to_sql
).select(:id)
@ -190,7 +144,6 @@ class LoadFromRepositoryProtocolsDatatable < CustomDatatable
criteria = super(query)
# Aight, now append another or
criteria = criteria.or(Protocol.arel_table[:id].in(records_having.arel))
criteria
criteria.or(Protocol.arel_table[:id].in(records_having.arel))
end
end

View file

@ -1,75 +1,24 @@
<div>
<p>
<%= t("my_modules.protocols.load_from_repository_modal.text") %>
</p>
<p style="font-weight: bold;">
<%= t("my_modules.protocols.load_from_repository_modal.text2") %>
</p>
<p style="font-weight: bold;"><%= t("my_modules.protocols.load_from_repository_modal.text") %></p>
</div>
<ul class="nav nav-tabs nav-settings" role="tablist">
<li role="presentation" class="active"><a href="#recent-tab" aria-controls="recent-tab" role="tab" data-toggle="tab"><%= t("my_modules.protocols.load_from_repository_modal.tab_recent") %></a></li>
<li role="presentation"><a href="#public-tab" aria-controls="public-tab" role="tab" data-toggle="tab"><%= t("my_modules.protocols.load_from_repository_modal.tab_public") %></a></li>
<li role="presentation"><a href="#private-tab" aria-controls="private-tab" role="tab" data-toggle="tab"><%= t("my_modules.protocols.load_from_repository_modal.tab_private") %></a></li>
</ul>
<!-- Tab panes -->
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="recent-tab">
<div class="protocols-datatable load-from-repository-protocols-datatable">
<table id="recent-protocols-table" class="table" data-role="datatable"
data-type="recent"
data-source="<%= load_from_repository_datatable_protocol_path(@protocol, type: :recent) %>"
data-default-order='[[6,"desc"]]'
>
<thead>
<tr>
<th class="all"></th>
<th class="all" id="protocol-name"><%= t("my_modules.protocols.load_from_repository_modal.thead_name") %></th>
<th class="min-tablet-p" id="protocol-keywords"><%= t("my_modules.protocols.load_from_repository_modal.thead_keywords") %></th>
<th class="min-tablet-l" id="protocol-nr-of-linked-children"><%= t("my_modules.protocols.load_from_repository_modal.thead_nr_of_linked_children") %></th>
<th class="min-tablet-l" id="protocol-published-by"><%= t("my_modules.protocols.load_from_repository_modal.thead_added_by") %></th>
<th class="min-desktop" id="protocol-published-on"><%= t("my_modules.protocols.load_from_repository_modal.thead_published_on") %></th>
<th class="min-desktop" id="protocol-updated-at"><%= t("my_modules.protocols.load_from_repository_modal.thead_updated_at") %></th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="public-tab">
<div class="protocols-datatable load-from-repository-protocols-datatable">
<table id="public-protocols-table" class="table" data-role="datatable" data-type="public" data-source="<%= load_from_repository_datatable_protocol_path(@protocol, type: :public) %>">
<thead>
<tr>
<th class="all"></th>
<th class="all" id="protocol-name"><%= t("my_modules.protocols.load_from_repository_modal.thead_name") %></th>
<th class="min-tablet-p" id="protocol-keywords"><%= t("my_modules.protocols.load_from_repository_modal.thead_keywords") %></th>
<th class="min-tablet-l" id="protocol-nr-of-linked-children"><%= t("my_modules.protocols.load_from_repository_modal.thead_nr_of_linked_children") %></th>
<th class="min-tablet-l" id="protocol-published-by"><%= t("my_modules.protocols.load_from_repository_modal.thead_published_by") %></th>
<th class="min-desktop" id="protocol-published-on"><%= t("my_modules.protocols.load_from_repository_modal.thead_published_on") %></th>
<th class="min-desktop" id="protocol-updated-at"><%= t("my_modules.protocols.load_from_repository_modal.thead_updated_at") %></th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="private-tab">
<div class="protocols-datatable load-from-repository-protocols-datatable">
<table id="private-protocols-table" class="table" data-role="datatable" data-type="private" data-protocol-id="<%= @protocol.id %>" data-source="<%= load_from_repository_datatable_protocol_path(@protocol, type: :private) %>">
<thead>
<tr>
<th class="all"></th>
<th class="all" id="protocol-name"><%= t("my_modules.protocols.load_from_repository_modal.thead_name") %></th>
<th class="min-tablet-p" id="protocol-keywords"><%= t("my_modules.protocols.load_from_repository_modal.thead_keywords") %></th>
<th class="min-tablet-l" id="protocol-nr-of-linked-children"><%= t("my_modules.protocols.load_from_repository_modal.thead_nr_of_linked_children") %></th>
<th class="min-tablet-l" id="protocol-added-by"><%= t("my_modules.protocols.load_from_repository_modal.thead_added_by") %></th>
<th class="min-desktop" id="protocol-created-at"><%= t("my_modules.protocols.load_from_repository_modal.thead_created_at") %></th>
<th class="min-desktop" id="protocol-updated-at"><%= t("my_modules.protocols.load_from_repository_modal.thead_updated_at") %></th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
<div id="load-protocols-datatable">
<div class="load-protocols-datatable tab-pane active">
<table id="protocols-table" class="table datatable"
data-role="datatable"
data-source="<%= load_from_repository_datatable_protocol_path(@protocol) %>">
<thead>
<tr>
<th class="all" id="protocol-name"><%= t("my_modules.protocols.load_from_repository_modal.thead_name") %></th>
<th class="min-tablet-p" id="protocol-versions"><%= t("my_modules.protocols.load_from_repository_modal.thead_version") %></th>
<th class="min-tablet-l" id="protocol-id"><%= t("my_modules.protocols.load_from_repository_modal.thead_id") %></th>
<th class="min-tablet-l" id="protocol-keywords"><%= t("my_modules.protocols.load_from_repository_modal.thead_keywords") %></th>
<th class="min-desktop" id="protocol-published-by"><%= t("my_modules.protocols.load_from_repository_modal.thead_published_by") %></th>
<th class="min-desktop" id="protocol-published-on"><%= t("my_modules.protocols.load_from_repository_modal.thead_published_on") %></th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>

View file

@ -1099,23 +1099,18 @@ en:
load_from_repository_error_locked: "Failed to load the protocol from the templates. One or more files in the protocol are currently being edited."
load_from_repository_modal:
title: "Load protocol from templates"
text: "Choose the protocol to be loaded to the task."
text2: "This action will overwrite the current protocol in the task and unlink it from templates. The current protocol will remain unchanged in templates."
tab_public: "Team protocols"
tab_private: "My protocols"
tab_recent: "Recent protocols"
tab_archive: "Archived protocols"
text: "This action will overwrite the current protocol in the task and unlink it from templates. The current protocol will remain unchanged in templates."
text2: "Choose the protocol to be loaded to the task."
filter_protocols: "Filter protocols"
thead_name: "Name"
thead_keywords: "Keywords"
thead_nr_of_linked_children: "No. of linked tasks"
thead_published_by: "Published by"
thead_added_by: "Added by"
thead_published_on: "Published at"
thead_created_at: "Created at"
thead_updated_at: "Last modified at"
thead_published_on: "Published on"
thead_version: "Ver."
thead_id: "ID"
confirm_message: "Are you sure you wish to load protocol from templates? This action will overwrite the current protocol in the task and unlink it from templates. The current protocol will remain unchanged in templates."
import_to_linked_task_rep: "Are you sure you wish to load protocol from templates? This action will overwrite the current protocol in the task and unlink it from templates. The current protocol will remain unchanged in templates."
load: "Load"
load: "Load protocol"
copy_to_repository_modal:
title: "Save to protocol templates"
name_label: "Templates protocol name"
@ -2987,7 +2982,7 @@ en:
error_flash: "Step %{step} couldn't be deleted. One or more files are locked."
edit:
head_title: "Edit protocol"
no_keywords: "No keywords"
no_keywords: ""
invite_users:
to_team: