diff --git a/.changeset/sharp-numbers-doubt.md b/.changeset/sharp-numbers-doubt.md new file mode 100644 index 0000000000..81e1313780 --- /dev/null +++ b/.changeset/sharp-numbers-doubt.md @@ -0,0 +1,26 @@ +--- +'@backstage/plugin-catalog-react': minor +--- + +Added EntityOrderFilter to sort entities by different fields/columns. This new filter allows users to specify the order in which entities are displayed in the catalog. + +Example usage: + +```ts +import { + EntityOrderFilter, + useEntityList, +} from '@backstage/plugin-catalog-react'; +// ... +const { updateFilters } = useEntityList(); + +// ... +updateFilters({ + order: new EntityOrderFilter([ + { + field: 'metadata.name', + order: 'desc', + }, + ]), +}); +``` diff --git a/plugins/catalog-react/report.api.md b/plugins/catalog-react/report.api.md index f7673e1bd8..bfebb2b13a 100644 --- a/plugins/catalog-react/report.api.md +++ b/plugins/catalog-react/report.api.md @@ -12,6 +12,7 @@ import { ComponentProps } from 'react'; import { CompoundEntityRef } from '@backstage/catalog-model'; import { Context } from 'react'; import { Entity } from '@backstage/catalog-model'; +import { EntityOrderQuery } from '@backstage/catalog-client'; import IconButton from '@material-ui/core/IconButton'; import { IconComponent } from '@backstage/core-plugin-api'; import { InfoCardVariants } from '@backstage/core-components'; @@ -197,6 +198,7 @@ export type DefaultEntityFilters = { orphan?: EntityOrphanFilter; error?: EntityErrorFilter; namespace?: EntityNamespaceFilter; + order?: EntityOrderFilter; }; // @public @@ -406,6 +408,17 @@ export interface EntityNamespacePickerProps { initiallySelectedNamespaces?: string[]; } +// @public +export class EntityOrderFilter implements EntityFilter { + constructor(values: [string, 'asc' | 'desc'][]); + // (undocumented) + getOrderFilters(): EntityOrderQuery; + // (undocumented) + toQueryValue(): string[]; + // (undocumented) + readonly values: [string, 'asc' | 'desc'][]; +} + // @public export class EntityOrphanFilter implements EntityFilter { constructor(value: boolean); diff --git a/plugins/catalog-react/src/components/UserListPicker/useAllEntitiesCount.ts b/plugins/catalog-react/src/components/UserListPicker/useAllEntitiesCount.ts index fa564382d8..c4bea1fa05 100644 --- a/plugins/catalog-react/src/components/UserListPicker/useAllEntitiesCount.ts +++ b/plugins/catalog-react/src/components/UserListPicker/useAllEntitiesCount.ts @@ -30,7 +30,7 @@ export function useAllEntitiesCount() { const request = useMemo(() => { const { user, ...allFilters } = filters; const compacted = compact(Object.values(allFilters)); - const catalogFilters = reduceCatalogFilters(compacted); + const { orderFields, ...catalogFilters } = reduceCatalogFilters(compacted); const newRequest: QueryEntitiesInitialRequest = { ...catalogFilters, limit: 0, diff --git a/plugins/catalog-react/src/components/UserListPicker/useOwnedEntitiesCount.ts b/plugins/catalog-react/src/components/UserListPicker/useOwnedEntitiesCount.ts index dbdc040165..489ae24d4e 100644 --- a/plugins/catalog-react/src/components/UserListPicker/useOwnedEntitiesCount.ts +++ b/plugins/catalog-react/src/components/UserListPicker/useOwnedEntitiesCount.ts @@ -38,7 +38,7 @@ export function useOwnedEntitiesCount() { ); const { user, owners, ...allFilters } = filters; - const catalogFilters = reduceCatalogFilters( + const { orderFields, ...catalogFilters } = reduceCatalogFilters( compact(Object.values(allFilters)), ); diff --git a/plugins/catalog-react/src/components/UserListPicker/useStarredEntitiesCount.ts b/plugins/catalog-react/src/components/UserListPicker/useStarredEntitiesCount.ts index 70dde024f8..f1fcea4572 100644 --- a/plugins/catalog-react/src/components/UserListPicker/useStarredEntitiesCount.ts +++ b/plugins/catalog-react/src/components/UserListPicker/useStarredEntitiesCount.ts @@ -34,7 +34,7 @@ export function useStarredEntitiesCount() { const request = useMemo(() => { const { user, ...allFilters } = filters; const compacted = compact(Object.values(allFilters)); - const catalogFilters = reduceCatalogFilters(compacted); + const { orderFields, ...catalogFilters } = reduceCatalogFilters(compacted); const facet = 'metadata.name'; diff --git a/plugins/catalog-react/src/filters.ts b/plugins/catalog-react/src/filters.ts index 3ac7bda73a..8a6e664585 100644 --- a/plugins/catalog-react/src/filters.ts +++ b/plugins/catalog-react/src/filters.ts @@ -23,6 +23,7 @@ import { import { AlphaEntity } from '@backstage/catalog-model/alpha'; import { EntityFilter, UserListFilterKind } from './types'; import { getEntityRelations } from './utils/getEntityRelations'; +import { EntityOrderQuery } from '@backstage/catalog-client'; /** * Filter entities based on Kind. @@ -331,3 +332,19 @@ export class EntityErrorFilter implements EntityFilter { return error !== undefined && this.value === error; } } + +/** + * Sort entities by a given field/column. + * @public + */ +export class EntityOrderFilter implements EntityFilter { + constructor(readonly values: [string, 'asc' | 'desc'][]) {} + + getOrderFilters(): EntityOrderQuery { + return this.values.map(([field, order]) => ({ field, order })); + } + + toQueryValue(): string[] { + return this.values.flat(); + } +} diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx index 4f4194da48..1d3afd8d9b 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx @@ -35,6 +35,7 @@ import { EntityKindFilter, EntityLifecycleFilter, EntityNamespaceFilter, + EntityOrderFilter, EntityOrphanFilter, EntityOwnerFilter, EntityTagFilter, @@ -64,6 +65,7 @@ export type DefaultEntityFilters = { orphan?: EntityOrphanFilter; error?: EntityErrorFilter; namespace?: EntityNamespaceFilter; + order?: EntityOrderFilter; }; /** @public */ @@ -287,7 +289,6 @@ export const EntityListProvider = ( ...backendFilter, limit, offset, - orderFields: [{ field: 'metadata.name', order: 'asc' }], }); setOutputState({ appliedFilters: requestedFilters, diff --git a/plugins/catalog-react/src/utils/filters.ts b/plugins/catalog-react/src/utils/filters.ts index 82d7358b91..1ff7334ffb 100644 --- a/plugins/catalog-react/src/utils/filters.ts +++ b/plugins/catalog-react/src/utils/filters.ts @@ -19,6 +19,7 @@ import { EntityFilter } from '../types'; import { EntityLifecycleFilter, EntityNamespaceFilter, + EntityOrderFilter, EntityOrphanFilter, EntityOwnerFilter, EntityTagFilter, @@ -26,18 +27,24 @@ import { EntityUserFilter, UserListFilter, } from '../filters'; +import { EntityOrderQuery } from '@backstage/catalog-client'; export interface CatalogFilters { filter: Record; fullTextFilter?: { term: string; }; + orderFields?: EntityOrderQuery; } function isEntityTextFilter(t: EntityFilter): t is EntityTextFilter { return !!(t as EntityTextFilter).getFullTextFilters; } +function isEntityOrderFilter(t: EntityFilter): t is EntityOrderFilter { + return !!(t as EntityOrderFilter).getOrderFilters; +} + export function reduceCatalogFilters(filters: EntityFilter[]): CatalogFilters { const condensedFilters = filters.reduce( (compoundFilter, filter) => { @@ -50,7 +57,14 @@ export function reduceCatalogFilters(filters: EntityFilter[]): CatalogFilters { ); const fullTextFilter = filters.find(isEntityTextFilter)?.getFullTextFilters(); - return { filter: condensedFilters, fullTextFilter }; + + const orderFields = filters.find(isEntityOrderFilter)?.getOrderFilters() || [ + { + field: 'metadata.name', + order: 'asc', + }, + ]; + return { filter: condensedFilters, fullTextFilter, orderFields }; } /**