From c008f74f8ffff7f898fead101b7d24cf70414b8a Mon Sep 17 00:00:00 2001 From: Crevil Date: Thu, 4 Aug 2022 17:32:41 +0200 Subject: [PATCH] Implement EntityKindPicker This change implements the EntityKindPicker allowing for selecting multiple kinds. Signed-off-by: Crevil --- .../EntityKindPicker/EntityKindPicker.tsx | 122 ++++++++++++++---- plugins/catalog-react/src/filters.ts | 15 ++- .../CatalogPage/DefaultCatalogPage.tsx | 2 + .../components/CatalogTable/CatalogTable.tsx | 9 +- 4 files changed, 117 insertions(+), 31 deletions(-) diff --git a/plugins/catalog-react/src/components/EntityKindPicker/EntityKindPicker.tsx b/plugins/catalog-react/src/components/EntityKindPicker/EntityKindPicker.tsx index 4cfd141509..774d07c544 100644 --- a/plugins/catalog-react/src/components/EntityKindPicker/EntityKindPicker.tsx +++ b/plugins/catalog-react/src/components/EntityKindPicker/EntityKindPicker.tsx @@ -14,41 +14,115 @@ * limitations under the License. */ -import React, { useEffect, useState } from 'react'; -import { Alert } from '@material-ui/lab'; -import { useEntityList } from '../../hooks'; +import { useApi } from '@backstage/core-plugin-api'; +import { + Box, + Checkbox, + FormControlLabel, + makeStyles, + TextField, + Typography, +} from '@material-ui/core'; +import CheckBoxIcon from '@material-ui/icons/CheckBox'; +import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import { Autocomplete } from '@material-ui/lab'; +import React, { useEffect, useMemo, useState } from 'react'; +import useAsync from 'react-use/lib/useAsync'; +import { catalogApiRef } from '../../api'; import { EntityKindFilter } from '../../filters'; +import { useEntityList } from '../../hooks'; -/** - * Props for {@link EntityKindPicker}. - * - * @public - */ -export interface EntityKindPickerProps { - initialFilter?: string; - hidden: boolean; -} +const useStyles = makeStyles( + { + input: {}, + }, + { + name: 'CatalogReactEntityKindPicker', + }, +); + +const icon = ; +const checkedIcon = ; /** @public */ -export const EntityKindPicker = (props: EntityKindPickerProps) => { - const { initialFilter, hidden } = props; - +export const EntityKindPicker = () => { + const classes = useStyles(); const { updateFilters, - queryParameters: { kind: kindParameter }, + filters, + queryParameters: { kind: kindsParameter }, } = useEntityList(); - const [selectedKind] = useState([kindParameter].flat()[0] ?? initialFilter); + + const catalogApi = useApi(catalogApiRef); + const { value: availableKinds } = useAsync(async () => { + const facet = 'kind'; + const { facets } = await catalogApi.getEntityFacets({ + facets: [facet], + }); + + return facets[facet].map(({ value }) => value); + }, [filters.kind]); + + const queryParamKinds = useMemo( + () => [kindsParameter].flat().filter(Boolean) as string[], + [kindsParameter], + ); + + const [selectedKinds, setSelectedKinds] = useState( + queryParamKinds.length ? queryParamKinds : filters.kind?.getKinds() ?? [], + ); + + // Set selected kinds on query parameter updates; this happens at initial page load and from + // external updates to the page location. + useEffect(() => { + if (queryParamKinds.length) { + setSelectedKinds(queryParamKinds); + } + }, [queryParamKinds]); useEffect(() => { updateFilters({ - kind: selectedKind ? new EntityKindFilter(selectedKind) : undefined, + kind: selectedKinds.length + ? new EntityKindFilter(selectedKinds) + : undefined, }); - }, [selectedKind, updateFilters]); + }, [selectedKinds, updateFilters]); - if (hidden) return null; + if (!availableKinds?.length) return null; - // TODO(timbonicus): This should load available kinds from the catalog-backend, similar to - // EntityTypePicker. - - return Kind filter not yet available; + return ( + + + Kinds + setSelectedKinds(value)} + renderOption={(option, { selected }) => ( + + } + label={option} + /> + )} + size="small" + popupIcon={} + renderInput={params => ( + + )} + /> + + + ); }; diff --git a/plugins/catalog-react/src/filters.ts b/plugins/catalog-react/src/filters.ts index 056b3b92c1..efa10a8e19 100644 --- a/plugins/catalog-react/src/filters.ts +++ b/plugins/catalog-react/src/filters.ts @@ -28,14 +28,19 @@ import { getEntityRelations } from './utils'; * @public */ export class EntityKindFilter implements EntityFilter { - constructor(readonly value: string) {} + constructor(readonly value: string | string[]) {} - getCatalogFilters(): Record { - return { kind: this.value }; + // Simplify `string | string[]` for consumers, always returns an array + getKinds(): string[] { + return Array.isArray(this.value) ? this.value : [this.value]; } - toQueryValue(): string { - return this.value; + getCatalogFilters(): Record { + return { kind: this.getKinds() }; + } + + toQueryValue(): string[] { + return this.getKinds(); } } diff --git a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx index 39c19d717a..3f34a950be 100644 --- a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx @@ -34,6 +34,7 @@ import { EntityTypePicker, UserListFilterKind, UserListPicker, + EntityKindPicker, } from '@backstage/plugin-catalog-react'; import React from 'react'; import { createComponentRouteRef } from '../../routes'; @@ -83,6 +84,7 @@ export function DefaultCatalogPage(props: DefaultCatalogPageProps) { + diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 6bc18d7a90..5a7f8aabbb 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -70,7 +70,12 @@ export const CatalogTable = (props: CatalogTableProps) => { const defaultColumns: TableColumn[] = useMemo(() => { return [ columnFactories.createTitleColumn({ hidden: true }), - columnFactories.createNameColumn({ defaultKind: filters.kind?.value }), + columnFactories.createNameColumn({ + defaultKind: + filters.kind?.getKinds()?.length === 1 + ? filters.kind?.getKinds()[0] + : undefined, + }), ...createEntitySpecificColumns(), columnFactories.createMetadataDescriptionColumn(), columnFactories.createTagsColumn(), @@ -100,7 +105,7 @@ export const CatalogTable = (props: CatalogTableProps) => { ]; } } - }, [filters.kind?.value]); + }, [filters.kind]); const showTypeColumn = filters.type === undefined; // TODO(timbonicus): remove the title from the CatalogTable once using EntitySearchBar