From 9173898fd6df3e65e31b02e43e6de9e321ec7df7 Mon Sep 17 00:00:00 2001 From: Levente Balogh Date: Fri, 10 Sep 2021 11:32:21 +0200 Subject: [PATCH] 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 --- packages/grafana-data/src/types/plugin.ts | 1 + public/app/features/plugins/admin/api.ts | 6 +- .../admin/components/PluginDetailsHeader.tsx | 17 +++-- .../PluginDetailsHeaderDependencies.tsx | 66 +++++++++++++++++++ .../admin/pages/PluginDetails.test.tsx | 10 +++ public/app/features/plugins/admin/types.ts | 8 +-- .../datasource/graphite/state/context.tsx | 4 +- 7 files changed, 99 insertions(+), 13 deletions(-) create mode 100644 public/app/features/plugins/admin/components/PluginDetailsHeaderDependencies.tsx diff --git a/packages/grafana-data/src/types/plugin.ts b/packages/grafana-data/src/types/plugin.ts index 4aa9c807606..94d037cea5e 100644 --- a/packages/grafana-data/src/types/plugin.ts +++ b/packages/grafana-data/src/types/plugin.ts @@ -87,6 +87,7 @@ interface PluginDependencyInfo { } export interface PluginDependencies { + grafanaDependency?: string; grafanaVersion: string; plugins: PluginDependencyInfo[]; } diff --git a/public/app/features/plugins/admin/api.ts b/public/app/features/plugins/admin/api.ts index 33ddce546cb..6af527707e1 100644 --- a/public/app/features/plugins/admin/api.ts +++ b/public/app/features/plugins/admin/api.ts @@ -16,13 +16,15 @@ export async function getCatalogPlugin(id: string): Promise { } export async function getPluginDetails(id: string): Promise { - const localPlugins = await getLocalPlugins(); // /api/plugins//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, diff --git a/public/app/features/plugins/admin/components/PluginDetailsHeader.tsx b/public/app/features/plugins/admin/components/PluginDetailsHeader.tsx index 8fa58a85564..caeaeb37b17 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsHeader.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsHeader.tsx @@ -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 -
+
{/* Org name */} {plugin.orgName} @@ -73,6 +74,11 @@ export function PluginDetailsHeader({ plugin, currentUrl, parentUrl }: Props): R
+ +

{plugin.description}

@@ -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}; `, diff --git a/public/app/features/plugins/admin/components/PluginDetailsHeaderDependencies.tsx b/public/app/features/plugins/admin/components/PluginDetailsHeaderDependencies.tsx new file mode 100644 index 00000000000..84431c14305 --- /dev/null +++ b/public/app/features/plugins/admin/components/PluginDetailsHeaderDependencies.tsx @@ -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 = { + 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 ( +
+
Dependencies:
+ + {/* Grafana dependency */} + {Boolean(grafanaDependency) && ( +
+ + Grafana {grafanaDependency} +
+ )} + + {/* Plugin dependencies */} + {pluginDependencies && pluginDependencies.length > 0 && ( +
+ {pluginDependencies.map((p) => { + return ( + + + {p.name} {p.version} + + ); + })} +
+ )} +
+ ); +} + +export const getStyles = (theme: GrafanaTheme2) => { + return { + textBold: css` + font-weight: ${theme.typography.fontWeightBold}; + `, + }; +}; diff --git a/public/app/features/plugins/admin/pages/PluginDetails.test.tsx b/public/app/features/plugins/admin/pages/PluginDetails.test.tsx index b1dea4f98bd..4cfa60aa45c 100644 --- a/public/app/features/plugins/admin/pages/PluginDetails.test.tsx +++ b/public/app/features/plugins/admin/pages/PluginDetails.test.tsx @@ -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 { @@ -237,6 +246,7 @@ function remotePlugin(plugin: Partial = {}): RemotePlugin { dependencies: { grafanaDependency: '>=7.3.0', grafanaVersion: '7.3', + plugins: [], }, info: { links: [], diff --git a/public/app/features/plugins/admin/types.ts b/public/app/features/plugins/admin/types.ts index 6fa9f122039..c2bbebfb568 100644 --- a/public/app/features/plugins/admin/types.ts +++ b/public/app/features/plugins/admin/types.ts @@ -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; diff --git a/public/app/plugins/datasource/graphite/state/context.tsx b/public/app/plugins/datasource/graphite/state/context.tsx index 6a9483e67a3..e5a4883b31a 100644 --- a/public/app/plugins/datasource/graphite/state/context.tsx +++ b/public/app/plugins/datasource/graphite/state/context.tsx @@ -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(