feat: add TemplateDetailButton to CardHeader and update tests
Signed-off-by: Ladislav Vitásek <ladislav.vitasek@gendigital.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-react': patch
|
||||
---
|
||||
|
||||
Scaffolding - Template card - button to show template entity detail
|
||||
@@ -50,8 +50,7 @@ export const ComponentAccordion: (props: {
|
||||
expanded?: boolean;
|
||||
Content: () => JSX.Element;
|
||||
Actions?: () => JSX.Element;
|
||||
Settings?: () => JSX./** @public */
|
||||
Element;
|
||||
Settings?: () => JSX.Element;
|
||||
ContextProvider?: (props: any) => JSX.Element;
|
||||
}) => JSX_2.Element;
|
||||
|
||||
|
||||
@@ -347,6 +347,7 @@ export const scaffolderReactTranslationRef: TranslationRef<
|
||||
readonly 'templateCategoryPicker.title': 'Categories';
|
||||
readonly 'templateCard.noDescription': 'No description';
|
||||
readonly 'templateCard.chooseButtonText': 'Choose';
|
||||
readonly 'cardHeader.detailBtnTitle': 'Show template entity details';
|
||||
readonly 'templateOutputs.title': 'Text Output';
|
||||
}
|
||||
>;
|
||||
|
||||
@@ -24,12 +24,21 @@ import {
|
||||
renderInTestApp,
|
||||
TestApiProvider,
|
||||
} from '@backstage/test-utils';
|
||||
import { starredEntitiesApiRef } from '@backstage/plugin-catalog-react';
|
||||
import {
|
||||
entityRouteRef,
|
||||
starredEntitiesApiRef,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { DefaultStarredEntitiesApi } from '@backstage/plugin-catalog';
|
||||
import Observable from 'zen-observable';
|
||||
import { stringifyEntityRef } from '@backstage/catalog-model';
|
||||
import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
|
||||
|
||||
const mountedRoutes = {
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
},
|
||||
};
|
||||
|
||||
describe('CardHeader', () => {
|
||||
it('should select the correct theme from the theme provider from the header', async () => {
|
||||
// Can't really test what we want here.
|
||||
@@ -64,6 +73,7 @@ describe('CardHeader', () => {
|
||||
/>
|
||||
</ThemeProvider>
|
||||
</TestApiProvider>,
|
||||
mountedRoutes,
|
||||
);
|
||||
|
||||
expect(mockTheme.getPageTheme).toHaveBeenCalledWith({ themeId: 'service' });
|
||||
@@ -93,6 +103,7 @@ describe('CardHeader', () => {
|
||||
}}
|
||||
/>
|
||||
</TestApiProvider>,
|
||||
mountedRoutes,
|
||||
);
|
||||
|
||||
expect(getByText('service')).toBeInTheDocument();
|
||||
@@ -118,6 +129,7 @@ describe('CardHeader', () => {
|
||||
<TestApiProvider apis={[[starredEntitiesApiRef, starredEntitiesApi]]}>
|
||||
<CardHeader template={mockTemplate} />
|
||||
</TestApiProvider>,
|
||||
mountedRoutes,
|
||||
);
|
||||
|
||||
const favorite = getByRole('button', { name: 'Add to favorites' });
|
||||
@@ -129,6 +141,38 @@ describe('CardHeader', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('renders TemplateDetailButton with link to entity page', async () => {
|
||||
const { getByTitle } = await renderInTestApp(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[
|
||||
starredEntitiesApiRef,
|
||||
new DefaultStarredEntitiesApi({
|
||||
storageApi: mockApis.storage(),
|
||||
}),
|
||||
],
|
||||
]}
|
||||
>
|
||||
<CardHeader
|
||||
template={{
|
||||
apiVersion: 'scaffolder.backstage.io/v1beta3',
|
||||
kind: 'Template',
|
||||
metadata: { name: 'test-template', namespace: 'default' },
|
||||
spec: {
|
||||
steps: [],
|
||||
type: 'service',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</TestApiProvider>,
|
||||
mountedRoutes,
|
||||
);
|
||||
|
||||
const detailButton = getByTitle('Show template entity details');
|
||||
const link = detailButton.querySelector('a');
|
||||
expect(link).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the name of the entity', async () => {
|
||||
const { getByText } = await renderInTestApp(
|
||||
<TestApiProvider
|
||||
@@ -153,6 +197,7 @@ describe('CardHeader', () => {
|
||||
}}
|
||||
/>
|
||||
</TestApiProvider>,
|
||||
mountedRoutes,
|
||||
);
|
||||
|
||||
expect(getByText('bob')).toBeInTheDocument();
|
||||
@@ -182,6 +227,7 @@ describe('CardHeader', () => {
|
||||
}}
|
||||
/>
|
||||
</TestApiProvider>,
|
||||
mountedRoutes,
|
||||
);
|
||||
|
||||
expect(getByText('Iamtitle')).toBeInTheDocument();
|
||||
|
||||
@@ -15,10 +15,11 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Theme, makeStyles, useTheme } from '@material-ui/core/styles';
|
||||
import { makeStyles, Theme, useTheme } from '@material-ui/core/styles';
|
||||
import { ItemCardHeader } from '@backstage/core-components';
|
||||
import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
|
||||
import { FavoriteEntity } from '@backstage/plugin-catalog-react';
|
||||
import { TemplateDetailButton } from './TemplateDetailButton.tsx';
|
||||
|
||||
const useStyles = makeStyles<
|
||||
Theme,
|
||||
@@ -66,7 +67,11 @@ export const CardHeader = (props: CardHeaderProps) => {
|
||||
<div className={styles.subtitleWrapper}>
|
||||
<div>{type}</div>
|
||||
<div>
|
||||
<FavoriteEntity entity={props.template} style={{ padding: 0 }} />
|
||||
<TemplateDetailButton template={props.template} />
|
||||
<FavoriteEntity
|
||||
entity={props.template}
|
||||
style={{ padding: 0, marginLeft: 6 }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -33,6 +33,12 @@ import { permissionApiRef } from '@backstage/plugin-permission-react';
|
||||
import { AuthorizeResult } from '@backstage/plugin-permission-common';
|
||||
import { SWRConfig } from 'swr';
|
||||
|
||||
const mountedRoutes = {
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
},
|
||||
};
|
||||
|
||||
describe('TemplateCard', () => {
|
||||
it('should render the card title', async () => {
|
||||
const mockTemplate: TemplateEntityV1beta3 = {
|
||||
@@ -59,6 +65,7 @@ describe('TemplateCard', () => {
|
||||
>
|
||||
<TemplateCard template={mockTemplate} />
|
||||
</TestApiProvider>,
|
||||
mountedRoutes,
|
||||
);
|
||||
|
||||
expect(getByText('bob')).toBeInTheDocument();
|
||||
@@ -89,6 +96,7 @@ describe('TemplateCard', () => {
|
||||
>
|
||||
<TemplateCard template={mockTemplate} />
|
||||
</TestApiProvider>,
|
||||
mountedRoutes,
|
||||
);
|
||||
|
||||
const description = getByText('hello');
|
||||
@@ -121,6 +129,7 @@ describe('TemplateCard', () => {
|
||||
>
|
||||
<TemplateCard template={mockTemplate} />
|
||||
</TestApiProvider>,
|
||||
mountedRoutes,
|
||||
);
|
||||
|
||||
expect(getByText('No description')).toBeInTheDocument();
|
||||
@@ -151,6 +160,7 @@ describe('TemplateCard', () => {
|
||||
>
|
||||
<TemplateCard template={mockTemplate} />
|
||||
</TestApiProvider>,
|
||||
mountedRoutes,
|
||||
);
|
||||
|
||||
expect(queryByTestId('template-card-separator')).toBeInTheDocument();
|
||||
@@ -187,6 +197,7 @@ describe('TemplateCard', () => {
|
||||
>
|
||||
<TemplateCard template={mockTemplate} />
|
||||
</TestApiProvider>,
|
||||
mountedRoutes,
|
||||
);
|
||||
|
||||
for (const tag of mockTemplate.metadata.tags!) {
|
||||
@@ -227,11 +238,7 @@ describe('TemplateCard', () => {
|
||||
>
|
||||
<TemplateCard template={mockTemplate} />
|
||||
</TestApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:kind/:namespace/:name': entityRouteRef,
|
||||
},
|
||||
},
|
||||
mountedRoutes,
|
||||
);
|
||||
|
||||
expect(queryByTestId('template-card-separator')).toBeInTheDocument();
|
||||
@@ -272,11 +279,7 @@ describe('TemplateCard', () => {
|
||||
>
|
||||
<TemplateCard template={mockTemplate} additionalLinks={[]} />
|
||||
</TestApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:kind/:namespace/:name': entityRouteRef,
|
||||
},
|
||||
},
|
||||
mountedRoutes,
|
||||
);
|
||||
|
||||
expect(queryByTestId('template-card-separator')).toBeInTheDocument();
|
||||
@@ -321,11 +324,7 @@ describe('TemplateCard', () => {
|
||||
>
|
||||
<TemplateCard template={mockTemplate} additionalLinks={[]} />
|
||||
</TestApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:kind/:namespace/:name': entityRouteRef,
|
||||
},
|
||||
},
|
||||
mountedRoutes,
|
||||
);
|
||||
|
||||
expect(queryByTestId('template-card-separator')).not.toBeInTheDocument();
|
||||
@@ -364,17 +363,13 @@ describe('TemplateCard', () => {
|
||||
>
|
||||
<TemplateCard template={mockTemplate} />
|
||||
</TestApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:kind/:namespace/:name': entityRouteRef,
|
||||
},
|
||||
},
|
||||
mountedRoutes,
|
||||
);
|
||||
|
||||
expect(getByRole('link', { name: /.*my-test-user$/ })).toBeInTheDocument();
|
||||
expect(getByRole('link', { name: /.*my-test-user$/ })).toHaveAttribute(
|
||||
'href',
|
||||
'/catalog/group/default/my-test-user',
|
||||
'/catalog/default/group/my-test-user',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -404,11 +399,7 @@ describe('TemplateCard', () => {
|
||||
>
|
||||
<TemplateCard template={mockTemplate} onSelected={mockOnSelected} />
|
||||
</TestApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:kind/:namespace/:name': entityRouteRef,
|
||||
},
|
||||
},
|
||||
mountedRoutes,
|
||||
);
|
||||
|
||||
expect(getByRole('button', { name: 'Choose' })).toBeInTheDocument();
|
||||
@@ -448,11 +439,7 @@ describe('TemplateCard', () => {
|
||||
<TemplateCard template={mockTemplate} onSelected={mockOnSelected} />
|
||||
</TestApiProvider>
|
||||
</SWRConfig>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:kind/:namespace/:name': entityRouteRef,
|
||||
},
|
||||
},
|
||||
mountedRoutes,
|
||||
);
|
||||
|
||||
expect(queryByText('Choose')).toBeNull();
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2025 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 Tooltip from '@material-ui/core/Tooltip';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import DescriptionIcon from '@material-ui/icons/Description';
|
||||
import { Link } from '@backstage/core-components';
|
||||
import {
|
||||
entityRouteParams,
|
||||
entityRouteRef,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { useRouteRef } from '@backstage/core-plugin-api';
|
||||
import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
|
||||
import { scaffolderReactTranslationRef } from '../../../translation';
|
||||
import { useTranslationRef } from '@backstage/frontend-plugin-api';
|
||||
|
||||
export interface TemplateDetailButtonProps {
|
||||
template: Entity;
|
||||
}
|
||||
|
||||
export const TemplateDetailButton = ({
|
||||
template,
|
||||
}: TemplateDetailButtonProps) => {
|
||||
const catalogEntityRoute = useRouteRef(entityRouteRef);
|
||||
const { t } = useTranslationRef(scaffolderReactTranslationRef);
|
||||
const entityRef = stringifyEntityRef(template);
|
||||
|
||||
return (
|
||||
<Tooltip id={`tooltip-${entityRef}`} title={t('cardHeader.detailBtnTitle')}>
|
||||
<IconButton
|
||||
aria-label={t('cardHeader.detailBtnTitle')}
|
||||
id={`viewDetail-${entityRef}`}
|
||||
style={{ padding: 0 }}
|
||||
color="inherit"
|
||||
>
|
||||
<Typography component="span">
|
||||
<Link
|
||||
to={catalogEntityRoute(entityRouteParams(template))}
|
||||
style={{ display: 'flex', alignItems: 'center' }}
|
||||
>
|
||||
<DescriptionIcon />
|
||||
</Link>
|
||||
</Typography>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -44,6 +44,9 @@ export const scaffolderReactTranslationRef = createTranslationRef({
|
||||
noDescription: 'No description',
|
||||
chooseButtonText: 'Choose',
|
||||
},
|
||||
cardHeader: {
|
||||
detailBtnTitle: 'Show template entity details',
|
||||
},
|
||||
templateOutputs: {
|
||||
title: 'Text Output',
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user