mirror of
https://github.com/grafana/grafana.git
synced 2025-02-11 16:15:42 -06:00
Plugins: Refactor installation buttons component (#37642)
* refactor(catalog): split out installcontrols into multiple components * test(catalog): update tests for plugindetails page * refactor(catalog): rename installcontrols -> index * refactor(catalog): remove redundant curlies Co-authored-by: Levente Balogh <balogh.levente.hu@gmail.com> * tests(plugindetails): fix assertions and naming of tests Co-authored-by: Levente Balogh <balogh.levente.hu@gmail.com> * refactor(installcontrols): prefer enum over duplicate union type, rename disabled prop * refactor(installcontrols): use PluginStatus enum for installcontrols pluginStatus * refactor(installcontrols): remove redundant curlies Co-authored-by: Levente Balogh <balogh.levente.hu@gmail.com>
This commit is contained in:
parent
568549e810
commit
2cb141d4ec
@ -1,186 +0,0 @@
|
||||
import React from 'react';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { satisfies } from 'semver';
|
||||
|
||||
import { config } from '@grafana/runtime';
|
||||
import { Button, HorizontalGroup, Icon, LinkButton, useStyles2 } from '@grafana/ui';
|
||||
import { AppEvents, GrafanaTheme2 } from '@grafana/data';
|
||||
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { CatalogPluginDetails, ActionTypes } from '../types';
|
||||
import { api } from '../api';
|
||||
import { isGrafanaAdmin } from '../helpers';
|
||||
|
||||
interface Props {
|
||||
plugin: CatalogPluginDetails;
|
||||
isInflight: boolean;
|
||||
hasUpdate: boolean;
|
||||
hasInstalledPanel: boolean;
|
||||
isInstalled: boolean;
|
||||
dispatch: React.Dispatch<any>;
|
||||
}
|
||||
|
||||
export const InstallControls = ({ plugin, isInflight, hasUpdate, isInstalled, hasInstalledPanel, dispatch }: Props) => {
|
||||
const isExternallyManaged = config.pluginAdminExternalManageEnabled;
|
||||
const externalManageLink = getExternalManageLink(plugin);
|
||||
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
if (!plugin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const onInstall = async () => {
|
||||
dispatch({ type: ActionTypes.INFLIGHT });
|
||||
try {
|
||||
await api.installPlugin(plugin.id, plugin.version);
|
||||
appEvents.emit(AppEvents.alertSuccess, [`Installed ${plugin.name}`]);
|
||||
dispatch({ type: ActionTypes.INSTALLED, payload: plugin.type === 'panel' });
|
||||
} catch (error) {
|
||||
dispatch({ type: ActionTypes.ERROR, payload: { error } });
|
||||
}
|
||||
};
|
||||
|
||||
const onUninstall = async () => {
|
||||
dispatch({ type: ActionTypes.INFLIGHT });
|
||||
try {
|
||||
await api.uninstallPlugin(plugin.id);
|
||||
appEvents.emit(AppEvents.alertSuccess, [`Uninstalled ${plugin.name}`]);
|
||||
dispatch({ type: ActionTypes.UNINSTALLED });
|
||||
} catch (error) {
|
||||
dispatch({ type: ActionTypes.ERROR, payload: error });
|
||||
}
|
||||
};
|
||||
|
||||
const onUpdate = async () => {
|
||||
dispatch({ type: ActionTypes.INFLIGHT });
|
||||
try {
|
||||
await api.installPlugin(plugin.id, plugin.version);
|
||||
appEvents.emit(AppEvents.alertSuccess, [`Updated ${plugin.name}`]);
|
||||
dispatch({ type: ActionTypes.UPDATED });
|
||||
} catch (error) {
|
||||
dispatch({ type: ActionTypes.ERROR, payload: error });
|
||||
}
|
||||
};
|
||||
|
||||
const grafanaDependency = plugin.grafanaDependency;
|
||||
const unsupportedGrafanaVersion = grafanaDependency
|
||||
? !satisfies(config.buildInfo.version, grafanaDependency, {
|
||||
// needed for when running against master
|
||||
includePrerelease: true,
|
||||
})
|
||||
: false;
|
||||
|
||||
const isDevelopmentBuild = Boolean(plugin.isDev);
|
||||
const isEnterprise = plugin.isEnterprise;
|
||||
const isCore = plugin.isCore;
|
||||
const hasPermission = isGrafanaAdmin();
|
||||
|
||||
if (isCore) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isEnterprise && !config.licenseInfo?.hasValidLicense) {
|
||||
return (
|
||||
<HorizontalGroup height="auto" align="center">
|
||||
<span className={styles.message}>No valid Grafana Enterprise license detected.</span>
|
||||
<LinkButton
|
||||
href={`${getExternalManageLink(plugin)}?utm_source=grafana_catalog_learn_more`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
size="sm"
|
||||
fill="text"
|
||||
icon="external-link-alt"
|
||||
>
|
||||
Learn more
|
||||
</LinkButton>
|
||||
</HorizontalGroup>
|
||||
);
|
||||
}
|
||||
|
||||
if (isDevelopmentBuild) {
|
||||
return (
|
||||
<div className={styles.message}>This is a development build of the plugin and can't be uninstalled.</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!hasPermission && !isExternallyManaged) {
|
||||
const pluginStatus = isInstalled ? 'uninstall' : hasUpdate ? 'update' : 'install';
|
||||
const message = `You do not have permission to ${pluginStatus} this plugin.`;
|
||||
return <div className={styles.message}>{message}</div>;
|
||||
}
|
||||
|
||||
if (isInstalled) {
|
||||
return (
|
||||
<HorizontalGroup height="auto">
|
||||
{hasUpdate &&
|
||||
(isExternallyManaged ? (
|
||||
<LinkButton href={externalManageLink} target="_blank" rel="noopener noreferrer">
|
||||
{'Update via grafana.com'}
|
||||
</LinkButton>
|
||||
) : (
|
||||
<Button disabled={isInflight || !hasPermission} onClick={onUpdate}>
|
||||
{isInflight ? 'Updating' : 'Update'}
|
||||
</Button>
|
||||
))}
|
||||
|
||||
{isExternallyManaged ? (
|
||||
<LinkButton variant="destructive" href={externalManageLink} target="_blank" rel="noopener noreferrer">
|
||||
{'Uninstall via grafana.com'}
|
||||
</LinkButton>
|
||||
) : (
|
||||
<>
|
||||
<Button variant="destructive" disabled={isInflight || !hasPermission} onClick={onUninstall}>
|
||||
{isInflight && !hasUpdate ? 'Uninstalling' : 'Uninstall'}
|
||||
</Button>
|
||||
{hasInstalledPanel && (
|
||||
<div className={cx(styles.message, styles.messageMargin)}>
|
||||
Please refresh your browser window before using this plugin.
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</HorizontalGroup>
|
||||
);
|
||||
}
|
||||
|
||||
if (unsupportedGrafanaVersion) {
|
||||
return (
|
||||
<div className={styles.message}>
|
||||
<Icon name="exclamation-triangle" />
|
||||
This plugin doesn't support your version of Grafana.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<HorizontalGroup height="auto">
|
||||
{isExternallyManaged ? (
|
||||
<LinkButton href={externalManageLink} target="_blank" rel="noopener noreferrer">
|
||||
{'Install via grafana.com'}
|
||||
</LinkButton>
|
||||
) : (
|
||||
<>
|
||||
<Button disabled={isInflight || !hasPermission} onClick={onInstall}>
|
||||
{isInflight ? 'Installing' : 'Install'}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</HorizontalGroup>
|
||||
);
|
||||
};
|
||||
|
||||
function getExternalManageLink(plugin: CatalogPluginDetails): string {
|
||||
return `https://grafana.com/grafana/plugins/${plugin.id}`;
|
||||
}
|
||||
|
||||
export const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
message: css`
|
||||
color: ${theme.colors.text.secondary};
|
||||
`,
|
||||
messageMargin: css`
|
||||
margin-left: ${theme.spacing()};
|
||||
`,
|
||||
};
|
||||
};
|
@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import { HorizontalGroup, LinkButton } from '@grafana/ui';
|
||||
import { getExternalManageLink } from '../../helpers';
|
||||
import { PluginStatus } from '../../types';
|
||||
|
||||
type ExternallyManagedButtonProps = {
|
||||
pluginId: string;
|
||||
pluginStatus: PluginStatus;
|
||||
};
|
||||
|
||||
export function ExternallyManagedButton({ pluginId, pluginStatus }: ExternallyManagedButtonProps) {
|
||||
const externalManageLink = getExternalManageLink(pluginId);
|
||||
|
||||
if (pluginStatus === PluginStatus.UPDATE) {
|
||||
return (
|
||||
<HorizontalGroup height="auto">
|
||||
<LinkButton href={externalManageLink} target="_blank" rel="noopener noreferrer">
|
||||
Update via grafana.com
|
||||
</LinkButton>
|
||||
<LinkButton variant="destructive" href={externalManageLink} target="_blank" rel="noopener noreferrer">
|
||||
Uninstall via grafana.com
|
||||
</LinkButton>
|
||||
</HorizontalGroup>
|
||||
);
|
||||
}
|
||||
|
||||
if (pluginStatus === PluginStatus.UNINSTALL) {
|
||||
return (
|
||||
<LinkButton variant="destructive" href={externalManageLink} target="_blank" rel="noopener noreferrer">
|
||||
Uninstall via grafana.com
|
||||
</LinkButton>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<LinkButton href={externalManageLink} target="_blank" rel="noopener noreferrer">
|
||||
Install via grafana.com
|
||||
</LinkButton>
|
||||
);
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
import React from 'react';
|
||||
import { AppEvents } from '@grafana/data';
|
||||
import { Button, HorizontalGroup, useStyles2 } from '@grafana/ui';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { api } from '../../api';
|
||||
import { ActionTypes, CatalogPlugin, PluginStatus } from '../../types';
|
||||
import { getStyles } from './index';
|
||||
|
||||
type InstallControlsButtonProps = {
|
||||
isInProgress: boolean;
|
||||
hasInstalledPanel: boolean;
|
||||
dispatch: React.Dispatch<any>;
|
||||
plugin: CatalogPlugin;
|
||||
pluginStatus: PluginStatus;
|
||||
};
|
||||
|
||||
export function InstallControlsButton({
|
||||
isInProgress,
|
||||
dispatch,
|
||||
plugin,
|
||||
pluginStatus,
|
||||
hasInstalledPanel,
|
||||
}: InstallControlsButtonProps) {
|
||||
const uninstallBtnText = isInProgress ? 'Uninstalling' : 'Uninstall';
|
||||
const updateBtnText = isInProgress ? 'Updating' : 'Update';
|
||||
const installBtnText = isInProgress ? 'Installing' : 'Install';
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const onInstall = async () => {
|
||||
dispatch({ type: ActionTypes.INFLIGHT });
|
||||
try {
|
||||
await api.installPlugin(plugin.id, plugin.version);
|
||||
appEvents.emit(AppEvents.alertSuccess, [`Installed ${plugin.name}`]);
|
||||
dispatch({ type: ActionTypes.INSTALLED, payload: plugin.type === 'panel' });
|
||||
} catch (error) {
|
||||
dispatch({ type: ActionTypes.ERROR, payload: { error } });
|
||||
}
|
||||
};
|
||||
|
||||
const onUninstall = async () => {
|
||||
dispatch({ type: ActionTypes.INFLIGHT });
|
||||
try {
|
||||
await api.uninstallPlugin(plugin.id);
|
||||
appEvents.emit(AppEvents.alertSuccess, [`Uninstalled ${plugin.name}`]);
|
||||
dispatch({ type: ActionTypes.UNINSTALLED });
|
||||
} catch (error) {
|
||||
dispatch({ type: ActionTypes.ERROR, payload: error });
|
||||
}
|
||||
};
|
||||
|
||||
const onUpdate = async () => {
|
||||
dispatch({ type: ActionTypes.INFLIGHT });
|
||||
try {
|
||||
await api.installPlugin(plugin.id, plugin.version);
|
||||
appEvents.emit(AppEvents.alertSuccess, [`Updated ${plugin.name}`]);
|
||||
dispatch({ type: ActionTypes.UPDATED });
|
||||
} catch (error) {
|
||||
dispatch({ type: ActionTypes.ERROR, payload: error });
|
||||
}
|
||||
};
|
||||
|
||||
if (pluginStatus === PluginStatus.UNINSTALL) {
|
||||
return (
|
||||
<HorizontalGroup height="auto">
|
||||
<Button variant="destructive" disabled={isInProgress} onClick={onUninstall}>
|
||||
{uninstallBtnText}
|
||||
</Button>
|
||||
{hasInstalledPanel && (
|
||||
<div className={styles.message}>Please refresh your browser window before using this plugin.</div>
|
||||
)}
|
||||
</HorizontalGroup>
|
||||
);
|
||||
}
|
||||
|
||||
if (pluginStatus === PluginStatus.UPDATE) {
|
||||
return (
|
||||
<HorizontalGroup height="auto">
|
||||
<Button disabled={isInProgress} onClick={onUpdate}>
|
||||
{updateBtnText}
|
||||
</Button>
|
||||
<Button variant="destructive" disabled={isInProgress} onClick={onUninstall}>
|
||||
{uninstallBtnText}
|
||||
</Button>
|
||||
</HorizontalGroup>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button disabled={isInProgress} onClick={onInstall}>
|
||||
{installBtnText}
|
||||
</Button>
|
||||
);
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
import React from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { satisfies } from 'semver';
|
||||
|
||||
import { config } from '@grafana/runtime';
|
||||
import { HorizontalGroup, Icon, LinkButton, useStyles2 } from '@grafana/ui';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
|
||||
import { CatalogPluginDetails, PluginStatus } from '../../types';
|
||||
import { isGrafanaAdmin, getExternalManageLink } from '../../helpers';
|
||||
import { ExternallyManagedButton } from './ExternallyManagedButton';
|
||||
import { InstallControlsButton } from './InstallControlsButton';
|
||||
|
||||
interface Props {
|
||||
plugin: CatalogPluginDetails;
|
||||
isInflight: boolean;
|
||||
hasUpdate: boolean;
|
||||
hasInstalledPanel: boolean;
|
||||
isInstalled: boolean;
|
||||
dispatch: React.Dispatch<any>;
|
||||
}
|
||||
|
||||
export const InstallControls = ({ plugin, isInflight, hasUpdate, isInstalled, hasInstalledPanel, dispatch }: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
const isExternallyManaged = config.pluginAdminExternalManageEnabled;
|
||||
const hasPermission = isGrafanaAdmin();
|
||||
const grafanaDependency = plugin.grafanaDependency;
|
||||
const unsupportedGrafanaVersion = grafanaDependency
|
||||
? !satisfies(config.buildInfo.version, grafanaDependency, {
|
||||
// needed for when running against master
|
||||
includePrerelease: true,
|
||||
})
|
||||
: false;
|
||||
const pluginStatus = isInstalled ? (hasUpdate ? PluginStatus.UPDATE : PluginStatus.UNINSTALL) : PluginStatus.INSTALL;
|
||||
|
||||
if (plugin.isCore) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (plugin.isEnterprise && !config.licenseInfo?.hasValidLicense) {
|
||||
return (
|
||||
<HorizontalGroup height="auto" align="center">
|
||||
<span className={styles.message}>No valid Grafana Enterprise license detected.</span>
|
||||
<LinkButton
|
||||
href={`${getExternalManageLink(plugin.id)}?utm_source=grafana_catalog_learn_more`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
size="sm"
|
||||
fill="text"
|
||||
icon="external-link-alt"
|
||||
>
|
||||
Learn more
|
||||
</LinkButton>
|
||||
</HorizontalGroup>
|
||||
);
|
||||
}
|
||||
|
||||
if (plugin.isDev) {
|
||||
return (
|
||||
<div className={styles.message}>This is a development build of the plugin and can't be uninstalled.</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!hasPermission && !isExternallyManaged) {
|
||||
const message = `You do not have permission to ${pluginStatus} this plugin.`;
|
||||
return <div className={styles.message}>{message}</div>;
|
||||
}
|
||||
|
||||
if (unsupportedGrafanaVersion) {
|
||||
return (
|
||||
<div className={styles.message}>
|
||||
<Icon name="exclamation-triangle" />
|
||||
This plugin doesn't support your version of Grafana.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isExternallyManaged) {
|
||||
return <ExternallyManagedButton pluginId={plugin.id} pluginStatus={pluginStatus} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<InstallControlsButton
|
||||
isInProgress={isInflight}
|
||||
dispatch={dispatch}
|
||||
plugin={plugin}
|
||||
pluginStatus={pluginStatus}
|
||||
hasInstalledPanel={hasInstalledPanel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
message: css`
|
||||
color: ${theme.colors.text.secondary};
|
||||
`,
|
||||
};
|
||||
};
|
@ -3,16 +3,7 @@ import { render, screen } from '@testing-library/react';
|
||||
import { PluginSignatureStatus } from '@grafana/data';
|
||||
import { PluginBadges } from './PluginBadges';
|
||||
import { CatalogPlugin } from '../types';
|
||||
|
||||
const runtimeMock = jest.requireMock('@grafana/runtime');
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
config: {
|
||||
licenseInfo: {
|
||||
hasValidLicense: false,
|
||||
},
|
||||
},
|
||||
}));
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
describe('PluginBadges', () => {
|
||||
const plugin: CatalogPlugin = {
|
||||
@ -39,6 +30,10 @@ describe('PluginBadges', () => {
|
||||
isEnterprise: false,
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders a plugin signature badge', () => {
|
||||
render(<PluginBadges plugin={plugin} />);
|
||||
|
||||
@ -53,14 +48,14 @@ describe('PluginBadges', () => {
|
||||
});
|
||||
|
||||
it('renders an enterprise badge (when a license is valid)', () => {
|
||||
runtimeMock.config.licenseInfo.hasValidLicense = true;
|
||||
config.licenseInfo.hasValidLicense = true;
|
||||
render(<PluginBadges plugin={{ ...plugin, isEnterprise: true }} />);
|
||||
expect(screen.getByText(/enterprise/i)).toBeVisible();
|
||||
expect(screen.queryByRole('button', { name: /learn more/i })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders an enterprise badge with icon and link (when a license is invalid)', () => {
|
||||
runtimeMock.config.licenseInfo.hasValidLicense = false;
|
||||
config.licenseInfo.hasValidLicense = false;
|
||||
render(<PluginBadges plugin={{ ...plugin, isEnterprise: true }} />);
|
||||
expect(screen.getByText(/enterprise/i)).toBeVisible();
|
||||
expect(screen.getByLabelText(/lock icon/i)).toBeInTheDocument();
|
||||
|
@ -166,3 +166,5 @@ export const matchesKeyword: PluginFilter = (plugin, query) => {
|
||||
|
||||
return fields.some((f) => f.includes(query.toLowerCase()));
|
||||
};
|
||||
|
||||
export const getExternalManageLink = (pluginId: string) => `https://grafana.com/grafana/plugins/${pluginId}`;
|
||||
|
@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
import { render, RenderResult, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { PluginSignatureStatus, PluginSignatureType, PluginType } from '@grafana/data';
|
||||
import PluginDetailsPage from './PluginDetails';
|
||||
import { API_ROOT, GRAFANA_API_ROOT } from '../constants';
|
||||
@ -17,12 +19,30 @@ jest.mock('@grafana/runtime', () => {
|
||||
case `${GRAFANA_API_ROOT}/plugins/not-installed/versions`:
|
||||
case `${GRAFANA_API_ROOT}/plugins/enterprise/versions`:
|
||||
return Promise.resolve([]);
|
||||
case `${GRAFANA_API_ROOT}/plugins/installed/versions`:
|
||||
return Promise.resolve({
|
||||
items: [
|
||||
{
|
||||
version: '1.0.0',
|
||||
createdAt: '2016-04-06T20:23:41.000Z',
|
||||
},
|
||||
],
|
||||
});
|
||||
case API_ROOT:
|
||||
return Promise.resolve([localPlugin(), corePlugin()]);
|
||||
return Promise.resolve([
|
||||
localPlugin(),
|
||||
localPlugin({ id: 'installed', signature: PluginSignatureStatus.valid }),
|
||||
localPlugin({ id: 'has-update', signature: PluginSignatureStatus.valid }),
|
||||
localPlugin({ id: 'core', signature: PluginSignatureStatus.internal }),
|
||||
]);
|
||||
case `${GRAFANA_API_ROOT}/plugins/core`:
|
||||
return Promise.resolve(corePlugin());
|
||||
return Promise.resolve(localPlugin({ id: 'core', signature: PluginSignatureStatus.internal }));
|
||||
case `${GRAFANA_API_ROOT}/plugins/not-installed`:
|
||||
return Promise.resolve(remotePlugin());
|
||||
case `${GRAFANA_API_ROOT}/plugins/has-update`:
|
||||
return Promise.resolve(remotePlugin({ slug: 'has-update', version: '2.0.0' }));
|
||||
case `${GRAFANA_API_ROOT}/plugins/installed`:
|
||||
return Promise.resolve(remotePlugin({ slug: 'installed' }));
|
||||
case `${GRAFANA_API_ROOT}/plugins/enterprise`:
|
||||
return Promise.resolve(remotePlugin({ status: 'enterprise' }));
|
||||
default:
|
||||
@ -53,18 +73,84 @@ function setup(pluginId: string): RenderResult {
|
||||
}
|
||||
|
||||
describe('Plugin details page', () => {
|
||||
it('should display install button for uninstalled plugins', async () => {
|
||||
const { getByText } = setup('not-installed');
|
||||
let dateNow: any;
|
||||
|
||||
const expected = 'Install';
|
||||
|
||||
await waitFor(() => expect(getByText(expected)).toBeInTheDocument());
|
||||
beforeAll(() => {
|
||||
dateNow = jest.spyOn(Date, 'now').mockImplementation(() => 1609470000000); // 2021-01-01 04:00:00
|
||||
});
|
||||
|
||||
it('should not display install button for enterprise plugins', async () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
dateNow.mockRestore();
|
||||
});
|
||||
|
||||
it('should display an overview (plugin readme) by default', async () => {
|
||||
const { queryByText } = setup('not-installed');
|
||||
await waitFor(() => expect(queryByText(/licensed under the apache 2.0 license/i)).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('should display version history', async () => {
|
||||
const { queryByText, getByText, getByRole } = setup('installed');
|
||||
await waitFor(() => expect(queryByText(/version history/i)).toBeInTheDocument());
|
||||
userEvent.click(getByText(/version history/i));
|
||||
expect(
|
||||
getByRole('columnheader', {
|
||||
name: /version/i,
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
getByRole('columnheader', {
|
||||
name: /last updated/i,
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
getByRole('cell', {
|
||||
name: /1\.0\.0/i,
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
getByRole('cell', {
|
||||
name: /5 years ago/i,
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display install button for a plugin that isn't installed", async () => {
|
||||
const { queryByRole } = setup('not-installed');
|
||||
|
||||
await waitFor(() => expect(queryByRole('button', { name: /install/i })).toBeInTheDocument());
|
||||
expect(queryByRole('button', { name: /uninstall/i })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display uninstall button for an installed plugin', async () => {
|
||||
const { queryByRole } = setup('installed');
|
||||
await waitFor(() => expect(queryByRole('button', { name: /uninstall/i })).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('should display update and uninstall buttons for a plugin with update', async () => {
|
||||
const { queryByRole } = setup('has-update');
|
||||
|
||||
await waitFor(() => expect(queryByRole('button', { name: /update/i })).toBeInTheDocument());
|
||||
expect(queryByRole('button', { name: /uninstall/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display install button for enterprise plugins if license is valid', async () => {
|
||||
config.licenseInfo.hasValidLicense = true;
|
||||
const { queryByRole } = setup('enterprise');
|
||||
|
||||
await waitFor(() => expect(queryByRole('button', { name: /(un)?install/i })).not.toBeInTheDocument());
|
||||
await waitFor(() => expect(queryByRole('button', { name: /install/i })).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('should not display install button for enterprise plugins if license is invalid', async () => {
|
||||
config.licenseInfo.hasValidLicense = false;
|
||||
const { queryByRole, queryByText } = setup('enterprise');
|
||||
|
||||
await waitFor(() => expect(queryByRole('button', { name: /install/i })).not.toBeInTheDocument());
|
||||
expect(queryByText(/no valid Grafana Enterprise license detected/i)).toBeInTheDocument();
|
||||
expect(queryByRole('link', { name: /learn more/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not display install / uninstall buttons for core plugins', async () => {
|
||||
@ -72,6 +158,27 @@ describe('Plugin details page', () => {
|
||||
|
||||
await waitFor(() => expect(queryByRole('button', { name: /(un)?install/i })).not.toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('should display install link with pluginAdminExternalManageEnabled true', async () => {
|
||||
config.pluginAdminExternalManageEnabled = true;
|
||||
const { queryByRole } = setup('not-installed');
|
||||
|
||||
await waitFor(() => expect(queryByRole('link', { name: /install via grafana.com/i })).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('should display uninstall link for an installed plugin with pluginAdminExternalManageEnabled true', async () => {
|
||||
config.pluginAdminExternalManageEnabled = true;
|
||||
const { queryByRole } = setup('installed');
|
||||
await waitFor(() => expect(queryByRole('link', { name: /uninstall via grafana.com/i })).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('should display update and uninstall links for a plugin with update and pluginAdminExternalManageEnabled true', async () => {
|
||||
config.pluginAdminExternalManageEnabled = true;
|
||||
const { queryByRole } = setup('has-update');
|
||||
|
||||
await waitFor(() => expect(queryByRole('link', { name: /update via grafana.com/i })).toBeInTheDocument());
|
||||
expect(queryByRole('link', { name: /uninstall via grafana.com/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
function remotePlugin(plugin: Partial<RemotePlugin> = {}): RemotePlugin {
|
||||
@ -106,7 +213,8 @@ function remotePlugin(plugin: Partial<RemotePlugin> = {}): RemotePlugin {
|
||||
versionSignedByOrg: 'alexanderzobnin',
|
||||
versionSignedByOrgName: 'Alexander Zobnin',
|
||||
userId: 0,
|
||||
readme: '',
|
||||
readme:
|
||||
'<h1>Zabbix plugin for Grafana</h1>\n<p>:copyright: 2015-2021 Alexander Zobnin alexanderzobnin@gmail.com</p>\n<p>Licensed under the Apache 2.0 License</p>',
|
||||
json: {
|
||||
dependencies: {
|
||||
grafanaDependency: '>=7.3.0',
|
||||
@ -122,65 +230,40 @@ function remotePlugin(plugin: Partial<RemotePlugin> = {}): RemotePlugin {
|
||||
|
||||
function localPlugin(plugin: Partial<LocalPlugin> = {}): LocalPlugin {
|
||||
return {
|
||||
category: '',
|
||||
defaultNavUrl: '/plugins/alertmanager/',
|
||||
name: 'Akumuli',
|
||||
type: PluginType.datasource,
|
||||
id: 'akumuli-datasource',
|
||||
enabled: true,
|
||||
pinned: false,
|
||||
info: {
|
||||
author: {
|
||||
name: 'Prometheus alertmanager',
|
||||
url: 'https://grafana.com',
|
||||
name: 'Eugene Lazin',
|
||||
url: 'https://akumuli.org',
|
||||
},
|
||||
description: 'Datasource plugin for Akumuli time-series database',
|
||||
links: [
|
||||
{
|
||||
name: 'Project site',
|
||||
url: 'https://github.com/akumuli/Akumuli',
|
||||
},
|
||||
],
|
||||
logos: {
|
||||
small: 'public/plugins/akumuli-datasource/img/logo.svg.png',
|
||||
large: 'public/plugins/akumuli-datasource/img/logo.svg.png',
|
||||
},
|
||||
build: {},
|
||||
description: '',
|
||||
links: [],
|
||||
logos: {
|
||||
small: '',
|
||||
large: '',
|
||||
},
|
||||
updated: '',
|
||||
version: '',
|
||||
screenshots: null,
|
||||
version: '1.3.12',
|
||||
updated: '2019-12-19',
|
||||
},
|
||||
enabled: true,
|
||||
latestVersion: '1.3.12',
|
||||
hasUpdate: false,
|
||||
id: 'alertmanager',
|
||||
latestVersion: '',
|
||||
name: 'Alert Manager',
|
||||
pinned: false,
|
||||
signature: PluginSignatureStatus.internal,
|
||||
signatureOrg: '',
|
||||
signatureType: '',
|
||||
state: 'alpha',
|
||||
type: PluginType.datasource,
|
||||
...plugin,
|
||||
};
|
||||
}
|
||||
|
||||
function corePlugin(plugin: Partial<LocalPlugin> = {}): LocalPlugin {
|
||||
return {
|
||||
category: 'sql',
|
||||
defaultNavUrl: '/plugins/postgres/',
|
||||
enabled: true,
|
||||
hasUpdate: false,
|
||||
id: 'core',
|
||||
info: {
|
||||
author: { name: 'Grafana Labs', url: 'https://grafana.com' },
|
||||
build: {},
|
||||
description: 'Data source for PostgreSQL and compatible databases',
|
||||
links: [],
|
||||
logos: {
|
||||
small: 'public/app/plugins/datasource/postgres/img/postgresql_logo.svg',
|
||||
large: 'public/app/plugins/datasource/postgres/img/postgresql_logo.svg',
|
||||
},
|
||||
updated: '',
|
||||
version: '',
|
||||
},
|
||||
latestVersion: '',
|
||||
name: 'PostgreSQL',
|
||||
pinned: false,
|
||||
signature: PluginSignatureStatus.internal,
|
||||
signatureOrg: '',
|
||||
signatureType: '',
|
||||
defaultNavUrl: '/plugins/akumuli-datasource/',
|
||||
category: '',
|
||||
state: '',
|
||||
type: PluginType.datasource,
|
||||
signature: PluginSignatureStatus.valid,
|
||||
signatureType: 'community',
|
||||
signatureOrg: 'Grafana Labs',
|
||||
...plugin,
|
||||
};
|
||||
}
|
||||
|
@ -223,3 +223,9 @@ export type PluginsByFilterType = {
|
||||
};
|
||||
|
||||
export type PluginFilter = (plugin: CatalogPlugin, query: string) => boolean;
|
||||
|
||||
export enum PluginStatus {
|
||||
INSTALL = 'INSTALL',
|
||||
UNINSTALL = 'UNINSTALL',
|
||||
UPDATE = 'UPDATE',
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user