From 1e6c19a4fb54c6096762860a2105abb29f3d48d6 Mon Sep 17 00:00:00 2001 From: Martin Artnik Date: Mon, 7 Apr 2025 11:59:53 +0200 Subject: [PATCH] Add cell value filtering to inventory items API [SCI-11768] --- .../api/v1/inventory_items_controller.rb | 15 ++++++-- app/models/repository_row.rb | 30 ++++++++++++++- .../api/v1/inventory_items_controller_spec.rb | 37 ++++++++++++++++--- 3 files changed, 72 insertions(+), 10 deletions(-) diff --git a/app/controllers/api/v1/inventory_items_controller.rb b/app/controllers/api/v1/inventory_items_controller.rb index d66d966d4..a3dbcd6f9 100644 --- a/app/controllers/api/v1/inventory_items_controller.rb +++ b/app/controllers/api/v1/inventory_items_controller.rb @@ -19,9 +19,18 @@ module Api .active .preload(repository_cells: :repository_column) .preload(repository_cells: { value: @inventory.cell_preload_includes }) - .page(params.dig(:page, :number)) - .per(params.dig(:page, :size)) - .order(:id) + + if params.dig(:filter, :inventory_column) + items = items.filtered_by_column_value( + @inventory.repository_columns.find(params[:filter][:inventory_column][:id]), + params[:filter][:inventory_column][:value] + ) + end + + items = + items.page(params.dig(:page, :number)) + .per(params.dig(:page, :size)) + .order(:id) render jsonapi: items, each_serializer: InventoryItemSerializer, include: include_params end diff --git a/app/models/repository_row.rb b/app/models/repository_row.rb index e028210b8..82741ff3e 100644 --- a/app/models/repository_row.rb +++ b/app/models/repository_row.rb @@ -121,6 +121,35 @@ class RepositoryRow < ApplicationRecord left_outer_joins_active_reminders(repository, user).where.not(repository_cells_with_active_reminders: { id: nil }) } + scope :filtered_by_column_value, lambda { |repository_column, filter_params| + value_class_name = repository_column.data_type + value_class = value_class_name.constantize + value_type = value_class_name.underscore + + repository_rows = + repository_column.repository_rows + .joins( + "INNER JOIN repository_cells AS #{value_type}_cells " \ + "ON repository_rows.id = #{value_type}_cells.repository_row_id " \ + "AND #{value_type}_cells.repository_column_id = '#{repository_column.id}' " + ).joins( + "INNER JOIN #{value_type.pluralize} AS values " \ + "ON values.id = #{value_type}_cells.value_id" + ) + + repository_rows = value_class.add_filter_condition( + repository_rows, + 'values', + RepositoryTableFilterElement.new( + operator: filter_params[:operator], + repository_column: repository_column, + parameters: filter_params + ) + ) + + where(id: repository_rows.select(:id)) + } + def code "#{ID_PREFIX}#{parent_id || id}" end @@ -134,7 +163,6 @@ class RepositoryRow < ApplicationRecord query = nil, current_team = nil, options = {}) - teams = options[:teams] || current_team || user.teams.select(:id) searchable_row_fields = [RepositoryRow::PREFIXED_ID_SQL, 'repository_rows.name', 'users.full_name'] diff --git a/spec/requests/api/v1/inventory_items_controller_spec.rb b/spec/requests/api/v1/inventory_items_controller_spec.rb index e4e170b37..d85b06da4 100644 --- a/spec/requests/api/v1/inventory_items_controller_spec.rb +++ b/spec/requests/api/v1/inventory_items_controller_spec.rb @@ -17,7 +17,7 @@ RSpec.describe 'Api::V1::InventoryItemsController', type: :request do create(:repository, name: Faker::Name.unique.name, created_by: @another_user, team: @team2) - text_column = create(:repository_column, name: Faker::Name.unique.name, + @text_column = create(:repository_column, name: Faker::Name.unique.name, repository: @valid_inventory, data_type: :RepositoryTextValue) list_column = create(:repository_column, name: Faker::Name.unique.name, repository: @valid_inventory, data_type: :RepositoryListValue) @@ -27,13 +27,15 @@ RSpec.describe 'Api::V1::InventoryItemsController', type: :request do repository: @valid_inventory, data_type: :RepositoryAssetValue) asset = create(:asset) - create_list(:repository_row, 100, repository: @valid_inventory) + @repository_rows = create_list(:repository_row, 100, repository: @valid_inventory) - @valid_inventory.repository_rows.each do |row| + @searchable_repository_row = @repository_rows.last + + @valid_inventory.repository_rows.each_with_index do |row, i| create(:repository_text_value, - data: Faker::Name.name, + data: row.id == @searchable_repository_row.id ? 'SEARCH TEXT VALUE' : Faker::Name.name, repository_cell_attributes: - { repository_row: row, repository_column: text_column }) + { repository_row: row, repository_column: @text_column }) create(:repository_list_value, repository_list_item: list_item, repository_cell_attributes: { repository_row: row, repository_column: list_column }) @@ -42,6 +44,7 @@ RSpec.describe 'Api::V1::InventoryItemsController', type: :request do { repository_row: row, repository_column: file_column }) end + @valid_headers = { 'Authorization': 'Bearer ' + generate_token(@user.id), 'Content-Type': 'application/json' } @@ -54,7 +57,7 @@ RSpec.describe 'Api::V1::InventoryItemsController', type: :request do included: [ { type: 'inventory_cells', attributes: { - column_id: text_column.id, + column_id: @text_column.id, value: Faker::Name.unique.name } } ] } @@ -127,6 +130,28 @@ RSpec.describe 'Api::V1::InventoryItemsController', type: :request do expect(hash_body).not_to include('included') end + it 'When provided a column filter, finds correct item' do + hash_body = nil + get api_v1_team_inventory_items_path( + team_id: @team1.id, + inventory_id: @team1.repositories.first.id + ), params: { + filter: { + inventory_column: { + id: @text_column.id, + value: { + operator: 'contains', + text: 'SEARCH TEXT VALUE' + } + } + } + }, headers: @valid_headers + + expect { hash_body = json }.not_to raise_exception + expect(hash_body[:data].length).to eq(1) + expect(hash_body[:data].first[:id].to_i).to eq(@searchable_repository_row.id) + end + it 'When invalid request, user in not member of the team' do hash_body = nil get api_v1_team_inventory_items_path(