Merge pull request #20720 from backstage/camilal/migrate-catalog-entity-cards

[DI] Migrate catalog entity cards
This commit is contained in:
Patrik Oldsberg
2023-10-23 14:47:40 +02:00
committed by GitHub
9 changed files with 364 additions and 162 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog': patch
---
Migrate catalog entity cards to new frontend system extension format.
+51
View File
@@ -0,0 +1,51 @@
/*
* Copyright 2023 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 {
createApiFactory,
discoveryApiRef,
fetchApiRef,
storageApiRef,
} from '@backstage/core-plugin-api';
import { CatalogClient } from '@backstage/catalog-client';
import { createApiExtension } from '@backstage/frontend-plugin-api';
import {
catalogApiRef,
starredEntitiesApiRef,
} from '@backstage/plugin-catalog-react';
import { DefaultStarredEntitiesApi } from '../apis';
export const CatalogApi = createApiExtension({
factory: createApiFactory({
api: catalogApiRef,
deps: {
discoveryApi: discoveryApiRef,
fetchApi: fetchApiRef,
},
factory: ({ discoveryApi, fetchApi }) =>
new CatalogClient({ discoveryApi, fetchApi }),
}),
});
export const StarredEntitiesApi = createApiExtension({
factory: createApiFactory({
api: starredEntitiesApiRef,
deps: { storageApi: storageApiRef },
factory: ({ storageApi }) => new DefaultStarredEntitiesApi({ storageApi }),
}),
});
export default [CatalogApi, StarredEntitiesApi];
+102
View File
@@ -0,0 +1,102 @@
/*
* Copyright 2023 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 { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha';
export const EntityAboutCard = createEntityCardExtension({
id: 'about',
loader: async () =>
import('../components/AboutCard').then(m => (
<m.AboutCard variant="gridItem" />
)),
});
export const EntityLinksCard = createEntityCardExtension({
id: 'links',
loader: async () =>
import('../components/EntityLinksCard').then(m => {
return <m.EntityLinksCard variant="gridItem" />;
}),
});
export const EntityLabelsCard = createEntityCardExtension({
id: 'labels',
loader: async () =>
import('../components/EntityLabelsCard').then(m => (
<m.EntityLabelsCard variant="gridItem" />
)),
});
export const EntityDependsOnComponentsCard = createEntityCardExtension({
id: 'dependsOn.components',
loader: async () =>
import('../components/DependsOnComponentsCard').then(m => (
<m.DependsOnComponentsCard variant="gridItem" />
)),
});
export const EntityDependsOnResourcesCard = createEntityCardExtension({
id: 'dependsOn.resources',
loader: async () =>
import('../components/DependsOnResourcesCard').then(m => (
<m.DependsOnResourcesCard variant="gridItem" />
)),
});
export const EntityHasComponentsCard = createEntityCardExtension({
id: 'has.components',
loader: async () =>
import('../components/HasComponentsCard').then(m => (
<m.HasComponentsCard variant="gridItem" />
)),
});
export const EntityHasResourcesCard = createEntityCardExtension({
id: 'has.resources',
loader: async () =>
import('../components/HasResourcesCard').then(m => (
<m.HasResourcesCard variant="gridItem" />
)),
});
export const EntityHasSubcomponentsCard = createEntityCardExtension({
id: 'has.subcomponents',
loader: async () =>
import('../components/HasSubcomponentsCard').then(m => (
<m.HasSubcomponentsCard variant="gridItem" />
)),
});
export const EntityHasSystemsCard = createEntityCardExtension({
id: 'has.systems',
loader: async () =>
import('../components/HasSystemsCard').then(m => (
<m.HasSystemsCard variant="gridItem" />
)),
});
export default [
EntityAboutCard,
EntityLinksCard,
EntityLabelsCard,
EntityDependsOnComponentsCard,
EntityDependsOnResourcesCard,
EntityHasComponentsCard,
EntityHasResourcesCard,
EntityHasSubcomponentsCard,
EntityHasSystemsCard,
];
@@ -0,0 +1,46 @@
/*
* Copyright 2023 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 { Grid } from '@material-ui/core';
import {
coreExtensionData,
createExtensionInput,
} from '@backstage/frontend-plugin-api';
import { createEntityContentExtension } from '@backstage/plugin-catalog-react/alpha';
export const OverviewEntityContent = createEntityContentExtension({
id: 'overview',
defaultPath: '/',
defaultTitle: 'Overview',
disabled: false,
inputs: {
cards: createExtensionInput({
element: coreExtensionData.reactElement,
}),
},
loader: async ({ inputs }) => (
<Grid container spacing={3} alignItems="stretch">
{inputs.cards.map(card => (
<Grid item md={6} xs={12}>
{card.element}
</Grid>
))}
</Grid>
),
});
export default [OverviewEntityContent];
@@ -109,7 +109,7 @@ const CatalogUserListFilter = createCatalogFilterExtension({
},
});
export const builtInFilterExtensions = [
export default [
CatalogEntityTagFilter,
CatalogEntityKindFilter,
CatalogEntityTypeFilter,
+29
View File
@@ -0,0 +1,29 @@
/*
* Copyright 2023 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 HomeIcon from '@material-ui/icons/Home';
import { convertLegacyRouteRef } from '@backstage/core-plugin-api/alpha';
import { createNavItemExtension } from '@backstage/frontend-plugin-api';
import { rootRouteRef } from '../routes';
export const CatalogIndexNavItem = createNavItemExtension({
id: 'catalog.nav.index',
routeRef: convertLegacyRouteRef(rootRouteRef),
title: 'Catalog',
icon: HomeIcon,
});
export default [CatalogIndexNavItem];
+83
View File
@@ -0,0 +1,83 @@
/*
* Copyright 2023 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 { convertLegacyRouteRef } from '@backstage/core-plugin-api/alpha';
import {
createPageExtension,
coreExtensionData,
createExtensionInput,
} from '@backstage/frontend-plugin-api';
import {
AsyncEntityProvider,
entityRouteRef,
} from '@backstage/plugin-catalog-react';
import { entityContentTitleExtensionDataRef } from '@backstage/plugin-catalog-react/alpha';
import { rootRouteRef } from '../routes';
import { useEntityFromUrl } from '../components/CatalogEntityPage/useEntityFromUrl';
export const CatalogIndexPage = createPageExtension({
id: 'plugin.catalog.page.index',
defaultPath: '/catalog',
routeRef: convertLegacyRouteRef(rootRouteRef),
inputs: {
filters: createExtensionInput({
element: coreExtensionData.reactElement,
}),
},
loader: async ({ inputs }) => {
const { BaseCatalogPage } = await import('../components/CatalogPage');
const filters = inputs.filters.map(filter => filter.element);
return <BaseCatalogPage filters={<>{filters}</>} />;
},
});
export const CatalogEntityPage = createPageExtension({
id: 'plugin.catalog.page.entity',
defaultPath: '/catalog/:namespace/:kind/:name',
routeRef: convertLegacyRouteRef(entityRouteRef),
inputs: {
contents: createExtensionInput({
element: coreExtensionData.reactElement,
path: coreExtensionData.routePath,
routeRef: coreExtensionData.routeRef.optional(),
title: entityContentTitleExtensionDataRef,
}),
},
loader: async ({ inputs }) => {
const { EntityLayout } = await import('../components/EntityLayout');
const Component = () => {
return (
<AsyncEntityProvider {...useEntityFromUrl()}>
<EntityLayout>
{inputs.contents.map(content => (
<EntityLayout.Route
key={content.path}
path={content.path}
title={content.title}
>
{content.element}
</EntityLayout.Route>
))}
</EntityLayout>
</AsyncEntityProvider>
);
};
return <Component />;
},
});
export default [CatalogIndexPage, CatalogEntityPage];
+18 -161
View File
@@ -14,166 +14,25 @@
* limitations under the License.
*/
import React from 'react';
import HomeIcon from '@material-ui/icons/Home';
import {
createApiFactory,
discoveryApiRef,
fetchApiRef,
storageApiRef,
} from '@backstage/core-plugin-api';
import { convertLegacyRouteRef } from '@backstage/core-plugin-api/alpha';
import { CatalogClient } from '@backstage/catalog-client';
import {
createApiExtension,
createPageExtension,
createPlugin,
createNavItemExtension,
coreExtensionData,
createExtensionInput,
} from '@backstage/frontend-plugin-api';
import {
AsyncEntityProvider,
catalogApiRef,
entityRouteRef,
starredEntitiesApiRef,
} from '@backstage/plugin-catalog-react';
import {
createEntityContentExtension,
createEntityCardExtension,
entityContentTitleExtensionDataRef,
} from '@backstage/plugin-catalog-react/alpha';
import { createSearchResultListItemExtension } from '@backstage/plugin-search-react/alpha';
import { DefaultStarredEntitiesApi } from '../apis';
import { createPlugin } from '@backstage/frontend-plugin-api';
import { entityRouteRef } from '@backstage/plugin-catalog-react';
import {
createComponentRouteRef,
createFromTemplateRouteRef,
rootRouteRef,
viewTechDocRouteRef,
} from '../routes';
import { builtInFilterExtensions } from './builtInFilterExtensions';
import { useEntityFromUrl } from '../components/CatalogEntityPage/useEntityFromUrl';
import Grid from '@material-ui/core/Grid';
/** @alpha */
export const CatalogApi = createApiExtension({
factory: createApiFactory({
api: catalogApiRef,
deps: {
discoveryApi: discoveryApiRef,
fetchApi: fetchApiRef,
},
factory: ({ discoveryApi, fetchApi }) =>
new CatalogClient({ discoveryApi, fetchApi }),
}),
});
/** @alpha */
export const StarredEntitiesApi = createApiExtension({
factory: createApiFactory({
api: starredEntitiesApiRef,
deps: { storageApi: storageApiRef },
factory: ({ storageApi }) => new DefaultStarredEntitiesApi({ storageApi }),
}),
});
/** @alpha */
export const CatalogSearchResultListItemExtension =
createSearchResultListItemExtension({
id: 'catalog',
predicate: result => result.type === 'software-catalog',
component: () =>
import('../components/CatalogSearchResultListItem').then(
m => m.CatalogSearchResultListItem,
),
});
const CatalogIndexPage = createPageExtension({
id: 'plugin.catalog.page.index',
defaultPath: '/catalog',
routeRef: convertLegacyRouteRef(rootRouteRef),
inputs: {
filters: createExtensionInput({
element: coreExtensionData.reactElement,
}),
},
loader: async ({ inputs }) => {
const { BaseCatalogPage } = await import('../components/CatalogPage');
const filters = inputs.filters.map(filter => filter.element);
return <BaseCatalogPage filters={<>{filters}</>} />;
},
});
const CatalogEntityPage = createPageExtension({
id: 'plugin.catalog.page.entity',
defaultPath: '/catalog/:namespace/:kind/:name',
routeRef: convertLegacyRouteRef(entityRouteRef),
inputs: {
contents: createExtensionInput({
element: coreExtensionData.reactElement,
path: coreExtensionData.routePath,
routeRef: coreExtensionData.routeRef.optional(),
title: entityContentTitleExtensionDataRef,
}),
},
loader: async ({ inputs }) => {
const { EntityLayout } = await import('../components/EntityLayout');
const Component = () => {
return (
<AsyncEntityProvider {...useEntityFromUrl()}>
<EntityLayout>
{inputs.contents.map(content => (
<EntityLayout.Route
key={content.path}
path={content.path}
title={content.title}
>
{content.element}
</EntityLayout.Route>
))}
</EntityLayout>
</AsyncEntityProvider>
);
};
return <Component />;
},
});
const EntityAboutCard = createEntityCardExtension({
id: 'about',
loader: async () =>
import('../components/AboutCard').then(m => (
<m.AboutCard variant="gridItem" />
)),
});
const OverviewEntityContent = createEntityContentExtension({
id: 'overview',
defaultPath: '/',
defaultTitle: 'Overview',
disabled: false,
inputs: {
cards: createExtensionInput({
element: coreExtensionData.reactElement,
}),
},
loader: async ({ inputs }) => (
<Grid container spacing={3} alignItems="stretch">
{inputs.cards.map(card => (
<Grid item md={6} xs={12}>
{card.element}
</Grid>
))}
</Grid>
),
});
const CatalogNavItem = createNavItemExtension({
id: 'catalog.nav.index',
routeRef: convertLegacyRouteRef(rootRouteRef),
title: 'Catalog',
icon: HomeIcon,
});
import apis from './apis';
import pages from './pages';
import filters from './filters';
import navItems from './navItems';
import entityCards from './entityCards';
import entityContents from './entityContents';
import searchResultItems from './searchResultItems';
/** @alpha */
export default createPlugin({
@@ -188,14 +47,12 @@ export default createPlugin({
createFromTemplate: convertLegacyRouteRef(createFromTemplateRouteRef),
},
extensions: [
CatalogApi,
StarredEntitiesApi,
CatalogSearchResultListItemExtension,
CatalogIndexPage,
CatalogEntityPage,
CatalogNavItem,
OverviewEntityContent,
EntityAboutCard,
...builtInFilterExtensions,
...apis,
...pages,
...filters,
...navItems,
...entityCards,
...entityContents,
...searchResultItems,
],
});
@@ -0,0 +1,29 @@
/*
* Copyright 2023 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 { createSearchResultListItemExtension } from '@backstage/plugin-search-react/alpha';
export const CatalogSearchResultListItemExtension =
createSearchResultListItemExtension({
id: 'catalog',
predicate: result => result.type === 'software-catalog',
component: () =>
import('../components/CatalogSearchResultListItem').then(
m => m.CatalogSearchResultListItem,
),
});
export default [CatalogSearchResultListItemExtension];