mirror of
https://github.com/grafana/grafana.git
synced 2025-02-13 09:05:45 -06:00
* refactor(PluginDetails): use react-router hooks instead of props * Wip * refactor: remove unnecessary constant * feat: use the original plugin details page under connections * chore: use better wording in the not-found warning Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com> * chore: use the renderer utility everywhere in the test * chore: don't show a title while loading a plugin Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com>
792 lines
28 KiB
TypeScript
792 lines
28 KiB
TypeScript
import { getDefaultNormalizer, render, RenderResult, SelectorMatcherOptions, waitFor } from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
import React from 'react';
|
|
import { Provider } from 'react-redux';
|
|
import { MemoryRouter, Route } from 'react-router-dom';
|
|
|
|
import {
|
|
PluginErrorCode,
|
|
PluginSignatureStatus,
|
|
PluginType,
|
|
dateTimeFormatTimeAgo,
|
|
WithAccessControlMetadata,
|
|
} from '@grafana/data';
|
|
import { selectors } from '@grafana/e2e-selectors';
|
|
import { config } from '@grafana/runtime';
|
|
import { configureStore } from 'app/store/configureStore';
|
|
|
|
import { mockPluginApis, getCatalogPluginMock, getPluginsStateMock, mockUserPermissions } from '../__mocks__';
|
|
import * as api from '../api';
|
|
import { usePluginConfig } from '../hooks/usePluginConfig';
|
|
import { fetchRemotePlugins } from '../state/actions';
|
|
import {
|
|
CatalogPlugin,
|
|
CatalogPluginDetails,
|
|
PluginTabIds,
|
|
PluginTabLabels,
|
|
ReducerState,
|
|
RequestStatus,
|
|
} from '../types';
|
|
|
|
import PluginDetailsPage from './PluginDetails';
|
|
|
|
jest.mock('@grafana/runtime', () => {
|
|
const original = jest.requireActual('@grafana/runtime');
|
|
const mockedRuntime = { ...original };
|
|
mockedRuntime.config.buildInfo.version = 'v8.1.0';
|
|
return mockedRuntime;
|
|
});
|
|
|
|
jest.mock('../hooks/usePluginConfig.tsx', () => ({
|
|
usePluginConfig: jest.fn(() => ({
|
|
value: {
|
|
meta: {},
|
|
},
|
|
})),
|
|
}));
|
|
|
|
jest.mock('../helpers.ts', () => ({
|
|
...jest.requireActual('../helpers.ts'),
|
|
updatePanels: jest.fn(),
|
|
}));
|
|
|
|
jest.mock('app/core/core', () => ({
|
|
contextSrv: {
|
|
hasAccess: (action: string, fallBack: boolean) => true,
|
|
hasAccessInMetadata: (action: string, object: WithAccessControlMetadata, fallBack: boolean) => true,
|
|
},
|
|
}));
|
|
|
|
const renderPluginDetails = (
|
|
pluginOverride: Partial<CatalogPlugin>,
|
|
{
|
|
pageId,
|
|
pluginsStateOverride,
|
|
}: {
|
|
pageId?: PluginTabIds;
|
|
pluginsStateOverride?: ReducerState;
|
|
} = {}
|
|
): RenderResult => {
|
|
const plugin = getCatalogPluginMock(pluginOverride);
|
|
const { id } = plugin;
|
|
const store = configureStore({
|
|
plugins: pluginsStateOverride || getPluginsStateMock([plugin]),
|
|
});
|
|
|
|
return render(
|
|
<MemoryRouter initialEntries={[{ pathname: `/plugins/${id}`, search: pageId ? `?page=${pageId}` : '' }]}>
|
|
<Provider store={store}>
|
|
<Route path="/plugins/:pluginId" component={PluginDetailsPage} />
|
|
</Provider>
|
|
</MemoryRouter>
|
|
);
|
|
};
|
|
|
|
describe('Plugin details page', () => {
|
|
const id = 'my-plugin';
|
|
const originalWindowLocation = window.location;
|
|
let dateNow: jest.SpyInstance<number, []>;
|
|
|
|
beforeAll(() => {
|
|
dateNow = jest.spyOn(Date, 'now').mockImplementation(() => 1609470000000); // 2021-01-01 04:00:00
|
|
|
|
// Enabling / disabling the plugin is currently reloading the page to propagate the changes
|
|
Object.defineProperty(window, 'location', {
|
|
configurable: true,
|
|
value: { reload: jest.fn() },
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
config.pluginAdminExternalManageEnabled = false;
|
|
config.licenseInfo.enabledFeatures = {};
|
|
});
|
|
|
|
afterAll(() => {
|
|
dateNow.mockRestore();
|
|
Object.defineProperty(window, 'location', { configurable: true, value: originalWindowLocation });
|
|
});
|
|
|
|
describe('viewed as user with grafana admin permissions', () => {
|
|
beforeAll(() => {
|
|
mockUserPermissions({
|
|
isAdmin: true,
|
|
isDataSourceEditor: true,
|
|
isOrgAdmin: true,
|
|
});
|
|
});
|
|
|
|
// We are doing this very basic test to see if the API fetching and data-munging is working correctly from a high-level.
|
|
it('(SMOKE TEST) - should fetch and merge the remote and local plugin API responses correctly ', async () => {
|
|
const id = 'smoke-test-plugin';
|
|
|
|
mockPluginApis({
|
|
remote: { slug: id },
|
|
local: { id },
|
|
});
|
|
|
|
const { queryByText } = renderPluginDetails({ id });
|
|
|
|
await waitFor(() => expect(queryByText(/licensed under the apache 2.0 license/i)).toBeInTheDocument());
|
|
});
|
|
|
|
it('should display an overview (plugin readme) by default', async () => {
|
|
const { queryByText } = renderPluginDetails({ id });
|
|
|
|
expect(await queryByText(/licensed under the apache 2.0 license/i)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display an app config page by default for installed app plugins', async () => {
|
|
const name = 'Akumuli';
|
|
|
|
// @ts-ignore
|
|
usePluginConfig.mockReturnValue({
|
|
value: {
|
|
meta: {
|
|
type: PluginType.app,
|
|
enabled: false,
|
|
pinned: false,
|
|
jsonData: {},
|
|
},
|
|
configPages: [
|
|
{
|
|
title: 'Config',
|
|
icon: 'cog',
|
|
id: 'configPage',
|
|
body: function ConfigPage() {
|
|
return <div>Custom Config Page!</div>;
|
|
},
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
const { queryByText } = renderPluginDetails({
|
|
name,
|
|
isInstalled: true,
|
|
type: PluginType.app,
|
|
});
|
|
|
|
expect(await queryByText(/custom config page/i)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display the number of downloads in the header', async () => {
|
|
// depending on what locale you have the Intl.NumberFormat will return a format that contains
|
|
// whitespaces. In that case we don't want testing library to remove whitespaces.
|
|
const downloads = 24324;
|
|
const options: SelectorMatcherOptions = { normalizer: getDefaultNormalizer({ collapseWhitespace: false }) };
|
|
const expected = new Intl.NumberFormat().format(downloads);
|
|
|
|
const { queryByText } = renderPluginDetails({ id, downloads });
|
|
expect(await queryByText(expected, options)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display the installed version if a plugin is installed', async () => {
|
|
const installedVersion = '1.3.443';
|
|
const { queryByText } = renderPluginDetails({ id, installedVersion });
|
|
|
|
expect(await queryByText(`${installedVersion}`)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display the latest compatible version in the header if a plugin is not installed', async () => {
|
|
const details: CatalogPluginDetails = {
|
|
links: [],
|
|
versions: [
|
|
{ version: '1.3.0', createdAt: '', isCompatible: false, grafanaDependency: '>=9.0.0' },
|
|
{ version: '1.2.0', createdAt: '', isCompatible: false, grafanaDependency: '>=8.3.0' },
|
|
{ version: '1.1.1', createdAt: '', isCompatible: true, grafanaDependency: '>=8.0.0' },
|
|
{ version: '1.1.0', createdAt: '', isCompatible: true, grafanaDependency: '>=8.0.0' },
|
|
{ version: '1.0.0', createdAt: '', isCompatible: true, grafanaDependency: '>=7.0.0' },
|
|
],
|
|
};
|
|
|
|
const { findByText, queryByText } = renderPluginDetails({ id, details });
|
|
expect(await findByText('1.1.1')).toBeInTheDocument();
|
|
expect(queryByText(/>=8.0.0/i)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display description in the header', async () => {
|
|
const description = 'This is my description';
|
|
const { queryByText } = renderPluginDetails({ id, description });
|
|
|
|
expect(await queryByText(description)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display a "Signed" badge if the plugin signature is verified', async () => {
|
|
const { queryByText } = renderPluginDetails({ id, signature: PluginSignatureStatus.valid });
|
|
|
|
expect(await queryByText('Signed')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display a "Missing signature" badge if the plugin signature is missing', async () => {
|
|
const { queryByText } = renderPluginDetails({ id, signature: PluginSignatureStatus.missing });
|
|
|
|
expect(await queryByText('Missing signature')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display a "Modified signature" badge if the plugin signature is modified', async () => {
|
|
const { queryByText } = renderPluginDetails({ id, signature: PluginSignatureStatus.modified });
|
|
|
|
expect(await queryByText('Modified signature')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display a "Invalid signature" badge if the plugin signature is invalid', async () => {
|
|
const { queryByText } = renderPluginDetails({ id, signature: PluginSignatureStatus.invalid });
|
|
|
|
expect(await queryByText('Invalid signature')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display version history if the plugin is published', async () => {
|
|
const versions = [
|
|
{
|
|
version: '1.2.0',
|
|
createdAt: '2018-04-06T20:23:41.000Z',
|
|
isCompatible: false,
|
|
grafanaDependency: '>=8.3.0',
|
|
},
|
|
{
|
|
version: '1.1.0',
|
|
createdAt: '2017-04-06T20:23:41.000Z',
|
|
isCompatible: true,
|
|
grafanaDependency: '>=8.0.0',
|
|
},
|
|
{
|
|
version: '1.0.0',
|
|
createdAt: '2016-04-06T20:23:41.000Z',
|
|
isCompatible: true,
|
|
grafanaDependency: '>=7.0.0',
|
|
},
|
|
];
|
|
|
|
const { findByRole, queryByText, getByRole } = renderPluginDetails(
|
|
{
|
|
id,
|
|
details: {
|
|
links: [],
|
|
versions,
|
|
},
|
|
},
|
|
{ pageId: PluginTabIds.VERSIONS }
|
|
);
|
|
|
|
// Check if version information is available
|
|
expect(await findByRole('tab', { name: `Tab ${PluginTabLabels.VERSIONS}` })).toBeInTheDocument();
|
|
|
|
// Check the column headers
|
|
expect(getByRole('columnheader', { name: /version/i })).toBeInTheDocument();
|
|
expect(getByRole('columnheader', { name: /last updated/i })).toBeInTheDocument();
|
|
|
|
// Check the data
|
|
for (const version of versions) {
|
|
expect(getByRole('cell', { name: new RegExp(version.version, 'i') })).toBeInTheDocument();
|
|
expect(
|
|
getByRole('cell', { name: new RegExp(dateTimeFormatTimeAgo(version.createdAt), 'i') })
|
|
).toBeInTheDocument();
|
|
|
|
// Check the latest compatible version
|
|
expect(queryByText('1.1.0 (latest compatible version)')).toBeInTheDocument();
|
|
}
|
|
});
|
|
|
|
it("should display an install button for a plugin that isn't installed", async () => {
|
|
const { queryByRole } = renderPluginDetails({ id, isInstalled: false });
|
|
|
|
expect(await queryByRole('button', { name: /^install/i })).toBeInTheDocument();
|
|
// Does not display "uninstall" button
|
|
expect(queryByRole('button', { name: /uninstall/i })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should display an uninstall button for an already installed plugin', async () => {
|
|
const { queryByRole } = renderPluginDetails({ id, isInstalled: true });
|
|
|
|
expect(await queryByRole('button', { name: /uninstall/i })).toBeInTheDocument();
|
|
// Does not display "install" button
|
|
expect(queryByRole('button', { name: /^install/i })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should display update and uninstall buttons for a plugin with update', async () => {
|
|
const { queryByRole } = renderPluginDetails({ id, isInstalled: true, hasUpdate: true });
|
|
|
|
// Displays an "update" button
|
|
expect(await queryByRole('button', { name: /update/i })).toBeInTheDocument();
|
|
expect(queryByRole('button', { name: /uninstall/i })).toBeInTheDocument();
|
|
|
|
// Does not display "install" button
|
|
expect(queryByRole('button', { name: /^install/i })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should display an install button for enterprise plugins if license is valid', async () => {
|
|
config.licenseInfo.enabledFeatures = { 'enterprise.plugins': true };
|
|
|
|
const { queryByRole } = renderPluginDetails({ id, isInstalled: false, isEnterprise: true });
|
|
|
|
expect(await queryByRole('button', { name: /install/i })).toBeInTheDocument();
|
|
});
|
|
|
|
it('should not display install button for enterprise plugins if license is invalid', async () => {
|
|
config.licenseInfo.enabledFeatures = {};
|
|
|
|
const { queryByRole, queryByText } = renderPluginDetails({ id, isInstalled: true, isEnterprise: true });
|
|
|
|
expect(await 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 () => {
|
|
const { queryByRole } = renderPluginDetails({ id, isInstalled: true, isCore: true });
|
|
|
|
expect(await queryByRole('button', { name: /update/i })).not.toBeInTheDocument();
|
|
expect(await queryByRole('button', { name: /(un)?install/i })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should not display install / uninstall buttons for disabled plugins', async () => {
|
|
const { queryByRole } = renderPluginDetails({ id, isInstalled: true, isDisabled: true });
|
|
|
|
expect(await queryByRole('button', { name: /update/i })).not.toBeInTheDocument();
|
|
expect(await queryByRole('button', { name: /(un)?install/i })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should not display install / uninstall buttons for renderer plugins', async () => {
|
|
const { queryByRole } = renderPluginDetails({ id, type: PluginType.renderer });
|
|
|
|
expect(await queryByRole('button', { name: /update/i })).not.toBeInTheDocument();
|
|
expect(await queryByRole('button', { name: /(un)?install/i })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should display install link with `config.pluginAdminExternalManageEnabled` set to true', async () => {
|
|
config.pluginAdminExternalManageEnabled = true;
|
|
|
|
const { queryByRole } = renderPluginDetails({ id, isInstalled: false });
|
|
|
|
expect(await queryByRole('link', { name: /install via grafana.com/i })).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display uninstall link for an installed plugin with `config.pluginAdminExternalManageEnabled` set to true', async () => {
|
|
config.pluginAdminExternalManageEnabled = true;
|
|
|
|
const { queryByRole } = renderPluginDetails({ id, isInstalled: true });
|
|
|
|
expect(await queryByRole('link', { name: /uninstall via grafana.com/i })).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display update and uninstall links for a plugin with an available update and `config.pluginAdminExternalManageEnabled` set to true', async () => {
|
|
config.pluginAdminExternalManageEnabled = true;
|
|
|
|
const { queryByRole } = renderPluginDetails({ id, isInstalled: true, hasUpdate: true });
|
|
|
|
expect(await queryByRole('link', { name: /update via grafana.com/i })).toBeInTheDocument();
|
|
expect(queryByRole('link', { name: /uninstall via grafana.com/i })).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display alert with information about why the plugin is disabled', async () => {
|
|
const { queryByLabelText } = renderPluginDetails({
|
|
id,
|
|
isInstalled: true,
|
|
isDisabled: true,
|
|
error: PluginErrorCode.modifiedSignature,
|
|
});
|
|
|
|
expect(await queryByLabelText(selectors.pages.PluginPage.disabledInfo)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display grafana dependencies for a plugin if they are available', async () => {
|
|
const { queryByText } = renderPluginDetails({
|
|
id,
|
|
details: {
|
|
pluginDependencies: [],
|
|
grafanaDependency: '>=8.0.0',
|
|
links: [],
|
|
},
|
|
});
|
|
|
|
// Wait for the dependencies part to be loaded
|
|
expect(await queryByText('Grafana >=8.0.0')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should show a confirm modal when trying to uninstall a plugin', async () => {
|
|
// @ts-ignore
|
|
api.uninstallPlugin = jest.fn();
|
|
|
|
const { queryByText, getByRole, findByRole } = renderPluginDetails({
|
|
id,
|
|
name: 'Akumuli',
|
|
isInstalled: true,
|
|
details: {
|
|
pluginDependencies: [],
|
|
grafanaDependency: '>=8.0.0',
|
|
links: [],
|
|
versions: [
|
|
{
|
|
version: '1.0.0',
|
|
createdAt: '',
|
|
isCompatible: true,
|
|
grafanaDependency: '>=8.0.0',
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
// Wait for the install controls to be loaded
|
|
expect(await findByRole('tab', { name: `Tab ${PluginTabLabels.OVERVIEW}` })).toBeInTheDocument();
|
|
|
|
// Open the confirmation modal
|
|
await userEvent.click(getByRole('button', { name: /uninstall/i }));
|
|
|
|
expect(queryByText('Uninstall Akumuli')).toBeInTheDocument();
|
|
expect(queryByText('Are you sure you want to uninstall this plugin?')).toBeInTheDocument();
|
|
expect(api.uninstallPlugin).toHaveBeenCalledTimes(0);
|
|
|
|
// Confirm the uninstall
|
|
await userEvent.click(getByRole('button', { name: /confirm/i }));
|
|
expect(api.uninstallPlugin).toHaveBeenCalledTimes(1);
|
|
expect(api.uninstallPlugin).toHaveBeenCalledWith(id);
|
|
|
|
// Check if the modal disappeared
|
|
expect(queryByText('Uninstall Akumuli')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should not display the install / uninstall / update buttons if the GCOM api is not available', async () => {
|
|
let rendered: RenderResult;
|
|
const plugin = getCatalogPluginMock({ id });
|
|
const state = getPluginsStateMock([plugin]);
|
|
|
|
// Mock the store like if the remote plugins request was rejected
|
|
const pluginsStateOverride = {
|
|
...state,
|
|
requests: {
|
|
...state.requests,
|
|
[fetchRemotePlugins.typePrefix]: {
|
|
status: RequestStatus.Rejected,
|
|
},
|
|
},
|
|
};
|
|
|
|
// Does not show an Install button
|
|
rendered = renderPluginDetails({ id }, { pluginsStateOverride });
|
|
expect(await rendered.queryByRole('button', { name: /(un)?install/i })).not.toBeInTheDocument();
|
|
rendered.unmount();
|
|
|
|
// Does not show a Uninstall button
|
|
rendered = renderPluginDetails({ id, isInstalled: true }, { pluginsStateOverride });
|
|
expect(await rendered.queryByRole('button', { name: /(un)?install/i })).not.toBeInTheDocument();
|
|
rendered.unmount();
|
|
|
|
// Does not show an Update button
|
|
rendered = renderPluginDetails({ id, isInstalled: true, hasUpdate: true }, { pluginsStateOverride });
|
|
expect(await rendered.queryByRole('button', { name: /update/i })).not.toBeInTheDocument();
|
|
|
|
// Shows a message to the user
|
|
// TODO<Import these texts from a single source of truth instead of having them defined in multiple places>
|
|
const message = 'The install controls have been disabled because the Grafana server cannot access grafana.com.';
|
|
expect(rendered.getByText(message)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should not display the install / uninstall / update buttons if `pluginAdminEnabled` flag is set to FALSE in the Grafana config', async () => {
|
|
let rendered: RenderResult;
|
|
|
|
// Disable the install controls for the plugins catalog
|
|
config.pluginAdminEnabled = false;
|
|
|
|
// Should not show an "Install" button
|
|
rendered = renderPluginDetails({ id, isInstalled: false });
|
|
expect(await rendered.queryByRole('button', { name: /^install/i })).not.toBeInTheDocument();
|
|
|
|
// Should not show an "Uninstall" button
|
|
rendered = renderPluginDetails({ id, isInstalled: true });
|
|
expect(await rendered.queryByRole('button', { name: /^uninstall/i })).not.toBeInTheDocument();
|
|
|
|
// Should not show an "Update" button
|
|
rendered = renderPluginDetails({ id, isInstalled: true, hasUpdate: true });
|
|
expect(await rendered.queryByRole('button', { name: /^update/i })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should display a "Create" button as a post installation step for installed data source plugins', async () => {
|
|
const name = 'Akumuli';
|
|
const { queryByText } = renderPluginDetails({
|
|
name,
|
|
isInstalled: true,
|
|
type: PluginType.datasource,
|
|
});
|
|
|
|
await waitFor(() => queryByText('Uninstall'));
|
|
expect(queryByText(`Create a ${name} data source`)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should not display a "Create" button as a post installation step for disabled data source plugins', async () => {
|
|
const name = 'Akumuli';
|
|
const { queryByText } = renderPluginDetails({
|
|
name,
|
|
isInstalled: true,
|
|
isDisabled: true,
|
|
type: PluginType.datasource,
|
|
});
|
|
|
|
await waitFor(() => queryByText('Uninstall'));
|
|
expect(queryByText(`Create a ${name} data source`)).toBeNull();
|
|
});
|
|
|
|
it('should not display post installation step for panel plugins', async () => {
|
|
const name = 'Akumuli';
|
|
const { queryByText } = renderPluginDetails({
|
|
name,
|
|
isInstalled: true,
|
|
type: PluginType.panel,
|
|
});
|
|
|
|
await waitFor(() => queryByText('Uninstall'));
|
|
expect(queryByText(`Create a ${name} data source`)).toBeNull();
|
|
});
|
|
|
|
it('should display an enable button for app plugins that are not enabled as a post installation step', async () => {
|
|
const name = 'Akumuli';
|
|
|
|
// @ts-ignore
|
|
usePluginConfig.mockReturnValue({
|
|
value: {
|
|
meta: {
|
|
enabled: false,
|
|
pinned: false,
|
|
jsonData: {},
|
|
},
|
|
},
|
|
});
|
|
|
|
const { queryByText, queryByRole } = renderPluginDetails({
|
|
name,
|
|
isInstalled: true,
|
|
type: PluginType.app,
|
|
});
|
|
|
|
await waitFor(() => queryByText('Uninstall'));
|
|
|
|
expect(queryByRole('button', { name: /enable/i })).toBeInTheDocument();
|
|
expect(queryByRole('button', { name: /disable/i })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should display a disable button for app plugins that are enabled as a post installation step', async () => {
|
|
const name = 'Akumuli';
|
|
|
|
// @ts-ignore
|
|
usePluginConfig.mockReturnValue({
|
|
value: {
|
|
meta: {
|
|
enabled: true,
|
|
pinned: false,
|
|
jsonData: {},
|
|
},
|
|
},
|
|
});
|
|
|
|
const { queryByText, queryByRole } = renderPluginDetails({
|
|
name,
|
|
isInstalled: true,
|
|
type: PluginType.app,
|
|
});
|
|
|
|
await waitFor(() => queryByText('Uninstall'));
|
|
|
|
expect(queryByRole('button', { name: /disable/i })).toBeInTheDocument();
|
|
expect(queryByRole('button', { name: /enable/i })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should be possible to enable an app plugin', async () => {
|
|
const id = 'akumuli-datasource';
|
|
const name = 'Akumuli';
|
|
|
|
// @ts-ignore
|
|
api.updatePluginSettings = jest.fn();
|
|
|
|
// @ts-ignore
|
|
usePluginConfig.mockReturnValue({
|
|
value: {
|
|
meta: {
|
|
enabled: false,
|
|
pinned: false,
|
|
jsonData: {},
|
|
},
|
|
},
|
|
});
|
|
|
|
const { queryByText, getByRole } = renderPluginDetails({
|
|
id,
|
|
name,
|
|
isInstalled: true,
|
|
type: PluginType.app,
|
|
});
|
|
|
|
// Wait for the header to be loaded
|
|
await waitFor(() => queryByText('Uninstall'));
|
|
|
|
// Click on "Enable"
|
|
await userEvent.click(getByRole('button', { name: /enable/i }));
|
|
|
|
// Check if the API request was initiated
|
|
expect(api.updatePluginSettings).toHaveBeenCalledTimes(1);
|
|
expect(api.updatePluginSettings).toHaveBeenCalledWith(id, {
|
|
enabled: true,
|
|
pinned: true,
|
|
jsonData: {},
|
|
});
|
|
});
|
|
|
|
it('should be possible to disable an app plugin', async () => {
|
|
const id = 'akumuli-datasource';
|
|
const name = 'Akumuli';
|
|
|
|
// @ts-ignore
|
|
api.updatePluginSettings = jest.fn();
|
|
|
|
// @ts-ignore
|
|
usePluginConfig.mockReturnValue({
|
|
value: {
|
|
meta: {
|
|
enabled: true,
|
|
pinned: true,
|
|
jsonData: {},
|
|
},
|
|
},
|
|
});
|
|
|
|
const { queryByText, getByRole } = renderPluginDetails({
|
|
id,
|
|
name,
|
|
isInstalled: true,
|
|
type: PluginType.app,
|
|
});
|
|
|
|
// Wait for the header to be loaded
|
|
await waitFor(() => queryByText('Uninstall'));
|
|
|
|
// Click on "Disable"
|
|
await userEvent.click(getByRole('button', { name: /disable/i }));
|
|
|
|
// Check if the API request was initiated
|
|
expect(api.updatePluginSettings).toHaveBeenCalledTimes(1);
|
|
expect(api.updatePluginSettings).toHaveBeenCalledWith(id, {
|
|
enabled: false,
|
|
pinned: false,
|
|
jsonData: {},
|
|
});
|
|
});
|
|
|
|
it('should not display versions tab for plugins not published to gcom', async () => {
|
|
const { queryByRole } = renderPluginDetails({
|
|
name: 'Akumuli',
|
|
isInstalled: true,
|
|
type: PluginType.app,
|
|
isPublished: false,
|
|
});
|
|
|
|
expect(await queryByRole('tab', { name: `Tab ${PluginTabLabels.VERSIONS}` })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should not display update for plugins not published to gcom', async () => {
|
|
const { findByRole, queryByRole } = renderPluginDetails({
|
|
name: 'Akumuli',
|
|
isInstalled: true,
|
|
hasUpdate: true,
|
|
type: PluginType.app,
|
|
isPublished: false,
|
|
});
|
|
|
|
expect(await findByRole('tab', { name: `Tab ${PluginTabLabels.OVERVIEW}` })).toBeInTheDocument();
|
|
|
|
expect(queryByRole('button', { name: /update/i })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should not display install for plugins not published to gcom', async () => {
|
|
const { findByRole, queryByRole } = renderPluginDetails({
|
|
name: 'Akumuli',
|
|
isInstalled: false,
|
|
hasUpdate: false,
|
|
type: PluginType.app,
|
|
isPublished: false,
|
|
});
|
|
|
|
expect(await findByRole('tab', { name: `Tab ${PluginTabLabels.OVERVIEW}` })).toBeInTheDocument();
|
|
|
|
expect(queryByRole('button', { name: /^install/i })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should not display uninstall for plugins not published to gcom', async () => {
|
|
const { findByRole, queryByRole } = renderPluginDetails({
|
|
name: 'Akumuli',
|
|
isInstalled: true,
|
|
hasUpdate: false,
|
|
type: PluginType.app,
|
|
isPublished: false,
|
|
});
|
|
|
|
expect(await findByRole('tab', { name: `Tab ${PluginTabLabels.OVERVIEW}` })).toBeInTheDocument();
|
|
|
|
expect(queryByRole('button', { name: /uninstall/i })).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('viewed as user without grafana admin permissions', () => {
|
|
beforeAll(() => {
|
|
mockUserPermissions({
|
|
isAdmin: false,
|
|
isDataSourceEditor: false,
|
|
isOrgAdmin: false,
|
|
});
|
|
});
|
|
|
|
it("should not display an install button for a plugin that isn't installed", async () => {
|
|
const { queryByRole, findByRole } = renderPluginDetails({ id, isInstalled: false });
|
|
|
|
expect(await findByRole('tab', { name: `Tab ${PluginTabLabels.OVERVIEW}` })).toBeInTheDocument();
|
|
|
|
expect(queryByRole('button', { name: /^install/i })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should not display an uninstall button for an already installed plugin', async () => {
|
|
const { queryByRole, findByRole } = renderPluginDetails({ id, isInstalled: true });
|
|
|
|
expect(await findByRole('tab', { name: `Tab ${PluginTabLabels.OVERVIEW}` })).toBeInTheDocument();
|
|
|
|
expect(queryByRole('button', { name: /uninstall/i })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should not display update or uninstall buttons for a plugin with update', async () => {
|
|
const { queryByRole, findByRole } = renderPluginDetails({ id, isInstalled: true, hasUpdate: true });
|
|
|
|
expect(await findByRole('tab', { name: `Tab ${PluginTabLabels.OVERVIEW}` })).toBeInTheDocument();
|
|
|
|
expect(queryByRole('button', { name: /update/i })).not.toBeInTheDocument();
|
|
expect(queryByRole('button', { name: /uninstall/i })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should not display an install button for enterprise plugins if license is valid', async () => {
|
|
const { findByRole, queryByRole } = renderPluginDetails({ id, isInstalled: false, isEnterprise: true });
|
|
|
|
expect(await findByRole('tab', { name: `Tab ${PluginTabLabels.OVERVIEW}` })).toBeInTheDocument();
|
|
expect(await queryByRole('button', { name: /^install/i })).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('viewed as user without data source edit permissions', () => {
|
|
beforeAll(() => {
|
|
mockUserPermissions({
|
|
isAdmin: true,
|
|
isDataSourceEditor: false,
|
|
isOrgAdmin: true,
|
|
});
|
|
});
|
|
|
|
it('should not display the data source post installation step', async () => {
|
|
const name = 'Akumuli';
|
|
const { queryByText } = renderPluginDetails({
|
|
name,
|
|
isInstalled: true,
|
|
type: PluginType.app,
|
|
});
|
|
|
|
await waitFor(() => queryByText('Uninstall'));
|
|
expect(queryByText(`Create a ${name} data source`)).toBeNull();
|
|
});
|
|
});
|
|
});
|