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:
@@ -105,6 +105,13 @@ export const catalogPlugin: BackstagePlugin<
|
||||
},
|
||||
true
|
||||
>;
|
||||
createFromTemplate: ExternalRouteRef<
|
||||
{
|
||||
namespace: string;
|
||||
templateName: string;
|
||||
},
|
||||
true
|
||||
>;
|
||||
},
|
||||
CatalogInputPluginOptions
|
||||
>;
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user