From 41ae9c5ea4bfb78522f682f655a0cd0cc0e032ff Mon Sep 17 00:00:00 2001 From: Doug Martin Date: Wed, 28 Aug 2024 10:20:36 -0400 Subject: [PATCH] feat: Add projects to solr search [PT-188115817] This adds projects to the solr search endpoint. --- .../controllers/api/v1/search_controller.rb | 45 +++++++- rails/app/models/admin/project.rb | 23 ++++ rails/app/models/search.rb | 25 ++++- rails/config/locales/en.yml | 2 + rails/spec/models/search_spec.rb | 102 ++++++++++++++++++ 5 files changed, 195 insertions(+), 2 deletions(-) diff --git a/rails/app/controllers/api/v1/search_controller.rb b/rails/app/controllers/api/v1/search_controller.rb index 6dca85db30..439e75245a 100644 --- a/rails/app/controllers/api/v1/search_controller.rb +++ b/rails/app/controllers/api/v1/search_controller.rb @@ -39,7 +39,11 @@ def search_results_data results = [] @search.results.each do |type, values| next if type == :all - results.push group_data(type.downcase, values) + if type == :project + results.push project_data(values) + else + results.push group_data(type.downcase, values) + end end results end @@ -63,6 +67,45 @@ def group_data(type, collection) } end + def project_data(collection) + projects = [] + collection.each do |project| + tags = {} + tags['subject_areas'] = [] + tags['grade_levels'] = [] + + tags.each do |key, value| + list = project.send(key) + list.each do |o| + tags[key].push o.name + end + end + + project_data = { + id: project.id, + name: project.name, + subject_areas: tags['subject_areas'], + grade_levels: tags['grade_levels'], + } + + projects.push project_data + end + + { + type: "projects", + header: view_context.t("HomePage.SearchResults.Projects"), + projects: projects, + pagination: { + current_page: collection.current_page, + total_pages: collection.total_pages, + start_item: collection.offset + 1, + end_item: collection.offset + collection.length, + total_items: collection.total_entries, + per_page: collection.per_page + } + } + end + def search_filters_data filters = {} filters[:number_authored_resources] = @search.number_authored_resources diff --git a/rails/app/models/admin/project.rb b/rails/app/models/admin/project.rb index 1ec9f9a536..7596c79fbb 100644 --- a/rails/app/models/admin/project.rb +++ b/rails/app/models/admin/project.rb @@ -38,6 +38,29 @@ def project_researchers project_users.where(is_researcher:true).map { |ru| ru.user } end + # this has to be set AFTER SearchableModel is included so that sunspot doesn't alias #solr_search to #search + searchable do + text :name + string :name + + text :landing_page_content + text :project_card_description + text :landing_page_slug + + boolean :public + + time :updated_at + time :created_at + + string :grade_levels, :multiple => true do + grade_level_list + end + + string :subject_areas, :multiple => true do + subject_area_list + end + end + has_many :project_materials, dependent: :destroy has_many :external_activities, through: :project_materials, source: :material, source_type: 'ExternalActivity' has_many :interactives, through: :project_materials, source: :material, source_type: 'Interactive' diff --git a/rails/app/models/search.rb b/rails/app/models/search.rb index a14f3f39d0..ac8c31c3b5 100644 --- a/rails/app/models/search.rb +++ b/rails/app/models/search.rb @@ -27,6 +27,7 @@ class Search attr_accessor :no_sensors attr_accessor :sensors attr_accessor :project_ids + attr_accessor :search_projects attr_accessor :available_subject_areas attr_accessor :available_grade_level_groups @@ -144,7 +145,8 @@ def initialize(opts={}) self.include_mine = opts[:include_mine] || false self.include_official = opts[:include_official] || false self.include_templates = opts[:include_templates] || false - self.show_archived = opts[:show_archived] || false + self.show_archived = opts[:show_archived] || false + self.search_projects = opts[:search_projects] || false self.fetch_available_filter_options() @@ -206,6 +208,27 @@ def search self.hits[:all] = [] self.total_entries[:all] = 0 + if self.search_projects + # need to use #solr_search instead of #search as the project model already has a non-solr search method + _results = Admin::Project.solr_search do |s| + s.fulltext(self.text) + s.with(:public, true) + search_by_grade_levels(s) + search_by_subject_areas(s) + + s.order_by(:name) + + # no pagination on project searches, all results are shown on the first page load + end + + self.results[:all] += _results.results + self.hits[:all] += _results.hits + self.total_entries[:all] += _results.results.total_entries + self.results[:project] = _results.results + self.hits[:project] = _results.hits + self.total_entries[:project] = _results.results.total_entries + end + self.clean_material_types.each do |type| _results = self.engine.search(self.searchable_models) do |s| diff --git a/rails/config/locales/en.yml b/rails/config/locales/en.yml index 0638ac4d06..48981a82ab 100644 --- a/rails/config/locales/en.yml +++ b/rails/config/locales/en.yml @@ -140,6 +140,8 @@ en: in the upper left to search all resources or view curated Collections of resources by clicking the Collections link above.

