mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Plugins Catalog: fix overflowing text in plugin cards (#39862)
* refactor(Plugins/Admin): add a "badge" for displaying available updates * refactor(Plugins/Admin): rename component * refactor(Plugins/Admin): use the PluginListItemBadges component
This commit is contained in:
parent
fffbdf4c82
commit
3ad5ee87a3
@ -0,0 +1,33 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { Tooltip, useStyles2 } from '@grafana/ui';
|
||||||
|
import { CatalogPlugin } from '../../types';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
plugin: CatalogPlugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function PluginUpdateAvailableBadge({ plugin }: Props): React.ReactElement | null {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
|
if (plugin.hasUpdate && !plugin.isCore) {
|
||||||
|
return (
|
||||||
|
<Tooltip content={plugin.version}>
|
||||||
|
<p className={styles.hasUpdate}>Update available!</p>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getStyles = (theme: GrafanaTheme2) => {
|
||||||
|
return {
|
||||||
|
hasUpdate: css`
|
||||||
|
color: ${theme.colors.text.secondary};
|
||||||
|
font-size: ${theme.typography.bodySmall.fontSize};
|
||||||
|
margin-bottom: 0;
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
@ -1,3 +1,4 @@
|
|||||||
export { PluginDisabledBadge } from './PluginDisabledBadge';
|
export { PluginDisabledBadge } from './PluginDisabledBadge';
|
||||||
export { PluginInstalledBadge } from './PluginInstallBadge';
|
export { PluginInstalledBadge } from './PluginInstallBadge';
|
||||||
export { PluginEnterpriseBadge } from './PluginEnterpriseBadge';
|
export { PluginEnterpriseBadge } from './PluginEnterpriseBadge';
|
||||||
|
export { PluginUpdateAvailableBadge } from './PluginUpdateAvailableBadge';
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { PluginErrorCode, PluginSignatureStatus } from '@grafana/data';
|
import { PluginErrorCode, PluginSignatureStatus } from '@grafana/data';
|
||||||
import { PluginListBadges } from './PluginListBadges';
|
import { PluginListItemBadges } from './PluginListItemBadges';
|
||||||
import { CatalogPlugin } from '../types';
|
import { CatalogPlugin } from '../types';
|
||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
|
|
||||||
describe('PluginBadges', () => {
|
describe('PluginListItemBadges', () => {
|
||||||
const plugin: CatalogPlugin = {
|
const plugin: CatalogPlugin = {
|
||||||
description: 'The test plugin',
|
description: 'The test plugin',
|
||||||
downloads: 5,
|
downloads: 5,
|
||||||
@ -36,13 +36,13 @@ describe('PluginBadges', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('renders a plugin signature badge', () => {
|
it('renders a plugin signature badge', () => {
|
||||||
render(<PluginListBadges plugin={plugin} />);
|
render(<PluginListItemBadges plugin={plugin} />);
|
||||||
|
|
||||||
expect(screen.getByText(/signed/i)).toBeVisible();
|
expect(screen.getByText(/signed/i)).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders an installed badge', () => {
|
it('renders an installed badge', () => {
|
||||||
render(<PluginListBadges plugin={{ ...plugin, isInstalled: true }} />);
|
render(<PluginListItemBadges plugin={{ ...plugin, isInstalled: true }} />);
|
||||||
|
|
||||||
expect(screen.getByText(/signed/i)).toBeVisible();
|
expect(screen.getByText(/signed/i)).toBeVisible();
|
||||||
expect(screen.getByText(/installed/i)).toBeVisible();
|
expect(screen.getByText(/installed/i)).toBeVisible();
|
||||||
@ -50,21 +50,21 @@ describe('PluginBadges', () => {
|
|||||||
|
|
||||||
it('renders an enterprise badge (when a license is valid)', () => {
|
it('renders an enterprise badge (when a license is valid)', () => {
|
||||||
config.licenseInfo.hasValidLicense = true;
|
config.licenseInfo.hasValidLicense = true;
|
||||||
render(<PluginListBadges plugin={{ ...plugin, isEnterprise: true }} />);
|
render(<PluginListItemBadges plugin={{ ...plugin, isEnterprise: true }} />);
|
||||||
expect(screen.getByText(/enterprise/i)).toBeVisible();
|
expect(screen.getByText(/enterprise/i)).toBeVisible();
|
||||||
expect(screen.queryByRole('button', { name: /learn more/i })).not.toBeInTheDocument();
|
expect(screen.queryByRole('button', { name: /learn more/i })).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders an enterprise badge with icon and link (when a license is invalid)', () => {
|
it('renders an enterprise badge with icon and link (when a license is invalid)', () => {
|
||||||
config.licenseInfo.hasValidLicense = false;
|
config.licenseInfo.hasValidLicense = false;
|
||||||
render(<PluginListBadges plugin={{ ...plugin, isEnterprise: true }} />);
|
render(<PluginListItemBadges plugin={{ ...plugin, isEnterprise: true }} />);
|
||||||
expect(screen.getByText(/enterprise/i)).toBeVisible();
|
expect(screen.getByText(/enterprise/i)).toBeVisible();
|
||||||
expect(screen.getByLabelText(/lock icon/i)).toBeInTheDocument();
|
expect(screen.getByLabelText(/lock icon/i)).toBeInTheDocument();
|
||||||
expect(screen.getByRole('button', { name: /learn more/i })).toBeInTheDocument();
|
expect(screen.getByRole('button', { name: /learn more/i })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a error badge (when plugin has an error', () => {
|
it('renders a error badge (when plugin has an error', () => {
|
||||||
render(<PluginListBadges plugin={{ ...plugin, isDisabled: true, error: PluginErrorCode.modifiedSignature }} />);
|
render(<PluginListItemBadges plugin={{ ...plugin, isDisabled: true, error: PluginErrorCode.modifiedSignature }} />);
|
||||||
expect(screen.getByText(/disabled/i)).toBeVisible();
|
expect(screen.getByText(/disabled/i)).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -1,27 +1,29 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { HorizontalGroup, PluginSignatureBadge } from '@grafana/ui';
|
import { HorizontalGroup, PluginSignatureBadge } from '@grafana/ui';
|
||||||
import { CatalogPlugin } from '../types';
|
import { CatalogPlugin } from '../types';
|
||||||
import { PluginEnterpriseBadge, PluginDisabledBadge, PluginInstalledBadge } from './Badges';
|
import { PluginEnterpriseBadge, PluginDisabledBadge, PluginInstalledBadge, PluginUpdateAvailableBadge } from './Badges';
|
||||||
|
|
||||||
type PluginBadgeType = {
|
type PluginBadgeType = {
|
||||||
plugin: CatalogPlugin;
|
plugin: CatalogPlugin;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function PluginListBadges({ plugin }: PluginBadgeType) {
|
export function PluginListItemBadges({ plugin }: PluginBadgeType) {
|
||||||
if (plugin.isEnterprise) {
|
if (plugin.isEnterprise) {
|
||||||
return (
|
return (
|
||||||
<HorizontalGroup>
|
<HorizontalGroup height="auto" wrap>
|
||||||
<PluginEnterpriseBadge plugin={plugin} />
|
<PluginEnterpriseBadge plugin={plugin} />
|
||||||
{plugin.isDisabled && <PluginDisabledBadge error={plugin.error} />}
|
{plugin.isDisabled && <PluginDisabledBadge error={plugin.error} />}
|
||||||
|
<PluginUpdateAvailableBadge plugin={plugin} />
|
||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HorizontalGroup>
|
<HorizontalGroup height="auto" wrap>
|
||||||
<PluginSignatureBadge status={plugin.signature} />
|
<PluginSignatureBadge status={plugin.signature} />
|
||||||
{plugin.isDisabled && <PluginDisabledBadge error={plugin.error} />}
|
{plugin.isDisabled && <PluginDisabledBadge error={plugin.error} />}
|
||||||
{plugin.isInstalled && <PluginInstalledBadge />}
|
{plugin.isInstalled && <PluginInstalledBadge />}
|
||||||
|
<PluginUpdateAvailableBadge plugin={plugin} />
|
||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Icon, useStyles2, HorizontalGroup, Tooltip, CardContainer, VerticalGroup } from '@grafana/ui';
|
import { Icon, useStyles2, CardContainer, VerticalGroup } from '@grafana/ui';
|
||||||
import { CatalogPlugin, PluginIconName, PluginListDisplayMode, PluginTabIds } from '../types';
|
import { CatalogPlugin, PluginIconName, PluginListDisplayMode, PluginTabIds } from '../types';
|
||||||
import { PluginLogo } from './PluginLogo';
|
import { PluginLogo } from './PluginLogo';
|
||||||
import { PluginListBadges } from './PluginListBadges';
|
import { PluginListItemBadges } from './PluginListItemBadges';
|
||||||
import { getStyles, LOGO_SIZE } from './PluginListItem';
|
import { getStyles, LOGO_SIZE } from './PluginListItem';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -17,28 +17,30 @@ export function PluginListItemCard({ plugin, pathName }: Props) {
|
|||||||
<CardContainer href={`${pathName}/${plugin.id}?page=${PluginTabIds.OVERVIEW}`} className={styles.cardContainer}>
|
<CardContainer href={`${pathName}/${plugin.id}?page=${PluginTabIds.OVERVIEW}`} className={styles.cardContainer}>
|
||||||
<VerticalGroup spacing="md">
|
<VerticalGroup spacing="md">
|
||||||
<div className={styles.headerWrap}>
|
<div className={styles.headerWrap}>
|
||||||
|
{/* Logo */}
|
||||||
<PluginLogo
|
<PluginLogo
|
||||||
src={plugin.info.logos.small}
|
src={plugin.info.logos.small}
|
||||||
alt={`${plugin.name} logo`}
|
alt={`${plugin.name} logo`}
|
||||||
className={styles.image}
|
className={styles.image}
|
||||||
height={LOGO_SIZE}
|
height={LOGO_SIZE}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Name */}
|
||||||
<h2 className={styles.name}>{plugin.name}</h2>
|
<h2 className={styles.name}>{plugin.name}</h2>
|
||||||
|
|
||||||
|
{/* Type Icon */}
|
||||||
{plugin.type && (
|
{plugin.type && (
|
||||||
<div className={styles.icon} data-testid={`${plugin.type} plugin icon`}>
|
<div className={styles.icon} data-testid={`${plugin.type} plugin icon`}>
|
||||||
<Icon name={PluginIconName[plugin.type]} />
|
<Icon name={PluginIconName[plugin.type]} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Org */}
|
||||||
<p className={styles.orgName}>By {plugin.orgName}</p>
|
<p className={styles.orgName}>By {plugin.orgName}</p>
|
||||||
<HorizontalGroup align="center">
|
|
||||||
<PluginListBadges plugin={plugin} />
|
{/* Badges */}
|
||||||
{plugin.hasUpdate && !plugin.isCore ? (
|
<PluginListItemBadges plugin={plugin} />
|
||||||
<Tooltip content={plugin.version}>
|
|
||||||
<p className={styles.hasUpdate}>Update available!</p>
|
|
||||||
</Tooltip>
|
|
||||||
) : null}
|
|
||||||
</HorizontalGroup>
|
|
||||||
</VerticalGroup>
|
</VerticalGroup>
|
||||||
</CardContainer>
|
</CardContainer>
|
||||||
);
|
);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Icon, useStyles2, HorizontalGroup, Tooltip, CardContainer, VerticalGroup } from '@grafana/ui';
|
import { Icon, useStyles2, CardContainer, VerticalGroup } from '@grafana/ui';
|
||||||
import { CatalogPlugin, PluginIconName, PluginListDisplayMode, PluginTabIds } from '../types';
|
import { CatalogPlugin, PluginIconName, PluginListDisplayMode, PluginTabIds } from '../types';
|
||||||
import { PluginLogo } from './PluginLogo';
|
import { PluginLogo } from './PluginLogo';
|
||||||
import { PluginListBadges } from './PluginListBadges';
|
import { PluginListItemBadges } from './PluginListItemBadges';
|
||||||
import { getStyles, LOGO_SIZE } from './PluginListItem';
|
import { getStyles, LOGO_SIZE } from './PluginListItem';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -17,24 +17,26 @@ export function PluginListItemRow({ plugin, pathName }: Props) {
|
|||||||
<CardContainer href={`${pathName}/${plugin.id}?page=${PluginTabIds.OVERVIEW}`} className={styles.cardContainer}>
|
<CardContainer href={`${pathName}/${plugin.id}?page=${PluginTabIds.OVERVIEW}`} className={styles.cardContainer}>
|
||||||
<VerticalGroup spacing="md">
|
<VerticalGroup spacing="md">
|
||||||
<div className={styles.headerWrap}>
|
<div className={styles.headerWrap}>
|
||||||
|
{/* Logo */}
|
||||||
<PluginLogo
|
<PluginLogo
|
||||||
src={plugin.info.logos.small}
|
src={plugin.info.logos.small}
|
||||||
alt={`${plugin.name} logo`}
|
alt={`${plugin.name} logo`}
|
||||||
className={styles.image}
|
className={styles.image}
|
||||||
height={LOGO_SIZE}
|
height={LOGO_SIZE}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
{/* Name */}
|
||||||
<h3 className={styles.name}>{plugin.name}</h3>
|
<h3 className={styles.name}>{plugin.name}</h3>
|
||||||
|
|
||||||
|
{/* Org */}
|
||||||
<p className={styles.orgName}>By {plugin.orgName}</p>
|
<p className={styles.orgName}>By {plugin.orgName}</p>
|
||||||
<HorizontalGroup height="auto">
|
|
||||||
<PluginListBadges plugin={plugin} />
|
{/* Badges */}
|
||||||
{plugin.hasUpdate && !plugin.isCore && (
|
<PluginListItemBadges plugin={plugin} />
|
||||||
<Tooltip content={plugin.version}>
|
|
||||||
<p className={styles.hasUpdate}>Update available!</p>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</HorizontalGroup>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Type Icon */}
|
||||||
{plugin.type && (
|
{plugin.type && (
|
||||||
<div className={styles.icon}>
|
<div className={styles.icon}>
|
||||||
<Icon name={PluginIconName[plugin.type]} aria-label={`${plugin.type} plugin icon`} />
|
<Icon name={PluginIconName[plugin.type]} aria-label={`${plugin.type} plugin icon`} />
|
||||||
|
Loading…
Reference in New Issue
Block a user