Skip to content

Commit da53a24

Browse files
darvsorceixlasoh1985c5346163lasoh
authored
fix: CXSPA-8494 check for keyword redirect before loading default results (#20072)
Co-authored-by: Artur Lasocha <[email protected]> Co-authored-by: Konrad Dzikowski <[email protected]> Co-authored-by: lasoh <[email protected]>
1 parent a9b20ab commit da53a24

File tree

3 files changed

+121
-16
lines changed

3 files changed

+121
-16
lines changed

Diff for: projects/core/src/product/facade/searchbox.service.ts

+41-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7-
import { Injectable } from '@angular/core';
8-
import { select } from '@ngrx/store';
9-
import { Observable } from 'rxjs';
7+
import { inject, Injectable } from '@angular/core';
8+
import { select, ActionsSubject } from '@ngrx/store';
9+
import { Observable, filter, take } from 'rxjs';
1010
import { ProductSearchPage, Suggestion } from '../../model/index';
1111
import { SearchConfig } from '../model/index';
1212
import { ProductActions } from '../store/actions/index';
@@ -17,6 +17,8 @@ import { ProductSearchService } from './product-search.service';
1717
providedIn: 'root',
1818
})
1919
export class SearchboxService extends ProductSearchService {
20+
private actionsSubject = inject(ActionsSubject);
21+
2022
/**
2123
* dispatch the search for the search box
2224
*/
@@ -32,6 +34,24 @@ export class SearchboxService extends ProductSearchService {
3234
);
3335
}
3436

37+
/**
38+
* Performs search and returns an Observable that emits when the search is completed
39+
*/
40+
searchWithCompletion(
41+
query: string,
42+
searchConfig?: SearchConfig
43+
): Observable<boolean> {
44+
this.search(query, searchConfig);
45+
return this.actionsSubject.pipe(
46+
filter(
47+
(action: any) =>
48+
action.type === ProductActions.SEARCH_PRODUCTS_SUCCESS ||
49+
action.type === ProductActions.SEARCH_PRODUCTS_FAIL
50+
),
51+
take(1)
52+
);
53+
}
54+
3555
getResults(): Observable<ProductSearchPage> {
3656
return this.store.pipe(select(ProductSelectors.getAuxSearchResults));
3757
}
@@ -59,4 +79,22 @@ export class SearchboxService extends ProductSearchService {
5979
})
6080
);
6181
}
82+
83+
/**
84+
* Performs suggestions search and returns an Observable that emits when the operation is completed
85+
*/
86+
searchSuggestionsWithCompletion(
87+
query: string,
88+
searchConfig?: SearchConfig
89+
): Observable<boolean> {
90+
this.searchSuggestions(query, searchConfig);
91+
return this.actionsSubject.pipe(
92+
filter(
93+
(action: any) =>
94+
action.type === ProductActions.GET_PRODUCT_SUGGESTIONS_SUCCESS ||
95+
action.type === ProductActions.GET_PRODUCT_SUGGESTIONS_FAIL
96+
),
97+
take(1)
98+
);
99+
}
62100
}

Diff for: projects/storefrontlib/cms-components/navigation/search-box/search-box-component.service.spec.ts

