Fix protocols datatable [SCI-7984] (#5046)

This commit is contained in:
Alex Kriuchykhin 2023-03-01 15:04:53 +01:00 committed by GitHub
parent 976ddca672
commit 9908b31d11
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 112 additions and 81 deletions

View file

@ -123,7 +123,7 @@ class ProtocolsController < ApplicationController
return render_403 unless @protocol.in_repository_published_original? ||
(@protocol.in_repository_draft? && @protocol.parent_id.blank?)
@published_versions = @protocol.published_versions.order(version_number: :desc)
@published_versions = @protocol.published_versions_with_original.order(version_number: :desc)
render json: {
html: render_to_string(partial: 'protocols/index/protocol_versions_modal.html.erb')
}

View file

@ -1,9 +1,13 @@
# frozen_string_literal: true
class ProtocolsDatatable < CustomDatatable
# Needed for sanitize_sql_like method
include ActiveRecord::Sanitization::ClassMethods
include InputSanitizeHelper
include Rails.application.routes.url_helpers
PREFIXED_ID_SQL = "('#{Protocol::ID_PREFIX}' || COALESCE(\"protocols\".\"parent_id\", \"protocols\".\"id\"))"
def_delegator :@view, :can_read_protocol_in_repository?
def_delegator :@view, :can_manage_protocol_in_repository?
def_delegator :@view, :edit_protocol_path
@ -16,42 +20,38 @@ class ProtocolsDatatable < CustomDatatable
def initialize(view, team, type, user)
super(view)
@team = team
# :public, :private or :archive
@type = type
@type = type # :active or :archived
@user = user
end
def sortable_columns
@sortable_columns ||= [
'Protocol.name',
'Protocol.id',
'name',
'adjusted_parent_id',
'nr_of_versions',
'protocol_keywords_str',
'Protocol.nr_of_linked_children',
'nr_of_linked_children',
'nr_of_assigned_users',
'full_username_str',
'Protocol.published_on',
'Protocol.updated_at',
'published_on',
'updated_at',
'archived_full_username_str',
'Protocol.archived_on'
'archived_on'
]
end
def searchable_columns
@searchable_columns ||= [
"Protocol.name",
'Protocol.name',
'Protocol.archived_on',
'Protocol.published_on',
"Protocol.#{Protocol::PREFIXED_ID_SQL}",
"Protocol.updated_at"
"Protocol.#{PREFIXED_ID_SQL}",
'Protocol.updated_at',
'ProtocolKeyword.name'
]
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.
# See https://github.com/antillas21/ajax-datatables-rails/issues/112
def as_json(options = {})
def as_json(_options = {})
{
draw: dt_params[:draw].to_i,
recordsTotal: get_raw_records.length,
@ -67,8 +67,8 @@ class ProtocolsDatatable < CustomDatatable
model, column = column.split('.', 2)
model = model.constantize
case column
when Protocol::PREFIXED_ID_SQL
casted_column = ::Arel::Nodes::SqlLiteral.new(Protocol::PREFIXED_ID_SQL)
when PREFIXED_ID_SQL
casted_column = ::Arel::Nodes::SqlLiteral.new(PREFIXED_ID_SQL)
when 'published_on'
casted_column = ::Arel::Nodes::NamedFunction.new('CAST',
[ Arel.sql("to_char( protocols.created_at, '#{ formated_date }' ) AS VARCHAR") ] )
@ -87,20 +87,21 @@ class ProtocolsDatatable < CustomDatatable
# Returns json of current protocols (already paginated)
def data
records.map do |record|
parent = record.parent || record
{
DT_RowId: record.id,
DT_RowAttr: {
'data-permissions-url': permissions_protocol_path(record)
'data-permissions-url': permissions_protocol_path(parent)
},
'1': name_html(record),
'2': record.code,
'1': name_html(parent),
'2': parent.code,
'3': versions_html(record),
'4': keywords_html(record),
'5': modules_html(record),
'6': access_html(record),
'7': escape_input(record.full_username_str),
'8': I18n.l(record.published_on || record.created_at, format: :full),
'9': I18n.l(record.updated_at, format: :full),
'6': access_html(parent),
'7': published_by(record),
'8': published_timestamp(record),
'9': modified_timestamp(record),
'10': escape_input(record.archived_full_username_str),
'11': (I18n.l(record.archived_on, format: :full) if record.archived_on)
}
@ -136,27 +137,37 @@ class ProtocolsDatatable < CustomDatatable
end
def get_raw_records_base
original_without_versions = @team.protocols
.left_outer_joins(:published_versions)
.where(protocol_type: Protocol.protocol_types[:in_repository_published_original])
.where(published_versions: { id: nil })
.select(:id)
published_versions = @team.protocols
.where(protocol_type: Protocol.protocol_types[:in_repository_published_version])
.select('DISTINCT ON (parent_id) id')
new_drafts = @team.protocols
.where(protocol_type: Protocol.protocol_types[:in_repository_draft], parent_id: nil)
.select(:id)
records = Protocol.where(
"protocols.id IN ((#{original_without_versions.to_sql}) " \
"UNION " \
"(#{published_versions.to_sql}) " \
"UNION " \
"(#{new_drafts.to_sql}))"
)
records =
Protocol
.where(team: @team)
.where('protocols.protocol_type = ? OR protocols.protocol_type = ? AND protocols.parent_id IS NULL',
Protocol.protocol_types[:in_repository_published_original],
Protocol.protocol_types[:in_repository_draft])
.joins('LEFT OUTER JOIN "user_assignments" "all_user_assignments" '\
'ON "all_user_assignments"."assignable_type" = \'Protocol\' '\
'AND "all_user_assignments"."assignable_id" = "protocols"."id"')
.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 protocols protocol_drafts "\
"ON protocol_drafts.protocol_type = #{Protocol.protocol_types[:in_repository_draft]} "\
"AND protocol_drafts.parent_id = protocols.id")
.joins('LEFT OUTER JOIN "protocol_protocol_keywords" '\
records
.preload(:parent, :latest_published_version, :draft, :protocol_keywords, user_assignments: %i(user user_role))
.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.parent_id")
.joins('LEFT OUTER JOIN "protocol_protocol_keywords" ' \
'ON "protocol_protocol_keywords"."protocol_id" = "protocols"."id"')
.joins('LEFT OUTER JOIN "protocol_keywords" '\
.joins('LEFT OUTER JOIN "protocol_keywords" ' \
'ON "protocol_protocol_keywords"."protocol_keyword_id" = "protocol_keywords"."id"')
.with_granted_permissions(@user, ProtocolPermissions::READ)
.preload(user_assignments: %i(user user_role))
records = records.joins('LEFT OUTER JOIN "users" "archived_users"
ON "archived_users"."id" = "protocols"."archived_by_id"')
@ -173,9 +184,11 @@ class ProtocolsDatatable < CustomDatatable
def get_raw_records
get_raw_records_base.select(
'"protocols".*',
'COALESCE("protocols"."parent_id", "protocols"."id") AS adjusted_parent_id',
'STRING_AGG(DISTINCT("protocol_keywords"."name"), \', \') AS "protocol_keywords_str"',
'COUNT(DISTINCT("protocol_versions"."id")) + 1 AS "nr_of_versions"', # User assignments generate duplicates
'COUNT("protocol_drafts"."id") AS "nr_of_drafts"',
"CASE WHEN protocols.protocol_type = #{Protocol.protocol_types[:in_repository_draft]}" \
"THEN COUNT(DISTINCT(\"protocol_versions\".\"id\")) ELSE COUNT(DISTINCT(\"protocol_versions\".\"id\")) + 1 " \
"END AS nr_of_versions",
'COUNT("user_assignments"."id") AS "nr_of_assigned_users"',
'MAX("users"."full_name") AS "full_username_str"', # "Hack" to get single username
'MAX("archived_users"."full_name") AS "archived_full_username_str"'
@ -185,19 +198,23 @@ class ProtocolsDatatable < CustomDatatable
# Various helper methods
def name_html(record)
"<a href='#{protocol_path(record)}'>#{escape_input(record.name)}</a>"
path =
if record.in_repository_published_original? && record.latest_published_version.present?
protocol_path(record.latest_published_version)
else
protocol_path(record)
end
"<a href='#{path}'>#{escape_input(record.name)}</a>"
end
def keywords_html(record)
if !record.protocol_keywords_str || record.protocol_keywords_str.blank?
"<i>#{I18n.t("protocols.no_keywords")}</i>"
if record.protocol_keywords.blank?
"<i>#{I18n.t('protocols.no_keywords')}</i>"
else
kws = record.protocol_keywords_str.split(", ")
res = []
kws.sort_by{ |word| word.downcase }.each do |kw|
sanitized_kw = sanitize_input(kw)
res << "<a href='#' data-action='filter' " \
"data-param='#{sanitized_kw}'>#{sanitized_kw}</a>"
record.protocol_keywords.sort_by { |kw| kw.name.downcase }.each do |kw|
sanitized_kw = sanitize_input(kw.name)
res << "<a href='#' data-action='filter' data-param='#{sanitized_kw}'>#{sanitized_kw}</a>"
end
res.join(', ')
end
@ -206,7 +223,7 @@ class ProtocolsDatatable < CustomDatatable
def modules_html(record)
"<a href='#' data-action='load-linked-children'" \
"data-url='#{linked_children_protocol_path(record)}'>" \
"#{record.nr_of_linked_children}" \
"#{record.nr_of_linked_children}" \
"</a>"
end
@ -219,12 +236,20 @@ class ProtocolsDatatable < CustomDatatable
@view.controller.render_to_string(partial: 'protocols/index/protocol_access.html.erb', locals: { protocol: record })
end
def timestamp_column_html(record)
if @type == :archived
I18n.l(record.archived_on, format: :full)
else
I18n.l(record.published_on || record.created_at, format: :full)
end
def published_by(record)
return '' if record.published_by.blank?
escape_input(record.published_by.full_name)
end
def published_timestamp(record)
return '' if record.published_on.blank?
I18n.l(record.published_on, format: :full)
end
def modified_timestamp(record)
I18n.l(record.updated_at, format: :full)
end
# OVERRIDE - This is only called when filtering results;

View file

@ -129,6 +129,21 @@ class Protocol < ApplicationRecord
foreign_key: 'previous_version_id',
inverse_of: :previous_version,
dependent: :destroy
has_one :latest_published_version,
lambda {
in_repository_published_version.select('DISTINCT ON (parent_id) *')
.order(:parent_id, version_number: :desc)
},
class_name: 'Protocol',
foreign_key: 'parent_id'
has_one :draft,
-> { in_repository_draft.select('DISTINCT ON (parent_id) *').order(:parent_id) },
class_name: 'Protocol',
foreign_key: 'parent_id'
has_many :published_versions,
-> { in_repository_published_version },
class_name: 'Protocol',
foreign_key: 'parent_id'
has_many :protocol_protocol_keywords,
inverse_of: :protocol,
dependent: :destroy
@ -213,7 +228,6 @@ class Protocol < ApplicationRecord
user_id: user.id))
end
def self.filter_by_teams(teams = [])
teams.blank? ? self : where(team: teams)
end
@ -234,27 +248,13 @@ class Protocol < ApplicationRecord
in_module? ? my_module.created_by : added_by
end
def draft
return self if in_repository_draft? && parent_id.blank?
return nil unless in_repository_published_original?
team.protocols.in_repository_draft.find_by(parent: self)
end
def published_versions
return self.class.none unless in_repository_published_original?
def published_versions_with_original
team.protocols
.in_repository_published_version
.where(parent: self)
.or(team.protocols.in_repository_published_original.where(id: id))
end
def latest_published_version
published_versions.order(version_number: :desc).first
end
def permission_parent
in_module? ? my_module : team
end

View file

@ -1,10 +1,16 @@
<%= link_to versions_modal_protocol_path(protocol), class: 'protocol-versions-link', remote: true do %>
<% if protocol.in_repository_published_original? %>
<% if protocol.in_repository_published_original? || protocol.in_repository_published_version? %>
<% parent = protocol.parent || protocol %>
<%= link_to versions_modal_protocol_path(parent), class: 'protocol-versions-link', remote: true do %>
<%= protocol.nr_of_versions %>
<% if protocol.nr_of_drafts.positive? %>
/ <%= t("protocols.index.table.draft") %>
<% end %>
<% if protocol.draft.present? %>
/
<%= link_to protocol_path(parent.draft) do %>
<%= t("protocols.index.table.draft") %>
<% end %>
<% elsif protocol.in_repository_draft? %>
<% end %>
<% elsif protocol.in_repository_draft? %>
<%= link_to protocol_path(protocol) do %>
<%= t("protocols.index.table.draft") %>
<% end %>
<% end %>