Improve filtering in protocols datatable [SCI-8020] (#5068)

This commit is contained in:
Alex Kriuchykhin 2023-03-07 09:42:18 +01:00 committed by GitHub
parent b2cf5548f6
commit 00c602a818
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -54,8 +54,8 @@ class ProtocolsDatatable < CustomDatatable
def as_json(_options = {}) def as_json(_options = {})
{ {
draw: dt_params[:draw].to_i, draw: dt_params[:draw].to_i,
recordsTotal: get_raw_records.length, recordsTotal: get_raw_records_base.distinct.count,
recordsFiltered: filter_records(get_raw_records).length, recordsFiltered: records.present? ? records.first.filtered_count : 0,
data: data data: data
} }
end end
@ -79,7 +79,7 @@ class ProtocolsDatatable < CustomDatatable
casted_column = ::Arel::Nodes::NamedFunction.new('CAST', casted_column = ::Arel::Nodes::NamedFunction.new('CAST',
[model.arel_table[column.to_sym].as(typecast)]) [model.arel_table[column.to_sym].as(typecast)])
end end
casted_column.matches("%#{value}%") casted_column.matches("%#{ActiveRecord::Base.sanitize_sql_like(value)}%")
end end
private private
@ -108,6 +108,10 @@ class ProtocolsDatatable < CustomDatatable
end end
end end
def fetch_records
super.select('COUNT("protocols"."id") OVER() AS filtered_count')
end
def filter_protocols_records(records) def filter_protocols_records(records)
if params[:name_and_keywords].present? if params[:name_and_keywords].present?
records = records.where_attributes_like(['protocols.name', 'protocol_keywords.name'], params[:name_and_keywords]) records = records.where_attributes_like(['protocols.name', 'protocol_keywords.name'], params[:name_and_keywords])
@ -164,8 +168,16 @@ class ProtocolsDatatable < CustomDatatable
"(#{new_drafts.to_sql}))" "(#{new_drafts.to_sql}))"
) )
records = @type == :archived ? records.archived : records.active
records.with_granted_permissions(@user, ProtocolPermissions::READ)
end
# Query database for records (this will be later paginated and filtered)
# after that "data" function will return json
def get_raw_records
records = records =
records get_raw_records_base
.preload(:parent, :latest_published_version, :draft, :protocol_keywords, user_assignments: %i(user user_role)) .preload(:parent, :latest_published_version, :draft, :protocol_keywords, user_assignments: %i(user user_role))
.joins("LEFT OUTER JOIN protocols protocol_versions " \ .joins("LEFT OUTER JOIN protocols protocol_versions " \
"ON protocol_versions.protocol_type = #{Protocol.protocol_types[:in_repository_published_version]} " \ "ON protocol_versions.protocol_type = #{Protocol.protocol_types[:in_repository_published_version]} " \
@ -174,26 +186,16 @@ class ProtocolsDatatable < CustomDatatable
'ON "protocol_protocol_keywords"."protocol_id" = "protocols"."id"') '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"') 'ON "protocol_protocol_keywords"."protocol_keyword_id" = "protocol_keywords"."id"')
.with_granted_permissions(@user, ProtocolPermissions::READ) .joins('LEFT OUTER JOIN "users" "archived_users" ON "archived_users"."id" = "protocols"."archived_by_id"')
.joins('LEFT OUTER JOIN "users" ON "users"."id" = "protocols"."published_by_id"')
records = records.joins('LEFT OUTER JOIN "users" "archived_users" .group('"protocols"."id"')
ON "archived_users"."id" = "protocols"."archived_by_id"')
records = records.joins('LEFT OUTER JOIN "users" ON "users"."id" = "protocols"."published_by_id"')
records = @type == :archived ? records.archived : records.active
records = filter_protocols_records(records) records = filter_protocols_records(records)
records.group('"protocols"."id"') records.select(
end
# Query database for records (this will be later paginated and filtered)
# after that "data" function will return json
def get_raw_records
get_raw_records_base.select(
'"protocols".*', '"protocols".*',
'COALESCE("protocols"."parent_id", "protocols"."id") AS adjusted_parent_id', 'COALESCE("protocols"."parent_id", "protocols"."id") AS adjusted_parent_id',
'STRING_AGG(DISTINCT("protocol_keywords"."name"), \', \') AS "protocol_keywords_str"', 'STRING_AGG(DISTINCT("protocol_keywords"."name"), \', \') AS "protocol_keywords_str"',
"CASE WHEN protocols.protocol_type = #{Protocol.protocol_types[:in_repository_draft]}" \ "CASE WHEN protocols.protocol_type = #{Protocol.protocol_types[:in_repository_draft]} " \
"THEN COUNT(DISTINCT(\"protocol_versions\".\"id\")) ELSE COUNT(DISTINCT(\"protocol_versions\".\"id\")) + 1 " \ "THEN COUNT(DISTINCT(\"protocol_versions\".\"id\")) ELSE COUNT(DISTINCT(\"protocol_versions\".\"id\")) + 1 " \
"END AS nr_of_versions", "END AS nr_of_versions",
'COUNT("user_assignments"."id") AS "nr_of_assigned_users"', 'COUNT("user_assignments"."id") AS "nr_of_assigned_users"',
@ -258,36 +260,4 @@ class ProtocolsDatatable < CustomDatatable
def modified_timestamp(record) def modified_timestamp(record)
I18n.l(record.updated_at, format: :full) I18n.l(record.updated_at, format: :full)
end end
# OVERRIDE - This is only called when filtering results;
# when using GROUP BY function, SQL cannot perform a WHERE
# clause on aggregated columns (protocol keywords & users' full_name), but
# since we want those 2 columns to be searchable/filterable, we do an "inner"
# query where we select only protocol IDs which are filtered by those 2 columns
# using HAVING keyword (which is the correct way to filter aggregated columns).
# Another OR is then appended to the WHERE clause, checking if protocol is inside
# this list of IDs.
# def build_conditions_for(query)
# # Inner query to retrieve list of protocol IDs where concatenated
# # protocol keywords string, or user's full_name contains searched query
# search_val = dt_params[:search][:value]
# records_having = get_raw_records_base.having(
# ::Arel::Nodes::NamedFunction.new(
# 'CAST',
# [::Arel::Nodes::SqlLiteral.new("string_agg(\"protocol_keywords\".\"name\", ' ') AS #{typecast}")]
# ).matches("%#{sanitize_sql_like(search_val)}%").to_sql +
# " OR " +
# ::Arel::Nodes::NamedFunction.new(
# 'CAST',
# [::Arel::Nodes::SqlLiteral.new("max(\"users\".\"full_name\") AS #{typecast}")]
# ).matches("%#{sanitize_sql_like(search_val)}%").to_sql
# ).select(:id)
# # Call parent function
# criteria = super(query)
# # Aight, now append another or
# criteria = criteria.or(Protocol.arel_table[:id].in(records_having.arel))
# criteria
# end
end end