Skip to content

Commit 5d27675

Browse files
committed
fix: filters issues when toggling external products on and off
1 parent ad5bcc6 commit 5d27675

4 files changed

Lines changed: 90 additions & 9 deletions

File tree

angular/src/app/search/filters/filters.component.ts

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ export class FiltersComponent implements OnInit, AfterViewInit, OnDestroy {
130130
fullFacetCountsComplete: boolean = false; // made public for template gating
131131
private fullFacetCountsSubscription: any = null;
132132
private searchResponseSub: any = null;
133+
private externalToggleSubscription: any = null;
133134

134135
filterStyle = {
135136
width: "100%",
@@ -211,13 +212,8 @@ export class FiltersComponent implements OnInit, AfterViewInit, OnDestroy {
211212
) {
212213
//Clear filters when we conduct a new search
213214
this.clearFilters();
214-
// Reset facet aggregation state so new query can trigger fresh expanded fetch
215-
this.fullFacetCountsInFlight = false;
216-
this.fullFacetCountsComplete = false;
217-
// Show skeletons again for all facet groups until rebuilt
218-
this.recordHasLoading = true;
219-
this.themeLoading = true;
220-
this.resourceTypeLoading = true;
215+
// Reset facet aggregation state so new query can trigger fresh expanded fetch
216+
this.resetFacetAggregationState();
221217
this.onSearchValueChanged();
222218
}
223219
}
@@ -239,12 +235,20 @@ export class FiltersComponent implements OnInit, AfterViewInit, OnDestroy {
239235
if(this.lastOutboundFilterString === str) return; // ignore self echo
240236
this.applyFilterStringToSelections(str);
241237
});
238+
239+
// External toggle changes should reset facet aggregation to avoid stale filters
240+
this.externalToggleSubscription = this.searchService
241+
.watchExternalProducts()
242+
.subscribe(() => {
243+
this.resetFacetAggregationState();
244+
});
242245
}
243246

244247
ngOnDestroy(): void {
245248
if(this.searchResponseSub) { try { this.searchResponseSub.unsubscribe(); } catch {} }
246249
if(this.fullFacetCountsSubscription) { try { this.fullFacetCountsSubscription.unsubscribe(); } catch {} }
247250
if(this.filterWatcherSub) { try { this.filterWatcherSub.unsubscribe(); } catch {} }
251+
if(this.externalToggleSubscription) { try { this.externalToggleSubscription.unsubscribe(); } catch {} }
248252
}
249253

