Catalog: Add update text to PluginListCard (#39087)

* feat(catalog): add update info to PluginListCard

* refactor(catalog): use IconName enum and minor styling changes to PluginHeaderDependencies

* fix(catalog): add a semver range to grafanaVersion for dependency checks in InstallControls
This commit is contained in:
Jack Westbrook 2021-09-10 17:17:57 +02:00 committed by GitHub
parent cbf16bebc6
commit c9620b2202
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 50 additions and 31 deletions

View File

@ -21,9 +21,16 @@ export async function getPluginDetails(id: string): Promise<CatalogPluginDetails
const isInstalled = Boolean(local);
const [remote, versions] = await Promise.all([getRemotePlugin(id, isInstalled), getPluginVersions(id)]);
const dependencies = remote?.json?.dependencies;
// Prepend semver range when we fallback to grafanaVersion (deprecated in favour of grafanaDependency)
// otherwise plugins cannot be installed.
const grafanaDependency = dependencies?.grafanaDependency
? dependencies?.grafanaDependency
: dependencies?.grafanaVersion
? `>=${dependencies?.grafanaVersion}`
: '';
return {
grafanaDependency: dependencies?.grafanaDependency || dependencies?.grafanaVersion || '',
grafanaDependency,
pluginDependencies: dependencies?.plugins || [],
links: remote?.json?.info.links || local?.info.links || [],
readme: remote?.readme,

View File

@ -2,22 +2,13 @@ import React from 'react';
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2, Icon } from '@grafana/ui';
import { CatalogPlugin } from '../types';
import { CatalogPlugin, IconName } from '../types';
type Props = {
plugin: CatalogPlugin;
className?: string;
};
const PluginIconClassName: Record<string, string> = {
datasource: 'gicon gicon-datasources',
panel: 'icon-gf icon-gf-panel',
app: 'icon-gf icon-gf-apps',
page: 'icon-gf icon-gf-endpoint-tiny',
dashboard: 'gicon gicon-dashboard',
default: 'icon-gf icon-gf-apps',
};
export function PluginDetailsHeaderDependencies({ plugin, className }: Props): React.ReactElement | null {
const styles = useStyles2(getStyles);
const pluginDependencies = plugin.details?.pluginDependencies;
@ -30,12 +21,12 @@ export function PluginDetailsHeaderDependencies({ plugin, className }: Props): R
return (
<div className={className}>
<div className={styles.textBold}>Dependencies:</div>
<div className={styles.dependencyTitle}>Dependencies:</div>
{/* Grafana dependency */}
{Boolean(grafanaDependency) && (
<div>
<Icon name="grafana" />
<Icon name="grafana" className={styles.icon} />
Grafana {grafanaDependency}
</div>
)}
@ -46,7 +37,7 @@ export function PluginDetailsHeaderDependencies({ plugin, className }: Props): R
{pluginDependencies.map((p) => {
return (
<span key={p.name}>
<i className={PluginIconClassName[p.type] || PluginIconClassName.default} />
<Icon name={IconName[p.type]} className={styles.icon} />
{p.name} {p.version}
</span>
);
@ -59,8 +50,18 @@ export function PluginDetailsHeaderDependencies({ plugin, className }: Props): R
export const getStyles = (theme: GrafanaTheme2) => {
return {
textBold: css`
dependencyTitle: css`
font-weight: ${theme.typography.fontWeightBold};
margin-right: ${theme.spacing(0.5)};
&::after {
content: '';
padding: 0;
}
`,
icon: css`
color: ${theme.colors.text.secondary};
margin-right: ${theme.spacing(0.5)};
`,
};
};

View File

@ -1,31 +1,23 @@
import React from 'react';
import { css } from '@emotion/css';
import { Icon, useStyles2, CardContainer, VerticalGroup } from '@grafana/ui';
import { Icon, useStyles2, CardContainer, HorizontalGroup, VerticalGroup, Tooltip } from '@grafana/ui';
import { GrafanaTheme2 } from '@grafana/data';
import { CatalogPlugin } from '../types';
import { CatalogPlugin, IconName } from '../types';
import { PluginLogo } from './PluginLogo';
import { PluginListBadges } from './PluginListBadges';
const LOGO_SIZE = '48px';
enum IconName {
app = 'apps',
datasource = 'database',
panel = 'credit-card',
renderer = 'pen',
}
type PluginListCardProps = {
plugin: CatalogPlugin;
pathName: string;
};
export function PluginListCard({ plugin, pathName }: PluginListCardProps) {
const { name, id, orgName, type } = plugin;
const styles = useStyles2(getStyles);
return (
<CardContainer href={`${pathName}/${id}`} className={styles.cardContainer}>
<CardContainer href={`${pathName}/${plugin.id}`} className={styles.cardContainer}>
<VerticalGroup spacing="md">
<div className={styles.headerWrap}>
<PluginLogo
@ -34,15 +26,22 @@ export function PluginListCard({ plugin, pathName }: PluginListCardProps) {
className={styles.image}
height={LOGO_SIZE}
/>
<h3 className={styles.name}>{name}</h3>
{type && (
<h3 className={styles.name}>{plugin.name}</h3>
{plugin.type && (
<div className={styles.icon}>
<Icon name={IconName[type]} aria-label={`${type} plugin icon`} />
<Icon name={IconName[plugin.type]} aria-label={`${plugin.type} plugin icon`} />
</div>
)}
</div>
<p className={styles.orgName}>By {orgName}</p>
<PluginListBadges plugin={plugin} />
<p className={styles.orgName}>By {plugin.orgName}</p>
<HorizontalGroup align="center">
<PluginListBadges plugin={plugin} />
{plugin.hasUpdate && !plugin.isCore ? (
<Tooltip content={plugin.version}>
<p className={styles.hasUpdate}>Update available!</p>
</Tooltip>
) : null}
</HorizontalGroup>
</VerticalGroup>
</CardContainer>
);
@ -78,4 +77,9 @@ const getStyles = (theme: GrafanaTheme2) => ({
color: ${theme.colors.text.secondary};
margin-bottom: 0;
`,
hasUpdate: css`
color: ${theme.colors.text.secondary};
font-size: ${theme.typography.bodySmall.fontSize};
margin-bottom: 0;
`,
});

View File

@ -13,6 +13,13 @@ export enum PluginAdminRoutes {
DetailsAdmin = 'plugins-details-admin',
}
export enum IconName {
app = 'apps',
datasource = 'database',
panel = 'credit-card',
renderer = 'pen',
}
export interface CatalogPlugin {
description: string;
downloads: number;