Merge pull request #17868 from luchillo17/feat/BCKSTG-148-catalog-template-link-in-abou

feat(catalog): Link launch template in Entity's AboutCard
This commit is contained in:
Fredrik Adelöw
2023-05-25 11:40:49 +02:00
committed by GitHub
11 changed files with 223 additions and 8 deletions
+7
View File
@@ -105,6 +105,13 @@ export const catalogPlugin: BackstagePlugin<
},
true
>;
createFromTemplate: ExternalRouteRef<
{
namespace: string;
templateName: string;
},
true
>;
},
CatalogInputPluginOptions
>;
+1
View File
@@ -40,6 +40,7 @@
"@backstage/integration-react": "workspace:^",
"@backstage/plugin-catalog-common": "workspace:^",
"@backstage/plugin-catalog-react": "workspace:^",
"@backstage/plugin-scaffolder-common": "workspace:^",
"@backstage/plugin-search-common": "workspace:^",
"@backstage/plugin-search-react": "workspace:^",
"@backstage/theme": "workspace:^",
@@ -30,7 +30,7 @@ import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import userEvent from '@testing-library/user-event';
import { screen } from '@testing-library/react';
import React from 'react';
import { viewTechDocRouteRef } from '../../routes';
import { createFromTemplateRouteRef, viewTechDocRouteRef } from '../../routes';
import { AboutCard } from './AboutCard';
describe('<AboutCard />', () => {
@@ -505,4 +505,165 @@ describe('<AboutCard />', () => {
expect(screen.getByText('View TechDocs')).toBeVisible();
expect(screen.getByText('View TechDocs').closest('a')).toBeNull();
});
it('renders launch template link', async () => {
const entity = {
apiVersion: 'scaffolder.backstage.io/v1beta3',
kind: 'Template',
metadata: {
name: 'create-react-app-template',
namespace: 'default',
},
};
await renderInTestApp(
<TestApiProvider
apis={[
[
scmIntegrationsApiRef,
ScmIntegrationsApi.fromConfig(
new ConfigReader({
integrations: {
github: [
{
host: 'github.com',
token: '...',
},
],
},
}),
),
],
[catalogApiRef, catalogApi],
]}
>
<EntityProvider entity={entity}>
<AboutCard />
</EntityProvider>
</TestApiProvider>,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name': entityRouteRef,
'/create/templates/:namespace/:templateName':
createFromTemplateRouteRef,
},
},
);
expect(screen.getByText('Launch Template')).toBeVisible();
expect(screen.getByText('Launch Template').closest('a')).toHaveAttribute(
'href',
'/create/templates/default/create-react-app-template',
);
});
it.each([
{
testName: 'entity is not a template',
entity: {
apiVersion: 'scaffolder.backstage.io/v1beta3',
kind: 'Component',
metadata: {
name: 'create-react-app-template',
namespace: 'default',
},
},
},
{
testName: 'apiVersion is not scaffolder.backstage.io/v1beta3',
entity: {
apiVersion: 'v1',
kind: 'Template',
metadata: {
name: 'create-react-app-template',
namespace: 'default',
},
},
},
])(
'should not render launch template link when $testName',
async ({ entity }) => {
await renderInTestApp(
<TestApiProvider
apis={[
[
scmIntegrationsApiRef,
ScmIntegrationsApi.fromConfig(
new ConfigReader({
integrations: {
github: [
{
host: 'github.com',
token: '...',
},
],
},
}),
),
],
[catalogApiRef, catalogApi],
]}
>
<EntityProvider entity={entity}>
<AboutCard />
</EntityProvider>
</TestApiProvider>,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name': entityRouteRef,
'/create/templates/:namespace/:templateName':
createFromTemplateRouteRef,
},
},
);
expect(screen.queryByText('Launch Template')).toBeNull();
},
);
it('renders disabled launch template link when route is not bound', async () => {
const entity = {
apiVersion: 'scaffolder.backstage.io/v1beta3',
kind: 'Template',
metadata: {
name: 'create-react-app-template',
namespace: 'default',
},
};
await renderInTestApp(
<TestApiProvider
apis={[
[
scmIntegrationsApiRef,
ScmIntegrationsApi.fromConfig(
new ConfigReader({
integrations: {
github: [
{
host: 'github.com',
token: '...',
},
],
},
}),
),
],
[catalogApiRef, catalogApi],
]}
>
<EntityProvider entity={entity}>
<AboutCard />
</EntityProvider>
</TestApiProvider>,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name': entityRouteRef,
},
},
);
expect(screen.getByText('Launch Template')).toBeVisible();
expect(screen.getByText('Launch Template').closest('a')).toBeNull();
});
});
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
ANNOTATION_EDIT_URL,
ANNOTATION_LOCATION,
@@ -30,6 +29,7 @@ import {
alertApiRef,
errorApiRef,
useApi,
useApp,
useRouteRef,
} from '@backstage/core-plugin-api';
import {
@@ -41,6 +41,7 @@ import {
getEntitySourceLocation,
useEntity,
} from '@backstage/plugin-catalog-react';
import { isTemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
import {
Card,
CardContent,
@@ -49,11 +50,13 @@ import {
IconButton,
makeStyles,
} from '@material-ui/core';
import CreateComponentIcon from '@material-ui/icons/AddCircleOutline';
import CachedIcon from '@material-ui/icons/Cached';
import DocsIcon from '@material-ui/icons/Description';
import EditIcon from '@material-ui/icons/Edit';
import React, { useCallback } from 'react';
import { viewTechDocRouteRef } from '../../routes';
import { createFromTemplateRouteRef, viewTechDocRouteRef } from '../../routes';
import { AboutContent } from './AboutContent';
const useStyles = makeStyles({
@@ -90,6 +93,7 @@ export interface AboutCardProps {
*/
export function AboutCard(props: AboutCardProps) {
const { variant } = props;
const app = useApp();
const classes = useStyles();
const { entity } = useEntity();
const scmIntegrationsApi = useApi(scmIntegrationsApiRef);
@@ -97,6 +101,7 @@ export function AboutCard(props: AboutCardProps) {
const alertApi = useApi(alertApiRef);
const errorApi = useApi(errorApiRef);
const viewTechdocLink = useRouteRef(viewTechDocRouteRef);
const templateRoute = useRouteRef(createFromTemplateRouteRef);
const entitySourceLocation = getEntitySourceLocation(
entity,
@@ -126,6 +131,26 @@ export function AboutCard(props: AboutCardProps) {
}),
};
const subHeaderLinks = [viewInSource, viewInTechDocs];
if (isTemplateEntityV1beta3(entity)) {
const Icon = app.getSystemIcon('scaffolder') ?? CreateComponentIcon;
const launchTemplate: IconLinkVerticalProps = {
label: 'Launch Template',
icon: <Icon />,
disabled: !templateRoute,
href:
templateRoute &&
templateRoute({
templateName: entity.metadata.name,
namespace: entity.metadata.namespace || DEFAULT_NAMESPACE,
}),
};
subHeaderLinks.push(launchTemplate);
}
let cardClass = '';
if (variant === 'gridItem') {
cardClass = classes.gridItemCard;
@@ -179,7 +204,7 @@ export function AboutCard(props: AboutCardProps) {
</IconButton>
</>
}
subheader={<HeaderIconLinkRow links={[viewInSource, viewInTechDocs]} />}
subheader={<HeaderIconLinkRow links={subHeaderLinks} />}
/>
<Divider />
<CardContent className={cardContentClass}>
+6 -1
View File
@@ -21,7 +21,11 @@ import {
entityRouteRef,
starredEntitiesApiRef,
} from '@backstage/plugin-catalog-react';
import { createComponentRouteRef, viewTechDocRouteRef } from './routes';
import {
createComponentRouteRef,
createFromTemplateRouteRef,
viewTechDocRouteRef,
} from './routes';
import {
createApiFactory,
createComponentExtension,
@@ -77,6 +81,7 @@ export const catalogPlugin = createPlugin({
externalRoutes: {
createComponent: createComponentRouteRef,
viewTechDoc: viewTechDocRouteRef,
createFromTemplate: createFromTemplateRouteRef,
},
__experimentalConfigure(
options?: CatalogInputPluginOptions,
+6
View File
@@ -30,6 +30,12 @@ export const viewTechDocRouteRef = createExternalRouteRef({
params: ['namespace', 'kind', 'name'],
});
export const createFromTemplateRouteRef = createExternalRouteRef({
id: 'create-from-template',
optional: true,
params: ['namespace', 'templateName'],
});
export const rootRouteRef = createRouteRef({
id: 'catalog',
});
@@ -26,8 +26,8 @@ export const createGitlabProjectAccessTokenAction: (options: {
integrations: ScmIntegrationRegistry;
}) => TemplateAction<
{
projectId: string | number;
repoUrl: string;
projectId: string | number;
token?: string | undefined;
name?: string | undefined;
accessLevel?: number | undefined;
@@ -44,8 +44,8 @@ export const createGitlabProjectDeployTokenAction: (options: {
}) => TemplateAction<
{
name: string;
projectId: string | number;
repoUrl: string;
projectId: string | number;
token?: string | undefined;
username?: string | undefined;
scopes?: string[] | undefined;
@@ -63,8 +63,8 @@ export const createGitlabProjectVariableAction: (options: {
{
key: string;
value: string;
projectId: string | number;
repoUrl: string;
projectId: string | number;
variableType: string;
token?: string | undefined;
variableProtected?: boolean | undefined;