+ SearchResults: + Projects: Collections search: only_mine: Resources I authored diff --git a/rails/spec/models/search_spec.rb b/rails/spec/models/search_spec.rb index f5f080f7a6..aa4513a66f 100644 --- a/rails/spec/models/search_spec.rb +++ b/rails/spec/models/search_spec.rb @@ -648,5 +648,107 @@ def collection(factory, count=3, opts={}) end end + context "for projects" do + # create projects + let(:foo_project) { + FactoryBot.create(:project, name: "Foo", landing_page_slug: "first-project", public: true, + landing_page_content: "The foo project has content about cats", + project_card_description: "This is the description about felines", + grade_level_list: ["1", "2"], subject_area_list: ["Math"] + ) + } + let(:bar_project) { + FactoryBot.create(:project, name: "Bar", landing_page_slug: "second-project", public: true, + landing_page_content: "The bar project also has content about cats", + project_card_description: "This is also the description about felines", + grade_level_list: ["1", "3"], subject_area_list: ["Math", "Chemistry"] + ) + } + let(:baz_project) { + FactoryBot.create(:project, name: "Baz", landing_page_slug: "third-project", public: false, + landing_page_content: "The baz project is private and should not show in search results", + project_card_description: "This is the description about private projects", + grade_level_list: ["1", "2"], subject_area_list: ["Math"] + ) + } + let(:search_opts) { { :search_projects => true } } + + before(:each) do + foo_project + bar_project + baz_project + Admin::Project.reindex + Sunspot.commit + end + + describe "with no options" do + it "returns in alphabetical name order filtering out private projects" do + expect(subject.results[:project].length).to eq(2) + expect(subject.results[:project][0].public).to be(true) + expect(subject.results[:project][1].public).to be(true) + + expect(subject.results[:project][0].id).to be(bar_project.id) + expect(subject.results[:project][1].id).to be(foo_project.id) + end + end + + describe "by name" do + let(:search_opts) { {:search_projects => true, :search_term => "foo"} } + + it "results in 1 result" do + expect(subject.results[:project].length).to eq(1) + expect(subject.results[:project][0].id).to be(foo_project.id) + end + end + + describe "by landing page content" do + let(:search_opts) { {:search_projects => true, :search_term => "cats"} } + + it "results in 2 results" do + expect(subject.results[:project].length).to eq(2) + + expect(subject.results[:project][0].id).to be(bar_project.id) + expect(subject.results[:project][1].id).to be(foo_project.id) + end + end + + describe "by project card description" do + let(:search_opts) { {:search_projects => true, :search_term => "felines"} } + + it "results in 2 results" do + expect(subject.results[:project].length).to eq(2) + + expect(subject.results[:project][0].id).to be(bar_project.id) + expect(subject.results[:project][1].id).to be(foo_project.id) + end + end + + describe "by landing page slug" do + let(:search_opts) { {:search_projects => true, :search_term => "second"} } + + it "results in 1 result" do + expect(subject.results[:project].length).to eq(1) + expect(subject.results[:project][0].id).to be(bar_project.id) + end + end + + describe "by grade levels" do + let(:search_opts) { {:search_projects => true, :grade_level_groups => ["3-4"]} } + + it "results in 1 results" do + expect(subject.results[:project].length).to eq(1) + expect(subject.results[:project][0].id).to be(bar_project.id) + end + end + + describe "by subject areas" do + let(:search_opts) { {:search_projects => true, :subject_areas => ["Chemistry"]} } + + it "results in 1 results" do + expect(subject.results[:project].length).to eq(1) + expect(subject.results[:project][0].id).to be(bar_project.id) + end + end + end end end