Optimize cells preloads for inventory datatables, improve permission checking for columns [SCI-11134] (#7927)

This commit is contained in:
Alex Kriuchykhin 2024-10-04 14:29:33 +02:00 committed by GitHub
parent a24726ec39
commit 3d32296fc2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 83 additions and 82 deletions

View file

@ -15,7 +15,14 @@ class MyModuleRepositoriesController < ApplicationController
@draw = params[:draw].to_i
per_page = params[:length].to_i < 1 ? Constants::REPOSITORY_DEFAULT_PAGE_SIZE : params[:length].to_i
page = (params[:start].to_i / per_page) + 1
datatable_service = RepositoryDatatableService.new(@repository, params, current_user, @my_module)
if params[:simple_view]
rows_view = 'repository_rows/simple_view_index'
preload_cells = false
else
rows_view = 'repository_rows/index'
preload_cells = true
end
datatable_service = RepositoryDatatableService.new(@repository, params, current_user, @my_module, preload_cells: preload_cells)
@datatable_params = {
view_mode: params[:view_mode],
@ -26,21 +33,9 @@ class MyModuleRepositoriesController < ApplicationController
@all_rows_count = datatable_service.all_count
@columns_mappings = datatable_service.mappings
if params[:simple_view]
repository_rows = datatable_service.repository_rows
rows_view = 'repository_rows/simple_view_index'
else
repository_rows = datatable_service.repository_rows
.preload(:repository_columns,
:created_by,
:archived_by,
:last_modified_by,
repository_cells: { value: @repository.cell_preload_includes })
rows_view = 'repository_rows/index'
end
repository_rows = datatable_service.repository_rows
@repository_rows = repository_rows.page(page).per(per_page)
@filtered_rows_count = @repository_rows.load.take&.filtered_count || 0
render rows_view
end

View file

@ -16,20 +16,16 @@ class MyModuleRepositorySnapshotsController < ApplicationController
@all_rows_count = datatable_service.all_count
@columns_mappings = datatable_service.mappings
repository_rows = datatable_service.repository_rows
if params[:simple_view]
repository_rows = datatable_service.repository_rows
@repository = @repository_snapshot
rows_view = 'repository_rows/simple_view_index'
else
repository_rows =
datatable_service.repository_rows
.preload(:repository_columns,
:created_by,
repository_cells: { value: @repository_snapshot.cell_preload_includes })
rows_view = 'repository_rows/snapshot_index'
end
@repository_rows = repository_rows.page(page).per(per_page)
@filtered_rows_count = @repository_rows.load.take&.filtered_count || 0
render rows_view
end

View file

@ -62,7 +62,7 @@ class MyModuleShareableLinksController < ApplicationController
@draw = params[:draw].to_i
per_page = params[:length].to_i < 1 ? Constants::REPOSITORY_DEFAULT_PAGE_SIZE : params[:length].to_i
page = (params[:start].to_i / per_page) + 1
datatable_service = RepositoryDatatableService.new(@repository, params, nil, @my_module)
datatable_service = RepositoryDatatableService.new(@repository, params, nil, @my_module, preload_cells: false)
@datatable_params = {
view_mode: params[:view_mode],
@ -76,6 +76,7 @@ class MyModuleShareableLinksController < ApplicationController
@columns_mappings = datatable_service.mappings
@repository_rows = datatable_service.repository_rows.page(page).per(per_page)
@filtered_rows_count = @repository_rows.load.take&.filtered_count || 0
render 'repository_rows/simple_view_index'
end
@ -84,13 +85,14 @@ class MyModuleShareableLinksController < ApplicationController
@draw = params[:draw].to_i
per_page = params[:length].to_i < 1 ? Constants::REPOSITORY_DEFAULT_PAGE_SIZE : params[:length].to_i
page = (params[:start].to_i / per_page) + 1
datatable_service = RepositorySnapshotDatatableService.new(@repository_snapshot, params, nil, @my_module)
datatable_service = RepositorySnapshotDatatableService.new(@repository_snapshot, params, nil, @my_module, preload_cells: false)
@all_rows_count = datatable_service.all_count
@columns_mappings = datatable_service.mappings
@repository = @repository_snapshot
@repository_rows = datatable_service.repository_rows.page(page).per(per_page)
@filtered_rows_count = @repository_rows.load.take&.filtered_count || 0
render 'repository_rows/simple_view_index'
end

View file

@ -28,12 +28,10 @@ class RepositoryRowsController < ApplicationController
@all_rows_count = datatable_service.all_count
@columns_mappings = datatable_service.mappings
@repository_rows = datatable_service.repository_rows
.preload(:repository_columns, repository_cells: { value: @repository.cell_preload_includes })
.page(page)
.per(per_page)
@repository_rows = @repository_rows.where(archived: params[:archived]) unless @repository.archived?
repository_rows = datatable_service.repository_rows
repository_rows = repository_rows.where(archived: params[:archived]) unless @repository.archived?
@repository_rows = repository_rows.page(page).per(per_page)
@filtered_rows_count = @repository_rows.load.take&.filtered_count || 0
rescue RepositoryFilters::ColumnNotFoundException
render json: { custom_error: I18n.t('repositories.show.repository_filter.errors.column_not_found') }
rescue RepositoryFilters::ValueNotFoundException

View file

@ -5,11 +5,12 @@ module RepositoryDatatableHelper
include Rails.application.routes.url_helpers
def prepare_row_columns(repository_rows, repository, columns_mappings, team, options = {})
# repository_rows collection is already preloaded in controllers, do not modify scopes or query params
# otherwise it will result in duplicated SQL queries
has_stock_management = repository.has_stock_management?
stock_management_column_exists = repository.repository_columns.stock_type.exists?
repository_row_connections_enabled = Repository.repository_row_connections_enabled?
reminders_enabled = Repository.reminders_enabled?
repository_rows = reminders_enabled ? with_reminders_status(repository_rows, repository) : repository_rows
stock_managable = has_stock_management && !options[:disable_stock_management] &&
can_manage_repository_stock?(repository) &&
!repository.is_a?(SoftLockedRepository)
@ -97,9 +98,10 @@ module RepositoryDatatableHelper
end
def prepare_simple_view_row_columns(repository_rows, repository, my_module, options = {})
# repository_rows collection is already preloaded in controllers, do not modify scopes or query params
# otherwise it will result in duplicated SQL queries
has_stock_management = repository.has_stock_management?
reminders_enabled = !options[:disable_reminders] && Repository.reminders_enabled?
repository_rows = reminders_enabled ? with_reminders_status(repository_rows, repository) : repository_rows
# Always disabled in a simple view
stock_managable = false
stock_consumption_permitted = has_stock_management && stock_consumption_permitted?(repository, my_module)
@ -304,35 +306,6 @@ module RepositoryDatatableHelper
''
end
def with_reminders_status(repository_rows, repository)
# don't load reminders for archived repositories or snapshots
if repository.archived? || repository.is_a?(RepositorySnapshot)
return repository_rows.select('FALSE AS has_active_stock_reminders')
.select('FALSE AS has_active_datetime_reminders')
end
repository_cells = RepositoryCell.joins(
"INNER JOIN repository_columns ON repository_columns.id = repository_cells.repository_column_id " \
"AND repository_columns.repository_id = #{repository.id}"
)
repository_rows
.joins(
"LEFT OUTER JOIN (#{RepositoryCell.stock_reminder_repository_cells_scope(repository_cells, current_user)
.select(:id, :repository_row_id).to_sql}) " \
"AS repository_cells_with_active_stock_reminders " \
"ON repository_cells_with_active_stock_reminders.repository_row_id = repository_rows.id"
)
.joins(
"LEFT OUTER JOIN (#{RepositoryCell.date_time_reminder_repository_cells_scope(repository_cells, current_user)
.select(:id, :repository_row_id).to_sql}) " \
"AS repository_cells_with_active_datetime_reminders " \
"ON repository_cells_with_active_datetime_reminders.repository_row_id = repository_rows.id"
)
.select('COUNT(repository_cells_with_active_stock_reminders.id) > 0 AS has_active_stock_reminders')
.select('COUNT(repository_cells_with_active_datetime_reminders.id) > 0 AS has_active_datetime_reminders')
end
def stock_consumption_permitted?(repository, my_module)
return false unless repository.is_a?(Repository) && current_user