250254
toggleMoreOptions() {
@@ -394,7 +398,7 @@ export class FiltersComponent implements OnInit, AfterViewInit, OnDestroy {
394398
const q = this.searchQueryService.buildQueryFromString(lSearchValue, null, this.fields);
395399
// Mark loading for possible UI skeletons
396400
this.recordHasLoading = true; this.themeLoading = true; this.resourceTypeLoading = true;
397-
this.searchService.fetchAllForFacetCounts(q, this.searchTaxonomyKey, targetSize, this.searchService['filterString']?.getValue?.()).subscribe(expanded => {
401+
this.fullFacetCountsSubscription = this.searchService.fetchAllForFacetCounts(q, this.searchTaxonomyKey, targetSize, this.searchService['filterString']?.getValue?.()).subscribe(expanded => {
398402
if (expanded && expanded.ResultData && expanded.ResultData.length) {
399403
this.buildFacetCounts(expanded.ResultData, true); // final pass
400404
} else {
@@ -411,6 +415,21 @@ export class FiltersComponent implements OnInit, AfterViewInit, OnDestroy {
411415
});
412416
}
413417

418+
private resetFacetAggregationState() {
419+
if (this.fullFacetCountsSubscription) {
420+
try {
421+
this.fullFacetCountsSubscription.unsubscribe();
422+
} catch {}
423+
this.fullFacetCountsSubscription = null;
424+
}
425+
this.fullFacetCountsInFlight = false;
426+
this.fullFacetCountsComplete = false;
427+
// Show skeletons again for all facet groups until rebuilt
428+
this.recordHasLoading = true;
429+
this.themeLoading = true;
430+
this.resourceTypeLoading = true;
431+
}
432+
414433
private buildFacetCounts(data: any[], finalPass: boolean = true) {
415434
this.themesWithCount = [];
416435
this.componentsWithCount = [];

angular/src/app/search/results/results.component.ts

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,15 @@ export class ResultsComponent implements OnInit {
248248

249249
this.externalToggleSubscription = this.searchService
250250
.watchExternalProducts()
251-
.subscribe(() => {
251+
.subscribe((enabled) => {
252252
if (!this.inited || this.startupGuard) return;
253+
if (!enabled) {
254+
const cleaned = this.stripExternalOnlyTypes(this.currentFilter);
255+
if (cleaned.changed) {
256+
this.searchService.setFilterString(cleaned.filter);
257+
return;
258+
}
259+
}
253260
this.search(
254261
null,
255262
this.currentPage || 1,
@@ -488,6 +495,57 @@ export class ResultsComponent implements OnInit {
488495
});
489496
}
490497

498+
private stripExternalOnlyTypes(filterQuery: string): {
499+
filter: string;
500+
changed: boolean;
501+
} {
502+
if (!filterQuery || filterQuery === "NoFilter") {
503+
return { filter: filterQuery, changed: false };
504+
}
505+
const segments = filterQuery.split("&").filter((seg) => !!seg);
506+
let changed = false;
507+
const nextSegments: string[] = [];
508+
segments.forEach((seg) => {
509+
const [key, value] = seg.split("=");
510+
if (!value) {
511+
nextSegments.push(seg);
512+
return;
513+
}
514+
if (key === "@type") {
515+
const values = value.split(",").filter((v) => !!v);
516+
const kept = values.filter(
517+
(v) => !this.isExternalOnlyTypeToken(v)
518+
);
519+
if (kept.length === values.length) {
520+
nextSegments.push(seg);
521+
return;
522+
}
523+
changed = true;
524+
if (kept.length) {
525+
nextSegments.push(`${key}=${kept.join(",")}`);
526+
}
527+
return;
528+
}
529+
nextSegments.push(seg);
530+
});
531+
if (!changed) {
532+
return { filter: filterQuery, changed: false };
533+
}
534+
return {
535+
filter: nextSegments.length ? nextSegments.join("&") : "NoFilter",
536+
changed: true,
537+
};
538+
}
539+
540+
private isExternalOnlyTypeToken(token: string): boolean {
541+
const normalized = String(token || "")
542+
.replace(/\s/g, "")
543+
.toLowerCase();
544+
if (!normalized) return false;
545+
if (normalized === "coderepository") return true;
546+
return normalized.startsWith("vcs:");
547+
}
548+
491549
/** Remove a single filter segment and re-run search without full reset */
492550
removeFilterTag(index: number) {
493551
if (index < 0 || index >= this.activeFilterTags.length) return;

angular/src/app/shared/search-service/search-service.service.mock.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ export class MockSearchService implements SearchService {
156156
* Start search
157157
*/
158158
public search(searchValue: string, url?: string): void {
159+
// Reset filters when starting a new search (avoid stale filters in SPA navigation).
160+
this.setFilterString("NoFilter");
159161
let params: NavigationExtras = {
160162
queryParams: {
161163
q: searchValue,

angular/src/app/shared/search-service/search-service.service.real.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,8 @@ export class RealSearchService implements SearchService {
342342
* @param url
343343
*/
344344
public search(searchValue: string, url?: string): void {
345+
// Reset filters when starting a new search (avoids stale filters across navigation).
346+
this.setFilterString("NoFilter");
345347
const queryParams: any = {
346348
q: searchValue,
347349
};

0 commit comments

Comments
 (0)