Class: SearchesController
- Inherits:
-
CrmController
- Object
- ActionController::Base
- ApplicationController
- CrmController
- SearchesController
- Defined in:
- app/controllers/searches_controller.rb
Overview
Controller: searches.
Constant Summary
Constants included from Controllers::ReferenceFindable
Controllers::ReferenceFindable::ID_EMBEDDED_PATTERNS
Constants included from Controllers::AnalyticsEvents
Controllers::AnalyticsEvents::MAX_QUEUED_EVENTS, Controllers::AnalyticsEvents::SESSION_KEY
Constants included from Controllers::ErrorRendering
Controllers::ErrorRendering::NON_CONTENT_PATH_PREFIXES
Instance Method Summary collapse
-
#advanced_search_panel ⇒ Object
Lazy-loaded body for the navbar's Advanced Search offcanvas.
-
#create ⇒ void
Creates a search from submitted params, seeded like
new. -
#create_onboarding_packet ⇒ void
Builds a zipped onboarding-packet CSV of the search's results and offers it as a download.
-
#destroy ⇒ void
Deletes the current user's search.
-
#edit ⇒ void
Loads a search for editing.
-
#get_first_search_result_link ⇒ String?
Finds a link (CRM route or polymorphic path) for the search's first result.
-
#index ⇒ void
Lists the employee's saved searches for the given search type, or routes to
search_typeswhen no type is given. -
#mass_action ⇒ void
Runs the requested mass action against the search's selected results.
-
#new ⇒ void
Builds a new, unsaved search, seeded from the given template's global favorites.
-
#onboarding_packet ⇒ void
Renders the onboarding-packet export screen for the search's results.
-
#perform_mass_action ⇒ void
Validates and runs the mass action submitted from the
mass_actionconfirmation. -
#pin ⇒ void
Marks the search pinned, if it has selected results.
-
#pin_all ⇒ void
Records every result and marks the search pinned.
-
#pinned_results ⇒ void
Returns the given employee's pinned search results.
-
#record ⇒ void
Records/unrecords the given result ids on the search's mass-action selection.
-
#refresh_pinned ⇒ Object
Prunes the user's pinned set against current data: keeps only the records that still match the saved criteria (e.g. drops an opportunity that was closed and no longer satisfies an "open" filter).
-
#reset ⇒ void
Clears the search's saved query params.
-
#search_and_pin ⇒ Object
Executes a search and pin in one shot.
-
#search_and_show ⇒ void
Builds, saves, and runs a search in one step, rendering results inline (used by embedded/turbo-frame search widgets).
-
#search_types ⇒ void
Lists the available search types.
-
#show ⇒ void
Runs the search and displays its paginated results.
-
#unpin ⇒ void
Unpins (destroys) a pinned search result.
-
#update ⇒ void
Updates a search from submitted params.
Methods inherited from CrmController
#access_denied, #context_id, #context_object, #crm_home_path, #current_ability, #default_url_options, #download_temp, #get_tempfile_path_for_download, #init_status_job_collector, #initialize_crm_lazy_chunks, #persist_enqueued_status_jobs, #record_not_found, #redirect_to_job_or_fallback, #render_edit_action, #set_context, #set_download_path, #stash_file_for_temp_download, #sync_admin_presence_cookie, #touch_employee_last_seen
Methods inherited from ApplicationController
#account_impersonated?, #add_to_flash, #after_sign_in_path_for, #bypass_forgery_protection?, #chat_enabled?, #cloudflare_cleared?, #default_catalog, #default_url_options, #enable_turbo_frames, #find_publication, #fix_invalid_accept_header, #init_js_utils, #is_globals_call?, #layout_by_resource, #locale_store, #redirect_to, #require_employee_for_crm, #set_base_host, #set_real_ip, #set_report_errors_for, #should_render_layout?, #skip_layout_for_turbo_frame?, #stamp_impersonation_context, #tab_frame_breakout_request?, #warmlyyours_canada_ip?, #warmlyyours_ip?, #y
Methods included from Controllers::ReturnPathHandling
#check_for_return_path, #redirect_to_return_path_or_default
Methods included from Controllers::AnalyticsEvents
#consume_queued_analytics_events, #registration_lead_type, #track_event
Methods included from Controllers::DeviceDetection
Methods included from Controllers::SubdomainDetection
#is_crm_request?, #is_www_request?, #json_request?
Methods included from Controllers::TurboSafeRedirect
Methods included from Controllers::TrackingDetection
#bot_request?, #gdpr_country?, #gdpr_country_data, #prevent_bots, #set_tracking_cookie, #track_visitor?
Methods included from Controllers::AcceleratedFileSending
#send_file_accelerated, #send_upload_accelerated
Methods included from Controllers::ErrorRendering
#excp_string, #mail_to_for_error_reporting, #render_400, #render_404, #render_406, #render_410, #render_500, #render_invalid_authenticity_token, #render_ip_spoof_error, #render_unpermitted_parameters, #safe_referer_or_fallback
Methods included from Controllers::TurnstileVerification
#load_turnstile_script_tag, #turnstile_lazy_widget, #turnstile_script_tag, #turnstile_widget, #validate_turnstile!
Methods included from Controllers::CloudflareCaching
edge_cached, #edge_cached_action?, #reset_cloudflare_cache, #set_cloudflare_cache, #skip_edge_cache!, #skip_session
Methods included from Controllers::Webpackable
#preload_webpack_fonts, #webpack_css_include, #webpack_css_url, #webpack_js_include, #wpd_is_running?
Methods included from Controllers::Localizable
#cloudflare_country_locale, #determine_request_locale, #geocoder_locale, #guest_user_locale_check, #locale_optional_www_auth_path?, #param_locale, #set_locale, #set_request_locale, #skip_localization?, #warmlyyours_ip_locale
Methods included from Controllers::Authenticable
#access_denied, #authenticate_account, #authenticate_account!, #authenticate_account_from_login_token!, #check_is_a_manager, #check_is_a_sales_manager, #check_is_an_admin, #check_is_an_employee, #check_party, #clear_mismatched_guest_user, #create_guest_user, #credentials?, #current_or_guest_user, #current_or_guest_user_id_read_only, #current_user, #devise_mapping, #fully_logged_in?, #generate_bot_id, #guest_user, #identifiable?, #init_current_user, #initialize_guest, #load_context_user, #logging_in, #resource, #resource_name, #restrict_access_for_non_employees, #scrubbed_request_path, #user_object, #warn_on_session_guest_id_leak
Methods included from UrlsHelper
#catalog_breadcrumb_links, #catalog_link, #catalog_link_for_product_line, #catalog_link_for_sku, #cms_link, #delocalized_path, #path_to_sales_product_sku, #path_to_sales_product_sku_for_product_line, #path_to_sales_product_sku_for_product_line_slug, #product_line_from_catalog_link, #protocol_neutral_url, #sanitize_external_url, #valid_external_url?
Instance Method Details
#advanced_search_panel ⇒ Object
Lazy-loaded body for the navbar's Advanced Search offcanvas. The navbar
renders an empty <turbo-frame id="advancedSearchOffcanvasFrame"> shell;
crm-navbar Stimulus sets the frame's src to this action on every
show.bs.offcanvas, so the per-type recent-search and template queries
only run when the user actually opens the panel.
expires_in 15.seconds, public: false lets the user's browser cache
the response for rapid re-opens (matches the lazy navbar dropdown
bodies) while keeping responses out of any shared cache. Per-user
data, so private is mandatory.
42 43 44 45 |
# File 'app/controllers/searches_controller.rb', line 42 def advanced_search_panel expires_in 15.seconds, public: false render partial: 'advanced_search_offcanvas_body', layout: false end |
#create ⇒ void
This method returns an undefined value.
Creates a search from submitted params, seeded like new.
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'app/controllers/searches_controller.rb', line 107 def create template_key = params[:template_key] || :default p = search_class.try(:global_favorites).try(:dig, template_key) || {} p = p.slice(:query_params, :sort_columns) p = p.deep_merge(params[:search].to_h) @search = search_class.new(p) @search.employee_id = employee.id :create, @search if @search.save if @search.pinned @search.perform(1, nil, nil, true) redir_url = get_first_search_result_link if redir_url flash[:info] = 'Search results were automatically pinned' redirect_to redir_url else redirect_to search_path(@search) end elsif params[:edit].present? redirect_to edit_search_path(@search) else redirect_to search_path(@search) end else render :new, status: :unprocessable_content end end |
#create_onboarding_packet ⇒ void
This method returns an undefined value.
Builds a zipped onboarding-packet CSV of the search's results and offers
it as a download.
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 |
# File 'app/controllers/searches_controller.rb', line 379 def create_onboarding_packet require 'zip' @search = Search.find(params[:id]) zip_name = "onboarding_#{@search.id}.zip" directory_path = Rails.root.join(Rails.application.config.x.temp_storage_path.to_s, 'users', current_user ? current_user.id.to_s : '') FileUtils.mkdir_p(directory_path) zip_path = Rails.root.join(directory_path, zip_name) Zip::File.open(zip_path, create: true) do |zipfile| file_name = "onboarding_#{@search.id}#{"_#{zipfile.size}" if zipfile.size.positive?}.csv" file_path = Rails.root.join(directory_path, file_name) CSV.open(file_path, 'w', force_quotes: true, encoding: 'WINDOWS-1252') do |csv| csv << ['item_name', 'item_sku', 'product_category', 'brand_name', 'collection', 'UPC', 'product_weight', 'product_height', 'product_width', 'product_depth', 'shipping_weight', 'shipping_height', 'shipping_width', 'shipping_depth', 'shipping_type', 'product_description', 'bullet_point_1', 'bullet_point_2', 'bullet_point_3', 'bullet_point_4', 'bullet_point_5', 'country_of_origin', 'Length', 'Amps', 'Weight', 'Width', 'Voltage', 'Heating Wire', 'Floor Types', 'Approvals', 'Watts', 'BTU per Hour', 'Heated Area'] @search.search_results.each do |result| row = [] catalog_item = result.resource item = catalog_item.item row.push(item.name, item.sku, item.brand_id.equal?(1) ? 'WarmlyYours USA' : 'WarmlyYours CA', 'collection', item.upc, item.base_weight, 'product_height', item.width, 'product_depth', item.shipping_weight, item.shipping_height, item.shipping_width, 'shipping_depth', 'shipping_type', item.detailed_description.presence || catalog_item.primary_product_line.description, 'bullet_point_1', 'bullet_point_2', 'bullet_point_3', 'bullet_point_4', 'bullet_point_5', item.coo) item.product_specifications.each do |spec| row << spec.get_specification_data(catalog_item).formatted end csv << row end end zipfile.add(file_name, file_path) end session[:download_path] = "/tempfile/#{zip_name}" flash[:info] = "File created <br><a href='#{session[:download_path]}' class='btn btn-outline-primary'>Download</a>" redirect_back_or_to(root_path) end |
#destroy ⇒ void
This method returns an undefined value.
Deletes the current user's search.
442 443 444 445 446 |
# File 'app/controllers/searches_controller.rb', line 442 def destroy @search = @context_user.searches.find(params[:id]) flash[:info] = "Search id #{@search.id} deleted." if @search.destroy redirect_to_return_path_or_default employee_searches_path(@context_user, type: @search.class.name) end |
#edit ⇒ void
This method returns an undefined value.
Loads a search for editing.
100 101 102 |
# File 'app/controllers/searches_controller.rb', line 100 def edit @search = Search.find(params[:id]) end |
#get_first_search_result_link ⇒ String?
Finds a link (CRM route or polymorphic path) for the search's first result.
250 251 252 253 254 255 256 257 258 259 260 261 262 |
# File 'app/controllers/searches_controller.rb', line 250 def get_first_search_result_link return unless (first_resource = @search.search_results.first&.resource) begin first_resource.crm_link rescue StandardError nil end || begin polymorphic_path(first_resource) rescue StandardError nil end end |
#index ⇒ void
This method returns an undefined value.
Lists the employee's saved searches for the given search type, or routes
to search_types when no type is given.
12 13 14 15 16 17 18 19 |
# File 'app/controllers/searches_controller.rb', line 12 def index if params[:type].present? search_class # sets the search class @searches = employee.searches.where(type: params[:type]).order(created_at: :desc) else redirect_to(search_types_searches_path) end end |
#mass_action ⇒ void
This method returns an undefined value.
Runs the requested mass action against the search's selected results.
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 |
# File 'app/controllers/searches_controller.rb', line 335 def mass_action expires_now @search = Search.find(params[:id]) @mass_action_type = params[:mass_action_type] if @search.search_results.empty? redirect_to search_path(@search), flash: { error: 'You must select some records before performing a mass action.' } elsif @mass_action_type.blank? || !@search.respond_to?(@mass_action_type) redirect_to search_path(@search), flash: { error: 'You must select a valid mass action to perform.' } else # Try to find if a partial is present to render a pre-execution form to capture params or an action to perform directly @mass_action = OpenStruct.new(params[:mass_action].to_h) view_root_path = Rails.root.join('app/views/searches') # Is there a search model specific view for this mass action? if File.exist?(view_root_path.join(@search.class.name.underscore, "_#{@mass_action_type}.html.erb")) @mass_action_partial_path = "/searches/#{@search.class.name.underscore}/#{@mass_action_type}" # Is there a generalized search view? elsif File.exist?(view_root_path.join("_#{@mass_action_type}.html.erb")) @mass_action_partial_path = "/searches/#{@mass_action_type}" # execute mass action directly else @search.send(@mass_action_type.to_sym, self) end end end |
#new ⇒ void
This method returns an undefined value.
Builds a new, unsaved search, seeded from the given template's global favorites.
84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'app/controllers/searches_controller.rb', line 84 def new template_key = params[:template_key] || :default p = search_class.try(:global_favorites).try(:dig, template_key) || {} p = p.slice(:query_params, :sort_columns) p = p.deep_merge(params[:search] || {}) p[:set_limit] ||= 50 p[:employee] ||= employee p[:query_params] = params[:query_params]&.to_h.presence || p[:query_params] || {} @search = search_class.new(p) @search.set_defaults :create, @search end |
#onboarding_packet ⇒ void
This method returns an undefined value.
Renders the onboarding-packet export screen for the search's results.
363 364 365 366 367 368 369 370 371 372 373 |
# File 'app/controllers/searches_controller.rb', line 363 def onboarding_packet @search = Search.find(params[:id]) # @search.search_results.each do |result| # catalog_item = result.resource # item = catalog_item.item # # end respond_to do |format| format.html end end |
#perform_mass_action ⇒ void
This method returns an undefined value.
Validates and runs the mass action submitted from the mass_action confirmation.
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 |
# File 'app/controllers/searches_controller.rb', line 423 def perform_mass_action mass_action_params = params[:mass_action].to_h.deep_squish.with_indifferent_access @search = Search.find(params[:id]) @mass_action = params[:mass_action_type] unless @mass_action.present? && @search.respond_to?(@mass_action) flash[:error] = "Mass action #{params[:mass_action]} not recognized" redirect_to search_path(@search) and return end # Every mass action method now owns its own background execution by enqueuing # a worker and returning { job_id: }. The controller always calls the method # directly and routes based on the response type. run_mass_action_synchronously(mass_action_params) end |
#pin ⇒ void
This method returns an undefined value.
Marks the search pinned, if it has selected results.
277 278 279 280 281 282 283 284 285 |
# File 'app/controllers/searches_controller.rb', line 277 def pin @search = Search.find(params[:id]) if @search.search_results.present? mark_pinned_and_redirect else flash[:error] = 'Your search has no results that can be pinned. Select records before attempting pinning.' redirect_to search_path(@search) end end |
#pin_all ⇒ void
This method returns an undefined value.
Records every result and marks the search pinned.
267 268 269 270 271 272 |
# File 'app/controllers/searches_controller.rb', line 267 def pin_all @search = Search.find(params[:id]) @search.record_list('*') @search.update_attribute!(:pinned, true) mark_pinned_and_redirect end |
#pinned_results ⇒ void
This method returns an undefined value.
Returns the given employee's pinned search results.
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 |
# File 'app/controllers/searches_controller.rb', line 290 def pinned_results results = {} @search = Search.where(employee_id: params[:employee_id]).where(pinned: true).first @context_type = params[:context_type] @context_id = params[:context_id] @current_path = params[:current_path] if @search @pagy, @results = pagy(@search.get_pinned_results, limit: 20) results[:status] = :ok results[:results] = @results else results[:status] = :not_found end respond_to do |format| format.turbo_stream format.json { render json: results } format.html { render layout: false } end end |
#record ⇒ void
This method returns an undefined value.
Records/unrecords the given result ids on the search's mass-action selection.
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'app/controllers/searches_controller.rb', line 160 def record @search = Search.find(params[:id]) check_list_ids = params[:check_list_ids] uncheck_list_ids = params[:uncheck_list_ids] @search.record_list(check_list_ids) @search.unrecord_list(uncheck_list_ids) respond_to do |format| # Primary client path: render the mass-action button (counter + # dropdown items) server-side. The server knows the post-update # selected count and total_count, so it renders the right state # without any client-side sync logic. format.turbo_stream do render turbo_stream: turbo_stream.replace("mass-action-button-#{@search.id}", partial: 'searches/search_actions') end # Kept for any non-browser caller. format.json { render json: { status: :ok, selected: @search.search_results.count, checked: check_list_ids, unchecked: uncheck_list_ids } } end end |
#refresh_pinned ⇒ Object
Prunes the user's pinned set against current data: keeps only the records
that still match the saved criteria (e.g. drops an opportunity that was
closed and no longer satisfies an "open" filter). Never auto-adds new
rows — pinning is a curated subset and we respect that. Mutating action —
POST only, and restricted to the current user's own pinned search.
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 |
# File 'app/controllers/searches_controller.rb', line 316 def refresh_pinned head :forbidden and return unless current_user.id.to_s == params[:employee_id].to_s @search = Search.where(employee_id: current_user.id, pinned: true).first @current_path = params[:current_path] if @search @search.refresh_pinned_results @pagy, @results = pagy(@search.get_pinned_results, limit: 20) end respond_to do |format| format.turbo_stream { render :pinned_results } end end |
#reset ⇒ void
This method returns an undefined value.
Clears the search's saved query params.
139 140 141 142 143 144 |
# File 'app/controllers/searches_controller.rb', line 139 def reset @search = Search.find(params[:id]) @search.query_params = nil @search.save redirect_to search_path(@search) end |
#search_and_pin ⇒ Object
Executes a search and pin in one shot
234 235 236 237 238 239 240 241 242 243 244 245 |
# File 'app/controllers/searches_controller.rb', line 234 def search_and_pin @search = params[:id].present? ? Search.find(params[:id]) : search_class.new(params[:search]) @search.employee_id = employee.id @search.pinned = true if @search.save @search.perform(1, nil, nil, true) # True stores the pinned search @results = @search.pinned_query end respond_to do |format| format.turbo_stream end end |
#search_and_show ⇒ void
This method returns an undefined value.
Builds, saves, and runs a search in one step, rendering results inline
(used by embedded/turbo-frame search widgets).
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'app/controllers/searches_controller.rb', line 197 def search_and_show @search = search_class.new(set_limit: params[:set_limit] || 50, employee: @context_user, query_params: params[:query_params].to_h.presence || {}, selected_columns: params[:selected_columns], sort_columns: params[:sort_columns]) @links = params[:links] || {} @target_id = params[:target_id] || 'search_and_show' action_view = params[:search_mode] == 'full' ? 'show' : 'search_and_show' unless @search.save # Search criteria failed validation: surface the error rather than # returning an empty 200 (which Turbo silently discards). flash.now[:error] = "Search could not be saved: #{@search.errors_to_s}" respond_to do |format| format.html { render plain: flash.now[:error], status: :unprocessable_content } format.turbo_stream { render partial: 'shared/flash', status: :unprocessable_content } end return end # Perform paginated query; Search will expose Pagy via pagy_from_search page = (params[:page] || 1).to_i # Pagy uses params[:items] for per-page count, fall back to saved set_limit per_page = params[:items].presence || @search.set_limit @results = @search.perform(page, params[:sort], params[:direction], false, per_page, true, false) # Create Pagy object with request context for URL generation if @search.pagy_from_search @pagy = pagy(:offset, @results, count: @search.pagy_from_search.count, page: @search.pagy_from_search.page, limit: @search.pagy_from_search.limit)[0] end respond_to do |format| format.html { render action: action_view, layout: should_render_layout? } format.turbo_stream end end |
#search_types ⇒ void
This method returns an undefined value.
Lists the available search types.
24 25 26 27 28 29 30 |
# File 'app/controllers/searches_controller.rb', line 24 def search_types @search_types = Search. # `should_render_layout?` keeps the response usable both as a real page # (e.g. when SearchesController#index redirects here with no `:type`) # and as a turbo-frame fragment. render layout: should_render_layout? end |
#show ⇒ void
This method returns an undefined value.
Runs the search and displays its paginated results.
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'app/controllers/searches_controller.rb', line 50 def show @search = Search.find(params[:id]) :read, @search begin # Ask the search to return the current page; it will set result_set_size and pagy_from_search page = (params[:page] || 1).to_i # Pagy uses params[:items] for per-page count, fall back to saved set_limit per_page = params[:items].presence || @search.set_limit @results = @search.perform(page, params[:sort], params[:direction], false, per_page, true, false) # Create Pagy object with request context for URL generation if @search.pagy_from_search @pagy = pagy(:offset, @results, count: @search.pagy_from_search.count, page: @search.pagy_from_search.page, limit: @search.pagy_from_search.limit)[0] end rescue StandardError => e Rails.logger.error "[SearchesController#show] perform failed for Search##{@search.id} (#{@search.type}): #{e.class} — #{e.}" Appsignal.report_error(e) do |transaction| transaction.(search_id: @search.id.to_s, search_type: @search.type) end @pagy = nil @results = [] end respond_to do |format| format.html end end |
#unpin ⇒ void
This method returns an undefined value.
Unpins (destroys) a pinned search result.
149 150 151 152 153 154 155 |
# File 'app/controllers/searches_controller.rb', line 149 def unpin @search_result_id = params[:search_result_id] @search_results = SearchResult.destroy_by(id: @search_result_id) respond_to do |format| format.turbo_stream end end |
#update ⇒ void
This method returns an undefined value.
Updates a search from submitted params.
184 185 186 187 188 189 190 191 |
# File 'app/controllers/searches_controller.rb', line 184 def update @search = Search.find(params[:id]) if @search.update(params[:search]) redirect_to search_path(@search) else render :edit, status: :unprocessable_content end end |