Skip to content

Commit 1a21656

Browse files
Extension: Fix Support Type filter count inconsistency when other filters are applied.
1 parent 986f8ad commit 1a21656

File tree

5 files changed

+176
-24
lines changed

5 files changed

+176
-24
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-marketplace': patch
3+
---
4+
5+
Fix Support Type filter count inconsistency when other filters are applied.

workspaces/marketplace/plugins/marketplace/src/components/MarketplacePluginFilter.tsx

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,23 @@ import { useSearchParams } from 'react-router-dom';
2222
import Box from '@mui/material/Box';
2323
import { MarketplaceAnnotation } from '@red-hat-developer-hub/backstage-plugin-marketplace-common';
2424

25-
import { usePluginFacet } from '../hooks/usePluginFacet';
26-
import { usePluginFacets } from '../hooks/usePluginFacets';
25+
import { usePluginFacetsWithFilters } from '../hooks/usePluginFacetsWithFilters';
2726
import { CustomSelectFilter } from '../shared-components/CustomSelectFilter';
2827
import { useQueryArrayFilter } from '../hooks/useQueryArrayFilter';
2928

3029
const CategoryFilter = () => {
31-
const categoriesFacet = usePluginFacet('spec.categories');
30+
// Exclude category filters from facets calculation to get accurate counts
31+
const categoriesFacet = usePluginFacetsWithFilters(
32+
{ facets: ['spec.categories'] },
33+
['spec.categories'],
34+
);
3235
const filter = useQueryArrayFilter('spec.categories');
33-
const categories = categoriesFacet.data;
36+
const categories = categoriesFacet.data?.['spec.categories'];
3437

3538
const items = useMemo(() => {
3639
if (!categories) return [];
3740
return categories.map(category => ({
38-
label: category.value,
41+
label: `${category.value} (${category.count})`,
3942
value: category.value,
4043
}));
4144
}, [categories]);
@@ -59,21 +62,25 @@ const CategoryFilter = () => {
5962
};
6063

6164
const AuthorFilter = () => {
62-
const authorsFacet = usePluginFacet('spec.authors.name');
63-
const authors = authorsFacet.data;
65+
// Exclude author filters from facets calculation to get accurate counts
66+
const authorsFacet = usePluginFacetsWithFilters(
67+
{ facets: ['spec.authors.name'] },
68+
['spec.authors.name'],
69+
);
70+
const authors = authorsFacet.data?.['spec.authors.name'];
6471
const filter = useQueryArrayFilter('spec.authors.name');
6572

6673
const items = useMemo(() => {
6774
if (!authors) return [];
6875
return authors.map(author => ({
69-
label: author.value,
76+
label: `${author.value} (${author.count})`,
7077
value: author.value,
7178
}));
7279
}, [authors]);
7380

7481
const handleChange = useCallback(
7582
(_e: any, value: SelectItem[]) => {
76-
const newSelection = value.map(v => v.label);
83+
const newSelection = value.map(v => v.value);
7784
filter.set(newSelection);
7885
},
7986
[filter],
@@ -109,7 +116,10 @@ const evaluateParams = (
109116

110117
const SupportTypeFilter = () => {
111118
const [searchParams, setSearchParams] = useSearchParams();
112-
const pluginFacets = usePluginFacets({ facets: facetsKeys });
119+
// Exclude support type filters from facets calculation to get accurate counts
120+
const pluginFacets = usePluginFacetsWithFilters({ facets: facetsKeys }, [
121+
'metadata.annotations.extensions.backstage.io/',
122+
]);
113123

114124
const facets = pluginFacets.data;
115125

workspaces/marketplace/plugins/marketplace/src/hooks/usePluginFacet.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,12 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { useQuery } from '@tanstack/react-query';
18-
19-
import { useMarketplaceApi } from './useMarketplaceApi';
17+
import { usePluginFacets } from './usePluginFacets';
2018

2119
export const usePluginFacet = (facet: string) => {
22-
const marketplaceApi = useMarketplaceApi();
23-
return useQuery({
24-
queryKey: ['marketplaceApi', 'getPluginFacet', facet],
25-
queryFn: () =>
26-
marketplaceApi
27-
.getPluginFacets({ facets: [facet] })
28-
.then(data => data.facets[facet]),
29-
});
20+
const facetsQuery = usePluginFacets({ facets: [facet] });
21+
return {
22+
...facetsQuery,
23+
data: facetsQuery.data?.[facet],
24+
};
3025
};

workspaces/marketplace/plugins/marketplace/src/hooks/usePluginFacets.ts

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,63 @@
1515
*/
1616

1717
import { useQuery } from '@tanstack/react-query';
18+
import { useSearchParams } from 'react-router-dom';
1819

19-
import { GetEntityFacetsRequest } from '@backstage/catalog-client';
20+
import {
21+
GetEntityFacetsRequest,
22+
EntityFilterQuery,
23+
} from '@backstage/catalog-client';
2024

2125
import { useMarketplaceApi } from './useMarketplaceApi';
2226

27+
/**
28+
* Converts search param filters to catalog filter format
29+
*/
30+
const parseFiltersFromSearchParams = (
31+
searchParams: URLSearchParams,
32+
): EntityFilterQuery => {
33+
const filters = searchParams.getAll('filter');
34+
const filterObj: EntityFilterQuery = {};
35+
36+
filters.forEach(filter => {
37+
const firstEqualIndex = filter.indexOf('=');
38+
if (firstEqualIndex === -1) return;
39+
40+
const key = filter.substring(0, firstEqualIndex);
41+
const value = filter.substring(firstEqualIndex + 1);
42+
43+
if (filterObj[key]) {
44+
// If the key already exists, convert to array or append to existing array
45+
if (Array.isArray(filterObj[key])) {
46+
(filterObj[key] as string[]).push(value);
47+
} else {
48+
filterObj[key] = [filterObj[key] as string, value];
49+
}
50+
} else {
51+
filterObj[key] = value;
52+
}
53+
});
54+
return filterObj;
55+
};
56+
2357
export const usePluginFacets = (request: GetEntityFacetsRequest) => {
58+
const [searchParams] = useSearchParams();
2459
const marketplaceApi = useMarketplaceApi();
60+
61+
// Build the request with current filters for accurate facet counts
62+
const requestWithFilters = {
63+
...request,
64+
filter: {
65+
...request.filter,
66+
...parseFiltersFromSearchParams(searchParams),
67+
},
68+
};
69+
2570
return useQuery({
26-
queryKey: ['marketplaceApi', 'getPluginFacets', request],
71+
queryKey: ['marketplaceApi', 'getPluginFacets', requestWithFilters],
2772
queryFn: () =>
28-
marketplaceApi.getPluginFacets(request).then(response => response.facets),
73+
marketplaceApi
74+
.getPluginFacets(requestWithFilters)
75+
.then(response => response.facets),
2976
});
3077
};
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright The Backstage Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { useQuery } from '@tanstack/react-query';
18+
import { useSearchParams } from 'react-router-dom';
19+
20+
import {
21+
GetEntityFacetsRequest,
22+
EntityFilterQuery,
23+
} from '@backstage/catalog-client';
24+
25+
import { useMarketplaceApi } from './useMarketplaceApi';
26+
27+
/**
28+
* Converts search param filters to catalog filter format, optionally excluding certain filter prefixes
29+
*/
30+
const parseFiltersFromSearchParams = (
31+
searchParams: URLSearchParams,
32+
excludeFilterPrefixes: string[] = [],
33+
): EntityFilterQuery => {
34+
const filters = searchParams.getAll('filter');
35+
const filterObj: EntityFilterQuery = {};
36+
37+
filters.forEach(filter => {
38+
const firstEqualIndex = filter.indexOf('=');
39+
if (firstEqualIndex === -1) return;
40+
41+
const key = filter.substring(0, firstEqualIndex);
42+
const value = filter.substring(firstEqualIndex + 1);
43+
44+
// Skip filters that match any of the excluded prefixes
45+
if (excludeFilterPrefixes.some(prefix => key.startsWith(prefix))) {
46+
return;
47+
}
48+
49+
if (filterObj[key]) {
50+
// If the key already exists, convert to array or append to existing array
51+
if (Array.isArray(filterObj[key])) {
52+
(filterObj[key] as string[]).push(value);
53+
} else {
54+
filterObj[key] = [filterObj[key] as string, value];
55+
}
56+
} else {
57+
filterObj[key] = value;
58+
}
59+
});
60+
61+
return filterObj;
62+
};
63+
64+
/**
65+
* Hook for getting plugin facets with current filters applied,
66+
* but excluding specific filter types to get accurate counts for those facet types
67+
*/
68+
export const usePluginFacetsWithFilters = (
69+
request: GetEntityFacetsRequest,
70+
excludeFilterPrefixes: string[] = [],
71+
) => {
72+
const [searchParams] = useSearchParams();
73+
const marketplaceApi = useMarketplaceApi();
74+
75+
// Build the request with current filters (excluding specified prefixes) for accurate facet counts
76+
const requestWithFilters = {
77+
...request,
78+
filter: {
79+
...request.filter,
80+
...parseFiltersFromSearchParams(searchParams, excludeFilterPrefixes),
81+
},
82+
};
83+
return useQuery({
84+
queryKey: [
85+
'marketplaceApi',
86+
'getPluginFacetsWithFilters',
87+
requestWithFilters,
88+
excludeFilterPrefixes,
89+
],
90+
queryFn: () =>
91+
marketplaceApi
92+
.getPluginFacets(requestWithFilters)
93+
.then(response => response.facets),
94+
});
95+
};

0 commit comments

Comments
 (0)