From 82f5e2c8003ed22c0916bba66a6d68cb6e5f853c Mon Sep 17 00:00:00 2001 From: Terri Chu Date: Mon, 5 Oct 2020 10:46:53 -0400 Subject: [PATCH 01/11] Enable single result redirect for advanced commits search If a user searches for a commit in a project and only finds one result, redirect the user to that result. This already exists for basic search and now is enabled for advanced search. Added a notice explaining the redirect with a link back to search results. --- app/controllers/search_controller.rb | 14 ++++-- app/helpers/search_helper.rb | 13 +++++- app/services/search_service.rb | 14 ++++++ .../img/project_search_sha_redirect.png | Bin 0 -> 16559 bytes doc/user/search/index.md | 8 ++++ ...-is-only-one-result-redirect-to-that-p.yml | 5 +++ ee/lib/gitlab/elastic/search_results.rb | 4 -- lib/gitlab/project_search_results.rb | 9 ---- lib/gitlab/search_results.rb | 4 -- locale/gitlab.pot | 3 ++ spec/requests/search_controller_spec.rb | 22 +++++++++ spec/services/search_service_spec.rb | 42 ++++++++++++++++-- spec/support/helpers/search_helpers.rb | 2 +- 13 files changed, 115 insertions(+), 25 deletions(-) create mode 100644 doc/user/search/img/project_search_sha_redirect.png create mode 100644 ee/changelogs/unreleased/233278-in-global-search-if-there-is-only-one-result-redirect-to-that-p.yml diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 0380bc1c5480a0..cb5e3651929b99 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -104,12 +104,20 @@ def eager_load_user_status end def check_single_commit_result - if @search_results.single_commit_result? - only_commit = @search_results.objects('commits').first + return if params[:force_search_results] + + if @search_service.single_commit_result? + # get single commit from results after redaction + only_commit = search_service.first_commit_result + return unless only_commit.present? + query = params[:search].strip.downcase found_by_commit_sha = Commit.valid_hash?(query) && only_commit.sha.start_with?(query) + return unless found_by_commit_sha.present? - redirect_to project_commit_path(@project, only_commit) if found_by_commit_sha + link = search_path(safe_params.merge(force_search_results: true)) + flash[:notice] = html_escape(_("You've been redirected to the only result. Head back to %{aOpen}search results%{aClose}.")) % { aOpen: "".html_safe, aClose: ''.html_safe } + redirect_to project_commit_path(@project, only_commit) end end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 63601485daf59a..3fd2c30a8897d5 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -1,7 +1,18 @@ # frozen_string_literal: true module SearchHelper - SEARCH_PERMITTED_PARAMS = [:search, :scope, :project_id, :group_id, :repository_ref, :snippets, :sort, :state, :confidential].freeze + SEARCH_PERMITTED_PARAMS = [ + :search, + :scope, + :project_id, + :group_id, + :repository_ref, + :snippets, + :sort, + :state, + :confidential, + :force_search_results + ].freeze def search_autocomplete_opts(term) return unless current_user diff --git a/app/services/search_service.rb b/app/services/search_service.rb index 3ccd67c8d30799..5df6c592c664f5 100644 --- a/app/services/search_service.rb +++ b/app/services/search_service.rb @@ -69,6 +69,20 @@ def search_highlight search_results.highlight_map(scope) end + def single_commit_result? + return false unless project.present? + return false if search_results.commits_count != 1 + + counts = %i(limited_milestones_count limited_notes_count + limited_merge_requests_count limited_issues_count + limited_blobs_count wiki_blobs_count) + counts.all? { |count_method| search_results.public_send(count_method) == 0 } # rubocop:disable GitlabSecurity/PublicSend + end + + def first_commit_result + redact_unauthorized_results(@search_results.objects('commits')).first + end + private def per_page diff --git a/doc/user/search/img/project_search_sha_redirect.png b/doc/user/search/img/project_search_sha_redirect.png new file mode 100644 index 0000000000000000000000000000000000000000..6fece401f9099d4ff0e0df6a2892e266ae14fb43 GIT binary patch literal 16559 zcmeAS@N?(olHy`uVBq!ia0y~yVE@Fxz{tYE%)r3FW7xBlfq{V~-O<;Pfnj4m_n$;o z1_lPk;vjb?hIQv;UNSH+a29w(7Beu2se&-0XOPMV1_q{*%#etZ2wxwoFbx5m+O@q>*W`v>l<2HTIw4Z z=^Gj87Nw-=7FXt#Bv$C=6)QswftllyTAW;zSx}OhpQivaH!&%{w8U0P31pE13_#qO zT9JvcDX$pnt>pY%eUOa4p`L+0Hf<%DX*jfjWFXqW{({(M12W7iv^cfMIX^cyHLt`j zIJqb_HLo}$zr+S?6N)6loe+%ynH8xy5iXgzsd>ej`FVCm2F4~(ZRk>1wMHUoH8Vig zi6n!h6XXXg|Dw#)yplvvAlez)=wpbXs}D*o&d(_YDG$xdEU`0!YC#i*>ax)X#S2nA zfddO78sy?;$7Q1rPA;INW5?w*XOT7o12aQ_Pl)U3M?cfj(ms6n@bBM0?wGX#8T)_z z`t|?+|J%238yFZ|x^(I9pFfWtJ<=5OQWCP&5OvoQ^X}jA=nl8GsHo_@d-ueWH}2T6 zW6heiclq?beEAYJ`HY2y#lJs)dV6~f4Gm8{{CVN&ub)4EwzjsKRP0Thab78Vo3XL6 zWZLG)$Vh?s^?!c<`SIh&#)}`09z80XxnZikZzjbwWg@%T{eEG7rwzhfw9V;uV+jZ*t>V{wQJY1=3lN`bGxajsiC1^?b&zFpFOj+wOz7g zN%z(VL4BuY9(bl+u*28a*QNRJ)TvWfpLy%(=vch;dSPMVzJ2@Tv$mc)cW&v)cX@ev zXV0D$5fRZW++|Q& zRjY2M%(_^x=&DJ>>9XZFrtN)Fx#mI8lq=fByWYQl|LN1GzhAyi*zKG Lm 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≷a#oGR-{y(9=jo&}_SfSdJ z<2)~3-ZGbY?qlU4^81J0-es%Vveb+fUp7g7=?i)gc)F3fY}4$#XP@fLzF)Xg^KJE= z(+?a~m!G=0rCD*-`GQ}!Cd#*em8d^r$gBHfs|~CAhI4B;&aXL9zj*b>3&*eOx&4$q ze{w?n+m@Q5mEkej``Jx@ZQkSI9d+sYuQ!LgQYx?Q2vIn-ETZVcpu3b`1Ryv z+uW^uC(gfEXRzYtJk|eV=WAW-PE1qQowUtrdB1DQHIbX(@QAoDOa0^ihf20-yEp&W z5}2oWF8BBz=?&#t`FA&c&Ht-l@2kRBJWaB9Bg>NsIqv^_UM{)F)cn0-rM=>_so%oz?TPyjm7m-UZAxa|Kaojh!8(?mf0nuYW3esr-fXwU zyy|`A#V?$#ekaQ9)t&3K4&HZ@{&e}M($q`u4KK?czZJjk(6eeYD+N{ChtXnuk?BDf zil^poxHsokf__!v@k_c3&a>YOY5(=|AIlux@_4U16V0D)^LX!(rZ_?0spWZ0-Gd!0 z%hk^)&oEQW6HWj0d<$3BJ@Jn|ncploWY!3Bna|{@JoxJ7%Z{2O{$(XGs=4gbnEq|3 zGkGEXDLiNMP80q4#wN8s>bcJQmOtB>?3f*QYu`8?tJ%q<7CDC>11Z+%5xd>5+0xZvE*1Vzo62+{%0Gx7OHIIWQe$RPcO4X zNOCu0=E;<%|Gtv9IqvbT+4l2To#Ch8j=9#F3yvtxRSyrDac3%<^Xyg)SG!eT98Pfl z-0^Dh=b{B&_ttrH*KLe@vOv-MVc@rUM?X#ss$^S#+*o=4Rxa-2FBVR|T94IiW zs}Aa}NeSXR;eL05^&Lq+|JlnE-)v?7&bh=aAde z4~~3CXFk@ed^qcu$DtLAJ}G~`{P($i-Oe(jJGEv?hVF4ccg_~=zhM*J((!I0|AFMz z9>tO;+c^vUg)`R7cRafy=KQe(BBvkDHr+RE?ZxhSC-xi4_r)G~Vw&`8msI@+&Yy-H z;%jXi3WHp0Gk>Yy@`#%Fer@Hum{2Lwo9XTU5{mdvf173SunGTU3|e4@s$tlzqU`Ym%e}C#Qf|}^JkSf{k_Wed*`PM zqMzQ*mwBi0%74wG{k!TM7PeQ*-qc}=o4oVk1ItM{R~dgkDO$5iK#XIp?+Nv1rKNWa z7k2fBOq82BH-5viWla|M4J+HNT)96lkbbgz-ehI@@`GCf+mHO)z$SdwI^?4KI;$)5 zs^9gW){!@CpD%wW`Aqb@pNSD%{?4M|g|okJ{J!Auk{feA#ozV+o`0@wxBgD$^&$nv zcSKK!D=rd$l3Xlc{ONY-WZw&I`BKqOed?dCF9^HJzx-{( zmQv$}S(kdf>nHGgP18MQ9CCiD@exngP4TO1r@UWT`As8Zt?|eGFE!*F+KtWbT z>gQpv@VoO~>%Iy05#HAp9zFR{?w0L@Gbi}#<}EwAbMB&(PfDib&RTq#gU_X_!>BZG z;{?5ps#7c{J??$d@w(3J_}7QRlPgZF?{jbPKfylJ#_Inekp~y5W1Z~H?=$uN`SeNa ze%SH0nxD-3w!BiB+hwRMFY`!s)lHT4q1l?<{5(w3pAP=6-Nfjhczf2xGWUJAEq`9N zuGqBTQ*Now3HIHtC0bjz%zG74EpRSZzb{Yn_8+?=g`A7MuQ4e-Y5t(L%iv<*U%xFT z?XfyRr&``E|NTB&{cxi^89-pD@ah{rT4Ej_?&9ldar2(~P*Z zo}IJ0{9{_IxRbR~{fRAyyEWymhxV_%u*?15%v2vkuTW1*4*9P>|J~W2RA&!XXa@O+c^M#KCj{I%0RhB=fyd_C9P$6WSoIqjWFXm4X8ZUyT z6=~#rbFGrgvAEBaS(c%u^S!{;dd}q7jqcO!)nm`aS6rVL%lzy>hrH&~t7|^@v3=V4 z^W2%#xi+=yr_>kP$3Do}VSPxpGWgWgjB~CXADDlhcIjQGyg#<%c0*B_`&4Vw6?Eh_X2JG^$VF^^X;7#ta{*YNi1*qOyztBOVRBP zEgbR_5=@OmW?oh4(H@JvaG78&^$^)P3n03o=iANU?j{SrgF09TH?a#eY%Q z6X%J!qT60>JnYYZ`_++NUfXalgNq3#?khOAMoj8DbkHiYd%5y`@6UHi9sj!j6K<@T zKfzy6f5QFZgo*Me*J^W%E%h-kluM2Ne$$9ixQk`OZN3Rl#P8eJ{`%7I|Mh_U{(434 zGZVvSPB^M7pty+ti9B0i%c|2-J^MB(?2qmE&AH0NzplwHN}VIcaU$ zE^thp=Sg+uyxn>$-k+#fnxH@7|Jr2PL#O_|o?OctR#yEh$)*(h6^Xuk++_BGXetb;7f&0DhepR)CN-ib+HMTE)XM-d~|uwXO4eLf}k8j#ZCD zTB4>@?_dx(&}4S@$;S5!E8lHf@J}gTXmQQ_q-cfx^JgT^`#t&Ty?x_5M&7T$D~?=+R4Hpgelk+0&4i}Igj|NOYsJ;Awdw!rtmRPDJ- zGw<%cx9p>A^s!ejTX*)_=bXL4yT+>(+pY%Pw@lV~Nsg-+c!a|qG?cX+8uK4%*hE1QI z?{A(y>8rn2!;Uwv%JwW+!*b-RxZlpKlsxz9MSea{+s%hb4Ef()IsdJ;G=}IhyLrxLj^^p3{Q}Rd|4rue z*!RSFj{Dz_Yi``rKmGS@l3CdS_LKS9pM38#KY0GhX3_4AI_IVL?iYQ%Ur7EX}} ztB!Qz<}F!Y{)MfH|HK$ESxNTj=9z&}CRO#?*Xm6t@gLC7U0P{!{Ey?jJk!eh)z{un z;Xm-J?`NpWS^1FFd?(M|y#MN}^;6~zYLPRaEp~i-|KVx2#WuQ|kG*?;>8rOLLw2|C z#ATWD%cT%UDpZ}^`gmP;EknjZ&Hp=YPpttnenfm?T)?`#<|KQA*p9lQBBh@U0(;NTzqfaC z4MWGhzn3oupJZ>iAzQcSu3sx~H;UMV?_%xcdGnAQf}lukHhhZ@RaoXd^$v z$Jf41-L(uI_vdu3Dg zUt4>=nY|%R<^EAShAj`1?7zm^F>F!%Ia#ZQ@q);PJ(oh2H-b(L@O1TaS?83{1OUK& Bz&-!~ literal 0 HcmV?d00001 diff --git a/doc/user/search/index.md b/doc/user/search/index.md index f52106e4fa8876..64c18afb990811 100644 --- a/doc/user/search/index.md +++ b/doc/user/search/index.md @@ -248,6 +248,14 @@ the search field on the top-right of your screen while the project page is open. ![code search dropdown](img/project_search_dropdown.png) ![code search results](img/project_code_search.png) +### SHA search + +You can quickly access a commit from within the project dashboard by entering the complete SHA +into the search field on the top right of the screen. If a single result is found, you will be +redirected to the commit result and given the option to return to the search results page. + +![project sha search redirect](img/project_search_sha_redirect.png) + ## Advanced Search **(STARTER)** Leverage Elasticsearch for faster, more advanced code search across your entire diff --git a/ee/changelogs/unreleased/233278-in-global-search-if-there-is-only-one-result-redirect-to-that-p.yml b/ee/changelogs/unreleased/233278-in-global-search-if-there-is-only-one-result-redirect-to-that-p.yml new file mode 100644 index 00000000000000..84e0e45e8e35c2 --- /dev/null +++ b/ee/changelogs/unreleased/233278-in-global-search-if-there-is-only-one-result-redirect-to-that-p.yml @@ -0,0 +1,5 @@ +--- +title: Enable single result redirect for advanced commits search +merge_request: 44308 +author: +type: changed diff --git a/ee/lib/gitlab/elastic/search_results.rb b/ee/lib/gitlab/elastic/search_results.rb index 67d21b60ff65fb..cac06058507cc0 100644 --- a/ee/lib/gitlab/elastic/search_results.rb +++ b/ee/lib/gitlab/elastic/search_results.rb @@ -130,10 +130,6 @@ def milestones_count alias_method :limited_merge_requests_count, :merge_requests_count alias_method :limited_milestones_count, :milestones_count - def single_commit_result? - false - end - def self.parse_search_result(result, project) ref = result["_source"]["blob"]["commit_sha"] path = result["_source"]["blob"]["path"] diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb index 333564bee018dc..fd08b560e18ac5 100644 --- a/lib/gitlab/project_search_results.rb +++ b/lib/gitlab/project_search_results.rb @@ -75,15 +75,6 @@ def commits_count @commits_count ||= commits(limit: count_limit).count end - def single_commit_result? - return false if commits_count != 1 - - counts = %i(limited_milestones_count limited_notes_count - limited_merge_requests_count limited_issues_count - limited_blobs_count wiki_blobs_count) - counts.all? { |count_method| public_send(count_method) == 0 } # rubocop:disable GitlabSecurity/PublicSend - end - private def paginated_commits(page, per_page) diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb index 3d4920456e2d6f..f05bc9c8619e6a 100644 --- a/lib/gitlab/search_results.rb +++ b/lib/gitlab/search_results.rb @@ -94,10 +94,6 @@ def limited_users_count @limited_users_count ||= limited_count(users) end - def single_commit_result? - false - end - def count_limit COUNT_LIMIT end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 3a790d87539456..4e36790598ead4 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -29949,6 +29949,9 @@ msgstr "" msgid "You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication." msgstr "" +msgid "You've been redirected to the only result. Head back to %{aOpen}search results%{aClose}." +msgstr "" + msgid "YouTube" msgstr "" diff --git a/spec/requests/search_controller_spec.rb b/spec/requests/search_controller_spec.rb index 52bfc4803130a9..2b3e1fee64ac01 100644 --- a/spec/requests/search_controller_spec.rb +++ b/spec/requests/search_controller_spec.rb @@ -62,5 +62,27 @@ def send_search_request(params) it_behaves_like 'an efficient database result' end + + context 'when searching by SHA' do + let(:sha) { '6d394385cf567f80a8fd85055db1ab4c5295806f' } + + it 'finds a commit and redirects to its page' do + send_search_request({ search: sha, scope: 'projects', project_id: project.id }) + + expect(response).to redirect_to(project_commit_path(project, sha)) + end + + it 'finds a commit in uppercase and redirects to its page' do + send_search_request( { search: sha.upcase, scope: 'projects', project_id: project.id }) + + expect(response).to redirect_to(project_commit_path(project, sha)) + end + + it 'goes to search results with the force_search_results param set' do + send_search_request({ search: sha, force_search_results: true, project_id: project.id }) + + expect(response).not_to redirect_to(project_commit_path(project, sha)) + end + end end end diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb index fc613a6224a0ab..815bc1e7209a0c 100644 --- a/spec/services/search_service_spec.rb +++ b/spec/services/search_service_spec.rb @@ -14,9 +14,9 @@ let_it_be(:inaccessible_project) { create(:project, :repository, :private, name: 'inaccessible_project') } - let(:snippet) { create(:snippet, author: user) } - let(:group_project) { create(:project, group: accessible_group, name: 'group_project') } - let(:public_project) { create(:project, :public, name: 'public_project') } + let_it_be(:snippet) { create(:snippet, author: user) } + let_it_be(:group_project) { create(:project, group: accessible_group, name: 'group_project') } + let_it_be(:public_project) { create(:project, :public, name: 'public_project') } let(:per_page) { described_class::DEFAULT_PER_PAGE } @@ -523,4 +523,40 @@ def kaminari_array(*objects) end end end + + describe '#single_commit_result?' do + let(:search) { accessible_project.commit.sha } + let(:scope) { 'issues' } + let(:search_service) { described_class.new(user, project_id: project_id, search: search, scope: scope) } + + subject { search_service.single_commit_result? } + + context 'when project_id not present' do + let(:project_id) { nil } + + it { is_expected.to eq(false) } + end + + context 'when project_id present' do + let(:project_id) { accessible_project.id } + + it { is_expected.to eq(true) } + + context 'but more than one commit found' do + before do + allow(search_service.search_results).to receive(:commits_count).and_return(2) + end + + it { is_expected.to eq(false) } + end + + context 'but other scopes returned results' do + before do + allow(search_service.search_results).to receive(:limited_milestones_count).and_return(1) + end + + it { is_expected.to eq(false) } + end + end + end end diff --git a/spec/support/helpers/search_helpers.rb b/spec/support/helpers/search_helpers.rb index 328f272724a261..57e605653a264a 100644 --- a/spec/support/helpers/search_helpers.rb +++ b/spec/support/helpers/search_helpers.rb @@ -9,7 +9,7 @@ def fill_in_search(text) wait_for_all_requests end - def submit_search(query, scope: nil) + def submit_search(query) page.within('.search-form, .search-page-form') do field = find_field('search') field.fill_in(with: query) -- GitLab From 16888982b58167a921d3decbb98e9191cc9c726f Mon Sep 17 00:00:00 2001 From: Terri Chu Date: Wed, 14 Oct 2020 09:37:07 -0400 Subject: [PATCH 02/11] fixup! Enable single result redirect for advanced commits search --- doc/user/search/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/search/index.md b/doc/user/search/index.md index 64c18afb990811..85be047cd9f079 100644 --- a/doc/user/search/index.md +++ b/doc/user/search/index.md @@ -250,7 +250,7 @@ the search field on the top-right of your screen while the project page is open. ### SHA search -You can quickly access a commit from within the project dashboard by entering the complete SHA +You can quickly access a commit from within the project dashboard by entering the SHA into the search field on the top right of the screen. If a single result is found, you will be redirected to the commit result and given the option to return to the search results page. -- GitLab From 7c614e29209cdc6576629e0bc0a9635e9a8f3414 Mon Sep 17 00:00:00 2001 From: Terri Chu Date: Wed, 14 Oct 2020 15:42:20 -0400 Subject: [PATCH 03/11] fixup! Enable single result redirect for advanced commits search --- app/controllers/search_controller.rb | 2 +- app/services/search_service.rb | 6 +----- locale/gitlab.pot | 8 +++----- spec/requests/search_controller_spec.rb | 7 +++++++ spec/services/search_service_spec.rb | 2 +- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index cb5e3651929b99..05bfbabeea8925 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -116,7 +116,7 @@ def check_single_commit_result return unless found_by_commit_sha.present? link = search_path(safe_params.merge(force_search_results: true)) - flash[:notice] = html_escape(_("You've been redirected to the only result. Head back to %{aOpen}search results%{aClose}.")) % { aOpen: "".html_safe, aClose: ''.html_safe } + flash[:notice] = html_escape(_("You have been redirected to the only result; see the %{aOpen}search results%{aClose} instead.")) % { aOpen: "".html_safe, aClose: ''.html_safe } redirect_to project_commit_path(@project, only_commit) end end diff --git a/app/services/search_service.rb b/app/services/search_service.rb index 5df6c592c664f5..65c203ca020d34 100644 --- a/app/services/search_service.rb +++ b/app/services/search_service.rb @@ -71,12 +71,8 @@ def search_highlight def single_commit_result? return false unless project.present? - return false if search_results.commits_count != 1 - counts = %i(limited_milestones_count limited_notes_count - limited_merge_requests_count limited_issues_count - limited_blobs_count wiki_blobs_count) - counts.all? { |count_method| search_results.public_send(count_method) == 0 } # rubocop:disable GitlabSecurity/PublicSend + search_results.commits_count == 1 end def first_commit_result diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 4e36790598ead4..6d6168c4fcbd1d 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8,8 +8,6 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-10-12 08:34+0200\n" -"PO-Revision-Date: 2020-10-12 08:34+0200\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" @@ -29751,6 +29749,9 @@ msgstr "" msgid "You have been invited" msgstr "" +msgid "You have been redirected to the only result; see the %{aOpen}search results%{aClose} instead." +msgstr "" + msgid "You have been unsubscribed from this thread." msgstr "" @@ -29949,9 +29950,6 @@ msgstr "" msgid "You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication." msgstr "" -msgid "You've been redirected to the only result. Head back to %{aOpen}search results%{aClose}." -msgstr "" - msgid "YouTube" msgstr "" diff --git a/spec/requests/search_controller_spec.rb b/spec/requests/search_controller_spec.rb index 2b3e1fee64ac01..f2263d7d1db503 100644 --- a/spec/requests/search_controller_spec.rb +++ b/spec/requests/search_controller_spec.rb @@ -78,6 +78,13 @@ def send_search_request(params) expect(response).to redirect_to(project_commit_path(project, sha)) end + it 'redirects to the commit even if another scope result is returned' do + create(:note, project: project, note: "This is the #{sha}") + send_search_request( { search: sha, scope: 'projects', project_id: project.id }) + + expect(response).to redirect_to(project_commit_path(project, sha)) + end + it 'goes to search results with the force_search_results param set' do send_search_request({ search: sha, force_search_results: true, project_id: project.id }) diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb index 815bc1e7209a0c..c5f0b1864d7c0e 100644 --- a/spec/services/search_service_spec.rb +++ b/spec/services/search_service_spec.rb @@ -555,7 +555,7 @@ def kaminari_array(*objects) allow(search_service.search_results).to receive(:limited_milestones_count).and_return(1) end - it { is_expected.to eq(false) } + it { is_expected.to eq(true) } end end end -- GitLab From 25bfcfec88d8ff2288346ead424fe951c59e56aa Mon Sep 17 00:00:00 2001 From: Terri Chu Date: Thu, 15 Oct 2020 14:53:29 -0400 Subject: [PATCH 04/11] fixup! Enable single result redirect for advanced commits search --- app/controllers/search_controller.rb | 4 +-- app/services/search_service.rb | 13 +++++--- spec/services/search_service_spec.rb | 45 +++++++++++++++------------- 3 files changed, 35 insertions(+), 27 deletions(-) diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 05bfbabeea8925..3d27c3088508e1 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -106,9 +106,9 @@ def eager_load_user_status def check_single_commit_result return if params[:force_search_results] - if @search_service.single_commit_result? + if @search_service.single_result?('commits') # get single commit from results after redaction - only_commit = search_service.first_commit_result + only_commit = search_service.first_result('commits') return unless only_commit.present? query = params[:search].strip.downcase diff --git a/app/services/search_service.rb b/app/services/search_service.rb index 65c203ca020d34..8cd84e6e0a3e56 100644 --- a/app/services/search_service.rb +++ b/app/services/search_service.rb @@ -69,14 +69,19 @@ def search_highlight search_results.highlight_map(scope) end - def single_commit_result? + def single_result?(scope) return false unless project.present? - search_results.commits_count == 1 + case scope + when 'commits' then + search_results.commits_count == 1 + else + false + end end - def first_commit_result - redact_unauthorized_results(@search_results.objects('commits')).first + def first_result(scope) + redact_unauthorized_results(@search_results.objects(scope)).first end private diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb index c5f0b1864d7c0e..49f639db7dc238 100644 --- a/spec/services/search_service_spec.rb +++ b/spec/services/search_service_spec.rb @@ -524,38 +524,41 @@ def kaminari_array(*objects) end end - describe '#single_commit_result?' do + describe '#single_result?' do let(:search) { accessible_project.commit.sha } - let(:scope) { 'issues' } - let(:search_service) { described_class.new(user, project_id: project_id, search: search, scope: scope) } + let(:search_service) { described_class.new(user, project_id: project_id, search: search, scope: 'issues') } - subject { search_service.single_commit_result? } + subject { search_service.single_result?(result_scope) } - context 'when project_id not present' do - let(:project_id) { nil } + context 'for commits' do + let(:result_scope) { 'commits' } - it { is_expected.to eq(false) } - end + context 'when project_id not present' do + let(:project_id) { } - context 'when project_id present' do - let(:project_id) { accessible_project.id } + it { is_expected.to eq(false) } + end - it { is_expected.to eq(true) } + context 'when project_id present' do + let(:project_id) { accessible_project.id } - context 'but more than one commit found' do - before do - allow(search_service.search_results).to receive(:commits_count).and_return(2) - end + it { is_expected.to eq(true) } - it { is_expected.to eq(false) } - end + context 'but more than one commit found' do + before do + allow(search_service.search_results).to receive(:commits_count).and_return(2) + end - context 'but other scopes returned results' do - before do - allow(search_service.search_results).to receive(:limited_milestones_count).and_return(1) + it { is_expected.to eq(false) } end - it { is_expected.to eq(true) } + context 'but other scopes returned results' do + before do + allow(search_service.search_results).to receive(:limited_milestones_count).and_return(1) + end + + it { is_expected.to eq(true) } + end end end end -- GitLab From 7b4fb675e870376dec62d8eb65415ce73e870459 Mon Sep 17 00:00:00 2001 From: Terri Chu Date: Mon, 19 Oct 2020 09:48:05 -0400 Subject: [PATCH 05/11] fixup! Enable single result redirect for advanced commits search --- app/controllers/search_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 3d27c3088508e1..931e368084f75f 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -116,7 +116,7 @@ def check_single_commit_result return unless found_by_commit_sha.present? link = search_path(safe_params.merge(force_search_results: true)) - flash[:notice] = html_escape(_("You have been redirected to the only result; see the %{aOpen}search results%{aClose} instead.")) % { aOpen: "".html_safe, aClose: ''.html_safe } + flash[:notice] = html_escape(_("You have been redirected to the only result; see the %{a_start}search results%{a_end} instead.")) % { a_start: "".html_safe, a_end: ''.html_safe } redirect_to project_commit_path(@project, only_commit) end end -- GitLab From 6e179cce9baacd945385ac2175b97a5136fde93e Mon Sep 17 00:00:00 2001 From: Terri Chu Date: Mon, 19 Oct 2020 10:54:15 -0400 Subject: [PATCH 06/11] fixup! Enable single result redirect for advanced commits search --- locale/gitlab.pot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 6d6168c4fcbd1d..3238bb69573a76 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -29749,7 +29749,7 @@ msgstr "" msgid "You have been invited" msgstr "" -msgid "You have been redirected to the only result; see the %{aOpen}search results%{aClose} instead." +msgid "You have been redirected to the only result; see the %{a_start}search results%{a_end} instead." msgstr "" msgid "You have been unsubscribed from this thread." -- GitLab From 2f768fb7e2308f4fd702e071e7948d060e2677bc Mon Sep 17 00:00:00 2001 From: Terri Chu Date: Tue, 20 Oct 2020 11:38:00 -0400 Subject: [PATCH 07/11] fixup! Enable single result redirect for advanced commits search --- app/controllers/search_controller.rb | 35 +++++++++---------- app/services/search_service.rb | 15 --------- spec/requests/search_controller_spec.rb | 17 +++++++++- spec/services/search_service_spec.rb | 45 ++----------------------- 4 files changed, 37 insertions(+), 75 deletions(-) diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 931e368084f75f..cc230f9c457586 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -35,6 +35,10 @@ def show return unless search_term_valid? + increment_search_counters + + return if check_single_commit_result? + @search_term = params[:search] @scope = search_service.scope @@ -45,10 +49,6 @@ def show render_commits if @scope == 'commits' eager_load_user_status if @scope == 'users' - - increment_search_counters - - check_single_commit_result end def count @@ -103,22 +103,23 @@ def eager_load_user_status @search_objects = @search_objects.eager_load(:status) # rubocop:disable CodeReuse/ActiveRecord end - def check_single_commit_result - return if params[:force_search_results] + def check_single_commit_result? + return false if params[:force_search_results] + return false unless @project.present? + # download_code project policy grants user the read_commit ability + return false unless Ability.allowed?(current_user, :download_code, @project) - if @search_service.single_result?('commits') - # get single commit from results after redaction - only_commit = search_service.first_result('commits') - return unless only_commit.present? + query = params[:search].strip.downcase + return false unless Commit.valid_hash?(query) - query = params[:search].strip.downcase - found_by_commit_sha = Commit.valid_hash?(query) && only_commit.sha.start_with?(query) - return unless found_by_commit_sha.present? + commit = @project.commit_by(oid: query) + return false unless commit.present? && commit.sha.start_with?(query) - link = search_path(safe_params.merge(force_search_results: true)) - flash[:notice] = html_escape(_("You have been redirected to the only result; see the %{a_start}search results%{a_end} instead.")) % { a_start: "".html_safe, a_end: ''.html_safe } - redirect_to project_commit_path(@project, only_commit) - end + link = search_path(safe_params.merge(force_search_results: true)) + flash[:notice] = html_escape(_("You have been redirected to the only result; see the %{a_start}search results%{a_end} instead.")) % { a_start: "".html_safe, a_end: ''.html_safe } + redirect_to project_commit_path(@project, commit) + + true end def increment_search_counters diff --git a/app/services/search_service.rb b/app/services/search_service.rb index 8cd84e6e0a3e56..3ccd67c8d30799 100644 --- a/app/services/search_service.rb +++ b/app/services/search_service.rb @@ -69,21 +69,6 @@ def search_highlight search_results.highlight_map(scope) end - def single_result?(scope) - return false unless project.present? - - case scope - when 'commits' then - search_results.commits_count == 1 - else - false - end - end - - def first_result(scope) - redact_unauthorized_results(@search_results.objects(scope)).first - end - private def per_page diff --git a/spec/requests/search_controller_spec.rb b/spec/requests/search_controller_spec.rb index f2263d7d1db503..9673f40c6afaee 100644 --- a/spec/requests/search_controller_spec.rb +++ b/spec/requests/search_controller_spec.rb @@ -7,7 +7,7 @@ let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, :public, :repository, :wiki_repo, name: 'awesome project', group: group) } - before_all do + before do login_as(user) end @@ -78,6 +78,12 @@ def send_search_request(params) expect(response).to redirect_to(project_commit_path(project, sha)) end + it 'finds a commit with a partial sha and redirects to its page' do + send_search_request( { search: sha[0..10], scope: 'projects', project_id: project.id }) + + expect(response).to redirect_to(project_commit_path(project, sha)) + end + it 'redirects to the commit even if another scope result is returned' do create(:note, project: project, note: "This is the #{sha}") send_search_request( { search: sha, scope: 'projects', project_id: project.id }) @@ -90,6 +96,15 @@ def send_search_request(params) expect(response).not_to redirect_to(project_commit_path(project, sha)) end + + it 'does not redirect if user cannot download_code from project' do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(user, :download_code, project).and_return(false) + + send_search_request({ search: sha, force_search_results: true, project_id: project.id }) + + expect(response).not_to redirect_to(project_commit_path(project, sha)) + end end end end diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb index 49f639db7dc238..fc613a6224a0ab 100644 --- a/spec/services/search_service_spec.rb +++ b/spec/services/search_service_spec.rb @@ -14,9 +14,9 @@ let_it_be(:inaccessible_project) { create(:project, :repository, :private, name: 'inaccessible_project') } - let_it_be(:snippet) { create(:snippet, author: user) } - let_it_be(:group_project) { create(:project, group: accessible_group, name: 'group_project') } - let_it_be(:public_project) { create(:project, :public, name: 'public_project') } + let(:snippet) { create(:snippet, author: user) } + let(:group_project) { create(:project, group: accessible_group, name: 'group_project') } + let(:public_project) { create(:project, :public, name: 'public_project') } let(:per_page) { described_class::DEFAULT_PER_PAGE } @@ -523,43 +523,4 @@ def kaminari_array(*objects) end end end - - describe '#single_result?' do - let(:search) { accessible_project.commit.sha } - let(:search_service) { described_class.new(user, project_id: project_id, search: search, scope: 'issues') } - - subject { search_service.single_result?(result_scope) } - - context 'for commits' do - let(:result_scope) { 'commits' } - - context 'when project_id not present' do - let(:project_id) { } - - it { is_expected.to eq(false) } - end - - context 'when project_id present' do - let(:project_id) { accessible_project.id } - - it { is_expected.to eq(true) } - - context 'but more than one commit found' do - before do - allow(search_service.search_results).to receive(:commits_count).and_return(2) - end - - it { is_expected.to eq(false) } - end - - context 'but other scopes returned results' do - before do - allow(search_service.search_results).to receive(:limited_milestones_count).and_return(1) - end - - it { is_expected.to eq(true) } - end - end - end - end end -- GitLab From 285c2750b5a19615fe7fe124f298b4521ef0881b Mon Sep 17 00:00:00 2001 From: Terri Chu Date: Tue, 20 Oct 2020 11:50:46 -0400 Subject: [PATCH 08/11] fixup! Enable single result redirect for advanced commits search --- .../img/project_search_sha_redirect.png | Bin 16559 -> 17031 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/user/search/img/project_search_sha_redirect.png b/doc/user/search/img/project_search_sha_redirect.png index 6fece401f9099d4ff0e0df6a2892e266ae14fb43..718a859d4af3650ffece27a76c69a3c2ec6e3e08 100644 GIT binary patch delta 16499 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 z%0AW!3oprljP-l`~Lm>n*8GR(=Fzl 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 FO#o;twS)iw delta 16023 zcmZo~WnACLsMs0c=g!L|#l^tD!0YMZ62!p3{)vHsk%fbqfq{X?uxIH;#eIy-Mh3={ zA2C`onoX8r+Fb86XOT7o12aQ_Pl)U3M?cfj(ms6n@bBM0?wGX#8T)_z`t|?+|J%23 z8yFZ|x^(I9pFfWtJ<=5OQWCP&5OvoQ^X}jA=nl8GsHo_@d-ueWH}2T6W6heiclq?b zeEAYJ`HY2y#lJs)dV6~f4Gm8{{CVN&ub)4EwzjsKRP0Thab78Vn{mCdv1Hoj$jC^6 z`1OB&|M~Ib$Ht2vjvhTKo4IA*t*?eXq!~iCuT_;6XDpvk4O>2q$ikk&zJ) z5ZH3*{pzQ~cfWOYb%ln8zI^$zwzjr;{T(YStK{Tl+xmk~o;-<(i8*%v zdthMT>({T3Klrie*sJyH*L!$)giowL{qNUriQGdMFJ9ced-sZyZ&s~Z(^b^K3}rTOsGsZ&>^7<;#~hY&I2s!7A?vgJ3X?R`?Y=0VVuE84}o-oJnU>C>mbU%pP*^L$?Yym>KG LmPnmzq zz3r%L%Mr)M!;>dZ&Ruxr)~#FLzkRD%ar1$?Q(RnJ-m<&X_P;c*-uLwB(-$vZ+!Zuf zaP(#Dtm_s{r)SKV;nQ{8zWv;sgU@~XFL`zxn>%;z&-;(RpT6|EqRY$6tE#HXtZLsw zLm$_!3o5zWf4_W_kdW}EZ}!X7`pPG6ag~*ot()#eOuzQ$-G}4HkH35Ou3_DsOV55= zx14>IU8i4j@=a~uV~gOoZ{POpe3m}<(k%v=d*Y@Q6&2qOo=lv3qkY?xS%+Tz|M&mb zljoW9E`7gra1a4kZU>aKhH+-6C;DdCq@Q_iTn_S4o{dF7(hnc*#y?S0wft~$H1`Q6gxvbgNq#l zs`>)Gc{kV@9vlq)$;ZGDu>PO(6J`du86bBxoM2}FYumwG58?+rWoBro>il#6|L-4$ z6Zsh&_Wo`@!Orj?G2~}I*j-Lff}cXnXxMe(zyUi3hLE2O3}9pGzjNrnWCWYvP<0R_ zh2fqL{4RF;Yxx-%R@E>t1jtwD%R`tr+*J7F^Xdu)h86X^AWLi}Xw5&t4zUYl@(%Zj z=O0%yFxW8s*>1(aU{ep1-`|t1BQ3<4dOYTh7Xw3Gs=d($dCA}nOHe=c8pTIT-sz192URr$N0s+Rxfo$j;qcivKV&+XPlzpK~W z{riTm|M-IH7wmqmyWiekx^3J4U01(VZQq_8bCDrN;-~qVPqXWtirikzak{^DUFn>( z*{jUruKn0xRrz%uOX{cew`wYtTGzLGswD0cP$n@`Pw_Sf+;#cg-T);kYzJYg5{Cw?&zB_)^q%EAk zVQyvQl9--emXz<8uI)AYrtMz;PkrKRULBsQ>FXqaTCU&zxGVQw_lfr(s*1lEr=)x= zd9-!UZ&s!HlhzgUmH$6|#U*aWP$pS+xbd%=^2ge@%ja*AUbz0o^`E6D7k{!beSf6)E)l=j z{xyC+pFz)b{Tt7D-ns<^<`&03%S!*H}Y|9qR{k`m(Sn{)L zKV1Stw_eSwKlWq$;(~vno2)*yUzy+$7W)0nz4gDD?6&UTylJy&?yHIOV`^U6M|IEJ z{I~B@-HM#dh`(<-^eUzVgl|&%)PCjC_O)@LvRm$3o8G>#@ykDtljUD;$ayJ;#b$-6 zELoMmPUh#<)jt;eI&?O6@zcAv&4ul%iN57>&aOatv>%et@ra|*M++aDuS%0T(19n zGghO{uG;=_fre= zuA04bzMIaiyZs^3-t6a_u6k!BW$vZ+vQL-)SILR%{+rb57_($!%v1jp+iIoyvr&gScIag%Z8tioed~(S{;fA> zoZz{gU+-D!&Hd@hEx-CdCZFtQxF1+(zhs8zt#@;0<#xzK?B0I=U4YcaU&o?0{$4dP zesfpKy{v?Tci4NMJ=<<$E&cQA@$&1aDRhJ#vZm{qB&l9_rU4Q2Czd$qHb+xdKKySLOTUgg#@^XEMs+x!2#{K5Ua z&_?e4755*{&7`C6XL+A5YGrw4@_GH#ALd;(wH^!Se45Jq`x!6W?}x2s^HXlGmEQHa zGO%Yw|Bt6D4y&oJQM@MhI@V%);q6~jHQZ9Zv!t%A|7E4W&@5Yg&n5BTHAdI8XRf#z ztNiQMJ>Nwu?vf{~*B|OG5Q$PKWq!!-|qv|HFS*jyv(clPxk=Dm;s4f$+~grt2S^VBY)Bsl@lSQjES}`}bD!^OoiZ(?vXX zZ|#d?H(E5Sc*$$VpItQuat2>jHc3rj&tJW9Q(w*3&q;yB>#9qe>+mo9 zo!P?N(6WkYWrk&Ibe9&*c;9^e(Z_v{W14reMAXZ^GyHe1K6Cz3#-BAi_w$}8Q<$%9 zbI3C9l+c}B!I%DW{WPpEd*7|^KksM1a-IK)^%d@10qhP(A8+(-Ve<4_ZSj4wnNP(1 zuMYpXMe-c{bDuFb)tJ|aE-YL+fB&QUv&tfiWxuKJ|JQ!*EXRd=r>AUYSU2v(k1Oa} z;`bGK;z4HSkG!AodzD^36~=q$+U8Z0FZ@a3d)(3WPD0^-Q|$lV`i+xp9qr6xxa))F zKW<*Z`*B4fV?cfP>(wjQP0XKC@cG7j@!F41Pa5y9e)PSiMk*?`{8P8$aY2UUdC{L2 zE}tj(A%0Wo-cN3Ihnn{7mEW^FTjSn&)pa}OEEby9V%HsVlfmD-BdkH{_n%#CBGM23 z<#ye4Wz7<9nqvCzfZdVfyjI5b0Tb*M-Pp1&>V*p2thgp@w_Vun?!@@5k(bry@>_k< zeL5|yUQu?|wa6B!iK1T)yV=dW9i;oyXqMTXsO5+GOf)`Zr5WBe{={z_bR%kC_sZkX z`RAwWvok)a{dVJdi2J@@S`uz)Ps-Ag675;?m*!2^G_mM7bB>>z*CgZ#_wt5aYy0>`8*a|u@r<{|JzBB7 zaOGC3JF1^D-^72uE&jhceBX~xOJ@G(c=Y{Cr0lHrTG1lCH`)6&Crx^oB3f<#MbpQ} zc7p$}luOUe7Ci`_QFx9ueM(*2oV3}UlVzF;>)m%%G+V98U1{;~Y3rY^&z;4G4N}YR zYwT8v*N<|&|6cdQ8g-iu!c}ih+iy2N9Uhuhr5L|{{{%kY*F^=(ly^6&Z>tzeRV(l-z38|R9>;2QGk((jdIPJsMy&f&_D=ARD!O@X z&pQu+J9+g^5B_p~{U)E-e&Oin>*=3@9REq~t5f;(n(u6B)~OSwcYb|Ys{KUz`v%1Y zb%9@hyT#XhoXCIT|L67g|9Qn|mkhceVZ9t>KeCVfKkPlY>Hk z{d~Sneqn!};*3qgY`ual_fDMtoHxP0D_vVbX7|L0oSxq!_r=w3skN-X_F;|snOnRY zpY;6^tmaMS*8H4zK5`#8;`dC*j|t?EZ`gc)X=%(GfjM?HnL;nz|EWHC9ofS^Ve5vP z=o#}^pWMIY_j?T}^Uqiw-xx9bj#Mh|+S{!P|4Yt4weT=J&F^t|`RS-)iN)^eE&G-x zdfofAZi;2oQ<-yHx7F{L?_XDT=F@w*&Ob7B*{jpGf4U#-yYK(|>nGH|Z`1c;`L(<5 z`HOCS|EWLvlm7kZ_%#3TwB-24KhAx%(m(&65ZUZ6W+f|jd8M7`&x2BpMfSx<{H=^b zfB&3rcB)PDz?6o%rkCH}efZ>57q;%H@y?R82W>TbGyblaQ|7vFUV*QfOTE^+zM3Gn zgR^|M=GshLe>L8tUCsZ=c_GtEW4Xe7Q+{ia`Xh=J0p_XtC$4XfaClm{(3s7yD>HJ{ z()PGkfnA9YxvB%g@9G6-D9l)?P<)H|r*_cY8QWr;E>1u5UZ3Z2cg*SZPh!_2FTc$? zVSn)Jna30A)vtdNwY=AGweI&%YyT(pmmi+W^6cZS`FM2ta`!*(t?&Q-yuLYozv#Z@ z3jY^=u$BHf`O@*2)Yz3Vaq;0B-PZkX4xDBo>7Ffjg8%b`W%3$toZhHPp1Y>{N&TS& zLo{D1Tm0RL>&4glYpu)6+tl$__Ik_ZqRP6}#x?rqH!7DMpT8&|!0}(7^{21Dq=nwB zQuD8Wa@ z_$_{!_f^v;tM9QSOFv$6-~QL5)4?kL7Ms8P`rt-*z4=Sc6ZHv`=Q00W$#AQ2ouzHZ z8k;X`bIhjkWX5KTwu-y*M6q6txO3^xg@mA&&woCzaQAJvW%+2~%a!HMC2`ixr*sbN zwlWBL!S=J0Vbz9pb+sAu-|kFy=klBV?!@)(XvH z^58~z|D(VO@>LI(#7qAyu)7ncwf6_#)Pr|_&n&s>sl0J((B<@C<_u4T#2sy6Vwc^D zu7$jma5Z1?KiR-ub6K`A$61%2FZCI%Z;0(g*W9n>wPMobETTEm>6%%C~Z=l-mj>zJ@c5 zM)%!&POMQqrWmutPxfck-_{qi_Dqa-P`vC_p%}lK`C*coWAl%QjKz6gg?)c{xkQcX z_mtG`ciK1Ys8N@`dT)rLRKf{$mVGnNm@gCWcsgCKN;Gcahnhng>t-^Bg`d^ile7Jo z=0$D|JL$)ZmvmlCsH*A(stTV%?DY(3cJ+XZM^YcP48`5&rao-pb7tF9}Ca2 za@@4|sGXkUP9etgrsnZAAD`~``g2@v$L;3ic-w;OoqyQ(KX~r2&rs6+#FhyWio9`` z*dJ>YtW&P%UASY$rOUz(eWhgoEnM;K^W^FeTVkdx&FhewIWbztKzpO}l$I0k7xue_ zpICm!DKYA_&YAjWMxtRh*-M`F{pnkDaq(x}pr#)@8XXrG-#xp?`_TFh)tK3rl#ah_ z{HFG`b@_{*KfnBxE!{lz%FV`_z5gyTtWwK=l71qm^3(^XeR1rQW}BKg^vv|#JtIir z|Ac8}{L5Y(`ue`NMz>o@_lK|KRW6%?{BD!{IVzTOuY%er@}9ZA^+r6kJI}mcb<1^1 zu9NkwA9I#y@qauhw}N}ceTkgJIk$hfOt{w+cthf+w)^>Q`N8r|^V&|Dzur-@M*04Y zwUb*cP{nWeNNBrEz{5KPPX1!HAwzR*7qw)2V`<~v8bvS0O|ts*d6|WE|F;M1 zTUN*43Yosj(>{NXq|$z#F6O99#(}p&_X{oGp|bf)wD#72Ef-Zk^+x}a6%W=r6U_Rt zdDX+#KYfctGyblbe~3fbWRdOrNd`aIBw`Foo`{Px)%@Ij!=GI~EG)iH)XI9|`s?xa zuR=^JmFL&FWt^7&Sta|d+~-!r|J)U71zoy+I@*E8+i{{7pRKmVV9sX70?{=BpQnr(kx zUN;xnwC~-nmEFI4|8UnG^|kM+dfB$$H~voXn!4CMHS2!I-aa}pexH_3t-`&}KR+zH zUUe&KTTIn6-CIkegR26Ut#HlW)j#FNuh|t-_dOKn)_>8;dDj=4$BO(6-QWNIc=7MuciAuSt9-g0*T1~<_x`SW(bHU$>z*!6D*6BP zZiQJ~j&}c_RcGt>?=6U)yUO=tbNapdy#{+Wef^#NFTtC$^TO$~#mo8PpPcWQy!iQs z5AD+D;*F-qE&v{aJ*67aOzAxu`%FRFb?%y!0y?@GX*^S`ITYdh2yfiIN`fm92 z*c<;={aY6D?(nSqTv?yw+h)19Zv9`B6B+qp!nBjjQ4HdLx|WK6`QQ8d**DX6ApOa3j(f0vy*b7SSpCyr}7qMz&O z+`bvTZD)yZ-pxDJ=5ykWpG`Z{+5bt=abmpDl>c`h_WpU7y)@^|+8_G6J@}?(9xGis z)tn)GO8as@UFj3+wfFA5tLn3Fj`FwFyZq0tuiUz(VBW{9v#NflmKSaLvtU``zB~qx zS*KUmf7;#tEqmECJ{=Le=!^o}_g>%o?aijd2go%D%KmJ1UiJ9G{@r+W%?on%UPEiM!Y-eDb!v5g_-2 zm;aR?!;YZ6#Xl#;nfzcl2 zPmB&z`59C+KCKt}37#R)x}P$UA2jw38gCap;#mcnEofL`uz?>mso)Xtq+b-G(8Ufc zqpkhvZWud*(D}-r3W4kH`p7V6vx>y^i0u;f5Hr!uMkpv+fmB^n($DPXILTAcY_^d zzDnB(Fx^oDp2$#=XZT^IbpPsi28EAvmav0^i3jTbsr;bH8&FUx$umgIJ5evM4GD49 z%d@7ZXKTllRes*$m3M#Xv;FaYmzWb2E}boW6jJlV{r{}XQ*ZCv_dI?o{{aEjx_xH1 zZ_QZuBh6%6TK4CcUAgh|Kigl>x}RIh@WQ?L?WbcYTb2}7<`%1NU;q7Q_)}&BlVw-a zq$gjRb}Z-Z7O%Yc{Ll6mPLv<9W2pZt_H*sYOrO_BQ;S@;uK!wex`z384~MU|)ok-O zC!c@M$#@rYafyAxhH2N$)Rr6eZauBNt9I|1{|(CVQ~eJu-Cml?_37uk^|7}vRk5m81PgC9bdVAmH%?3P|e0vw0xjekT@=E)`6>ELV zvg@^0*G{+Ge(tpTOE=fX`#b;NSUH)!Xs1F)uszQ#H`j&sUiTYL$Olf$Pne$~eRt}~ z9R5Ce>(X71FC0&_5|_ItxUKzdd-L0wKG!q;GAqSDaW-0WBHe6B@xhPH#`D+OS6Q5B zoiF!W>C_)(b^aFLwKM;4*|I3rKXIwMCHPzA#x|d4^(Wq)KNNGL?$PJ(WiIjdmSwB{ z9r>sH*ose;IdDeZRi*%~|ojBkRAyT|2AEhTHg+ z>`RXPyZ-*IZvUV9<&VOasNb)8?J3=^edt9^TDV@gg-`wQiG{LX*~?jWemJmj@$tj5 z?B(-&h3qr%VsT%-r;9(fi)}1~PBw8$W%U7;~2Ws$}fUT*rDxE1Q~4k4uif z`RlyUFW!VjrcQ4-c>*b5nTVB0-b<^yBN6p0ePVeLnrT;&5 z@B6vtRaSDed%o{f`*AuhJ~K8%Nu#d#R{Z<^bVbqmxpz&{PEOHXUH|L+*5~z0GS4iU z+kH)pd)LQ{w^#lC`F3-8-px&)!|M-DzkecbZ}I6};@8-wulcaLyKTdz{c5HUwf&Vn zmaf~E&{+HYn&(Pu`B;-D+K=Y0R6V?77XLD1x70`4Y3zwLYIW}pob0=sUYyFUU$xZM zrm)I++4s+%<#(@kxl=71yz=)VeeFe4pG(cIciDGBe&a;@e;-5oqj$Bf@m#F7B&c=P zyZTP!^m9{kQ|5b2eX;UYx!C;U(jV-<@A*-&aZ>EQ1s}dFe9kWM^X4NFu`fJqChN5Q zKF;$F|MA@T|LSda)`dxrKe+x|lpVa%bMm5RK0IwHk*0ogX6!wrv|n?+r}$*s>Dm0} z<$mj|`dEDNaK#$?fA&0!PS}_0%{}?ON_OA;DeF@7yX$LQ_MH&_dG}^b_Q_)(&uw+S zw#>FaCiaGey|`~#Z2d%e7i*4{=3(pOWc!VEeiVtPge!hj7ny(W_nii%`ddH6?neFi zS#Vf)Wu4Og<%jm~-1_y|!WCcAmj^v=*Znl#YiXGM#@yduFZP|CqZka-GWF(p@9*t>Ty>;B?cG_{mlYfeH|D(*`Kj>9^&i7t$$XLg>WA(2 z)mE!ygN57v1b^yQ{(o!Hi`L)Wsae@ye$2MbkC;&P_@n0Qyzb|#1swX__~rH{pWD0g z%J(u}l~2=M@^|E)n{&HM`nN%iRo=rX%ggSWzMNO5_2*hfSlgmY^4Ct(*DHTbOZ3mY z-sIG!YWV4L;Kcu#54CnXRz2JN_3PJ<`xhO$JoDwd?DuVsYV+SIf0`Z>KSlg!N6ke2 zPs!KC-41XszL0M(Vr%o+{@XcqlZh$354Jd2iT+ke5^-0w?2xcwxsu%@;U+R=L57Ws zL>h-nVhhj3ZWcoxE3jE` zUj2Ucs%vI$#zFoY3^wqZAuj8ZlzyJMe^`!aVr~SD${ZNC{#@lbNq}YGUe|%=; z$w_)23%|{%b7sF^QI*NX`)28uLsOnVzsbNLwFb17fIauu(ZcvA+&3A|oA^)9sB-<= zwDnlMp6zx|d)+Ua9qf(26u&k*vAl)#_kC+Vt0yyt-1UEZRY#5ba%-M zXQlbuLOm6x+&P{7@2g_HzhOsg+4J1A**E{(`gSU9-n_8M+bTDHU$^l6qZ)^QW#2hv zf9`s5;{T$gEq-RVf;W{fV|aXBmU*_l$~H*f#F9G6yq?pLYu|8=|HH?qdmow*b? zt4w)Ue{*C2>z&eP_u>}6lyz=2GO16TH2Z-?zrp-ZGQQ{ZDm6kj*WI+>^Kur8)^QCk z{_=4vhc8Rk*0b4`hu6LcK4CHUK^BMQN}p2R-zK{?6=xrkJ;>`x&(_?_J~2Nl_u3}Qo;4vVo;KT? z9)6LnTwHNfZ@0+LXUpsB{-3@azAf`qjnAyJW~J}%)@$dNJbAt7bbRX>4gR9F^MBjh zr2JbZqmUXOr@o}HJo&}t3HdWw<~^-4I{C*L-YfIn{jOY3 zVsU%o%%3ke)@GP7Hr8mnte+^e__3gVo=WnQ#nl{kwl)nPCQaB@WBsK3^a1H8y=kdJ zQ|qVsE=vyJoWL@>lfmyKf_E-*~-Z2O}dwpqYzt<-l5%LU&y*_`BC{V_Ez zs4)8Wjpjd{GQX2}`&K7OE;sZv(b^v#DZ2ZOlzd?Qlm9>G{(0=5w&7D`(XE<2PmY}#1-sfv)BLB>$tUT>lI(|pL0c*%~ZK~;m?0p zyKmnoT39XXU-Wo6C-Vy#k57Nt-0e)?7B0|_`@|CY^hE#1#}N(cdil+N6uw{n?)uO8 z_vFG;h1Vs{?>Sk|(fF>&q0a2Q9{*x4@qqmE1&^mk_%1W~`@vEBVz&O2&sB;qcAG!> z&0*;&)2lXV$&0;xHQuipCVXExg)#0;L&?tDI$KX2X}q@Lysr(r_sf$UmNS!n^iKTM zH2+binP<}4#cvNY+%zfjyR5x7|MMl@6Z%)LbPC3;_!FZOyla82s9JsRmlvM}vK#Cw z#XdRSpJ>{~_qXDets?u%DNQG)f6CwPR?E9im(}y%qm%RW*E~(}f17wpbyvVG&gY9$ z!-8(5e*M^cMD3#31T!~1fo*!REgjvfoImMx8oX6odM@ZTuaEn$Sg-y6 z_1)mxTV~n3ezW)Y{krH6FHc-|onC)iseW0|N*$J!X7lO~*lORKB=vcXw%z4)*L@p( zJ$`Q3nH8D8!{fm+DJxfloBw`&6o1p3SXBOmS^TcBt$O0MJ5y&&FlX4LK4+K89;x*U z8tsMq!x?sl_0@!@-rv;J_GVG72=BffT{VYwX8e;+Pp^KwYOm=n{xiMrqhnflo{6n2 zs9!x*_fmOI;oAuf_iN-og-CqrJe}F9*=eJ5k8Nwx+;9Pn}%By;sTh#Pu^zCqL2p9_gSu&!vN%`(gRQPVbMe(@wwk zRTMhxR`pIzblHiP4Yg5EtUm46oO0yrL|md z-ulT$wprK=#fj^?^_VA| z_r3qtX4f5g55F1pjn|%j`t5Uqy}RZ8y@$mU9_2ev_@7$+_~F``2cB=Lo);+RpE;|+ z?ZP+HBKD&6{Ks*RE#G~a%dkw&iD}Q}I?yt7q|pQ@$DCDBo{I^#;cnU;TF=)kDuTsMnIVGZw3$vMYw?0Z(_!EtGm_^LjS_mk6|_dJ`v_r!Dk!__^T z4NBB%*{;+zx;%;zPf*>fB=;=ad~Qaa`{OM2=sP>&iux+Q^?L5x;&fuoqtNo3bGOfo zxBdHaw|s%#{bQ5UqSN2UPBywN|FizplPP^Qu_cmyHC9L5Tp`Mr~_5J6by{_f?`Hr8b@WT`4X$y7MUn+WhX;&cs{9X4Rd{H`5nArNW z{%Oa`1=%o+8)D4}KawGQM@OuWI7{sb|?TmLB?T zdm?>rn4;iE;eCqdV<%2NeWST%UFZ5umW?`|zj_X5{hwHBGPShk-@NX<%0}yUb!1P_ ze-@Thk=|7K?RbZ`_uJ5=n@{Vxz46~~EiCw3Cv$dv`1gCJnyalom0o=M?u7q_y%&xx zb-uYj=%f>B+`<9jHNv^27qVUpVl9Gz1r-5HmM=|@) zZ(ds@IxUa(SEM9y7d)4oBIRA*1W6kMm%jE`_vV3ox?@n6#CrSKM@S{_z6D9io1dG4yoAknU zpZ(q!Jsj=d_~dsv3TqlZ34ge!aqT_kDe03>K7E?{?~KXPhn`D|T(cR!Pp?psW!>Dh zZMX9ONaJFQpT){XB0m{>#Uevi@9fvLJZh?TVdkXCKCe!A_TS2yR==cAwnpH7S! zn~#d~ojK}RWSiffRP*JlLwZ?p4!7yRZuAwMBq{)v9Z1pVI^5+~*7@T{oK_na+zF51NF+Kvx) z4*w7L$JcT1mrtIQld1jf$3~v|+28yBA9zC5dZ>pT{4 zdwmPha=W$iN`(HWy{tiY^*c9CPzhx&mPuQ?x$re-Ve~uppCvxKURiB1kYe>T37hxD zoPV_nL-W=8h*iExD??7LR;n*J^Las2-qT%+55!mOH+u5FfA43`%#@RlEtVbhTKfB- z0n1#0?TVHy6BOGURl*mmXmCojaTY#HXkNo1HKSosTvJE0hKADl`sX}nd`0d#+E&c^ zel0OEwv4YTA|kQ!{Rh6?+il+^zRv&eU2Zk=i^2PiU#)pAzO26y`ggfq=>A{Vw#x|x zurn~oUz9NR5j&us;b>pGG;m)}RYa>t`jg47SzAoySFfx0iNE78W2(ldlS+Fe<{mk2 z%z0CT`%0+g^OW@$*BnrouKnPR>pA%+#}((C;SKMX`gv~xKbO^#WX?Hp<{x(Ret!R@ z;=t!8e@+-~cddG1y*;+)Q~llZFMDgg^sSM-BKYP0&g*vT?_164&u}==zpuUEw9%>O z-)m<2sV^-|vv-LV)jcu2PIdPSk5B9KPPix6ygI?n^2xR11^ds#9A)n9ADx=a`Ylb; zvOcmM(mCS#-h5u;?bhvCt=l&2NZguLv)bqX=RbUY(N`}0irL5dbMXd&q`SSo^|RDf zW`6sW`{juIlj&=#a-wsc*@Hu^7wwq)YV^+*w>R-ciq+|Q+A06{-2bkc zxlPoTV|B{QxtV(Qp3xrnC(hsa_mbex?3z-!Zr52C`ZxR$csc9M<#~=byzEZR6!3ns zSWz=@>4PViW{H35{rko0#~bYl=PQe!7&}(iGgK+;U-EoI`N@TBb<<0}CrYvZnwY8m ziMjvRqAfBZ!S_y-+ozkKIBwgz(rQlG`n?OSB$QNjrHxLSdUIvoc){Rv>309FI?0mM zV~){D@sDo5IC-wK*5uFfzCFt8I%^wi1eJC<+MVBZ{&QOE={0Yh?r>#x+8wy=dFjc! z$KUTS`>0$0X`4Y#bmi-gKiwMhz1B?pQ+@T;wBHSX+TOPMPwINIL()3Wv|MJw`=yod zLN3gzobdnO_GvfNZ|1q(zigf}ZO-Wr2gTa{r~W^ozm4BN_gJCYljA%uUfwd7dG2H7 zA@ci&-ri-a*|OA(6<;<4&Awl_Q}eBU^_|lX995T}y11oT zan|{QU$-X8w||wWKVrzM`(vvOtNMm>YdFrYIZ?lO^~Vdxuj;w|ls$iPLj2p7nxd8A zG1>dsO@D3PH4oXhr3cLuk8p?IJGRI=)>fY#_so%oz?TPyjm7m-UZAxa|Kaojh!8(?mf0nuYW3esr-fXwUyy|`A#V?$# zekaQ9)t&3K4&HZ@{&e}M($q`u4KK?czZGAHZu5BWk)}97->Kz!Ox=SWEX&o;D9(&ij@>+nMZ`9e3n?{uKL;Mg6+MO@W*9dfpt+oBa2g>82pP>U-*+ z!e#D!_j}`H#r^4IX6DLs8S@ezpZ&4qSTMh!(!Ks?8@U#$Y~*B!xOGo2vqVU8H)H0> zl&1f_lD9eT@vYhR^H`nXr{Ipc)|v~BD9%+651DaiDx34{`c@5ByH#HtPH_I*@oMqs zq6J;|)_HT+ZH#-eK+*eQ;J0~4KTZs)WLtmSSb6_eF7D$m7EZog>UZHBC_JmH4(hH+ z3F159es_ZP9Z5g`*~=5(Y-Rt>xx>(IviBhiSG!j~Q@)E7L~0*MI@9#$klWJ_j(kUF zKGv&zIO~_kp%sfhDSxiN{P($i-Oe(jJGEv?hVF4ccg_~=zhM*J((!I0|AFMz9>tO; z+c^vUg)`R7cRafy=KQe(BBvkDHr+RE?ZxhSC-xi4_r)G~Vw&`8msI@+&Yy-H;%jXi z3WHp0Gk>Yy@`#%Fer@HumS9w zhfI{4IX8a8vSm#c_YEuCtz5Z3FOYt+dtUuyW%=@hTLRmU{M*1LeAYVTqWn6mEAy(~ z^`F*}H*B9Te<%4&^t_*m5nTSxqTz+Jzi<4$;P8?gb3Vo2_5YrKu5GvePUZC?1;%$o zPlzin5`U6hEMWZUcI%Gvj5X57eGjM4aNF=C!o+0X3vKyQ(NBHqpRO+myUM@(ZNrvQ z-P!a(=4u5l`1m@vCd6ykA-QO(SEi@yGoyHRK!GjoXfR{uMjw z=V7n#yYpV_z6tgb-q#i$J^4}YmhFTyC;02;Ejzk%?xK@VN~Yw_T6~#<&!wxws5Ecm z1ig)_Q!FPv?tRkny3XwQ*N4KBD^9HMb8qlJ!9LT*>i;2;2lW@KW1Z~H?=$uN`SeNa ze%SH0nxD-3w!BiB+hwRMFY`!s)lHT4q1l?<{5(w3pAP=6-Nfjhczf2xGWUJAEq`9N zuGqBTQ*Now3HIHtC0bjz%zG74EpRSZzb{Yn_8+?=g`A7MuQ4e-Y5t(L%iv<*U%xFT z?XfyRr&``E|NTB&{cx;J{V7mYp`Ca86~~iqfs4YQuAeZ5lLfACs-zIn#`| zw4R-_y8L5Wthkf4QvHc7hr2c9u7~!oy|Byu-^^4WL$6RzOAh(3KL6d>pHyef+shU8 z+T+hZk>9n-@}@tdkIR2sHSJ#48ky9x=YOj1BsJU0+I@U=H(_CUS=F_WdKKHSG z+WGU`nbf&9wd<$U7uv@@$k}0iNVYP#{?ycrbFLj9n17yj>0PJ1Kepp`LuKoM4NIq~ z?WpGFUiC5eP_x_w`(?Yn2#Bp@ZQws)AMQKzZ)ESOPes43{LwLf{+oHdiR-@Q&u(sB zaIjl{r*v%3r~RLqfBygek@bZ5!Q|Amwy$OP0&V^E3z=T??VS~@df;zKEN}Tt<$MQA z(d`cPEgbR_5=@OmW?oh4(H@JvaG78&^$^)P3n03o=iANU?j{SrgF09TH?a z#eY%Q6X%J!qT60>JnYYZ`_++NUfXalgNq3#?khOAMoj8DbkHiYd%5y`@6UHi9sj!j z6K<@TKfzy6f5QFZgo*Me*J^W%E%h-kluM2Ne$$9ixT~IJ!)?9^PsH!r*Z%s_@Bj6H z{Qi1H?=us_XHGb(E1#J{e|F68Iy{7XNUu_b)5 zU{HF=8Zl|_#QIh~>#oL)iM?OU1Tv>9MMzI?I9{h{f2pOQkbU-!wZ6r73;5i&)|eVq zY&mId+AeTRo##n)edfH~dMn z!1usZ?YT=c@9w_0?4xY-u~#o!chvh@KWVBFh&rN?w{GJU2h;V5yq^QkZ+&^-zzwO` zBU5iYc{hJYSN_HqyXy|$?q{2Je>=~U>dbk&bymDzT+>(+pY%Pw@lV~Nsg-+c!a|qG z?cX+8uK4%*hE1QI?{A(y>8rn2!;Uwv%JwW+!*b-RxZlppHH$BT(|SvCz0qAV)fyi`wl!R6$^jLnxpfMF(_W{ zK*`Qym(+UqHH1a6EJ|Gt!LCgi0B*UU`(K0P9L+QZzre^LrJ z*w5d4JKpc+**yI>twon7Go8$L|1|g0_4fylKHC4m=HL9nyPvIo&Rv|px%|D{m9zHq zH&vJ2-!FOSMlsXL>Gd`LE_^w(|5CN#SDSx}=L&qABc-EK|3C{&sWn^@Hj^ z6@KpDzu*0Pp6RFc4|{8q$~Na!U8=0yFlTS{{7?IRZ@%4A_u1v6<0t8z_9xrrdruok zmC5O+zt>y-M(uBo^_0A?j-R5Z)QJB+l3OfyD6hz_di(sj{x^NgoM%)Q^Vh4ye^Qi_ zPUVn3ynb`}Klsxz9MSea{+s%hb4Ef()IsdJ;G%n@` z=`y={&SsA0>7)Gu&#eDV=JVM1#CeYU-;Zl<+|xh(_id6{*#Y*G`PrX*?=wGm{>f(1 z?u|O`<$YbmQjwEm>dwg{_JI#27JIN%rXGnSoIzRrT7}>P;u{ zAJETTT4{3pkK?>N)5`kQ*WORzKk%#XXQ;|q`HXZX1L+TM3l_!)9N>cT%UDpZ}^`gmP;EknjZ&Hp=YPpttnenfm?T)?`# z<|KQA*p9lQBBh@U0(;NTzqfaC4MWGhzn3oupJZ>iAzQcSu3sx~H;UMV?_% zxcdGnAQf}lukHhhZ@RaoXd^$v$Jf41-L(uc9rx#Ya!lf9*yvp~*$ZUDNq=qe-wXo# z?_F`PVz|)tP XhCP=;l{YdlFfe$!`njxgN@xNA2!u5$ -- GitLab From fe0b2af2293fe327c1e336bbf86ca5f92a58ee3f Mon Sep 17 00:00:00 2001 From: Terri Chu Date: Tue, 20 Oct 2020 14:46:25 -0400 Subject: [PATCH 09/11] fixup! Enable single result redirect for advanced commits search --- ee/spec/requests/search_controller_spec.rb | 2 +- spec/requests/search_controller_spec.rb | 29 +++++++++++++++------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/ee/spec/requests/search_controller_spec.rb b/ee/spec/requests/search_controller_spec.rb index 61d69fa1291388..c467a76b172f48 100644 --- a/ee/spec/requests/search_controller_spec.rb +++ b/ee/spec/requests/search_controller_spec.rb @@ -7,7 +7,7 @@ let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, :public, :repository, :wiki_repo, name: 'awesome project', group: group) } - before_all do + before do login_as(user) end diff --git a/spec/requests/search_controller_spec.rb b/spec/requests/search_controller_spec.rb index 9673f40c6afaee..3b8a84abecc0dc 100644 --- a/spec/requests/search_controller_spec.rb +++ b/spec/requests/search_controller_spec.rb @@ -31,9 +31,11 @@ def send_search_request(params) context 'for issues scope' do let(:object) { :issue } - let(:creation_args) { { project: project } } - let(:params) { { search: '*', scope: 'issues' } } - let(:threshold) { 0 } + let(:creation_args) { { project: project, title: 'foo' } } + let(:params) { { search: 'foo', scope: 'issues' } } + # there are 4 additional queries run for the logged in user: + # (1) geo_nodes, (1) users, (2) broadcast_messages + let(:threshold) { 4 } it_behaves_like 'an efficient database result' end @@ -41,9 +43,16 @@ def send_search_request(params) context 'for merge_request scope' do let(:creation_traits) { [:unique_branches] } let(:object) { :merge_request } - let(:creation_args) { { source_project: project } } - let(:params) { { search: '*', scope: 'merge_requests' } } - let(:threshold) { 0 } + let(:creation_args) { { source_project: project, title: 'bar' } } + let(:params) { { search: 'bar', scope: 'merge_requests' } } + # some N+1 queries exist + # each merge request require 4 extra queries for: + # - one for projects + # - one for namespaces + # - two for routes + # plus 4 additional queries run for the logged in user: + # - (1) geo_nodes, (1) users, (2) broadcast_messages + let(:threshold) { 16 } it_behaves_like 'an efficient database result' end @@ -51,14 +60,16 @@ def send_search_request(params) context 'for project scope' do let(:creation_traits) { [:public] } let(:object) { :project } - let(:creation_args) { {} } - let(:params) { { search: '*', scope: 'projects' } } + let(:creation_args) { { name: 'project' } } + let(:params) { { search: 'project', scope: 'projects' } } # some N+1 queries still exist # each project requires 3 extra queries # - one count for forks # - one count for open MRs # - one count for open Issues - let(:threshold) { 9 } + # there are 4 additional queries run for the logged in user: + # (1) geo_nodes, (1) users, (2) broadcast_messages + let(:threshold) { 13 } it_behaves_like 'an efficient database result' end -- GitLab From 36be569f7c0004c0eafdc01602fac4ad09055b7f Mon Sep 17 00:00:00 2001 From: Terri Chu Date: Wed, 21 Oct 2020 11:37:21 -0400 Subject: [PATCH 10/11] fixup! Enable single result redirect for advanced commits search --- app/controllers/search_controller.rb | 6 +++--- spec/requests/search_controller_spec.rb | 12 ++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index cc230f9c457586..ab1b0c69c46771 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -35,8 +35,6 @@ def show return unless search_term_valid? - increment_search_counters - return if check_single_commit_result? @search_term = params[:search] @@ -49,6 +47,8 @@ def show render_commits if @scope == 'commits' eager_load_user_status if @scope == 'users' + + increment_search_counters end def count @@ -113,7 +113,7 @@ def check_single_commit_result? return false unless Commit.valid_hash?(query) commit = @project.commit_by(oid: query) - return false unless commit.present? && commit.sha.start_with?(query) + return false unless commit.present? link = search_path(safe_params.merge(force_search_results: true)) flash[:notice] = html_escape(_("You have been redirected to the only result; see the %{a_start}search results%{a_end} instead.")) % { a_start: "".html_safe, a_end: ''.html_safe } diff --git a/spec/requests/search_controller_spec.rb b/spec/requests/search_controller_spec.rb index 3b8a84abecc0dc..8dca85c3e45937 100644 --- a/spec/requests/search_controller_spec.rb +++ b/spec/requests/search_controller_spec.rb @@ -116,6 +116,18 @@ def send_search_request(params) expect(response).not_to redirect_to(project_commit_path(project, sha)) end + + it 'does not redirect if commit sha not found in project' do + send_search_request({ search: '23594bc765e25c5b22c17a8cca25ebd50f792598', force_search_results: true, project_id: project.id }) + + expect(response).not_to redirect_to(project_commit_path(project, sha)) + end + + it 'does not redirect if not using project scope' do + send_search_request({ search: sha, force_search_results: true, group_id: project.root_namespace.id }) + + expect(response).not_to redirect_to(project_commit_path(project, sha)) + end end end end -- GitLab From d9ee280f591deafa4e3f5298b65b442a2783451e Mon Sep 17 00:00:00 2001 From: Terri Chu Date: Wed, 21 Oct 2020 11:44:50 -0400 Subject: [PATCH 11/11] fixup! Enable single result redirect for advanced commits search --- spec/requests/search_controller_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/requests/search_controller_spec.rb b/spec/requests/search_controller_spec.rb index 8dca85c3e45937..700cdd96ba8657 100644 --- a/spec/requests/search_controller_spec.rb +++ b/spec/requests/search_controller_spec.rb @@ -112,19 +112,19 @@ def send_search_request(params) allow(Ability).to receive(:allowed?).and_call_original allow(Ability).to receive(:allowed?).with(user, :download_code, project).and_return(false) - send_search_request({ search: sha, force_search_results: true, project_id: project.id }) + send_search_request({ search: sha, project_id: project.id }) expect(response).not_to redirect_to(project_commit_path(project, sha)) end it 'does not redirect if commit sha not found in project' do - send_search_request({ search: '23594bc765e25c5b22c17a8cca25ebd50f792598', force_search_results: true, project_id: project.id }) + send_search_request({ search: '23594bc765e25c5b22c17a8cca25ebd50f792598', project_id: project.id }) expect(response).not_to redirect_to(project_commit_path(project, sha)) end it 'does not redirect if not using project scope' do - send_search_request({ search: sha, force_search_results: true, group_id: project.root_namespace.id }) + send_search_request({ search: sha, group_id: project.root_namespace.id }) expect(response).not_to redirect_to(project_commit_path(project, sha)) end -- GitLab