Add kind picker to the CatalogPage

Signed-off-by: Tim Hansen <timbonicus@gmail.com>
This commit is contained in:
Tim Hansen
2021-08-19 15:34:56 -06:00
parent 681b9e61ce
commit fee7a6bd49
9 changed files with 139 additions and 28 deletions
@@ -34,15 +34,14 @@ export const CustomCatalogPage = ({
}: CatalogPageProps) => {
return (
<PageWithHeader title={`${orgName} Catalog`} themeId="home">
<Content>
<ContentHeader title="Components">
<CreateButton title="Create Component" to={link} />
<SupportButton>All your software catalog entities</SupportButton>
</ContentHeader>
<EntityListProvider>
<EntityListProvider>
<Content>
<ContentHeader titleComponent={<CatalogKindHeader />}>
<CreateButton title="Create Component" to={link} />
<SupportButton>All your software catalog entities</SupportButton>
</ContentHeader>
<FilteredEntityLayout>
<FilterContainer>
<EntityKindPicker initialFilter="component" hidden />
<EntityTypePicker />
<UserListPicker initialFilter={initiallySelectedFilter} />
<EntityTagPicker />
@@ -51,8 +50,8 @@ export const CustomCatalogPage = ({
<CatalogTable columns={columns} actions={actions} />
</EntityListContainer>
</FilteredEntityLayout>
</EntityListProvider>
</Content>
</Content>
</EntityListProvider>
</PageWithHeader>
);
};
@@ -32,7 +32,7 @@ describe('<ContentHeader/>', () => {
it('should render with titleComponent', async () => {
const title = 'Custom title';
const titleComponent = () => <h1>{title}</h1>;
const titleComponent = <h1>{title}</h1>;
const rendered = await renderInTestApp(
<ContentHeader titleComponent={titleComponent} />,
);
@@ -77,7 +77,7 @@ const ContentHeaderTitle = ({
type ContentHeaderProps = {
title?: ContentHeaderTitleProps['title'];
titleComponent?: ComponentType;
titleComponent?: JSX.Element;
description?: string;
textAlign?: 'left' | 'right' | 'center';
};
@@ -92,7 +92,7 @@ export const ContentHeader = ({
const classes = useStyles({ textAlign })();
const renderedTitle = TitleComponent ? (
<TitleComponent />
TitleComponent
) : (
<ContentHeaderTitle title={title} className={classes.title} />
);
@@ -15,7 +15,7 @@
*/
import React from 'react';
import { fireEvent, render, waitFor } from '@testing-library/react';
import { fireEvent, waitFor } from '@testing-library/react';
import { capitalize } from 'lodash';
import { CatalogApi } from '@backstage/catalog-client';
import { Entity } from '@backstage/catalog-model';
@@ -26,6 +26,7 @@ import { EntityKindFilter, EntityTypeFilter } from '../../filters';
import { AlertApi, alertApiRef } from '@backstage/core-plugin-api';
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { renderWithEffects } from '@backstage/test-utils';
const entities: Entity[] = [
{
@@ -79,7 +80,7 @@ const apis = ApiRegistry.from([
describe('<EntityTypePicker/>', () => {
it('renders available entity types', async () => {
const rendered = render(
const rendered = await renderWithEffects(
<ApiProvider apis={apis}>
<MockEntityListContextProvider
value={{ filters: { kind: new EntityKindFilter('component') } }}
@@ -104,7 +105,7 @@ describe('<EntityTypePicker/>', () => {
it('sets the selected type filter', async () => {
const updateFilters = jest.fn();
const rendered = render(
const rendered = await renderWithEffects(
<ApiProvider apis={apis}>
<MockEntityListContextProvider
value={{
@@ -36,7 +36,7 @@ export const EntityTypePicker = () => {
}
}, [error, alertApi]);
if (!availableTypes || error) return null;
if (availableTypes.length === 0 || error) return null;
const items = [
{ value: 'all', label: 'All' },
+1
View File
@@ -22,6 +22,7 @@ export {
} from './useEntityListProvider';
export type { DefaultEntityFilters } from './useEntityListProvider';
export { useEntityTypeFilter } from './useEntityTypeFilter';
export { useEntityKinds } from './useEntityKinds';
export { useOwnUser } from './useOwnUser';
export { useRelatedEntities } from './useRelatedEntities';
export { useStarredEntities } from './useStarredEntities';
@@ -0,0 +1,36 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useEffect, useState } from 'react';
import { useApi } from '@backstage/core-plugin-api';
import { catalogApiRef } from '../api';
export function useEntityKinds() {
const [kinds, setKinds] = useState(['component']);
const catalogApi = useApi(catalogApiRef);
useEffect(() => {
async function loadKinds() {
const entities = await catalogApi
.getEntities({ fields: ['kind'] })
.then(response => response.items);
setKinds([...new Set(entities.map(e => e.kind))].sort());
}
loadKinds();
}, [catalogApi]);
return kinds;
}
@@ -0,0 +1,74 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useEffect, useState } from 'react';
import {
capitalize,
createStyles,
InputBase,
makeStyles,
MenuItem,
Select,
Theme,
} from '@material-ui/core';
import {
EntityKindFilter,
useEntityKinds,
useEntityListProvider,
} from '@backstage/plugin-catalog-react';
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
...theme.typography.h4,
},
}),
);
type CatalogKindHeaderProps = {
initialFilter?: string;
};
export const CatalogKindHeader = ({
initialFilter = 'Component',
}: CatalogKindHeaderProps) => {
const classes = useStyles();
const allKinds = useEntityKinds();
const { updateFilters, queryParameters } = useEntityListProvider();
const [selectedKind, setSelectedKind] = useState(
[queryParameters.kind].flat()[0] ?? initialFilter,
);
useEffect(() => {
updateFilters({
kind: selectedKind ? new EntityKindFilter(selectedKind) : undefined,
});
}, [selectedKind, updateFilters]);
return (
<Select
input={<InputBase value={selectedKind} />}
value={selectedKind}
onChange={e => setSelectedKind(e.target.value as string)}
classes={classes}
>
{allKinds.map(kind => (
<MenuItem value={kind}>{capitalize(kind)}s</MenuItem>
))}
</Select>
);
};
@@ -43,6 +43,7 @@ import {
EntityListContainer,
FilterContainer,
} from '../FilteredEntityLayout';
import { CatalogKindHeader } from './CatalogKindHeader';
export type CatalogPageProps = {
initiallySelectedFilter?: UserListFilterKind;
@@ -61,18 +62,17 @@ export const CatalogPage = ({
return (
<PageWithHeader title={`${orgName} Catalog`} themeId="home">
<Content>
<ContentHeader title="Components">
<CreateButton
title="Create Component"
to={createComponentLink && createComponentLink()}
/>
<SupportButton>All your software catalog entities</SupportButton>
</ContentHeader>
<EntityListProvider>
<EntityListProvider>
<Content>
<ContentHeader titleComponent={<CatalogKindHeader />}>
<CreateButton
title="Create Component"
to={createComponentLink && createComponentLink()}
/>
<SupportButton>All your software catalog entities</SupportButton>
</ContentHeader>
<FilteredEntityLayout>
<FilterContainer>
<EntityKindPicker initialFilter="component" hidden />
<EntityTypePicker />
<UserListPicker initialFilter={initiallySelectedFilter} />
<EntityOwnerPicker />
@@ -83,8 +83,8 @@ export const CatalogPage = ({
<CatalogTable columns={columns} actions={actions} />
</EntityListContainer>
</FilteredEntityLayout>
</EntityListProvider>
</Content>
</Content>
</EntityListProvider>
</PageWithHeader>
);
};