Merge pull request #5107 from backstage/mob/detach-badges
detach the badges plugin from the catalog plugin
This commit is contained in:
@@ -25,6 +25,7 @@ import {
|
||||
import { renderWithEffects } from '@backstage/test-utils';
|
||||
import { BadgesApi, badgesApiRef } from '../api';
|
||||
import { EntityBadgesDialog } from './EntityBadgesDialog';
|
||||
import { EntityProvider } from '@backstage/plugin-catalog-react';
|
||||
|
||||
describe('EntityBadgesDialog', () => {
|
||||
it('should render', async () => {
|
||||
@@ -50,7 +51,9 @@ describe('EntityBadgesDialog', () => {
|
||||
{} as ErrorApi,
|
||||
)}
|
||||
>
|
||||
<EntityBadgesDialog open entity={mockEntity} />
|
||||
<EntityProvider entity={mockEntity}>
|
||||
<EntityBadgesDialog open />
|
||||
</EntityProvider>
|
||||
</ApiProvider>,
|
||||
);
|
||||
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import {
|
||||
CodeSnippet,
|
||||
Progress,
|
||||
ResponseErrorPanel,
|
||||
useApi,
|
||||
} from '@backstage/core';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@@ -39,43 +39,44 @@ import { badgesApiRef } from '../api';
|
||||
type Props = {
|
||||
open: boolean;
|
||||
onClose?: () => any;
|
||||
entity: Entity;
|
||||
};
|
||||
|
||||
export const EntityBadgesDialog = ({ open, onClose, entity }: Props) => {
|
||||
export const EntityBadgesDialog = ({ open, onClose }: Props) => {
|
||||
const theme = useTheme();
|
||||
const { entity } = useEntity();
|
||||
const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const badgesApi = useApi(badgesApiRef);
|
||||
|
||||
const { value: badges, loading, error } = useAsync(async () => {
|
||||
if (open) {
|
||||
if (open && entity) {
|
||||
return await badgesApi.getEntityBadgeSpecs(entity);
|
||||
}
|
||||
|
||||
return [];
|
||||
}, [badgesApi, entity, open]);
|
||||
|
||||
const content = (badges || []).map(
|
||||
({ badge: { description }, id, url, markdown }) => (
|
||||
<DialogContentText>
|
||||
<Box m={4} />
|
||||
<img alt={description || id} src={url} />
|
||||
<CodeSnippet language="markdown" text={markdown} showCopyCodeButton />
|
||||
</DialogContentText>
|
||||
<Box marginTop={4} key={id}>
|
||||
<DialogContentText component="div">
|
||||
<img alt={description || id} src={url} />
|
||||
<CodeSnippet language="markdown" text={markdown} showCopyCodeButton />
|
||||
</DialogContentText>
|
||||
</Box>
|
||||
),
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog fullScreen={fullScreen} open={open} onClose={onClose}>
|
||||
<DialogTitle>Entity Badges</DialogTitle>
|
||||
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
Embed badges in other web sites that link back to this entity. Copy
|
||||
the relevant snippet of Markdown code to use the badge.
|
||||
</DialogContentText>
|
||||
|
||||
{loading && <Progress />}
|
||||
{error && <ResponseErrorPanel error={error} />}
|
||||
|
||||
{content}
|
||||
</DialogContent>
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
"@backstage/core": "^0.7.2",
|
||||
"@backstage/integration": "^0.5.1",
|
||||
"@backstage/integration-react": "^0.1.1",
|
||||
"@backstage/plugin-badges": "^0.1.1",
|
||||
"@backstage/plugin-catalog-react": "^0.1.2",
|
||||
"@backstage/theme": "^0.2.4",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
|
||||
@@ -14,22 +14,53 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import SearchIcon from '@material-ui/icons/Search';
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
import * as React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { EntityContextMenu } from './EntityContextMenu';
|
||||
|
||||
describe('ComponentContextMenu', () => {
|
||||
it('should call onUnregisterEntity on button click', async () => {
|
||||
await act(async () => {
|
||||
const mockCallback = jest.fn();
|
||||
const menu = render(
|
||||
<EntityContextMenu onUnregisterEntity={mockCallback} />,
|
||||
);
|
||||
const button = await menu.findByTestId('menu-button');
|
||||
fireEvent.click(button);
|
||||
const unregister = await menu.findByText('Unregister entity');
|
||||
expect(unregister).toBeInTheDocument();
|
||||
});
|
||||
const mockCallback = jest.fn();
|
||||
|
||||
await renderInTestApp(
|
||||
<EntityContextMenu onUnregisterEntity={mockCallback} />,
|
||||
);
|
||||
|
||||
const button = await screen.findByTestId('menu-button');
|
||||
expect(button).toBeInTheDocument();
|
||||
fireEvent.click(button);
|
||||
|
||||
const unregister = await screen.findByText('Unregister entity');
|
||||
expect(unregister).toBeInTheDocument();
|
||||
fireEvent.click(unregister);
|
||||
|
||||
expect(mockCallback).toBeCalled();
|
||||
});
|
||||
|
||||
it('supports extra items', async () => {
|
||||
const extra = {
|
||||
title: 'HELLO',
|
||||
Icon: SearchIcon,
|
||||
onClick: jest.fn(),
|
||||
};
|
||||
|
||||
await renderInTestApp(
|
||||
<EntityContextMenu
|
||||
onUnregisterEntity={jest.fn()}
|
||||
UNSTABLE_extraContextMenuItems={[extra]}
|
||||
/>,
|
||||
);
|
||||
|
||||
const button = await screen.findByTestId('menu-button');
|
||||
expect(button).toBeInTheDocument();
|
||||
fireEvent.click(button);
|
||||
|
||||
const item = await screen.findByText('HELLO');
|
||||
expect(item).toBeInTheDocument();
|
||||
fireEvent.click(item);
|
||||
|
||||
expect(extra.onClick).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,17 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { IconComponent } from '@backstage/core';
|
||||
import {
|
||||
Divider,
|
||||
IconButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
Popover,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import Cancel from '@material-ui/icons/Cancel';
|
||||
import BadgeIcon from '@material-ui/icons/CallToAction';
|
||||
import MoreVert from '@material-ui/icons/MoreVert';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
@@ -35,13 +36,21 @@ const useStyles = makeStyles({
|
||||
},
|
||||
});
|
||||
|
||||
// NOTE(freben): Intentionally not exported at this point, since it's part of
|
||||
// the unstable extra context menu items concept below
|
||||
type ExtraContextMenuItem = {
|
||||
title: string;
|
||||
Icon: IconComponent;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
onShowBadgesDialog?: () => void;
|
||||
UNSTABLE_extraContextMenuItems?: ExtraContextMenuItem[];
|
||||
onUnregisterEntity: () => void;
|
||||
};
|
||||
|
||||
export const EntityContextMenu = ({
|
||||
onShowBadgesDialog,
|
||||
UNSTABLE_extraContextMenuItems,
|
||||
onUnregisterEntity,
|
||||
}: Props) => {
|
||||
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement>();
|
||||
@@ -55,6 +64,24 @@ export const EntityContextMenu = ({
|
||||
setAnchorEl(undefined);
|
||||
};
|
||||
|
||||
const extraItems = UNSTABLE_extraContextMenuItems && [
|
||||
...UNSTABLE_extraContextMenuItems.map(item => (
|
||||
<MenuItem
|
||||
key={item.title}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
item.onClick();
|
||||
}}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<item.Icon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={item.title} />
|
||||
</MenuItem>
|
||||
)),
|
||||
<Divider key="the divider is here!" />,
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<IconButton
|
||||
@@ -75,19 +102,7 @@ export const EntityContextMenu = ({
|
||||
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||
>
|
||||
<MenuList>
|
||||
{onShowBadgesDialog && (
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
onClose();
|
||||
onShowBadgesDialog();
|
||||
}}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<BadgeIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<Typography variant="inherit">Badges</Typography>
|
||||
</MenuItem>
|
||||
)}
|
||||
{extraItems}
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
onClose();
|
||||
@@ -97,7 +112,7 @@ export const EntityContextMenu = ({
|
||||
<ListItemIcon>
|
||||
<Cancel fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<Typography variant="inherit">Unregister entity</Typography>
|
||||
<ListItemText primary="Unregister entity" />
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Popover>
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
Content,
|
||||
Header,
|
||||
HeaderLabel,
|
||||
IconComponent,
|
||||
Page,
|
||||
Progress,
|
||||
TabbedLayout,
|
||||
@@ -29,12 +30,7 @@ import {
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { Box } from '@material-ui/core';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import {
|
||||
default as React,
|
||||
PropsWithChildren,
|
||||
useContext,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { default as React, useContext, useState } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu';
|
||||
import { FavouriteEntity } from '../FavouriteEntity/FavouriteEntity';
|
||||
@@ -79,6 +75,19 @@ const headerProps = (
|
||||
};
|
||||
};
|
||||
|
||||
// NOTE(freben): Intentionally not exported at this point, since it's part of
|
||||
// the unstable extra context menu items concept below
|
||||
type ExtraContextMenuItem = {
|
||||
title: string;
|
||||
Icon: IconComponent;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
type EntityLayoutProps = {
|
||||
UNSTABLE_extraContextMenuItems?: ExtraContextMenuItem[];
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* EntityLayout is a compound component, which allows you to define a layout for
|
||||
* entities using a sub-navigation mechanism.
|
||||
@@ -94,7 +103,10 @@ const headerProps = (
|
||||
* </EntityLayout>
|
||||
* ```
|
||||
*/
|
||||
export const EntityLayout = ({ children }: PropsWithChildren<{}>) => {
|
||||
export const EntityLayout = ({
|
||||
UNSTABLE_extraContextMenuItems,
|
||||
children,
|
||||
}: EntityLayoutProps) => {
|
||||
const { kind, namespace, name } = useEntityCompoundName();
|
||||
const { entity, loading, error } = useContext(EntityContext);
|
||||
|
||||
@@ -132,7 +144,10 @@ export const EntityLayout = ({ children }: PropsWithChildren<{}>) => {
|
||||
label="Lifecycle"
|
||||
value={entity.spec?.lifecycle || 'unknown'}
|
||||
/>
|
||||
<EntityContextMenu onUnregisterEntity={showRemovalDialog} />
|
||||
<EntityContextMenu
|
||||
UNSTABLE_extraContextMenuItems={UNSTABLE_extraContextMenuItems}
|
||||
onUnregisterEntity={showRemovalDialog}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Header>
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
Progress,
|
||||
ResponseErrorPanel,
|
||||
WarningPanel,
|
||||
IconComponent,
|
||||
} from '@backstage/core';
|
||||
import {
|
||||
EntityContext,
|
||||
@@ -35,12 +36,11 @@ import {
|
||||
useEntityCompoundName,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { Box } from '@material-ui/core';
|
||||
import React, { PropsWithChildren, useContext, useState } from 'react';
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu';
|
||||
import { FavouriteEntity } from '../FavouriteEntity/FavouriteEntity';
|
||||
import { UnregisterEntityDialog } from '../UnregisterEntityDialog/UnregisterEntityDialog';
|
||||
import { EntityBadgesDialog } from '@backstage/plugin-badges';
|
||||
import { Tabbed } from './Tabbed';
|
||||
|
||||
const EntityPageTitle = ({
|
||||
@@ -99,7 +99,23 @@ const headerProps = (
|
||||
};
|
||||
};
|
||||
|
||||
export const EntityPageLayout = ({ children }: PropsWithChildren<{}>) => {
|
||||
// NOTE(freben): Intentionally not exported at this point, since it's part of
|
||||
// the unstable extra context menu items concept below
|
||||
type ExtraContextMenuItem = {
|
||||
title: string;
|
||||
Icon: IconComponent;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
type EntityPageLayoutProps = {
|
||||
UNSTABLE_extraContextMenuItems?: ExtraContextMenuItem[];
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
export const EntityPageLayout = ({
|
||||
children,
|
||||
UNSTABLE_extraContextMenuItems,
|
||||
}: EntityPageLayoutProps) => {
|
||||
const { kind, namespace, name } = useEntityCompoundName();
|
||||
const { entity, loading, error } = useContext(EntityContext);
|
||||
const { headerTitle, headerType } = headerProps(
|
||||
@@ -109,7 +125,6 @@ export const EntityPageLayout = ({ children }: PropsWithChildren<{}>) => {
|
||||
entity!,
|
||||
);
|
||||
|
||||
const [badgesDialogOpen, setBadgesDialogOpen] = useState(false);
|
||||
const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const cleanUpAfterRemoval = async () => {
|
||||
@@ -131,7 +146,7 @@ export const EntityPageLayout = ({ children }: PropsWithChildren<{}>) => {
|
||||
<>
|
||||
<EntityLabels entity={entity} />
|
||||
<EntityContextMenu
|
||||
onShowBadgesDialog={() => setBadgesDialogOpen(true)}
|
||||
UNSTABLE_extraContextMenuItems={UNSTABLE_extraContextMenuItems}
|
||||
onUnregisterEntity={showRemovalDialog}
|
||||
/>
|
||||
</>
|
||||
@@ -164,14 +179,6 @@ export const EntityPageLayout = ({ children }: PropsWithChildren<{}>) => {
|
||||
</Content>
|
||||
)}
|
||||
|
||||
{entity && (
|
||||
<EntityBadgesDialog
|
||||
open={badgesDialogOpen}
|
||||
entity={entity}
|
||||
onClose={() => setBadgesDialogOpen(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<UnregisterEntityDialog
|
||||
open={confirmationDialogOpen}
|
||||
entity={entity!}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-common": "0.5.6",
|
||||
"@backstage/backend-common": "^0.6.0",
|
||||
"@backstage/cli": "^0.6.0"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.5.3",
|
||||
"@backstage/backend-common": "^0.6.0",
|
||||
"@backstage/search-common": "^0.1.1",
|
||||
"@types/express": "^4.17.6",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^3.0.3",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"winston": "^3.2.1",
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user