mirror of
https://github.com/grafana/grafana.git
synced 2025-02-14 17:43:35 -06:00
Plugins Catalog: show Grafana and plugin dependencies (#39062)
* fix(@grafana/data): add a missing optional field to the plugin types * refactor(Plugins/ADmin): use the type from @grafana/data for plugin dependencies * fix(Datasources/Graphite): add missing `state` to useEffect dependencies * refactor(Plugins/Admin): remove unnecessary comment * feat(Plugins/Admin): add plugin and grafana dependencies to the CatalogPluginDetails * feat(Plugins/ADmin): show Grafana dependency under plugin details * feat(Plugins/Admin): show grafana and plugin dependencies for a plugin * test(Plugins/Admin): add a smoke test for plugin dependencies * refactor(Plugins/Admin): remove unused style from the header
This commit is contained in:
parent
fc73bc1161
commit
9173898fd6
@ -87,6 +87,7 @@ interface PluginDependencyInfo {
|
||||
}
|
||||
|
||||
export interface PluginDependencies {
|
||||
grafanaDependency?: string;
|
||||
grafanaVersion: string;
|
||||
plugins: PluginDependencyInfo[];
|
||||
}
|
||||
|
@ -16,13 +16,15 @@ export async function getCatalogPlugin(id: string): Promise<CatalogPlugin> {
|
||||
}
|
||||
|
||||
export async function getPluginDetails(id: string): Promise<CatalogPluginDetails> {
|
||||
const localPlugins = await getLocalPlugins(); // /api/plugins/<id>/settings
|
||||
const localPlugins = await getLocalPlugins();
|
||||
const local = localPlugins.find((p) => p.id === id);
|
||||
const isInstalled = Boolean(local);
|
||||
const [remote, versions] = await Promise.all([getRemotePlugin(id, isInstalled), getPluginVersions(id)]);
|
||||
const dependencies = remote?.json?.dependencies;
|
||||
|
||||
return {
|
||||
grafanaDependency: remote?.json?.dependencies?.grafanaDependency || '',
|
||||
grafanaDependency: dependencies?.grafanaDependency || dependencies?.grafanaVersion || '',
|
||||
pluginDependencies: dependencies?.plugins || [],
|
||||
links: remote?.json?.info.links || local?.info.links || [],
|
||||
readme: remote?.readme,
|
||||
versions,
|
||||
|
@ -1,10 +1,11 @@
|
||||
import React from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { useStyles2, Icon } from '@grafana/ui';
|
||||
|
||||
import { InstallControls } from './InstallControls';
|
||||
import { PluginDetailsHeaderSignature } from './PluginDetailsHeaderSignature';
|
||||
import { PluginDetailsHeaderDependencies } from './PluginDetailsHeaderDependencies';
|
||||
import { PluginLogo } from './PluginLogo';
|
||||
import { CatalogPlugin } from '../types';
|
||||
|
||||
@ -47,7 +48,7 @@ export function PluginDetailsHeader({ plugin, currentUrl, parentUrl }: Props): R
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div className={styles.headerInformation}>
|
||||
<div className={styles.headerInformationRow}>
|
||||
{/* Org name */}
|
||||
<span>{plugin.orgName}</span>
|
||||
|
||||
@ -73,6 +74,11 @@ export function PluginDetailsHeader({ plugin, currentUrl, parentUrl }: Props): R
|
||||
<PluginDetailsHeaderSignature plugin={plugin} />
|
||||
</div>
|
||||
|
||||
<PluginDetailsHeaderDependencies
|
||||
plugin={plugin}
|
||||
className={cx(styles.headerInformationRow, styles.headerInformationRowSecondary)}
|
||||
/>
|
||||
|
||||
<p>{plugin.description}</p>
|
||||
|
||||
<InstallControls plugin={plugin} />
|
||||
@ -106,11 +112,11 @@ export const getStyles = (theme: GrafanaTheme2) => {
|
||||
}
|
||||
}
|
||||
`,
|
||||
headerInformation: css`
|
||||
headerInformationRow: css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: ${theme.spacing()};
|
||||
margin-bottom: ${theme.spacing(3)};
|
||||
margin-bottom: ${theme.spacing()};
|
||||
|
||||
& > * {
|
||||
&::after {
|
||||
@ -124,6 +130,9 @@ export const getStyles = (theme: GrafanaTheme2) => {
|
||||
}
|
||||
font-size: ${theme.typography.h4.fontSize};
|
||||
`,
|
||||
headerInformationRowSecondary: css`
|
||||
font-size: ${theme.typography.body.fontSize};
|
||||
`,
|
||||
headerOrgName: css`
|
||||
font-size: ${theme.typography.h4.fontSize};
|
||||
`,
|
||||
|
@ -0,0 +1,66 @@
|
||||
import React from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { useStyles2, Icon } from '@grafana/ui';
|
||||
import { CatalogPlugin } 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;
|
||||
const grafanaDependency = plugin.details?.grafanaDependency;
|
||||
const hasNoDependencyInfo = !grafanaDependency && (!pluginDependencies || !pluginDependencies.length);
|
||||
|
||||
if (hasNoDependencyInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className={styles.textBold}>Dependencies:</div>
|
||||
|
||||
{/* Grafana dependency */}
|
||||
{Boolean(grafanaDependency) && (
|
||||
<div>
|
||||
<Icon name="grafana" />
|
||||
Grafana {grafanaDependency}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Plugin dependencies */}
|
||||
{pluginDependencies && pluginDependencies.length > 0 && (
|
||||
<div>
|
||||
{pluginDependencies.map((p) => {
|
||||
return (
|
||||
<span key={p.name}>
|
||||
<i className={PluginIconClassName[p.type] || PluginIconClassName.default} />
|
||||
{p.name} {p.version}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
textBold: css`
|
||||
font-weight: ${theme.typography.fontWeightBold};
|
||||
`,
|
||||
};
|
||||
};
|
@ -197,6 +197,15 @@ describe('Plugin details page', () => {
|
||||
await waitFor(() => expect(queryByRole('link', { name: /update via grafana.com/i })).toBeInTheDocument());
|
||||
expect(queryByRole('link', { name: /uninstall via grafana.com/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display grafana dependencies for a plugin if they are available', async () => {
|
||||
const { queryByText } = setup('not-installed');
|
||||
|
||||
// Wait for the dependencies part to be loaded
|
||||
await waitFor(() => expect(queryByText(/dependencies:/i)).toBeInTheDocument());
|
||||
|
||||
expect(queryByText('Grafana >=7.3.0')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
function remotePlugin(plugin: Partial<RemotePlugin> = {}): RemotePlugin {
|
||||
@ -237,6 +246,7 @@ function remotePlugin(plugin: Partial<RemotePlugin> = {}): RemotePlugin {
|
||||
dependencies: {
|
||||
grafanaDependency: '>=7.3.0',
|
||||
grafanaVersion: '7.3',
|
||||
plugins: [],
|
||||
},
|
||||
info: {
|
||||
links: [],
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { EntityState } from '@reduxjs/toolkit';
|
||||
import { PluginType, PluginSignatureStatus, PluginSignatureType } from '@grafana/data';
|
||||
import { PluginType, PluginSignatureStatus, PluginSignatureType, PluginDependencies } from '@grafana/data';
|
||||
import { StoreState, PluginsState } from 'app/types';
|
||||
|
||||
export type PluginTypeCode = 'app' | 'panel' | 'datasource';
|
||||
@ -44,6 +44,7 @@ export interface CatalogPluginDetails {
|
||||
url: string;
|
||||
}>;
|
||||
grafanaDependency?: string;
|
||||
pluginDependencies?: PluginDependencies['plugins'];
|
||||
}
|
||||
|
||||
export interface CatalogPluginInfo {
|
||||
@ -62,10 +63,7 @@ export type RemotePlugin = {
|
||||
id: number;
|
||||
internal: boolean;
|
||||
json?: {
|
||||
dependencies: {
|
||||
grafanaDependency: string;
|
||||
grafanaVersion: string;
|
||||
};
|
||||
dependencies: PluginDependencies;
|
||||
info: {
|
||||
links: Array<{
|
||||
name: string;
|
||||
|
@ -50,13 +50,13 @@ export const GraphiteQueryEditorContext = ({
|
||||
if (state) {
|
||||
dispatch(actions.queriesChanged(queries));
|
||||
}
|
||||
}, [dispatch, queries]);
|
||||
}, [dispatch, queries, state]);
|
||||
|
||||
useEffect(() => {
|
||||
if (state && state.target?.target !== query.target) {
|
||||
dispatch(actions.queryChanged(query));
|
||||
}
|
||||
}, [dispatch, query]);
|
||||
}, [dispatch, query, state]);
|
||||
|
||||
if (!state) {
|
||||
dispatch(
|
||||
|
Loading…
Reference in New Issue
Block a user