diff --git a/.changeset/lucky-bulldogs-own.md b/.changeset/lucky-bulldogs-own.md index d7b657fb83..4fb27d90b4 100644 --- a/.changeset/lucky-bulldogs-own.md +++ b/.changeset/lucky-bulldogs-own.md @@ -8,6 +8,7 @@ '@backstage/plugin-catalog-react': patch '@backstage/plugin-explore': patch '@backstage/plugin-fossa': patch +'@backstage/plugin-scaffolder': patch '@backstage/plugin-todo-backend': patch --- diff --git a/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx b/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx index 9ed27bbef8..4623cf5d9e 100644 --- a/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx +++ b/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx @@ -26,6 +26,7 @@ import { EntityKindFilter, EntityTypeFilter } from '../../filters'; import { alertApiRef } from '@backstage/core-plugin-api'; import { ApiProvider } from '@backstage/core-app-api'; import { renderWithEffects, TestApiRegistry } from '@backstage/test-utils'; +import { GetEntityFacetsResponse } from '@backstage/catalog-client'; const entities: Entity[] = [ { @@ -64,7 +65,14 @@ const apis = TestApiRegistry.from( [ catalogApiRef, { - getEntities: jest.fn().mockResolvedValue({ items: entities }), + getEntityFacets: jest.fn().mockResolvedValue({ + facets: { + 'spec.type': entities.map(e => ({ + value: (e.spec as any).type, + count: 1, + })), + }, + } as GetEntityFacetsResponse), }, ], [ diff --git a/plugins/catalog-react/src/hooks/useEntityTypeFilter.tsx b/plugins/catalog-react/src/hooks/useEntityTypeFilter.tsx index c4531d9117..00f3ecd675 100644 --- a/plugins/catalog-react/src/hooks/useEntityTypeFilter.tsx +++ b/plugins/catalog-react/src/hooks/useEntityTypeFilter.tsx @@ -17,6 +17,7 @@ import { useEffect, useMemo, useRef, useState } from 'react'; import useAsync from 'react-use/lib/useAsync'; import isEqual from 'lodash/isEqual'; +import sortBy from 'lodash/sortBy'; import { useApi } from '@backstage/core-plugin-api'; import { catalogApiRef } from '../api'; import { useEntityListProvider } from './useEntityListProvider'; @@ -67,51 +68,34 @@ export function useEntityTypeFilter(): EntityTypeReturn { const { error, loading, - value: entities, + value: facets, } = useAsync(async () => { if (kind) { const items = await catalogApi - .getEntities({ + .getEntityFacets({ filter: { kind }, - fields: ['spec.type'], + facets: ['spec.type'], }) - .then(response => response.items); + .then(response => response.facets['spec.type'] || []); return items; } return []; }, [kind, catalogApi]); - const entitiesRef = useRef(entities); + const facetsRef = useRef(facets); useEffect(() => { - const oldEntities = entitiesRef.current; - entitiesRef.current = entities; - // Delay processing hook until kind and entity load updates have settled to generate list of types; - // This prevents reseting the type filter due to saved type value from query params not matching the + const oldFacets = facetsRef.current; + facetsRef.current = facets; + // Delay processing hook until kind and facets load updates have settled to generate list of types; + // This prevents resetting the type filter due to saved type value from query params not matching the // empty set of type values while values are still being loaded; also only run this hook on changes - // to entities - if (loading || !kind || oldEntities === entities) { + // to facets + if (loading || !kind || oldFacets === facets || !facets) { return; } - // Resolve the unique set of types from returned entities; could be optimized by a new endpoint - // in the catalog-backend that does this, rather than loading entities with redundant types. - if (!entities) return; - - // Sort by entity count descending, so the most common types appear on top - const countByType = entities.reduce((acc, entity) => { - if (typeof entity.spec?.type !== 'string') return acc; - - const entityType = entity.spec.type.toLocaleLowerCase('en-US'); - if (!acc[entityType]) { - acc[entityType] = 0; - } - acc[entityType] += 1; - return acc; - }, {} as Record); - - const newTypes = Object.entries(countByType) - .sort(([, count1], [, count2]) => count2 - count1) - .map(([type]) => type); + // Sort by facet count descending, so the most common types appear on top + const newTypes = sortBy(facets, f => -f.count).map(f => f.value); setAvailableTypes(newTypes); // Update type filter to only valid values when the list of available types has changed @@ -121,7 +105,7 @@ export function useEntityTypeFilter(): EntityTypeReturn { if (!isEqual(selectedTypes, stillValidTypes)) { setSelectedTypes(stillValidTypes); } - }, [loading, kind, selectedTypes, setSelectedTypes, entities]); + }, [loading, kind, selectedTypes, setSelectedTypes, facets]); useEffect(() => { updateFilters({ diff --git a/plugins/catalog/src/components/CatalogKindHeader/CatalogKindHeader.test.tsx b/plugins/catalog/src/components/CatalogKindHeader/CatalogKindHeader.test.tsx index b320d6d220..795d57df47 100644 --- a/plugins/catalog/src/components/CatalogKindHeader/CatalogKindHeader.test.tsx +++ b/plugins/catalog/src/components/CatalogKindHeader/CatalogKindHeader.test.tsx @@ -16,6 +16,7 @@ import React from 'react'; import { fireEvent } from '@testing-library/react'; +import { GetEntityFacetsResponse } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; import { catalogApiRef, @@ -60,7 +61,15 @@ const entities: Entity[] = [ const apis = TestApiRegistry.from([ catalogApiRef, { - getEntities: jest.fn().mockResolvedValue({ items: entities }), + getEntityFacets: jest.fn().mockResolvedValue({ + facets: { + kind: [ + { value: 'Component', count: 2 }, + { value: 'Template', count: 1 }, + { value: 'System', count: 1 }, + ], + }, + } as GetEntityFacetsResponse), }, ]); diff --git a/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx b/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx index c70f251eac..aa6630d4aa 100644 --- a/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx +++ b/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx @@ -17,7 +17,6 @@ import React from 'react'; import { fireEvent } from '@testing-library/react'; import { capitalize } from 'lodash'; -import { CatalogApi } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; import { TemplateTypePicker } from './TemplateTypePicker'; import { @@ -28,6 +27,7 @@ import { import { AlertApi, alertApiRef } from '@backstage/core-plugin-api'; import { ApiProvider } from '@backstage/core-app-api'; import { renderWithEffects, TestApiRegistry } from '@backstage/test-utils'; +import { GetEntityFacetsResponse } from '@backstage/catalog-client'; const entities: Entity[] = [ { @@ -66,10 +66,15 @@ const apis = TestApiRegistry.from( [ catalogApiRef, { - getEntities: jest - .fn() - .mockImplementation(() => Promise.resolve({ items: entities })), - } as unknown as CatalogApi, + getEntityFacets: jest.fn().mockResolvedValue({ + facets: { + 'spec.type': entities.map(e => ({ + value: (e.spec as any).type, + count: 1, + })), + }, + } as GetEntityFacetsResponse), + }, ], [ alertApiRef,