Add swimlanes to scaffolder page
Add swimlane filtering to the scaffolder page. This will allow developers to surface specific templates ahead of others or group templates together. This is just a first pass, in the future we can also all developers to customize the listing component for fancier one Fixes https://github.com/backstage/backstage/issues/6661s Signed-off-by: jrusso1020 <jrusso@brex.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'example-app': patch
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
Add swim lane filtering to the scaffolder page so that individuals can surface specific templates to end users ahead of others, or group templates together
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
AlertDisplay,
|
||||
OAuthRequestDialog,
|
||||
SignInPage,
|
||||
ContentHeader,
|
||||
} from '@backstage/core-components';
|
||||
import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs';
|
||||
import {
|
||||
@@ -175,7 +176,20 @@ const routes = (
|
||||
>
|
||||
{techDocsPage}
|
||||
</Route>
|
||||
<Route path="/create" element={<ScaffolderPage />}>
|
||||
<Route
|
||||
path="/create"
|
||||
element={
|
||||
<ScaffolderPage
|
||||
ExtraSwimlanes={[
|
||||
{
|
||||
title: <ContentHeader title="Recommended" />,
|
||||
filter: entity =>
|
||||
entity?.metadata?.tags?.includes('recommended') || false,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ScaffolderFieldExtensions>
|
||||
<LowerCaseValuePickerFieldExtension />
|
||||
</ScaffolderFieldExtensions>
|
||||
|
||||
@@ -24,6 +24,7 @@ import { JSONSchema } from '@backstage/catalog-model';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { Observable } from '@backstage/types';
|
||||
import { default as React_2 } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { TemplateEntityV1beta2 } from '@backstage/catalog-model';
|
||||
@@ -213,12 +214,19 @@ export const ScaffolderFieldExtensions: React_2.ComponentType;
|
||||
// @public (undocumented)
|
||||
export const ScaffolderPage: ({
|
||||
TemplateCardComponent,
|
||||
ExtraSwimlanes,
|
||||
}: {
|
||||
TemplateCardComponent?:
|
||||
| ComponentType<{
|
||||
template: TemplateEntityV1beta2;
|
||||
}>
|
||||
| undefined;
|
||||
ExtraSwimlanes?:
|
||||
| {
|
||||
title: ReactNode;
|
||||
filter: (entity: Entity) => boolean;
|
||||
}[]
|
||||
| undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "scaffolderPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
@@ -240,7 +248,8 @@ export { scaffolderPlugin };
|
||||
// @public (undocumented)
|
||||
export const TemplateList: ({
|
||||
TemplateCardComponent,
|
||||
}: TemplateListProps) => JSX.Element;
|
||||
Swimlane,
|
||||
}: TemplateListProps) => JSX.Element | null;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "TemplateListProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
@@ -251,6 +260,10 @@ export type TemplateListProps = {
|
||||
template: TemplateEntityV1beta2;
|
||||
}>
|
||||
| undefined;
|
||||
Swimlane?: {
|
||||
title: React_2.ReactNode;
|
||||
filter: (entity: Entity) => boolean;
|
||||
};
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "TemplateTypePicker" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import React, { ComponentType } from 'react';
|
||||
import { Routes, Route, useOutlet } from 'react-router';
|
||||
import { TemplateEntityV1beta2 } from '@backstage/catalog-model';
|
||||
import { TemplateEntityV1beta2, Entity } from '@backstage/catalog-model';
|
||||
import { ScaffolderPage } from './ScaffolderPage';
|
||||
import { TemplatePage } from './TemplatePage';
|
||||
import { TaskPage } from './TaskPage';
|
||||
@@ -34,9 +34,16 @@ type RouterProps = {
|
||||
TemplateCardComponent?:
|
||||
| ComponentType<{ template: TemplateEntityV1beta2 }>
|
||||
| undefined;
|
||||
ExtraSwimlanes?: Array<{
|
||||
title: React.ReactNode;
|
||||
filter: (entity: Entity) => boolean;
|
||||
}>;
|
||||
};
|
||||
|
||||
export const Router = ({ TemplateCardComponent }: RouterProps) => {
|
||||
export const Router = ({
|
||||
TemplateCardComponent,
|
||||
ExtraSwimlanes,
|
||||
}: RouterProps) => {
|
||||
const outlet = useOutlet();
|
||||
|
||||
const customFieldExtensions = useElementFilter(outlet, elements =>
|
||||
@@ -64,7 +71,10 @@ export const Router = ({ TemplateCardComponent }: RouterProps) => {
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
<ScaffolderPage TemplateCardComponent={TemplateCardComponent} />
|
||||
<ScaffolderPage
|
||||
TemplateCardComponent={TemplateCardComponent}
|
||||
ExtraSwimlanes={ExtraSwimlanes}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
Page,
|
||||
SupportButton,
|
||||
} from '@backstage/core-components';
|
||||
import { TemplateEntityV1beta2 } from '@backstage/catalog-model';
|
||||
import { TemplateEntityV1beta2, Entity } from '@backstage/catalog-model';
|
||||
import { useRouteRef } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
EntityKindPicker,
|
||||
@@ -51,13 +51,17 @@ export type ScaffolderPageProps = {
|
||||
TemplateCardComponent?:
|
||||
| ComponentType<{ template: TemplateEntityV1beta2 }>
|
||||
| undefined;
|
||||
ExtraSwimlanes?: Array<{
|
||||
title: React.ReactNode;
|
||||
filter: (entity: Entity) => boolean;
|
||||
}>;
|
||||
};
|
||||
|
||||
export const ScaffolderPageContents = ({
|
||||
TemplateCardComponent,
|
||||
ExtraSwimlanes,
|
||||
}: ScaffolderPageProps) => {
|
||||
const styles = useStyles();
|
||||
|
||||
const registerComponentLink = useRouteRef(registerComponentRouteRef);
|
||||
|
||||
return (
|
||||
@@ -96,6 +100,13 @@ export const ScaffolderPageContents = ({
|
||||
<EntityTagPicker />
|
||||
</div>
|
||||
<div>
|
||||
{ExtraSwimlanes &&
|
||||
ExtraSwimlanes.map(swimlane => (
|
||||
<TemplateList
|
||||
TemplateCardComponent={TemplateCardComponent}
|
||||
Swimlane={swimlane}
|
||||
/>
|
||||
))}
|
||||
<TemplateList TemplateCardComponent={TemplateCardComponent} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -106,8 +117,12 @@ export const ScaffolderPageContents = ({
|
||||
|
||||
export const ScaffolderPage = ({
|
||||
TemplateCardComponent,
|
||||
ExtraSwimlanes,
|
||||
}: ScaffolderPageProps) => (
|
||||
<EntityListProvider>
|
||||
<ScaffolderPageContents TemplateCardComponent={TemplateCardComponent} />
|
||||
<ScaffolderPageContents
|
||||
TemplateCardComponent={TemplateCardComponent}
|
||||
ExtraSwimlanes={ExtraSwimlanes}
|
||||
/>
|
||||
</EntityListProvider>
|
||||
);
|
||||
|
||||
@@ -16,10 +16,13 @@
|
||||
|
||||
import React, { ComponentType } from 'react';
|
||||
import {
|
||||
Entity,
|
||||
stringifyEntityRef,
|
||||
TemplateEntityV1beta2,
|
||||
} from '@backstage/catalog-model';
|
||||
import {
|
||||
Content,
|
||||
ContentHeader,
|
||||
ItemCardGrid,
|
||||
Progress,
|
||||
WarningPanel,
|
||||
@@ -32,41 +35,84 @@ export type TemplateListProps = {
|
||||
TemplateCardComponent?:
|
||||
| ComponentType<{ template: TemplateEntityV1beta2 }>
|
||||
| undefined;
|
||||
Swimlane?: {
|
||||
title: React.ReactNode;
|
||||
filter: (entity: Entity) => boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export const TemplateList = ({ TemplateCardComponent }: TemplateListProps) => {
|
||||
type TemplateCardGridProps = {
|
||||
Card: ComponentType<{ template: TemplateEntityV1beta2 }>;
|
||||
entities: Entity[];
|
||||
};
|
||||
const TemplateCardGrid = ({ Card, entities }: TemplateCardGridProps) => {
|
||||
return (
|
||||
<ItemCardGrid>
|
||||
{entities &&
|
||||
entities?.length > 0 &&
|
||||
entities.map((template: Entity) => (
|
||||
<Card
|
||||
key={stringifyEntityRef(template)}
|
||||
template={template as TemplateEntityV1beta2}
|
||||
/>
|
||||
))}
|
||||
</ItemCardGrid>
|
||||
);
|
||||
};
|
||||
|
||||
export const TemplateList = ({
|
||||
TemplateCardComponent,
|
||||
Swimlane,
|
||||
}: TemplateListProps) => {
|
||||
const { loading, error, entities } = useEntityListProvider();
|
||||
const Card = TemplateCardComponent || TemplateCard;
|
||||
return (
|
||||
<>
|
||||
{loading && <Progress />}
|
||||
if (Swimlane === null || Swimlane === undefined) {
|
||||
return (
|
||||
<>
|
||||
{loading && <Progress />}
|
||||
|
||||
{error && (
|
||||
<WarningPanel title="Oops! Something went wrong loading the templates">
|
||||
{error.message}
|
||||
</WarningPanel>
|
||||
)}
|
||||
|
||||
{!error && !loading && !entities.length && (
|
||||
<Typography variant="body2">
|
||||
No templates found that match your filter. Learn more about{' '}
|
||||
<Link href="https://backstage.io/docs/features/software-templates/adding-templates">
|
||||
adding templates
|
||||
</Link>
|
||||
.
|
||||
</Typography>
|
||||
)}
|
||||
<Content>
|
||||
<ContentHeader title="All Templates" />
|
||||
<TemplateCardGrid Card={Card} entities={entities} />
|
||||
</Content>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const filteredEntities = entities.filter((entity: Entity) =>
|
||||
Swimlane.filter(entity),
|
||||
);
|
||||
|
||||
if (filteredEntities.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Content>
|
||||
{Swimlane.title}
|
||||
{loading && <Progress />}
|
||||
{error && (
|
||||
<WarningPanel title="Oops! Something went wrong loading the templates">
|
||||
{error.message}
|
||||
</WarningPanel>
|
||||
)}
|
||||
|
||||
{!error && !loading && !entities.length && (
|
||||
<Typography variant="body2">
|
||||
No templates found that match your filter. Learn more about{' '}
|
||||
<Link href="https://backstage.io/docs/features/software-templates/adding-templates">
|
||||
adding templates
|
||||
</Link>
|
||||
.
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
<ItemCardGrid>
|
||||
{entities &&
|
||||
entities?.length > 0 &&
|
||||
entities.map(template => (
|
||||
<Card
|
||||
key={stringifyEntityRef(template)}
|
||||
template={template as TemplateEntityV1beta2}
|
||||
/>
|
||||
))}
|
||||
</ItemCardGrid>
|
||||
</>
|
||||
<TemplateCardGrid Card={Card} entities={filteredEntities} />
|
||||
</Content>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user