Use lower-case links within techdocs plugin; allow legacy behavior with config.
Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
@@ -17,11 +17,35 @@
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { configApiRef } from '@backstage/core-plugin-api';
|
||||
import { DocsCardGrid } from './DocsCardGrid';
|
||||
|
||||
// Hacky way to mock a specific boolean config value.
|
||||
const getOptionalBooleanMock = jest.fn().mockReturnValue(false);
|
||||
jest.mock('@backstage/core-plugin-api', () => ({
|
||||
...jest.requireActual('@backstage/core-plugin-api'),
|
||||
useApi: (apiRef: any) => {
|
||||
const actualUseApi = jest.requireActual(
|
||||
'@backstage/core-plugin-api',
|
||||
).useApi;
|
||||
const actualApi = actualUseApi(apiRef);
|
||||
if (apiRef === configApiRef) {
|
||||
const configReader = actualApi;
|
||||
configReader.getOptionalBoolean = getOptionalBooleanMock;
|
||||
return configReader;
|
||||
}
|
||||
|
||||
return actualApi;
|
||||
},
|
||||
}));
|
||||
|
||||
describe('Entity Docs Card Grid', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should render all entities passed ot it', async () => {
|
||||
const { findByText } = render(
|
||||
const { findByText, findAllByRole } = render(
|
||||
wrapInTestApp(
|
||||
<DocsCardGrid
|
||||
entities={[
|
||||
@@ -51,5 +75,44 @@ describe('Entity Docs Card Grid', () => {
|
||||
);
|
||||
expect(await findByText('testName')).toBeInTheDocument();
|
||||
expect(await findByText('testName2')).toBeInTheDocument();
|
||||
const [button1, button2] = await findAllByRole('button');
|
||||
expect(button1.getAttribute('href')).toContain(
|
||||
'/default/testkind/testname',
|
||||
);
|
||||
expect(button2.getAttribute('href')).toContain(
|
||||
'/default/testkind2/testname2',
|
||||
);
|
||||
});
|
||||
|
||||
it('should fall back to case-sensitive links when configured', async () => {
|
||||
getOptionalBooleanMock.mockReturnValue(true);
|
||||
|
||||
const { findByRole } = render(
|
||||
wrapInTestApp(
|
||||
<DocsCardGrid
|
||||
entities={[
|
||||
{
|
||||
apiVersion: 'version',
|
||||
kind: 'TestKind',
|
||||
metadata: {
|
||||
name: 'testName',
|
||||
namespace: 'SomeNamespace',
|
||||
},
|
||||
spec: {
|
||||
owner: 'techdocs@example.com',
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>,
|
||||
),
|
||||
);
|
||||
|
||||
const button = await findByRole('button');
|
||||
expect(getOptionalBooleanMock).toHaveBeenCalledWith(
|
||||
'techdocs.legacyUseCaseSensitiveTripletPaths',
|
||||
);
|
||||
expect(button.getAttribute('href')).toContain(
|
||||
'/SomeNamespace/TestKind/testName',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,6 +18,7 @@ import React from 'react';
|
||||
import { generatePath } from 'react-router-dom';
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { useApi, configApiRef } from '@backstage/core-plugin-api';
|
||||
import { Card, CardActions, CardContent, CardMedia } from '@material-ui/core';
|
||||
import { rootDocsRouteRef } from '../../routes';
|
||||
|
||||
@@ -32,6 +33,13 @@ export const DocsCardGrid = ({
|
||||
}: {
|
||||
entities: Entity[] | undefined;
|
||||
}) => {
|
||||
// Lower-case entity triplets by default, but allow override.
|
||||
const toLowerMaybe = useApi(configApiRef).getOptionalBoolean(
|
||||
'techdocs.legacyUseCaseSensitiveTripletPaths',
|
||||
)
|
||||
? (str: string) => str
|
||||
: (str: string) => str.toLocaleLowerCase();
|
||||
|
||||
if (!entities) return null;
|
||||
return (
|
||||
<ItemCardGrid data-testid="docs-explore">
|
||||
@@ -46,9 +54,11 @@ export const DocsCardGrid = ({
|
||||
<CardActions>
|
||||
<Button
|
||||
to={generatePath(rootDocsRouteRef.path, {
|
||||
namespace: entity.metadata.namespace ?? 'default',
|
||||
kind: entity.kind,
|
||||
name: entity.metadata.name,
|
||||
namespace: toLowerMaybe(
|
||||
entity.metadata.namespace ?? 'default',
|
||||
),
|
||||
kind: toLowerMaybe(entity.kind),
|
||||
name: toLowerMaybe(entity.metadata.name),
|
||||
})}
|
||||
color="primary"
|
||||
>
|
||||
|
||||
@@ -16,9 +16,33 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import { configApiRef } from '@backstage/core-plugin-api';
|
||||
import { DocsTable } from './DocsTable';
|
||||
|
||||
// Hacky way to mock a specific boolean config value.
|
||||
const getOptionalBooleanMock = jest.fn().mockReturnValue(false);
|
||||
jest.mock('@backstage/core-plugin-api', () => ({
|
||||
...jest.requireActual('@backstage/core-plugin-api'),
|
||||
useApi: (apiRef: any) => {
|
||||
const actualUseApi = jest.requireActual(
|
||||
'@backstage/core-plugin-api',
|
||||
).useApi;
|
||||
const actualApi = actualUseApi(apiRef);
|
||||
if (apiRef === configApiRef) {
|
||||
const configReader = actualApi;
|
||||
configReader.getOptionalBoolean = getOptionalBooleanMock;
|
||||
return configReader;
|
||||
}
|
||||
|
||||
return actualApi;
|
||||
},
|
||||
}));
|
||||
|
||||
describe('DocsTable test', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should render documents passed', async () => {
|
||||
const { findByText } = render(
|
||||
wrapInTestApp(
|
||||
@@ -69,8 +93,56 @@ describe('DocsTable test', () => {
|
||||
),
|
||||
);
|
||||
|
||||
expect(await findByText('testName')).toBeInTheDocument();
|
||||
expect(await findByText('testName2')).toBeInTheDocument();
|
||||
const link1 = await findByText('testName');
|
||||
const link2 = await findByText('testName2');
|
||||
expect(link1).toBeInTheDocument();
|
||||
expect(link1.getAttribute('href')).toContain('/default/testkind/testname');
|
||||
expect(link2).toBeInTheDocument();
|
||||
expect(link2.getAttribute('href')).toContain(
|
||||
'/default/testkind2/testname2',
|
||||
);
|
||||
});
|
||||
|
||||
it('should fall back to case-sensitive links when configured', async () => {
|
||||
getOptionalBooleanMock.mockReturnValue(true);
|
||||
|
||||
const { findByText } = render(
|
||||
wrapInTestApp(
|
||||
<DocsTable
|
||||
entities={[
|
||||
{
|
||||
apiVersion: 'version',
|
||||
kind: 'TestKind',
|
||||
metadata: {
|
||||
name: 'testName',
|
||||
namespace: 'SomeNamespace',
|
||||
},
|
||||
spec: {
|
||||
owner: 'user:owned',
|
||||
},
|
||||
relations: [
|
||||
{
|
||||
target: {
|
||||
kind: 'user',
|
||||
namespace: 'default',
|
||||
name: 'owned',
|
||||
},
|
||||
type: 'ownedBy',
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>,
|
||||
),
|
||||
);
|
||||
|
||||
const button = await findByText('testName');
|
||||
expect(getOptionalBooleanMock).toHaveBeenCalledWith(
|
||||
'techdocs.legacyUseCaseSensitiveTripletPaths',
|
||||
);
|
||||
expect(button.getAttribute('href')).toContain(
|
||||
'/SomeNamespace/TestKind/testName',
|
||||
);
|
||||
});
|
||||
|
||||
it('should render empty state if no owned documents exist', async () => {
|
||||
|
||||
@@ -21,6 +21,7 @@ import { generatePath } from 'react-router-dom';
|
||||
import { IconButton, Tooltip } from '@material-ui/core';
|
||||
import ShareIcon from '@material-ui/icons/Share';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { useApi, configApiRef } from '@backstage/core-plugin-api';
|
||||
import { rootDocsRouteRef } from '../../routes';
|
||||
import {
|
||||
Table,
|
||||
@@ -38,6 +39,12 @@ export const DocsTable = ({
|
||||
title?: string | undefined;
|
||||
}) => {
|
||||
const [, copyToClipboard] = useCopyToClipboard();
|
||||
// Lower-case entity triplets by default, but allow override.
|
||||
const toLowerMaybe = useApi(configApiRef).getOptionalBoolean(
|
||||
'techdocs.legacyUseCaseSensitiveTripletPaths',
|
||||
)
|
||||
? (str: string) => str
|
||||
: (str: string) => str.toLocaleLowerCase();
|
||||
|
||||
if (!entities) return null;
|
||||
|
||||
@@ -48,9 +55,9 @@ export const DocsTable = ({
|
||||
owner: entity?.spec?.owner,
|
||||
type: entity?.spec?.type,
|
||||
docsUrl: generatePath(rootDocsRouteRef.path, {
|
||||
namespace: entity.metadata.namespace ?? 'default',
|
||||
kind: entity.kind,
|
||||
name: entity.metadata.name,
|
||||
namespace: toLowerMaybe(entity.metadata.namespace ?? 'default'),
|
||||
kind: toLowerMaybe(entity.kind),
|
||||
name: toLowerMaybe(entity.metadata.name),
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user