From fb66131e296498eebd5bffdbfef5a3cd2cb1f135 Mon Sep 17 00:00:00 2001 From: zmagod Date: Tue, 6 Mar 2018 16:32:39 +0100 Subject: [PATCH] setup endpoint for repository_rows paging/search --- app/controllers/repository_rows_controller.rb | 25 ++++++- app/helpers/repository_datatable_helper.rb | 46 ++++++++++++ app/models/repository.rb | 2 + app/models/repository_list_value.rb | 6 +- .../views/datatables/search_repository.rb | 21 ++++++ app/services/repository_datatable_service.rb | 73 +++++++++++++++++++ app/views/repository_rows/index.json.jbuilder | 6 ++ config/environments/development.rb | 2 +- config/routes.rb | 2 +- ...180306074931_create_search_repositories.rb | 5 ++ db/schema.rb | 63 +++++++++++++++- db/views/search_repositories_v01.sql | 28 +++++++ spec/models/repository_list_value_spec.rb | 58 ++++++++++++++- spec/models/search_repository_spec.rb | 5 ++ 14 files changed, 335 insertions(+), 7 deletions(-) create mode 100644 app/helpers/repository_datatable_helper.rb create mode 100644 app/models/views/datatables/search_repository.rb create mode 100644 app/services/repository_datatable_service.rb create mode 100644 app/views/repository_rows/index.json.jbuilder create mode 100644 db/migrate/20180306074931_create_search_repositories.rb create mode 100644 db/views/search_repositories_v01.sql create mode 100644 spec/models/search_repository_spec.rb diff --git a/app/controllers/repository_rows_controller.rb b/app/controllers/repository_rows_controller.rb index cb34496f3..ddb8dbd37 100644 --- a/app/controllers/repository_rows_controller.rb +++ b/app/controllers/repository_rows_controller.rb @@ -5,11 +5,23 @@ class RepositoryRowsController < ApplicationController 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) + before_action :load_repository, only: %i(create delete_records index) + before_action :load_columns_mappings, only: :index before_action :check_create_permissions, only: :create before_action :check_edit_permissions, only: %i(edit update) before_action :check_destroy_permissions, only: :delete_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, + @columns_mappings) + @repository_row_count = records.repository_rows.count + @repository_rows = records.repository_rows.page(page).per(per_page) + end + def create record = RepositoryRow.new(repository: @repository, created_by: current_user, @@ -238,6 +250,17 @@ class RepositoryRowsController < ApplicationController def load_repository @repository = Repository.find_by_id(params[:repository_id]) render_404 unless @repository + render_403 unless can_read_team?(@repository.team) + end + + def load_columns_mappings + # Make mappings of custom columns, so we have same id for every column + i = 5 + @columns_mappings = {} + @repository.repository_columns.order(:id).each do |column| + @columns_mappings[column.id] = i.to_s + i += 1 + end end def check_create_permissions diff --git a/app/helpers/repository_datatable_helper.rb b/app/helpers/repository_datatable_helper.rb new file mode 100644 index 000000000..c93bdf09d --- /dev/null +++ b/app/helpers/repository_datatable_helper.rb @@ -0,0 +1,46 @@ +module RepositoryDatatableHelper + include InputSanitizeHelper + def prepare_row_columns(repository_rows, repository, columns_mappings, team) + parsed_records = [] + repository_rows.each do |record| + row = { + 'DT_RowId': record.id, + '1': assigned_row(record), + '2': escape_input(record.name), + '3': I18n.l(record.created_at, format: :full), + '4': escape_input(record.created_by.full_name), + 'recordEditUrl': + Rails.application.routes.url_helpers + .edit_repository_repository_row_path(repository, + record.id), + 'recordUpdateUrl': + Rails.application.routes.url_helpers + .repository_repository_row_path(repository, record.id), + 'recordInfoUrl': + Rails.application.routes.url_helpers.repository_row_path(record.id) + } + + # Add custom columns + # byebug + record.repository_cells.each do |cell| + row[columns_mappings[cell.repository_column.id]] = + custom_auto_link( + display_tooltip(cell.value.data, + Constants::NAME_MAX_LENGTH), + simple_format: true, + team: team + ) + end + parsed_records << row + end + parsed_records + end + + def assigned_row(record) + # if @assigned_rows && @assigned_rows.include?(record) + # " " + # else + " " + # end + end +end diff --git a/app/models/repository.rb b/app/models/repository.rb index fce0a148d..ed5775492 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -12,6 +12,8 @@ class Repository < ApplicationRecord inverse_of: :repository, dependent: :destroy has_many :report_elements, inverse_of: :repository, dependent: :destroy has_many :repository_list_items, inverse_of: :repository, dependent: :destroy + has_many :repository_searchable_rows, + class_name: '::Views::Datatables::SearchRepository' auto_strip_attributes :name, nullify: false validates :name, diff --git a/app/models/repository_list_value.rb b/app/models/repository_list_value.rb index ae92b068b..c53856809 100644 --- a/app/models/repository_list_value.rb +++ b/app/models/repository_list_value.rb @@ -13,7 +13,11 @@ class RepositoryListValue < ApplicationRecord validates :repository_cell, presence: true def formatted - return '' unless repository_list_item + data.to_s + end + + def data + return nil unless repository_list_item repository_list_item.data end end diff --git a/app/models/views/datatables/search_repository.rb b/app/models/views/datatables/search_repository.rb new file mode 100644 index 000000000..2ff6a6f84 --- /dev/null +++ b/app/models/views/datatables/search_repository.rb @@ -0,0 +1,21 @@ +module Views + module Datatables + class SearchRepository < ApplicationRecord + belongs_to :repository + # def self.records(repository, search_value) + # # binding.pry + # # # where('repository_rows.repository_id', repository.id).to_a + # # where(repository_id: repository.id) + # # .where() + # end + + private + + # this isn't strictly necessary, but it will prevent + # rails from calling save, which would fail anyway. + def readonly? + true + end + end + end +end diff --git a/app/services/repository_datatable_service.rb b/app/services/repository_datatable_service.rb new file mode 100644 index 000000000..61fe08d12 --- /dev/null +++ b/app/services/repository_datatable_service.rb @@ -0,0 +1,73 @@ +class RepositoryDatatableService + + attr_reader :repository_rows + + def initialize(repository, params, mappings) + @mappings = mappings + @repository = repository + process_query(params) + end + + private + + def process_query(params) + contitions = build_conditions(params) + if contitions[:search_value].present? + @repository_rows = search(contitions[:search_value]) + else + @repository_rows = fetch_records + end + # byebug + end + + def fetch_records + RepositoryRow.preload(:repository_columns, + :created_by, + repository_cells: :value) + .joins(:created_by) + .where(repository: @repository) + end + + def search(value) + # binding.pry + filtered_rows = @repository.repository_searchable_rows.where( + 'name ILIKE :value + OR to_char(created_at, :time) ILIKE :value + OR user_full_name ILIKE :value + OR text_value ILIKE :value + OR date_value ILIKE :value + OR list_value ILIKE :value', + value: "%#{value}%", + time: "DD.MM.YYYY HH24:MI" + ).pluck(:id) + fetch_records.where(id: filtered_rows) + end + + def build_conditions(params) + search_value = params[:search][:value] + order_by_column = { column: params[:order][:column].to_i, + dir: params[:order][:dir] } + { search_value: search_value, order_by_column: order_by_column } + end + + def sortable_columns + sort_array = [ + 'assigned', + 'RepositoryRow.name', + 'RepositoryRow.created_at', + 'User.full_name' + ] + + sort_array.push(*repository_columns_sort_by) + @sortable_columns = sort_array + end + + def repository_columns_sort_by + array = [] + @repository.repository_columns.count.times do + array << 'RepositoryCell.value' + end + array + end + +end diff --git a/app/views/repository_rows/index.json.jbuilder b/app/views/repository_rows/index.json.jbuilder new file mode 100644 index 000000000..23af2e9f3 --- /dev/null +++ b/app/views/repository_rows/index.json.jbuilder @@ -0,0 +1,6 @@ +json.draw @draw +json.recordsTotal @repository_rows.total_count +json.recordsFiltered @repository_row_count +json.data do + json.array! prepare_row_columns(@repository_rows, @repository, @columns_mappings, @repository.team) +end diff --git a/config/environments/development.rb b/config/environments/development.rb index 5f6bbe557..580ca9c48 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -75,7 +75,7 @@ Rails.application.configure do config.assets.raise_runtime_errors = true # Only log info and higher on development - config.log_level = :info + config.log_level = :debug # Only allow Better Errors to work on trusted ip, use ifconfig to see which # one you use and put it into application.yml! diff --git a/config/routes.rb b/config/routes.rb index e28efbd58..e550e303d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -449,7 +449,7 @@ Rails.application.routes.draw do resources :repositories do post 'repository_index', - to: 'repositories#repository_table_index', + to: 'repository_rows#index', # repository_rows#index repositories#repository_table_index as: 'table_index', defaults: { format: 'json' } # Save repository table state diff --git a/db/migrate/20180306074931_create_search_repositories.rb b/db/migrate/20180306074931_create_search_repositories.rb new file mode 100644 index 000000000..9311752f9 --- /dev/null +++ b/db/migrate/20180306074931_create_search_repositories.rb @@ -0,0 +1,5 @@ +class CreateSearchRepositories < ActiveRecord::Migration[5.1] + def change + create_view :search_repositories + end +end diff --git a/db/schema.rb b/db/schema.rb index 4fac94123..a3a437072 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180207095200) do +ActiveRecord::Schema.define(version: 20180306074931) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -964,4 +964,65 @@ ActiveRecord::Schema.define(version: 20180207095200) do JOIN user_teams ON ((teams.id = user_teams.team_id))); SQL + create_view "search_repositories", sql_definition: <<-SQL + SELECT DISTINCT repository_rows.id, + repository_rows.repository_id, + repository_rows.created_by_id, + repository_rows.last_modified_by_id, + repository_rows.name, + repository_rows.created_at, + repository_rows.updated_at, + users.full_name AS user_full_name, + "values".text_value, + "values".date_value, + "values".list_value + FROM ((repository_rows + JOIN ( SELECT users_1.id, + users_1.full_name, + users_1.initials, + users_1.email, + users_1.encrypted_password, + users_1.reset_password_token, + users_1.reset_password_sent_at, + users_1.remember_created_at, + users_1.sign_in_count, + users_1.current_sign_in_at, + users_1.last_sign_in_at, + users_1.current_sign_in_ip, + users_1.last_sign_in_ip, + users_1.created_at, + users_1.updated_at, + users_1.avatar_file_name, + users_1.avatar_content_type, + users_1.avatar_file_size, + users_1.avatar_updated_at, + users_1.confirmation_token, + users_1.confirmed_at, + users_1.confirmation_sent_at, + users_1.unconfirmed_email, + users_1.invitation_token, + users_1.invitation_created_at, + users_1.invitation_sent_at, + users_1.invitation_accepted_at, + users_1.invitation_limit, + users_1.invited_by_type, + users_1.invited_by_id, + users_1.invitations_count, + users_1.tutorial_status, + users_1.current_team_id, + users_1.authentication_token, + users_1.settings + FROM users users_1) users ON ((users.id = repository_rows.created_by_id))) + LEFT JOIN ( SELECT repository_cells.repository_row_id, + repository_text_values.data AS text_value, + to_char(repository_date_values.data, 'DD.MM.YYYY HH24:MI'::text) AS date_value, + ( SELECT repository_list_items.data + FROM repository_list_items + WHERE (repository_list_items.id = repository_list_values.repository_list_item_id)) AS list_value + FROM (((repository_cells + JOIN repository_text_values ON ((repository_text_values.id = repository_cells.value_id))) + FULL JOIN repository_date_values ON ((repository_date_values.id = repository_cells.value_id))) + FULL JOIN repository_list_values ON ((repository_list_values.id = repository_cells.value_id)))) "values" ON (("values".repository_row_id = repository_rows.id))); + SQL + end diff --git a/db/views/search_repositories_v01.sql b/db/views/search_repositories_v01.sql new file mode 100644 index 000000000..e9112c506 --- /dev/null +++ b/db/views/search_repositories_v01.sql @@ -0,0 +1,28 @@ +SELECT DISTINCT + repository_rows.*, + users.full_name AS user_full_name, + values.text_value AS text_value, + values.date_value AS date_value, + values.list_value AS list_value + FROM repository_rows + INNER JOIN ( + SELECT users.* + FROM users + ) AS users + ON users.id = repository_rows.created_by_id + LEFT OUTER JOIN ( + SELECT repository_cells.repository_row_id, + repository_text_values.data AS text_value, + to_char(repository_date_values.data, 'DD.MM.YYYY HH24:MI') AS date_value, + ( SELECT repository_list_items.data + FROM repository_list_items + WHERE repository_list_items.id = repository_list_values.repository_list_item_id ) AS list_value + FROM repository_cells + INNER JOIN repository_text_values + ON repository_text_values.id = repository_cells.value_id + FULL OUTER JOIN repository_date_values + ON repository_date_values.id = repository_cells.value_id + FUll OUTER JOIN repository_list_values + ON repository_list_values.id = repository_cells.value_id + ) AS values + ON values.repository_row_id = repository_rows.id diff --git a/spec/models/repository_list_value_spec.rb b/spec/models/repository_list_value_spec.rb index dcae9396c..4c9905bab 100644 --- a/spec/models/repository_list_value_spec.rb +++ b/spec/models/repository_list_value_spec.rb @@ -18,7 +18,7 @@ RSpec.describe RepositoryListValue, type: :model do it { should accept_nested_attributes_for(:repository_cell) } end - describe '#data' do + describe '#formatted' do let!(:repository) { create :repository } let!(:repository_column) { create :repository_column, name: 'My column' } let!(:repository_column) do @@ -32,7 +32,7 @@ RSpec.describe RepositoryListValue, type: :model do } end - it 'returns the data of a selected item' do + it 'returns the formatted data of a selected item' do list_item = create :repository_list_item, data: 'my item', repository: repository, @@ -71,4 +71,58 @@ RSpec.describe RepositoryListValue, type: :model do expect(repository_list_value.reload.formatted).to eq '' end end + + describe '#data' do + let!(:repository) { create :repository } + let!(:repository_column) { create :repository_column, name: 'My column' } + let!(:repository_column) do + create :repository_column, data_type: :RepositoryListValue + end + let!(:repository_row) { create :repository_row, name: 'My row' } + let!(:repository_list_value) do + create :repository_list_value, repository_cell_attributes: { + repository_column: repository_column, + repository_row: repository_row + } + end + + it 'returns the data of a selected item' do + list_item = create :repository_list_item, + data: 'my item', + repository: repository, + repository_column: repository_column + repository_list_value.repository_list_item = list_item + repository_list_value.save + expect(repository_list_value.reload.data).to eq 'my item' + end + + it 'retuns only the the item related to the list' do + repository_row_two = create :repository_row, name: 'New row' + repository_list_value_two = + create :repository_list_value, repository_cell_attributes: { + repository_column: repository_column, + repository_row: repository_row_two + } + list_item = create :repository_list_item, + data: 'new item', + repository: repository, + repository_column: repository_column + repository_list_value.repository_list_item = list_item + expect(repository_list_value.reload.data).to_not eq 'my item' + expect(repository_list_value.data).to be_nil + end + + it 'returns an empty string if no item selected' do + list_item = create :repository_list_item, + data: 'my item', + repository: repository, + repository_column: repository_column + expect(repository_list_value.reload.data).to be_nil + end + + it 'returns an empty string if item does not exists' do + repository_list_value.repository_list_item = nil + expect(repository_list_value.reload.data).to be_nil + end + end end diff --git a/spec/models/search_repository_spec.rb b/spec/models/search_repository_spec.rb new file mode 100644 index 000000000..052920181 --- /dev/null +++ b/spec/models/search_repository_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe SearchRepository, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end