diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 09cfc4e51..fc2407543 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -42,6 +42,22 @@ class SearchController < ApplicationController def new end + def quick + results = [ + Project.first, + Experiment.first, + MyModule.first, + Protocol.first, + RepositoryRow.first, + Result.first, + Step.first, + Report.first, + LabelTemplate.first + ].compact + + render json: results, each_serializer: QuickSearchSerializer + end + private def load_vars diff --git a/app/javascript/vue/navigation/quick_search.vue b/app/javascript/vue/navigation/quick_search.vue new file mode 100644 index 000000000..6a363e3ab --- /dev/null +++ b/app/javascript/vue/navigation/quick_search.vue @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + {{ query }} + + + + + + {{ i18n.t('search.quick_search.experiments') }} + + + {{ i18n.t('search.quick_search.tasks') }} + + + {{ i18n.t('search.quick_search.results') }} + + + {{ i18n.t('search.quick_search.inventory_items') }} + + + + + + + (A) + + + {{ result.attributes.updated_at }} + + + + + / + {{ breadcrumb }} + + + + + + + + + + + + + + + + + {{ i18n.t('search.quick_search.empty_title') }} + + {{ i18n.t('search.quick_search.empty_description', {query: searchQuery}) }} + + + + + + {{ i18n.t('search.quick_search.all_results', {query: searchQuery}) }} + + + + + + + diff --git a/app/javascript/vue/navigation/top_menu.vue b/app/javascript/vue/navigation/top_menu.vue index 59082ab97..395d6e154 100644 --- a/app/javascript/vue/navigation/top_menu.vue +++ b/app/javascript/vue/navigation/top_menu.vue @@ -1,9 +1,6 @@ - - - - + - + @@ -36,7 +36,8 @@ export default { caret: { type: Boolean, default: false }, alwaysShow: { type: Boolean, default: false }, closeDropdown: { type: Boolean, default: false }, - fieldOnlyOpen: { type: Boolean, default: false } + fieldOnlyOpen: { type: Boolean, default: false }, + canOpen: { type: Boolean, default: true } }, data() { return { @@ -60,6 +61,13 @@ export default { } }, methods: { + toggleMenu() { + if (this.canOpen && (!this.isOpen || this.fieldOnlyOpen)) { + this.isOpen = true; + } else if (this.isOpen && !this.fieldOnlyOpen) { + this.isOpen = false; + } + }, closeMenu(e) { if (e && e.target.closest('.sn-dropdown, .sn-select-dropdown, .sn-menu-dropdown, .dp__instance_calendar')) return; this.isOpen = false; diff --git a/app/javascript/vue/shared/string_with_ellipsis.vue b/app/javascript/vue/shared/string_with_ellipsis.vue new file mode 100644 index 000000000..b846b30fd --- /dev/null +++ b/app/javascript/vue/shared/string_with_ellipsis.vue @@ -0,0 +1,26 @@ + + + + {{ text.slice(0, endCharacters * -1) }} + + + {{ text.slice(text.length - endCharacters) }} + + + + + diff --git a/app/serializers/concerns/breadcrumbs_helper.rb b/app/serializers/concerns/breadcrumbs_helper.rb new file mode 100644 index 000000000..dc17cdfaa --- /dev/null +++ b/app/serializers/concerns/breadcrumbs_helper.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module BreadcrumbsHelper + private + + def generate_breadcrumbs(subject, breadcrumbs) + return [] if subject.is_a?(NonExistantRecord) + + case subject + when Project + parent = subject.team + url = project_path(subject) + when Experiment + parent = subject.project + url = my_modules_experiment_path(subject) + when MyModule + parent = subject.experiment + url = protocols_my_module_path(subject) + when Protocol + if subject.in_repository? + parent = subject.team + url = protocol_path(subject) + else + parent = subject.my_module + url = protocols_my_module_path(subject.my_module) + end + when Result + parent = subject.my_module + url = my_module_results_path(subject.my_module) + when ProjectFolder + parent = subject.team + url = project_folder_path(subject) + when RepositoryBase + parent = subject.team + url = repository_path(subject) + when RepositoryRow + parent = subject.team + url = repository_path(subject.repository) + when Report + parent = subject.team + + url = if object.instance_of?(::Notification) + reports_path( + preview_report_id: subject.id, + preview_type: object.params[:report_type], + team_id: subject.team.id + ) + else + reports_path(team_id: subject.team.id) + end + when LabelTemplate + parent = subject.team + url = label_template_path(subject) + when Team + parent = nil + url = projects_path(team: subject.id) + end + + breadcrumbs << { name: subject.name, url: url } if subject.name.present? + + if parent + generate_breadcrumbs(parent, breadcrumbs) + else + breadcrumbs.reverse + end + end +end diff --git a/app/serializers/notification_serializer.rb b/app/serializers/notification_serializer.rb index e55866821..ebb8ef838 100644 --- a/app/serializers/notification_serializer.rb +++ b/app/serializers/notification_serializer.rb @@ -2,6 +2,7 @@ class NotificationSerializer < ActiveModel::Serializer include Rails.application.routes.url_helpers + include BreadcrumbsHelper attributes :id, :title, :message, :created_at, :read_at, :type, :breadcrumbs, :checked, :today @@ -30,62 +31,4 @@ class NotificationSerializer < ActiveModel::Serializer object.read_at.present? end - private - - def generate_breadcrumbs(subject, breadcrumbs) - return [] if subject.is_a?(NonExistantRecord) - - case subject - when Project - parent = subject.team - url = project_path(subject) - when Experiment - parent = subject.project - url = my_modules_experiment_path(subject) - when MyModule - parent = subject.experiment - url = protocols_my_module_path(subject) - when Protocol - if subject.in_repository? - parent = subject.team - url = protocol_path(subject) - else - parent = subject.my_module - url = protocols_my_module_path(subject.my_module) - end - when Result - parent = subject.my_module - url = my_module_results_path(subject.my_module) - when ProjectFolder - parent = subject.team - url = project_folder_path(subject) - when RepositoryBase - parent = subject.team - url = repository_path(subject) - when RepositoryRow - parent = subject.team - url = repository_path(subject.repository) - when Report - parent = subject.team - url = reports_path( - preview_report_id: subject.id, - preview_type: object.params[:report_type], - team_id: subject.team.id - ) - when LabelTemplate - parent = subject.team - url = label_template_path(subject) - when Team - parent = nil - url = projects_path(team: subject.id) - end - - breadcrumbs << { name: subject.name, url: url } if subject.name.present? - - if parent - generate_breadcrumbs(parent, breadcrumbs) - else - breadcrumbs.reverse - end - end end diff --git a/app/serializers/quick_search_serializer.rb b/app/serializers/quick_search_serializer.rb new file mode 100644 index 000000000..bb7faa2a9 --- /dev/null +++ b/app/serializers/quick_search_serializer.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +class QuickSearchSerializer < ActiveModel::Serializer + include Rails.application.routes.url_helpers + include BreadcrumbsHelper + + attributes :updated_at, :archived, :breadcrumbs, :code + + def archived + @object.archived? + rescue StandardError + false + end + + def code + @object.code + rescue StandardError + @object.id + end + + def updated_at + I18n.l(@object.updated_at, format: :full_date) + end + + def breadcrumbs + generate_breadcrumbs(@object, []) + end +end diff --git a/app/views/shared/navigation/_top.html.erb b/app/views/shared/navigation/_top.html.erb index 5cd942190..a99be29b2 100644 --- a/app/views/shared/navigation/_top.html.erb +++ b/app/views/shared/navigation/_top.html.erb @@ -3,6 +3,7 @@ <% else %> diff --git a/config/locales/en.yml b/config/locales/en.yml index ae724212c..1b8f842e8 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -426,6 +426,14 @@ en: list_html: "List" search: + quick_search: + experiments: "Experiments" + tasks: "Tasks" + results: "Results" + inventory_items: "Inventory items" + all_results: "All search results for \"%{query}\"" + empty_title: "No quick search results found." + empty_description: "Quick search only checks titles. Press enter or click \"All search results for '%{query}'\" to find all available matches." whole_word: "Match any whole word" whole_phrase: "Match whole phrase" match_case: "Match case sensitive" diff --git a/config/routes.rb b/config/routes.rb index 5b59f5d6c..e62944375 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -802,6 +802,11 @@ Rails.application.routes.draw do get 'search' => 'search#index' get 'search/new' => 'search#new', as: :new_search + resource :search, only: [], controller: :search do + collection do + get :quick + end + end # We cannot use 'resources :assets' because assets is a reserved route # in Rails (assets pipeline) and causes funky behavior