+3
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ describe('SearchBoxComponentService', () => {
127127
spyOn(service, 'launchSearchPage').and.callThrough();
128128

129129
service.launchSearchPage(mockQueryString);
130+
131+
service.searchCompleted.next(true);
132+
130133
expect(service.launchSearchPage).toHaveBeenCalled();
131134
expect(MockRoutingService.go).toHaveBeenCalledWith({
132135
cxRoute: 'search',

Diff for: projects/storefrontlib/cms-components/navigation/search-box/search-box-component.service.ts

+77-13
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ import {
1313
SearchboxService,
1414
TranslationService,
1515
WindowRef,
16+
ProductActions,
1617
} from '@spartacus/core';
17-
import { combineLatest, Observable, of, ReplaySubject } from 'rxjs';
18-
import { map, switchMap, tap } from 'rxjs/operators';
18+
import { combineLatest, Observable, of, ReplaySubject, Subject } from 'rxjs';
19+
import { map, switchMap, tap, filter, take } from 'rxjs/operators';
1920
import {
2021
SearchBoxProductSelectedEvent,
2122
SearchBoxSuggestionSelectedEvent,
@@ -30,10 +31,12 @@ const HAS_SEARCH_RESULT_CLASS = 'has-searchbox-results';
3031
export class SearchBoxComponentService {
3132
chosenWord = new ReplaySubject<string>();
3233
sharedEvent = new ReplaySubject<KeyboardEvent>();
34+
searchCompleted = new Subject<boolean>();
3335

3436
protected enableRecentSearches: boolean = false;
3537
protected enableTrendingSearches: boolean = false;
36-
38+
private currentQueryLength: number = 0;
39+
private hasKeywordRedirect: boolean = false;
3740
constructor(
3841
public searchService: SearchboxService,
3942
protected routingService: RoutingService,
@@ -48,6 +51,10 @@ export class SearchBoxComponentService {
4851
* products or suggestions.
4952
*/
5053
search(query: string, config: SearchBoxConfig): void {
54+
this.hasKeywordRedirect = false;
55+
this.currentQueryLength = query ? query.length : 0;
56+
this.searchCompleted.next(false);
57+
5158
if (
5259
!this.enableRecentSearches &&
5360
!this.enableTrendingSearches &&
@@ -63,16 +70,57 @@ export class SearchBoxComponentService {
6370
return;
6471
}
6572

73+
let productsComplete = !config.displayProducts;
74+
let suggestionsComplete = !config.displaySuggestions;
75+
6676
if (config.displayProducts) {
67-
this.searchService.search(query, {
68-
pageSize: config.maxProducts,
69-
});
77+
this.searchService
78+
.searchWithCompletion(query, {
79+
pageSize: config.maxProducts,
80+
})
81+
.subscribe((result: any) => {
82+
if (
83+
result?.type === ProductActions.SEARCH_PRODUCTS_SUCCESS &&
84+
result?.payload?.keywordRedirectUrl
85+
) {
86+
this.hasKeywordRedirect = true;
87+
}
88+
productsComplete = true;
89+
this.checkSearchCompletion(productsComplete, suggestionsComplete);
90+
});
7091
}
7192

7293
if (config.displaySuggestions) {
73-
this.searchService.searchSuggestions(query, {
74-
pageSize: config.maxSuggestions,
75-
});
94+
this.searchService
95+
.searchSuggestionsWithCompletion(query, {
96+
pageSize: config.maxSuggestions,
97+
})
98+
.subscribe((result: any) => {
99+
if (
100+
result?.type === ProductActions.GET_PRODUCT_SUGGESTIONS_SUCCESS &&
101+
result?.payload?.keywordRedirectUrl
102+
) {
103+
this.hasKeywordRedirect = true;
104+
}
105+
suggestionsComplete = true;
106+
this.checkSearchCompletion(productsComplete, suggestionsComplete);
107+
});
108+
}
109+
}
110+
111+
/**
112+
* Check if search operations are complete based on actual completion flags
113+
*/
114+
private checkSearchCompletion(
115+
productsComplete: boolean,
116+
suggestionsComplete: boolean
117+
): void {
118+
if (
119+
productsComplete &&
120+
suggestionsComplete &&
121+
this.currentQueryLength > 0
122+
) {
123+
this.searchCompleted.next(true);
76124
}
77125
}
78126

@@ -107,6 +155,10 @@ export class SearchBoxComponentService {
107155
clearResults() {
108156
this.searchService.clearResults();
109157
this.toggleBodyClass(HAS_SEARCH_RESULT_CLASS, false);
158+
159+
// Reset search completion state
160+
this.hasKeywordRedirect = false;
161+
this.currentQueryLength = 0;
110162
}
111163

112164
hasBodyClass(className: string): boolean {
@@ -261,12 +313,24 @@ export class SearchBoxComponentService {
261313

262314
/**
263315
* Navigates to the search result page with a given query
316+
* after waiting for all search operations to complete
264317
*/
265318
launchSearchPage(query: string): void {
266-
this.routingService.go({
267-
cxRoute: 'search',
268-
params: { query },
269-
});
319+
// Reset the completed state before starting new search
320+
this.searchCompleted.next(false);
321+
this.searchCompleted
322+
.pipe(
323+
filter((complete) => complete), // Only proceed when true
324+
take(1) // Take only the first completion
325+
)
326+
.subscribe(() => {
327+
if (!this.hasKeywordRedirect) {
328+
this.routingService.go({
329+
cxRoute: 'search',
330+
params: { query },
331+
});
332+
}
333+
});
270334
}
271335

272336
private fetchTranslation(

0 commit comments

Comments
 (0)