View file

@ -171,6 +171,35 @@ class RepositoryRow < ApplicationRecord
.update_all(created_by_id: new_owner.id)
end
def self.with_reminders_status(repository_rows, repository, user)
# don't load reminders for archived repositories or snapshots
if repository.archived? || repository.is_a?(RepositorySnapshot)
return repository_rows.select('FALSE AS has_active_stock_reminders')
.select('FALSE AS has_active_datetime_reminders')
end
repository_cells = RepositoryCell.joins(
"INNER JOIN repository_columns ON repository_columns.id = repository_cells.repository_column_id " \
"AND repository_columns.repository_id = #{repository.id}"
)
repository_rows
.joins(
"LEFT OUTER JOIN (#{RepositoryCell.stock_reminder_repository_cells_scope(repository_cells, user)
.select(:id, :repository_row_id).to_sql}) " \
"AS repository_cells_with_active_stock_reminders " \
"ON repository_cells_with_active_stock_reminders.repository_row_id = repository_rows.id"
)
.joins(
"LEFT OUTER JOIN (#{RepositoryCell.date_time_reminder_repository_cells_scope(repository_cells, user)
.select(:id, :repository_row_id).to_sql}) " \
"AS repository_cells_with_active_datetime_reminders " \
"ON repository_cells_with_active_datetime_reminders.repository_row_id = repository_rows.id"
)
.select('COUNT(repository_cells_with_active_stock_reminders.id) > 0 AS has_active_stock_reminders')
.select('COUNT(repository_cells_with_active_datetime_reminders.id) > 0 AS has_active_datetime_reminders')
end
def editable?
true
end

