class RepositoryRowsController < ApplicationController include InputSanitizeHelper include ActionView::Helpers::TextHelper include ApplicationHelper before_action :load_info_modal_vars, only: :show before_action :load_vars, only: %i(edit update) before_action :load_repository, only: %i(create delete_records index copy_records available_rows) before_action :check_create_permissions, only: :create before_action :check_manage_permissions, only: %i(edit update delete_records copy_records) def index @draw = params[:draw].to_i per_page = params[:length] == '-1' ? 100 : params[:length].to_i page = (params[:start].to_i / per_page) + 1 records = RepositoryDatatableService.new(@repository, params, current_user) @assigned_rows = records.assigned_rows @repository_row_count = records.repository_rows.length @columns_mappings = records.mappings @repository_rows = records.repository_rows .page(page) .per(per_page) .preload( :repository_columns, :created_by, repository_cells: :value ) end def create record = RepositoryRow.new(repository: @repository, created_by: current_user, last_modified_by: current_user) errors = { default_fields: [], repository_cells: [] } record.transaction do record.name = record_params[:repository_row_name] unless record_params[:repository_row_name].blank? errors[:default_fields] = record.errors.messages unless record.save if cell_params cell_params.each do |key, value| next if create_cell_value(record, key, value, errors).nil? end end raise ActiveRecord::Rollback if errors[:repository_cells].any? end respond_to do |format| format.json do if errors[:default_fields].empty? && errors[:repository_cells].empty? log_activity(:create_item_inventory, record) render json: { id: record.id, flash: t('repositories.create.success_flash', record: escape_input(record.name), repository: escape_input(@repository.name)) }, status: :ok else render json: errors, status: :bad_request end end end end def show respond_to do |format| format.json do render json: { html: render_to_string( partial: 'repositories/repository_row_info_modal.html.erb' ) } end end end def edit json = { repository_row: { name: escape_input(@record.name), repository_cells: {}, repository_column_items: fetch_columns_list_items } } # Add custom cells ids as key (easier lookup on js side) @record.repository_cells.each do |cell| if cell.value_type == 'RepositoryAssetValue' cell_value = cell.value.asset else cell_value = escape_input(cell.value.data) end json[:repository_row][:repository_cells][cell.repository_column_id] = { repository_cell_id: cell.id, cell_column_id: cell.repository_column.id, # needed for mappings value: cell_value, type: cell.value_type, list_items: fetch_list_items(cell) } end respond_to do |format| format.html format.json { render json: json } end end def update errors = { default_fields: [], repository_cells: [] } @record.transaction do @record.name = record_params[:repository_row_name].blank? ? nil : record_params[:repository_row_name] errors[:default_fields] = @record.errors.messages unless @record.save if cell_params cell_params.each do |key, value| existing = @record.repository_cells.detect do |c| c.repository_column_id == key.to_i end if existing # Cell exists and new value present, so update value if existing.value_type == 'RepositoryListValue' item = RepositoryListItem.where( repository_column: existing.repository_column ).find(value) unless value == '-1' if item existing.value.update_attribute( :repository_list_item_id, item.id ) else existing.delete end # ############################################ # # Temporary, will be refactored in next ticket # # ############################################ # elsif existing.value_type == 'RepositoryStatusValue' item = RepositoryStatusItem.where( repository_column: existing.repository_column ).find(value) unless value == '-1' if item existing.value.update_attribute( :repository_status_item_id, item.id ) else existing.delete end elsif existing.value_type == 'RepositoryAssetValue' existing.value.destroy && next if value == '-1' if existing.value.asset.update(file: value) existing.value.asset.created_by = current_user existing.value.asset.last_modified_by = current_user existing.value.asset.post_process_file(current_team) else errors[:repository_cells] << { "#{existing.repository_column_id}": { data: existing.value.asset.errors.messages[:file].first } } end else existing.value.destroy && next if value == '' existing.value.data = value if existing.value.save record_annotation_notification(@record, existing) else errors[:repository_cells] << { "#{existing.repository_column_id}": existing.value.errors.messages } end end else next if value == '' # Looks like it is a new cell, so we need to create new value, cell # will be created automatically next if create_cell_value(@record, key, value, errors).nil? end end else @record.repository_cells.each { |c| c.value.destroy } end raise ActiveRecord::Rollback if errors[:repository_cells].any? end respond_to do |format| format.json do if errors[:default_fields].empty? && errors[:repository_cells].empty? # Row sucessfully updated, so sending response to client log_activity(:edit_item_inventory, @record) render json: { id: @record.id, flash: t( 'repositories.update.success_flash', record: escape_input(@record.name), repository: escape_input(@repository.name) ) }, status: :ok else # Errors render json: errors, status: :bad_request end end end end def create_cell_value(record, key, value, errors) column = @repository.repository_columns.detect do |c| c.id == key.to_i end save_successful = false if column.data_type == 'RepositoryListValue' return if value == '-1' # check if item exists else revert the transaction list_item = RepositoryListItem.where(repository_column: column) .find(value) cell_value = RepositoryListValue.new( repository_list_item_id: list_item.id, created_by: current_user, last_modified_by: current_user, repository_cell_attributes: { repository_row: record, repository_column: column } ) save_successful = list_item && cell_value.save # ############################################ # # Temporary, will be refactored in next ticket # # ############################################ # elsif column.data_type == 'RepositoryStatusValue' return if value == '-1' status_item = RepositoryStatusItem.where(repository_column: column).find(value) cell_value = RepositoryStatusValue.new( repository_status_item_id: status_item.id, created_by: current_user, last_modified_by: current_user, repository_cell_attributes: { repository_row: record, repository_column: column } ) save_successful = status_item && cell_value.save elsif column.data_type == 'RepositoryAssetValue' return if value.blank? asset = Asset.new(file: value, created_by: current_user, last_modified_by: current_user, team: current_team) if asset.save asset.post_process_file(current_team) else errors[:repository_cells] << { "#{column.id}": { data: asset.errors.messages[:file].first } } end cell_value = RepositoryAssetValue.new( asset: asset, created_by: current_user, last_modified_by: current_user, repository_cell_attributes: { repository_row: record, repository_column: column } ) save_successful = cell_value.save else cell_value = RepositoryTextValue.new( data: value, created_by: current_user, last_modified_by: current_user, repository_cell_attributes: { repository_row: record, repository_column: column } ) if (save_successful = cell_value.save) record_annotation_notification(record, cell_value.repository_cell) end end unless save_successful errors[:repository_cells] << { "#{column.id}": cell_value.errors.messages } end end def delete_records deleted_count = 0 if selected_params selected_params.each do |row_id| row = @repository.repository_rows.find_by_id(row_id) next unless row && can_manage_repository_rows?(@repository) log_activity(:delete_item_inventory, row) row.destroy && deleted_count += 1 end if deleted_count.zero? flash = t('repositories.destroy.no_deleted_records_flash', other_records_number: selected_params.count) elsif deleted_count != selected_params.count not_deleted_count = selected_params.count - deleted_count flash = t('repositories.destroy.contains_other_records_flash', records_number: deleted_count, other_records_number: not_deleted_count) else flash = t('repositories.destroy.success_flash', records_number: deleted_count) end respond_to do |format| color = deleted_count.zero? ? 'info' : 'success' format.json { render json: { flash: flash, color: color }, status: :ok } end else respond_to do |format| format.json do render json: { flash: t('repositories.destroy.no_records_selected_flash') }, status: :bad_request end end end end def copy_records duplicate_service = RepositoryActions::DuplicateRows.new( current_user, @repository, params[:selected_rows] ) duplicate_service.call render json: { flash: t('repositories.copy_records_report', number: duplicate_service.number_of_duplicated_items) }, status: :ok end def available_rows if @repository.repository_rows.empty? no_items_string = "#{t('projects.reports.new.save_PDF_to_inventory_modal.no_items')} " \ "#{link_to(t('projects.reports.new.save_PDF_to_inventory_modal.here'), repository_path(@repository), data: { 'no-turbolink' => true })}" render json: { no_items: no_items_string }, status: :ok else render json: { results: load_available_rows(search_params[:q]) }, status: :ok end end private include StringUtility AvailableRepositoryRow = Struct.new(:id, :name, :has_file_attached) def load_info_modal_vars @repository_row = RepositoryRow.eager_load(:created_by, repository: [:team]) .find_by_id(params[:id]) @assigned_modules = MyModuleRepositoryRow.eager_load( my_module: [{ experiment: :project }] ).where(repository_row: @repository_row) render_404 and return unless @repository_row render_403 unless can_read_repository?(@repository_row.repository) end def load_vars @repository = Repository.accessible_by_teams(current_team) .eager_load(:repository_columns) .find_by_id(params[:repository_id]) @record = @repository.repository_rows .eager_load(:repository_columns) .find_by_id(params[:id]) render_404 unless @repository && @record end def load_repository @repository = Repository.accessible_by_teams(current_team).find_by_id(params[:repository_id]) render_404 unless @repository render_403 unless can_read_repository?(@repository) end def check_create_permissions render_403 unless can_create_repository_rows?(@repository) end def check_manage_permissions render_403 unless can_manage_repository_rows?(@repository) end def record_params params.permit(:repository_row_name).to_h end def cell_params params.permit(repository_cells: {}).to_h[:repository_cells] end def remove_file_columns_params JSON.parse(params.fetch(:remove_file_columns) { '[]' }) end def selected_params params.permit(selected_rows: []).to_h[:selected_rows] end def load_available_rows(query) @repository.repository_rows .includes(:repository_cells) .name_like(search_params[:q]) .limit(Constants::SEARCH_LIMIT) .select(:id, :name) .collect do |row| with_asset_cell = row.repository_cells.where( 'repository_cells.repository_column_id = ?', search_params[:repository_column_id] ) AvailableRepositoryRow.new(row.id, ellipsize(row.name, 75, 50), with_asset_cell.present?) end end def search_params params.permit(:q, :repository_id, :repository_column_id) end def record_annotation_notification(record, cell, old_text = nil) table_url = params.fetch(:request_url) { :request_url_must_be_present } smart_annotation_notification( old_text: (old_text if old_text), new_text: cell.value.data, title: t('notifications.repository_annotation_title', user: current_user.full_name, column: cell.repository_column.name, record: record.name, repository: record.repository.name), message: t('notifications.repository_annotation_message_html', record: link_to(record.name, table_url), column: link_to(cell.repository_column.name, table_url)) ) end def fetch_list_items(cell) return [] if cell.value_type != 'RepositoryListValue' RepositoryListItem.where(repository: @repository) .where(repository_column: cell.repository_column) .limit(Constants::SEARCH_LIMIT) .pluck(:id, :data) .map { |li| [li[0], escape_input(li[1])] } end def fetch_columns_list_items collection = [] @repository.repository_columns .list_type .preload(:repository_list_items) .each do |column| collection << { column_id: column.id, list_items: column.repository_list_items .limit(Constants::SEARCH_LIMIT) .pluck(:id, :data) .map { |li| [li[0], escape_input(li[1])] } } end collection end def log_activity(type_of, repository_row) Activities::CreateActivityService .call(activity_type: type_of, owner: current_user, subject: @repository, team: current_team, message_items: { repository_row: repository_row.id, repository: @repository.id }) end end