feat(scaffolder): update scaffolder frontend to support additional backend permissions
Signed-off-by: Frank Kong <frkong@redhat.com>
This commit is contained in:
@@ -54,6 +54,7 @@
|
||||
"@backstage/core-components": "workspace:^",
|
||||
"@backstage/core-plugin-api": "workspace:^",
|
||||
"@backstage/plugin-catalog-react": "workspace:^",
|
||||
"@backstage/plugin-permission-react": "workspace:^",
|
||||
"@backstage/plugin-scaffolder-common": "workspace:^",
|
||||
"@backstage/theme": "workspace:^",
|
||||
"@backstage/types": "workspace:^",
|
||||
@@ -87,13 +88,15 @@
|
||||
"@backstage/core-app-api": "workspace:^",
|
||||
"@backstage/plugin-catalog": "workspace:^",
|
||||
"@backstage/plugin-catalog-common": "workspace:^",
|
||||
"@backstage/plugin-permission-common": "workspace:^",
|
||||
"@backstage/test-utils": "workspace:^",
|
||||
"@testing-library/dom": "^10.0.0",
|
||||
"@testing-library/jest-dom": "^6.0.0",
|
||||
"@testing-library/react": "^15.0.0",
|
||||
"@testing-library/user-event": "^14.0.0",
|
||||
"@types/humanize-duration": "^3.18.1",
|
||||
"@types/luxon": "^3.0.0"
|
||||
"@types/luxon": "^3.0.0",
|
||||
"swr": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
starredEntitiesApiRef,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import {
|
||||
MockPermissionApi,
|
||||
MockStorageApi,
|
||||
renderInTestApp,
|
||||
TestApiProvider,
|
||||
@@ -28,6 +29,12 @@ import React from 'react';
|
||||
import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
|
||||
import { RELATION_OWNED_BY } from '@backstage/catalog-model';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import {
|
||||
PermissionApi,
|
||||
permissionApiRef,
|
||||
} from '@backstage/plugin-permission-react';
|
||||
import { AuthorizeResult } from '@backstage/plugin-permission-common';
|
||||
import { SWRConfig } from 'swr';
|
||||
|
||||
describe('TemplateCard', () => {
|
||||
it('should render the card title', async () => {
|
||||
@@ -50,6 +57,7 @@ describe('TemplateCard', () => {
|
||||
storageApi: MockStorageApi.create(),
|
||||
}),
|
||||
],
|
||||
[permissionApiRef, new MockPermissionApi()],
|
||||
]}
|
||||
>
|
||||
<TemplateCard template={mockTemplate} />
|
||||
@@ -79,6 +87,7 @@ describe('TemplateCard', () => {
|
||||
storageApi: MockStorageApi.create(),
|
||||
}),
|
||||
],
|
||||
[permissionApiRef, new MockPermissionApi()],
|
||||
]}
|
||||
>
|
||||
<TemplateCard template={mockTemplate} />
|
||||
@@ -110,6 +119,7 @@ describe('TemplateCard', () => {
|
||||
storageApi: MockStorageApi.create(),
|
||||
}),
|
||||
],
|
||||
[permissionApiRef, new MockPermissionApi()],
|
||||
]}
|
||||
>
|
||||
<TemplateCard template={mockTemplate} />
|
||||
@@ -139,6 +149,7 @@ describe('TemplateCard', () => {
|
||||
storageApi: MockStorageApi.create(),
|
||||
}),
|
||||
],
|
||||
[permissionApiRef, new MockPermissionApi()],
|
||||
]}
|
||||
>
|
||||
<TemplateCard template={mockTemplate} />
|
||||
@@ -174,6 +185,7 @@ describe('TemplateCard', () => {
|
||||
storageApi: MockStorageApi.create(),
|
||||
}),
|
||||
],
|
||||
[permissionApiRef, new MockPermissionApi()],
|
||||
]}
|
||||
>
|
||||
<TemplateCard template={mockTemplate} />
|
||||
@@ -213,6 +225,7 @@ describe('TemplateCard', () => {
|
||||
storageApi: MockStorageApi.create(),
|
||||
}),
|
||||
],
|
||||
[permissionApiRef, new MockPermissionApi()],
|
||||
]}
|
||||
>
|
||||
<TemplateCard template={mockTemplate} />
|
||||
@@ -257,6 +270,7 @@ describe('TemplateCard', () => {
|
||||
storageApi: MockStorageApi.create(),
|
||||
}),
|
||||
],
|
||||
[permissionApiRef, new MockPermissionApi()],
|
||||
]}
|
||||
>
|
||||
<TemplateCard template={mockTemplate} additionalLinks={[]} />
|
||||
@@ -305,6 +319,7 @@ describe('TemplateCard', () => {
|
||||
storageApi: MockStorageApi.create(),
|
||||
}),
|
||||
],
|
||||
[permissionApiRef, new MockPermissionApi()],
|
||||
]}
|
||||
>
|
||||
<TemplateCard template={mockTemplate} additionalLinks={[]} />
|
||||
@@ -347,6 +362,7 @@ describe('TemplateCard', () => {
|
||||
storageApi: MockStorageApi.create(),
|
||||
}),
|
||||
],
|
||||
[permissionApiRef, new MockPermissionApi()],
|
||||
]}
|
||||
>
|
||||
<TemplateCard template={mockTemplate} />
|
||||
@@ -386,6 +402,7 @@ describe('TemplateCard', () => {
|
||||
storageApi: MockStorageApi.create(),
|
||||
}),
|
||||
],
|
||||
[permissionApiRef, new MockPermissionApi()],
|
||||
]}
|
||||
>
|
||||
<TemplateCard template={mockTemplate} onSelected={mockOnSelected} />
|
||||
@@ -403,4 +420,44 @@ describe('TemplateCard', () => {
|
||||
|
||||
expect(mockOnSelected).toHaveBeenCalledWith(mockTemplate);
|
||||
});
|
||||
it('should not render the choose button when user has insufficient permissions', async () => {
|
||||
const mockTemplate: TemplateEntityV1beta3 = {
|
||||
apiVersion: 'scaffolder.backstage.io/v1beta3',
|
||||
kind: 'Template',
|
||||
metadata: { name: 'bob', tags: ['cpp', 'react'] },
|
||||
spec: {
|
||||
steps: [],
|
||||
type: 'service',
|
||||
},
|
||||
};
|
||||
const mockOnSelected = jest.fn();
|
||||
const mockAuthorize = jest
|
||||
.fn()
|
||||
.mockImplementation(async () => ({ result: AuthorizeResult.DENY }));
|
||||
// SWR used by the usePermission hook needs cache to be reset for each test
|
||||
const { queryByText } = await renderInTestApp(
|
||||
<SWRConfig value={{ provider: () => new Map() }}>
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[
|
||||
starredEntitiesApiRef,
|
||||
new DefaultStarredEntitiesApi({
|
||||
storageApi: MockStorageApi.create(),
|
||||
}),
|
||||
],
|
||||
[permissionApiRef, new MockPermissionApi(mockAuthorize)],
|
||||
]}
|
||||
>
|
||||
<TemplateCard template={mockTemplate} onSelected={mockOnSelected} />
|
||||
</TestApiProvider>
|
||||
</SWRConfig>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:kind/:namespace/:name': entityRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(queryByText('Choose')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,6 +35,8 @@ import LanguageIcon from '@material-ui/icons/Language';
|
||||
import React from 'react';
|
||||
import { CardHeader } from './CardHeader';
|
||||
import { CardLink } from './CardLink';
|
||||
import { usePermission } from '@backstage/plugin-permission-react';
|
||||
import { taskCreatePermission } from '@backstage/plugin-scaffolder-common/alpha';
|
||||
|
||||
const useStyles = makeStyles<Theme>(theme => ({
|
||||
box: {
|
||||
@@ -103,6 +105,11 @@ export const TemplateCard = (props: TemplateCardProps) => {
|
||||
!!props.additionalLinks?.length || !!template.metadata.links?.length;
|
||||
const displayDefaultDivider = !hasTags && !hasLinks;
|
||||
|
||||
const { allowed: canCreateTask } = usePermission({
|
||||
permission: taskCreatePermission,
|
||||
resourceRef: 'task',
|
||||
});
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader template={template} />
|
||||
@@ -186,14 +193,16 @@ export const TemplateCard = (props: TemplateCardProps) => {
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => props.onSelected?.(template)}
|
||||
>
|
||||
Choose
|
||||
</Button>
|
||||
{canCreateTask ? (
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => props.onSelected?.(template)}
|
||||
>
|
||||
Choose
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</CardActions>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user