diff --git a/app/models/queries/projects/filters/name_filter.rb b/app/models/queries/projects/filters/name_filter.rb index 91c8f0c5d3e4..96aa957334a6 100644 --- a/app/models/queries/projects/filters/name_filter.rb +++ b/app/models/queries/projects/filters/name_filter.rb @@ -39,8 +39,12 @@ def where ["LOWER(projects.name) IN (?)", sql_value] when "!" ["LOWER(projects.name) NOT IN (?)", sql_value] - when "~", "**" + when "~" ["LOWER(projects.name) LIKE ?", "%#{sql_value}%"] + when "**" + terms = values.first.downcase.split + conditions = Array.new(terms.size, "LOWER(projects.name) LIKE ?").join(" AND ") + [conditions, *terms.map { |t| "%#{t}%" }] when "!~" ["LOWER(projects.name) NOT LIKE ?", "%#{sql_value}%"] end diff --git a/frontend/src/app/shared/components/header-project-select/header-project-select.component.ts b/frontend/src/app/shared/components/header-project-select/header-project-select.component.ts index ef6b27a2ff1b..c01fc2de5479 100644 --- a/frontend/src/app/shared/components/header-project-select/header-project-select.component.ts +++ b/frontend/src/app/shared/components/header-project-select/header-project-select.component.ts @@ -81,7 +81,8 @@ export class OpHeaderProjectSelectComponent extends UntilDestroyedMixin implemen (project) => { const searchText = this.searchableProjectListService.searchText; if (searchText.length) { - const matches = project.name.toLowerCase().includes(searchText.toLowerCase()); + const terms = searchText.toLowerCase().split(/\s+/).filter((t) => t.length > 0); + const matches = terms.every((term) => project.name.toLowerCase().includes(term)); if (!matches) { return false; diff --git a/frontend/src/app/shared/components/searchable-project-list/searchable-project-list.service.ts b/frontend/src/app/shared/components/searchable-project-list/searchable-project-list.service.ts index 561cc9d4c9a7..e83ce6f29149 100644 --- a/frontend/src/app/shared/components/searchable-project-list/searchable-project-list.service.ts +++ b/frontend/src/app/shared/components/searchable-project-list/searchable-project-list.service.ts @@ -84,12 +84,12 @@ export class SearchableProjectListService { return of([[] as IProject[], searchText, loadingEnabled as boolean, favoriteIds]); } - const nameFilter:ApiV3ListFilter[] = []; + const searchFilter:ApiV3ListFilter[] = []; if (searchText.length > 0) { - nameFilter.push(['name', '~', [searchText]]); + searchFilter.push(['typeahead', '**', [searchText]]); } - return this.fetchProjects(nameFilter) + return this.fetchProjects(searchFilter) .pipe(map((collection) => [collection._embedded.elements, searchText, loadingEnabled as boolean, favoriteIds])); }), switchMap(([projects, searchText, loadingEnabled, favoriteIds]:[IProject[],string,boolean,string[]]) => { diff --git a/spec/features/projects/project_autocomplete_spec.rb b/spec/features/projects/project_autocomplete_spec.rb index f9a349a152f8..7af5b299cff0 100644 --- a/spec/features/projects/project_autocomplete_spec.rb +++ b/spec/features/projects/project_autocomplete_spec.rb @@ -110,10 +110,11 @@ expect(page).to have_no_css("strong") end - # Expect fuzzy matches for plain + # Expect fuzzy matches for multiple substrings top_menu.search "Plain pr" top_menu.expect_result "Plain project" - top_menu.expect_no_result "Plain other project" + top_menu.expect_result "Plain other project" + top_menu.expect_no_result "Project with different name and identifier" # Expect search to match names only and not the identifier top_menu.clear_search