scinote-web/app/controllers/search_controller.rb
Martin Artnik 2ff3477956
Merge pull request #7427 from rekonder/aj_SCI_10246
Implement quick search backend [SCI-10246]
2024-04-15 10:36:12 +02:00

529 lines
20 KiB
Ruby

class SearchController < ApplicationController
include IconsHelper
include ProjectFoldersHelper
before_action :load_vars, only: :index
def index
respond_to do |format|
format.html do
redirect_to new_search_path unless @search_query
end
format.json do
redirect_to new_search_path unless @search_query
case params[:group]
when 'projects'
@project_search_count = fetch_cached_count(Project)
search_projects
if params[:preview] == 'true'
results = @project_results.limit(Constants::GLOBAL_SEARCH_PREVIEW_LIMIT)
else
results = @project_results.page(params[:page]).per(Constants::SEARCH_LIMIT)
end
render json: results.includes(:team, :project_folder),
each_serializer: GlobalSearch::ProjectSerializer,
meta: {
total: @search_count,
next_page: (results.next_page if results.respond_to?(:next_page)),
}
when 'project_folders'
@project_folder_search_count = fetch_cached_count ProjectFolder
search_project_folders
results = if params[:preview] == 'true'
@project_folder_results.limit(Constants::GLOBAL_SEARCH_PREVIEW_LIMIT)
else
@project_folder_results.page(params[:page]).per(Constants::SEARCH_LIMIT)
end
render json: results.includes(:team, :parent_folder),
each_serializer: GlobalSearch::ProjectFolderSerializer,
meta: {
total: @search_count,
next_page: results.try(:next_page)
}
return
when 'reports'
@report_search_count = fetch_cached_count Report
search_reports
results = if params[:preview] == 'true'
@report_results.limit(Constants::GLOBAL_SEARCH_PREVIEW_LIMIT)
else
@report_results.page(params[:page]).per(Constants::SEARCH_LIMIT)
end
render json: results.includes(:team, :project, :user),
each_serializer: GlobalSearch::ReportSerializer,
meta: {
total: @search_count,
next_page: results.try(:next_page)
}
return
when 'module_protocols'
search_module_protocols
results = if params[:preview] == 'true'
@module_protocol_results.limit(Constants::GLOBAL_SEARCH_PREVIEW_LIMIT)
else
@module_protocol_results.page(params[:page]).per(Constants::SEARCH_LIMIT)
end
render json: results.joins({ my_module: :experiment }, :team),
each_serializer: GlobalSearch::MyModuleProtocolSerializer,
meta: {
total: @search_count,
next_page: results.try(:next_page)
}
return
when 'experiments'
@experiment_search_count = fetch_cached_count Experiment
search_experiments
results = if params[:preview] == 'true'
@experiment_results.limit(Constants::GLOBAL_SEARCH_PREVIEW_LIMIT)
else
@experiment_results.page(params[:page]).per(Constants::SEARCH_LIMIT)
end
render json: results.includes(project: :team),
each_serializer: GlobalSearch::ExperimentSerializer,
meta: {
total: @search_count,
next_page: results.try(:next_page)
}
return
when 'tasks'
@module_search_count = fetch_cached_count MyModule
search_modules
results = if params[:preview] == 'true'
@module_results.limit(Constants::GLOBAL_SEARCH_PREVIEW_LIMIT)
else
@module_results.page(params[:page]).per(Constants::SEARCH_LIMIT)
end
render json: results.includes(experiment: { project: :team }),
each_serializer: GlobalSearch::MyModuleSerializer,
meta: {
total: @search_count,
next_page: results.try(:next_page)
}
return
when 'results'
@result_search_count = fetch_cached_count(Result)
search_results
results = if params[:preview] == 'true'
@result_results.limit(Constants::GLOBAL_SEARCH_PREVIEW_LIMIT)
else
@result_results.page(params[:page]).per(Constants::SEARCH_LIMIT)
end
render json: results.includes(my_module: { experiment: { project: :team } }),
each_serializer: GlobalSearch::ResultSerializer,
meta: {
total: @search_count,
next_page: results.try(:next_page)
}
return
when 'protocols'
@protocol_search_count = fetch_cached_count(Protocol)
search_protocols
results = if params[:preview] == 'true'
@protocol_results.take(4)
else
@protocol_results.page(params[:page]).per(Constants::SEARCH_LIMIT)
end
render json: results,
each_serializer: GlobalSearch::ProtocolSerializer,
meta: {
total: @search_count,
next_page: (results.next_page if results.respond_to?(:next_page))
}
return
when 'label_templates'
return render json: [], meta: { disabled: true }, status: :ok unless LabelTemplate.enabled?
@label_template_search_count = fetch_cached_count(LabelTemplate)
search_label_templates
results = if params[:preview] == 'true'
@label_template_results.take(4)
else
@label_template_results.page(params[:page]).per(Constants::SEARCH_LIMIT)
end
render json: results,
each_serializer: GlobalSearch::LabelTemplateSerializer,
meta: {
total: @search_count,
next_page: (results.next_page if results.respond_to?(:next_page))
}
return
when 'repository_rows'
@repository_row_search_count = fetch_cached_count(RepositoryRow)
search_repository_rows
results = if params[:preview] == 'true'
@repository_row_results.take(4)
else
@repository_row_results.page(params[:page]).per(Constants::SEARCH_LIMIT)
end
render json: results,
each_serializer: GlobalSearch::RepositoryRowSerializer,
meta: {
total: @search_count,
next_page: (results.next_page if results.respond_to?(:next_page))
}
return
when 'assets'
@asset_search_count = fetch_cached_count(Asset)
search_assets
includes = [{ step: { protocol: { my_module: :experiment } } }, { result: { my_module: :experiment } }, :team]
results = if params[:preview] == 'true'
@asset_results.limit(Constants::GLOBAL_SEARCH_PREVIEW_LIMIT)
else
@asset_results.page(params[:page]).per(Constants::SEARCH_LIMIT)
end
render json: results.includes(includes),
each_serializer: GlobalSearch::AssetSerializer,
meta: {
total: @search_count,
next_page: (results.next_page if results.respond_to?(:next_page))
}
return
end
#@search_id = params[:search_id] ? params[:search_id] : generate_search_id
#
#count_search_results
#
#search_projects if @search_category == :projects
#search_project_folders if @search_category == :project_folders
#search_experiments if @search_category == :experiments
#search_modules if @search_category == :modules
#search_results if @search_category == :results
#search_tags if @search_category == :tags
#search_reports if @search_category == :reports
#search_protocols if @search_category == :protocols
#search_steps if @search_category == :steps
#search_checklists if @search_category == :checklists
#if @search_category == :repositories && params[:repository]
# search_repository
#end
#search_assets if @search_category == :assets
#search_tables if @search_category == :tables
#search_comments if @search_category == :comments
#@search_pages = (@search_count.to_f / Constants::SEARCH_LIMIT.to_f).ceil
#@start_page = @search_page - 2
#@start_page = 1 if @start_page < 1
#@end_page = @start_page + 4
#if @end_page > @search_pages
# @end_page = @search_pages
# @start_page = @end_page - 4
# @start_page = 1 if @start_page < 1
#end
end
end
end
def new
end
def quick
results = if params[:filter].present?
object_quick_search(params[:filter].singularize,
search_by_id: Constants::QUICK_SEARCH_SEARCHABLE_BY_NAME
.exclude?(params[:filter].singularize))
else
Constants::QUICK_SEARCH_SEARCHABLE_OBJECTS.filter_map do |object|
next if object == 'label_template' && !LabelTemplate.enabled?
object_quick_search(object, search_by_id: Constants::QUICK_SEARCH_SEARCHABLE_BY_NAME.exclude?(object))
end.flatten.sort_by(&:updated_at).reverse.take(Constants::QUICK_SEARCH_LIMIT)
end
render json: results, each_serializer: QuickSearchSerializer
end
private
def object_quick_search(class_name, search_by_id: true)
search_method = class_name.to_s.camelize.constantize.method(search_by_id ? :search_by_name_and_id : :search_by_name)
search_method.call(current_user,
current_team,
params[:query],
limit: Constants::QUICK_SEARCH_LIMIT)
.order(updated_at: :desc)
end
def load_vars
query = (params.fetch(:q) { '' }).strip
@search_category = params[:category] || ''
@search_category = @search_category.to_sym
@search_page = params[:page].to_i || 1
@search_case = params[:match_case] == 'true'
@search_whole_word = params[:whole_word] == 'true'
@search_whole_phrase = params[:whole_phrase] == 'true'
@display_query = query
if @search_whole_phrase || query.count(' ').zero?
if query.length < Constants::NAME_MIN_LENGTH
flash[:error] = t('general.query.length_too_short',
min_length: Constants::NAME_MIN_LENGTH)
redirect_back(fallback_location: root_path)
elsif query.length > Constants::TEXT_MAX_LENGTH
flash[:error] = t('general.query.length_too_long',
max_length: Constants::TEXT_MAX_LENGTH)
redirect_back(fallback_location: root_path)
else
@search_query = query
end
else
# splits the search query to validate all entries
splited_query = query.split
@search_query = ''
splited_query.each_with_index do |w, i|
if w.length >= Constants::NAME_MIN_LENGTH &&
w.length <= Constants::TEXT_MAX_LENGTH
@search_query += "#{splited_query[i]} "
end
end
if @search_query.blank?
flash[:error] = t('general.query.wrong_query',
min_length: Constants::NAME_MIN_LENGTH,
max_length: Constants::TEXT_MAX_LENGTH)
redirect_back(fallback_location: root_path)
else
@search_query.strip!
end
end
@search_page = 1 if @search_page < 1
end
protected
def generate_search_id
SecureRandom.urlsafe_base64(32)
end
def search_by_name(model)
model.search(current_user,
true,
@search_query,
@search_page,
nil,
match_case: @search_case,
whole_word: @search_whole_word,
whole_phrase: @search_whole_phrase)
.order(created_at: :desc)
end
def count_by_name(model)
model.search(current_user,
true,
@search_query,
Constants::SEARCH_NO_LIMIT,
nil,
match_case: @search_case,
whole_word: @search_whole_word,
whole_phrase: @search_whole_phrase).size
end
def count_by_repository
@repository_search_count =
Rails.cache.fetch("#{@search_id}/repository_search_count",
expires_in: 5.minutes) do
search_count = {}
search_results = Repository.search(current_user,
@search_query,
Constants::SEARCH_NO_LIMIT,
nil,
match_case: @search_case,
whole_word: @search_whole_word,
whole_phrase: @search_whole_phrase)
current_user.teams.includes(:repositories).each do |team|
team_results = {}
team_results[:team] = team
team_results[:count] = 0
team_results[:repositories] = {}
Repository.accessible_by_teams(team).each do |repository|
repository_results = {}
repository_results[:id] = repository.id
repository_results[:repository] = repository
repository_results[:count] = 0
search_results.each do |result|
repository_results[:count] += result.counter if repository.id == result.id
end
team_results[:repositories][repository.name] = repository_results
team_results[:count] += repository_results[:count]
end
search_count[team.name] = team_results
end
search_count
end
count_total = 0
@repository_search_count.each_value do |team_results|
count_total += team_results[:count]
end
count_total
end
def current_repository_search_count
@repository_search_count.each_value do |counter|
res = counter[:repositories].values.detect do |rep|
rep[:id] == @repository.id
end
return res[:count] if res && res[:count]
end
end
def count_search_results
@project_search_count = fetch_cached_count Project
@project_folder_search_count = fetch_cached_count ProjectFolder
@experiment_search_count = fetch_cached_count Experiment
@module_search_count = fetch_cached_count MyModule
@result_search_count = fetch_cached_count Result
@tag_search_count = fetch_cached_count Tag
@report_search_count = fetch_cached_count Report
@protocol_search_count = fetch_cached_count Protocol
@step_search_count = fetch_cached_count Step
@checklist_search_count = fetch_cached_count Checklist
@repository_search_count_total = count_by_repository
@asset_search_count = fetch_cached_count Asset
@table_search_count = fetch_cached_count Table
@comment_search_count = fetch_cached_count Comment
@search_results_count = @project_search_count
@search_results_count += @project_folder_search_count
@search_results_count += @experiment_search_count
@search_results_count += @module_search_count
@search_results_count += @result_search_count
@search_results_count += @tag_search_count
@search_results_count += @report_search_count
@search_results_count += @protocol_search_count
@search_results_count += @step_search_count
@search_results_count += @checklist_search_count
@search_results_count += @repository_search_count_total
@search_results_count += @asset_search_count
@search_results_count += @table_search_count
@search_results_count += @comment_search_count
end
def fetch_cached_count(type)
exp = 5.minutes
Rails.cache.fetch(
"#{@search_id}/#{type.name.underscore}_search_count", expires_in: exp
) do
count_by_name type
end
end
def search_projects
@project_results = Project.none
@project_results = search_by_name(Project) if @project_search_count.positive?
@search_count = @project_search_count
end
def search_project_folders
@project_folder_results = ProjectFolder.none
@project_folder_results = search_by_name(ProjectFolder) if @project_folder_search_count.positive?
@search_count = @project_folder_search_count
end
def search_experiments
@experiment_results = Experiment.none
@experiment_results = search_by_name(Experiment) if @experiment_search_count.positive?
@search_count = @experiment_search_count
end
def search_modules
@module_results = MyModule.none
@module_results = search_by_name(MyModule) if @module_search_count.positive?
@search_count = @module_search_count
end
def search_module_protocols
@module_protocol_results = search_by_name(Protocol)
@search_count = @module_protocol_results.joins(:my_module).count
end
def search_results
@result_results = Result.none
@result_results = search_by_name(Result) if @result_search_count.positive?
@search_count = @result_search_count
end
def search_tags
@tag_results = []
@tag_results = search_by_name(Tag) if @tag_search_count.positive?
@search_count = @tag_search_count
end
def search_reports
@report_results = Report.none
@report_results = search_by_name(Report) if @report_search_count.positive?
@search_count = @report_search_count
end
def search_protocols
@protocol_results = Protocol.none
@protocol_results = search_by_name(Protocol) if @protocol_search_count.positive?
@search_count = @protocol_search_count
end
def search_label_templates
@label_template_results = LabelTemplate.none
@label_template_results = search_by_name(LabelTemplate) if @label_template_search_count.positive?
@search_count = @label_template_search_count
end
def search_steps
@step_results = []
@step_results = search_by_name(Step) if @step_search_count.positive?
@search_count = @step_search_count
end
def search_checklists
@checklist_results = []
@checklist_results = search_by_name(Checklist) if @checklist_search_count.positive?
@search_count = @checklist_search_count
end
def search_repository
@repository = Repository.find_by(id: params[:repository])
unless current_user.teams.include?(@repository.team) || @repository.private_shared_with?(current_user.teams)
render_403
end
@repository_results = []
if @repository_search_count_total.positive?
@repository_results =
Repository.search(current_user, @search_query, @search_page,
@repository,
match_case: @search_case,
whole_word: @search_whole_word,
whole_phrase: @search_whole_phrase)
end
@search_count = current_repository_search_count
end
def search_repository_rows
@repository_row_results = RepositoryRow.none
@repository_row_results = search_by_name(RepositoryRow) if @repository_row_search_count.positive?
@search_count = @repository_row_search_count
end
def search_assets
@asset_results = Asset.none
@asset_results = search_by_name(Asset) if @asset_search_count.positive?
@search_count = @asset_search_count
end
def search_tables
@table_results = []
@table_results = search_by_name(Table) if @table_search_count.positive?
@search_count = @table_search_count
end
def search_comments
@comment_results = []
@comment_results = search_by_name(Comment) if @comment_search_count.positive?
@search_count = @comment_search_count
end
end