Merge pull request #6895 from backstage/timbonicus/kind-picker
Add kind picker to the CatalogPage
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
---
|
||||
|
||||
Added a `useEntityKinds` hook to load a unique list of entity kinds from the catalog.
|
||||
Fixed a bug in `EntityTypePicker` where the component did not hide when no types were available in returned entities.
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
'@backstage/core-components': minor
|
||||
---
|
||||
|
||||
Changed the `titleComponent` prop on `ContentHeader` to accept `ReactNode` instead of a React `ComponentType`. Usages of this prop should be converted from passing a component to passing in the rendered element:
|
||||
|
||||
```diff
|
||||
-<ContentHeader titleComponent={MyComponent}>
|
||||
+<ContentHeader titleComponent={<MyComponent />}>
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog': patch
|
||||
---
|
||||
|
||||
Added the ability to switch entity kind on the catalog index page. This is a non-breaking change, but if you created a custom `CatalogPage` and wish to use this feature, make the modifications shown on [#6895](https://github.com/backstage/backstage/pull/6895).
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -15,7 +15,6 @@ import { Column } from '@material-table/core';
|
||||
import { CommonProps } from '@material-ui/core/OverridableComponent';
|
||||
import { ComponentClass } from 'react';
|
||||
import { ComponentProps } from 'react';
|
||||
import { ComponentType } from 'react';
|
||||
import { Context } from 'react';
|
||||
import { default as CSS_2 } from 'csstype';
|
||||
import { CSSProperties } from 'react';
|
||||
|
||||
@@ -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} />,
|
||||
);
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
* TODO favoriteable capability
|
||||
*/
|
||||
|
||||
import React, { ComponentType, PropsWithChildren } from 'react';
|
||||
import React, { PropsWithChildren, ReactNode } from 'react';
|
||||
import { Typography, makeStyles } from '@material-ui/core';
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
||||
@@ -77,7 +77,7 @@ const ContentHeaderTitle = ({
|
||||
|
||||
type ContentHeaderProps = {
|
||||
title?: ContentHeaderTitleProps['title'];
|
||||
titleComponent?: ComponentType;
|
||||
titleComponent?: ReactNode;
|
||||
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} />
|
||||
);
|
||||
|
||||
@@ -693,7 +693,7 @@ export const MockEntityListContextProvider: ({
|
||||
children,
|
||||
value,
|
||||
}: React_2.PropsWithChildren<{
|
||||
value: Partial<EntityListContextProps>;
|
||||
value?: Partial<EntityListContextProps<DefaultEntityFilters>> | undefined;
|
||||
}>) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "reduceCatalogFilters" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
@@ -750,6 +750,15 @@ export const useEntityCompoundName: () => {
|
||||
// @public (undocumented)
|
||||
export const useEntityFromUrl: () => EntityLoadingStatus;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "useEntityKinds" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export function useEntityKinds(): {
|
||||
error: Error | undefined;
|
||||
loading: boolean;
|
||||
kinds: string[] | undefined;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "useEntityListProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -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[] = [
|
||||
{
|
||||
@@ -60,26 +61,15 @@ const entities: Entity[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const apis = ApiRegistry.from([
|
||||
[
|
||||
catalogApiRef,
|
||||
{
|
||||
getEntities: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve({ items: entities })),
|
||||
} as unknown as CatalogApi,
|
||||
],
|
||||
[
|
||||
alertApiRef,
|
||||
{
|
||||
post: jest.fn(),
|
||||
} as unknown as AlertApi,
|
||||
],
|
||||
]);
|
||||
const apis = ApiRegistry.with(catalogApiRef, {
|
||||
getEntities: jest.fn().mockResolvedValue({ items: entities }),
|
||||
} as unknown as CatalogApi).with(alertApiRef, {
|
||||
post: jest.fn(),
|
||||
} as unknown as AlertApi);
|
||||
|
||||
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 +94,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' },
|
||||
|
||||
@@ -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,91 @@
|
||||
/*
|
||||
* 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, { PropsWithChildren } from 'react';
|
||||
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { catalogApiRef } from '../api';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { useEntityKinds } from './useEntityKinds';
|
||||
|
||||
const entities: Entity[] = [
|
||||
{
|
||||
apiVersion: '1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'component-1',
|
||||
},
|
||||
},
|
||||
{
|
||||
apiVersion: '1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'component-2',
|
||||
},
|
||||
},
|
||||
{
|
||||
apiVersion: '1',
|
||||
kind: 'Template',
|
||||
metadata: {
|
||||
name: 'template',
|
||||
},
|
||||
},
|
||||
{
|
||||
apiVersion: '1',
|
||||
kind: 'System',
|
||||
metadata: {
|
||||
name: 'system',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const mockCatalogApi: Partial<CatalogApi> = {
|
||||
getEntities: jest.fn().mockResolvedValue({ items: entities }),
|
||||
};
|
||||
|
||||
const wrapper = ({ children }: PropsWithChildren<{}>) => {
|
||||
return (
|
||||
<ApiProvider apis={ApiRegistry.with(catalogApiRef, mockCatalogApi)}>
|
||||
{children}
|
||||
</ApiProvider>
|
||||
);
|
||||
};
|
||||
|
||||
describe('useEntityKinds', () => {
|
||||
it('does not return duplicate kinds', async () => {
|
||||
const { result, waitForValueToChange } = renderHook(
|
||||
() => useEntityKinds(),
|
||||
{
|
||||
wrapper,
|
||||
},
|
||||
);
|
||||
await waitForValueToChange(() => result.current);
|
||||
expect(result.current.kinds).toBeDefined();
|
||||
expect(result.current.kinds!.length).toBe(3);
|
||||
});
|
||||
|
||||
it('sorts entity kinds', async () => {
|
||||
const { result, waitForValueToChange } = renderHook(
|
||||
() => useEntityKinds(),
|
||||
{
|
||||
wrapper,
|
||||
},
|
||||
);
|
||||
await waitForValueToChange(() => result.current);
|
||||
expect(result.current.kinds).toEqual(['Component', 'System', 'Template']);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 { useAsync } from 'react-use';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { catalogApiRef } from '../api';
|
||||
|
||||
// Retrieve a list of unique entity kinds present in the catalog
|
||||
export function useEntityKinds() {
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
|
||||
const {
|
||||
error,
|
||||
loading,
|
||||
value: kinds,
|
||||
} = useAsync(async () => {
|
||||
const entities = await catalogApi
|
||||
.getEntities({ fields: ['kind'] })
|
||||
.then(response => response.items);
|
||||
|
||||
return [...new Set(entities.map(e => e.kind))].sort();
|
||||
});
|
||||
return { error, loading, kinds };
|
||||
}
|
||||
@@ -25,12 +25,12 @@ export const MockEntityListContextProvider = ({
|
||||
children,
|
||||
value,
|
||||
}: PropsWithChildren<{
|
||||
value: Partial<EntityListContextProps>;
|
||||
value?: Partial<EntityListContextProps>;
|
||||
}>) => {
|
||||
// Provides a default implementation that stores filter state, for testing components that
|
||||
// reflect filter state.
|
||||
const [filters, setFilters] = useState<DefaultEntityFilters>(
|
||||
value.filters ?? {},
|
||||
value?.filters ?? {},
|
||||
);
|
||||
const updateFilters = useCallback(
|
||||
(
|
||||
@@ -60,7 +60,7 @@ export const MockEntityListContextProvider = ({
|
||||
|
||||
// Extract value.filters to avoid overwriting it; some tests exercise filter updates. The value
|
||||
// provided is used as the initial seed in useState above.
|
||||
const { filters: _, ...otherContextFields } = value;
|
||||
const { filters: _, ...otherContextFields } = value ?? {};
|
||||
|
||||
return (
|
||||
<EntityListContext.Provider
|
||||
|
||||
@@ -114,6 +114,14 @@ export const CatalogIndexPage: ({
|
||||
initiallySelectedFilter,
|
||||
}: CatalogPageProps) => JSX.Element;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "CatalogKindHeaderProps" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "CatalogKindHeader" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const CatalogKindHeader: ({
|
||||
initialFilter,
|
||||
}: CatalogKindHeaderProps) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "catalogPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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 from 'react';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import {
|
||||
CatalogApi,
|
||||
catalogApiRef,
|
||||
EntityKindFilter,
|
||||
MockEntityListContextProvider,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
|
||||
import { renderWithEffects } from '@backstage/test-utils';
|
||||
import { CatalogKindHeader } from './CatalogKindHeader';
|
||||
|
||||
const entities: Entity[] = [
|
||||
{
|
||||
apiVersion: '1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'component-1',
|
||||
},
|
||||
},
|
||||
{
|
||||
apiVersion: '1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'component-2',
|
||||
},
|
||||
},
|
||||
{
|
||||
apiVersion: '1',
|
||||
kind: 'Template',
|
||||
metadata: {
|
||||
name: 'template',
|
||||
},
|
||||
},
|
||||
{
|
||||
apiVersion: '1',
|
||||
kind: 'System',
|
||||
metadata: {
|
||||
name: 'system',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const apis = ApiRegistry.with(catalogApiRef, {
|
||||
getEntities: jest.fn().mockResolvedValue({ items: entities }),
|
||||
} as Partial<CatalogApi>);
|
||||
|
||||
describe('<CatalogKindHeader />', () => {
|
||||
it('renders available kinds', async () => {
|
||||
const rendered = await renderWithEffects(
|
||||
<ApiProvider apis={apis}>
|
||||
<MockEntityListContextProvider>
|
||||
<CatalogKindHeader />
|
||||
</MockEntityListContextProvider>
|
||||
</ApiProvider>,
|
||||
);
|
||||
|
||||
const input = rendered.getByText('Components');
|
||||
fireEvent.mouseDown(input);
|
||||
|
||||
entities.map(entity => {
|
||||
expect(
|
||||
rendered.getByRole('option', { name: `${entity.kind}s` }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders unknown kinds provided in query parameters', async () => {
|
||||
const rendered = await renderWithEffects(
|
||||
<ApiProvider apis={apis}>
|
||||
<MockEntityListContextProvider
|
||||
value={{ queryParameters: { kind: 'frob' } }}
|
||||
>
|
||||
<CatalogKindHeader />
|
||||
</MockEntityListContextProvider>
|
||||
</ApiProvider>,
|
||||
);
|
||||
|
||||
expect(rendered.getByText('Frobs')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('updates the kind filter', async () => {
|
||||
const updateFilters = jest.fn();
|
||||
const rendered = await renderWithEffects(
|
||||
<ApiProvider apis={apis}>
|
||||
<MockEntityListContextProvider value={{ updateFilters }}>
|
||||
<CatalogKindHeader />
|
||||
</MockEntityListContextProvider>
|
||||
</ApiProvider>,
|
||||
);
|
||||
|
||||
const input = rendered.getByText('Components');
|
||||
fireEvent.mouseDown(input);
|
||||
|
||||
const option = rendered.getByRole('option', { name: 'Templates' });
|
||||
fireEvent.click(option);
|
||||
|
||||
expect(updateFilters).toHaveBeenCalledWith({
|
||||
kind: new EntityKindFilter('template'),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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 { kinds: allKinds = [] } = useEntityKinds();
|
||||
const { updateFilters, queryParameters } = useEntityListProvider();
|
||||
|
||||
const [selectedKind, setSelectedKind] = useState(
|
||||
([queryParameters.kind].flat()[0] ?? initialFilter).toLocaleLowerCase(
|
||||
'en-US',
|
||||
),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
updateFilters({
|
||||
kind: selectedKind ? new EntityKindFilter(selectedKind) : undefined,
|
||||
});
|
||||
}, [selectedKind, updateFilters]);
|
||||
|
||||
// Before allKinds is loaded, or when a kind is entered manually in the URL, selectedKind may not
|
||||
// be present in allKinds. It should still be shown in the dropdown, but may not have the nice
|
||||
// enforced casing from the catalog-backend. This makes a key/value record for the Select options,
|
||||
// including selectedKind if it's unknown - but allows the selectedKind to get clobbered by the
|
||||
// more proper catalog kind if it exists.
|
||||
const options = [capitalize(selectedKind)]
|
||||
.concat(allKinds)
|
||||
.sort()
|
||||
.reduce((acc, kind) => {
|
||||
acc[kind.toLocaleLowerCase('en-US')] = kind;
|
||||
return acc;
|
||||
}, {} as Record<string, string>);
|
||||
|
||||
return (
|
||||
<Select
|
||||
input={<InputBase value={selectedKind} />}
|
||||
value={selectedKind}
|
||||
onChange={e => setSelectedKind(e.target.value as string)}
|
||||
classes={classes}
|
||||
>
|
||||
{Object.keys(options).map(kind => (
|
||||
<MenuItem value={kind} key={kind}>
|
||||
{`${options[kind]}s`}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
export { CatalogKindHeader } from './CatalogKindHeader';
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
} from '@backstage/core-components';
|
||||
import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
EntityKindPicker,
|
||||
EntityLifecyclePicker,
|
||||
EntityListProvider,
|
||||
EntityOwnerPicker,
|
||||
@@ -43,6 +42,7 @@ import {
|
||||
EntityListContainer,
|
||||
FilterContainer,
|
||||
} from '../FilteredEntityLayout';
|
||||
import { CatalogKindHeader } from '../CatalogKindHeader';
|
||||
|
||||
export type CatalogPageProps = {
|
||||
initiallySelectedFilter?: UserListFilterKind;
|
||||
@@ -61,18 +61,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 +82,8 @@ export const CatalogPage = ({
|
||||
<CatalogTable columns={columns} actions={actions} />
|
||||
</EntityListContainer>
|
||||
</FilteredEntityLayout>
|
||||
</EntityListProvider>
|
||||
</Content>
|
||||
</Content>
|
||||
</EntityListProvider>
|
||||
</PageWithHeader>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
import Edit from '@material-ui/icons/Edit';
|
||||
import OpenInNew from '@material-ui/icons/OpenInNew';
|
||||
import { capitalize } from 'lodash';
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import * as columnFactories from './columns';
|
||||
import { EntityRow } from './types';
|
||||
import {
|
||||
@@ -38,16 +38,6 @@ import {
|
||||
WarningPanel,
|
||||
} from '@backstage/core-components';
|
||||
|
||||
const defaultColumns: TableColumn<EntityRow>[] = [
|
||||
columnFactories.createNameColumn(),
|
||||
columnFactories.createSystemColumn(),
|
||||
columnFactories.createOwnerColumn(),
|
||||
columnFactories.createSpecTypeColumn(),
|
||||
columnFactories.createSpecLifecycleColumn(),
|
||||
columnFactories.createMetadataDescriptionColumn(),
|
||||
columnFactories.createTagsColumn(),
|
||||
];
|
||||
|
||||
type CatalogTableProps = {
|
||||
columns?: TableColumn<EntityRow>[];
|
||||
actions?: TableProps<EntityRow>['actions'];
|
||||
@@ -57,6 +47,19 @@ export const CatalogTable = ({ columns, actions }: CatalogTableProps) => {
|
||||
const { isStarredEntity, toggleStarredEntity } = useStarredEntities();
|
||||
const { loading, error, entities, filters } = useEntityListProvider();
|
||||
|
||||
const defaultColumns: TableColumn<EntityRow>[] = useMemo(
|
||||
() => [
|
||||
columnFactories.createNameColumn({ defaultKind: filters.kind?.value }),
|
||||
columnFactories.createSystemColumn(),
|
||||
columnFactories.createOwnerColumn(),
|
||||
columnFactories.createSpecTypeColumn(),
|
||||
columnFactories.createSpecLifecycleColumn(),
|
||||
columnFactories.createMetadataDescriptionColumn(),
|
||||
columnFactories.createTagsColumn(),
|
||||
],
|
||||
[filters.kind?.value],
|
||||
);
|
||||
|
||||
const showTypeColumn = filters.type === undefined;
|
||||
// TODO(timbonicus): remove the title from the CatalogTable once using EntitySearchBar
|
||||
const titlePreamble = capitalize(filters.user?.value ?? 'all');
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
export { CatalogClientWrapper } from './CatalogClientWrapper';
|
||||
export * from './components/AboutCard';
|
||||
export * from './components/CatalogKindHeader';
|
||||
export * from './components/CatalogResultListItem';
|
||||
export { CatalogTable } from './components/CatalogTable';
|
||||
export type { EntityRow as CatalogTableRow } from './components/CatalogTable';
|
||||
|
||||
Reference in New Issue
Block a user