View file

@ -104,6 +104,10 @@ Canaid::Permissions.register_for(Repository) do
repository.permission_granted?(user, RepositoryPermissions::COLUMNS_CREATE)
end
can :manage_repository_columns do |user, repository|
repository.repository_snapshots.provisioning.none? && can_create_repository_columns?(user, repository)
end
# repository: create/update/delete filters
can :manage_repository_filters do |user, repository|
repository.permission_granted?(user, RepositoryPermissions::FILTERS_MANAGE)
@ -113,3 +117,11 @@ Canaid::Permissions.register_for(Repository) do
RepositoryBase.stock_management_enabled? && can_manage_repository_rows?(user, repository)
end
end
Canaid::Permissions.register_for(RepositoryColumn) do
# repository: update/delete field
# Tested in scope of RepositoryPermissions spec
can :manage_repository_column do |user, repository_column|
repository_column.repository.repository_snapshots.provisioning.none? && can_create_repository_columns?(user, repository_column.repository)
end
end

View file

@ -1,12 +0,0 @@
# frozen_string_literal: true
Canaid::Permissions.register_for(RepositoryColumn) do
# repository: update/delete field
# Tested in scope of RepositoryPermissions spec
can :manage_repository_column do |user, repository_column|
managable = repository_column.repository.repository_snapshots.provisioning.none? &&
can_create_repository_columns?(user, repository_column.repository)
managable
end
end

View file

@ -13,7 +13,7 @@ module Reports::Docx::DrawMyModuleRepository
return false unless repository_data[:rows].any? && can_read_repository?(@user, repository)
table = prepare_row_columns(repository_data, my_module, repository)
table = prepare_row_columns_for_docx(repository_data, my_module, repository)
@docx.p
@docx.p I18n.t('projects.reports.elements.module_repository.name',

View file

@ -4,7 +4,7 @@ module Reports::Docx::RepositoryHelper
include InputSanitizeHelper
include ActionView::Helpers::NumberHelper
def prepare_row_columns(repository_data, my_module, repository)
def prepare_row_columns_for_docx(repository_data, my_module, repository)
result = [repository_data[:headers]]
repository_data[:rows].each do |record|
row = []

View file

@ -13,10 +13,11 @@ class RepositoryDatatableService
PREDEFINED_COLUMNS = %w(row_id row_name added_on added_by archived_on archived_by
assigned relationships updated_on updated_by).freeze
def initialize(repository, params, user, my_module = nil)
def initialize(repository, params, user, my_module = nil, preload_cells: true)
@repository = repository
@user = user
@my_module = my_module
@preload_cells = preload_cells
@params = params
@assigned_view = @params[:assigned].in?(%w(assigned assigned_simple))
@sortable_columns = build_sortable_columns
@ -77,6 +78,8 @@ class RepositoryDatatableService
.group('current_my_module_repository_rows.id')
end
end
repository_rows = RepositoryRow.with_reminders_status(repository_rows, @repository, @user) if Repository.reminders_enabled?
repository_rows = repository_rows
.left_outer_joins(my_module_repository_rows: { my_module: :experiment })
.select('COUNT(DISTINCT all_my_module_repository_rows.id) AS "assigned_my_modules_count"')
@ -85,6 +88,8 @@ class RepositoryDatatableService
.select('COALESCE(parent_connections_count, 0) + COALESCE(child_connections_count, 0)
AS "relationships_count"')
repository_rows = repository_rows.preload(Extends::REPOSITORY_ROWS_PRELOAD_RELATIONS)
repository_rows = repository_rows.preload(:repository_columns, repository_cells: { value: @repository.cell_preload_includes }) if @preload_cells
repository_rows = repository_rows.preload(:repository_stock_cell, :repository_stock_value) if @repository.has_stock_management?
sort_rows(order_by_column, repository_rows)
end
@ -101,7 +106,7 @@ class RepositoryDatatableService
.where(my_module_repository_rows: { my_module_id: @my_module })
.count
else
repository_rows.count
@repository.repository_rows_count
end
repository_rows = repository_rows.where(external_id: @params[:external_ids]) if @params[:external_ids]
@ -621,7 +626,7 @@ class RepositoryDatatableService
.group('values.value')
.order("values.value #{dir}")
when 'users.full_name'
records.group('users.full_name').order("users.full_name #{dir}")
records.group('created_by.full_name').order("created_by.full_name #{dir}")
when 'consumed_stock'
records.order("#{@sortable_columns[column_id - 1]} #{dir}")
when 'relationships'

View file

@ -18,6 +18,8 @@ class RepositorySnapshotDatatableService < RepositoryDatatableService
order_by_column = { column: order_params[:column].to_i, dir: order_params[:dir] }
repository_rows = fetch_rows(search_value).preload(Extends::REPOSITORY_ROWS_PRELOAD_RELATIONS)
repository_rows = repository_rows.preload(:repository_columns, repository_cells: { value: @repository.cell_preload_includes }) if @preload_cells
repository_rows = repository_rows.preload(:repository_stock_cell, :repository_stock_value) if @repository.has_stock_management?
sort_rows(order_by_column, repository_rows)
end

View file

@ -48,6 +48,7 @@
<% if @repository.is_a?(LinkedRepository) %>
<th id="row-external-id"><%= t('repositories.table.external_id') %></th>
<% end %>
<% columns_editable = can_manage_repository_columns?(@repository) && !repository.is_a?(SoftLockedRepository) %>
<% repository.repository_columns.order(:id).each do |column| %>
<th
class="repository-column <%= 'row-stock item-stock' if column.data_type == 'RepositoryStockValue' %>"
@ -55,7 +56,7 @@
data-type="<%= column.data_type %>"
data-edit-column-url="<%= edit_repository_repository_column_path(repository, column) %>"
data-destroy-column-url="<%= repository_columns_destroy_html_path(repository, column) %>"
data-editable-row="<%= can_manage_repository_column?(column) && !repository.is_a?(SoftLockedRepository) %>"
data-editable-row="<%= columns_editable %>"
<% column.metadata.each do |k, v| %>
<%= "data-metadata-#{k}=#{v}" %>
<% end %>

View file

@ -8,5 +8,5 @@ json.data do
@repository.team,
(@datatable_params || {}).merge(omit_editable: true))
end
json.recordsFiltered @repository_rows.first ? @repository_rows.first.filtered_count : 0
json.recordsFiltered @filtered_rows_count
json.recordsTotal @all_rows_count

View file

@ -6,5 +6,5 @@ json.data do
@repository_rows, @repository, @my_module, @datatable_params || {}
)
end
json.recordsFiltered @repository_rows.first ? @repository_rows.first.filtered_count : 0
json.recordsFiltered @filtered_rows_count
json.recordsTotal @all_rows_count

View file

@ -7,5 +7,5 @@ json.data do
@repository_snapshot.team,
@repository_snapshot)
end
json.recordsFiltered @repository_rows.first ? @repository_rows.first.filtered_count : 0
json.recordsFiltered @filtered_rows_count
json.recordsTotal @all_rows_count