mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Plugins Catalog: refactor the mocking for our tests (#39165)
* refactor(Plugins/Admin): add a type for version coming back from the API * refactor(Plugins/Admin): only add necessary version information to the state * test(Plugins/Admin): add a mock for a CatalogPlugin * test(Plugins/ADmin): add a mock for a LocalPlugin * test(Plugins/Admin): add a test for a RemotePlugin * test(Plugins/Admin): add helpers for mocking plugin states * refactor(Plugins/Admin): mock the Redux state instead of the API responses This makes it simpler to add new test cases and also makes the tests easier to reason about. * refactor(Plugins/Admin): mock the Redux state instead of the API responses
This commit is contained in:
parent
bff3d91b0d
commit
3c433dc36d
File diff suppressed because one or more lines are too long
3
public/app/features/plugins/admin/__mocks__/index.ts
Normal file
3
public/app/features/plugins/admin/__mocks__/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export { default as remotePluginMock } from './remotePlugin.mock';
|
||||
export { default as localPluginMock } from './localPlugin.mock';
|
||||
export * from './mockHelpers';
|
@ -0,0 +1,72 @@
|
||||
import { LocalPlugin } from '../types';
|
||||
|
||||
// Copied from /api/plugins
|
||||
export default {
|
||||
name: 'Zabbix',
|
||||
type: 'app',
|
||||
id: 'alexanderzobnin-zabbix-app',
|
||||
enabled: false,
|
||||
pinned: false,
|
||||
info: {
|
||||
author: {
|
||||
name: 'Alexander Zobnin',
|
||||
url: 'https://github.com/alexanderzobnin',
|
||||
},
|
||||
description: 'Zabbix plugin for Grafana',
|
||||
links: [
|
||||
{
|
||||
name: 'GitHub',
|
||||
url: 'https://github.com/alexanderzobnin/grafana-zabbix',
|
||||
},
|
||||
{
|
||||
name: 'Docs',
|
||||
url: 'https://alexanderzobnin.github.io/grafana-zabbix',
|
||||
},
|
||||
{
|
||||
name: 'License',
|
||||
url: 'https://github.com/alexanderzobnin/grafana-zabbix/blob/master/LICENSE',
|
||||
},
|
||||
],
|
||||
logos: {
|
||||
small: 'public/plugins/alexanderzobnin-zabbix-app/img/icn-zabbix-app.svg',
|
||||
large: 'public/plugins/alexanderzobnin-zabbix-app/img/icn-zabbix-app.svg',
|
||||
},
|
||||
build: {
|
||||
time: 1629903250076,
|
||||
repo: 'git@github.com:alexanderzobnin/grafana-zabbix.git',
|
||||
hash: 'e9db978235cd6d01a095a37f3aa711ea8ea0f7ab',
|
||||
},
|
||||
screenshots: [
|
||||
{
|
||||
path: 'public/plugins/alexanderzobnin-zabbix-app/img/screenshot-showcase.png',
|
||||
name: 'Showcase',
|
||||
},
|
||||
{
|
||||
path: 'public/plugins/alexanderzobnin-zabbix-app/img/screenshot-dashboard01.png',
|
||||
name: 'Dashboard',
|
||||
},
|
||||
{
|
||||
path: 'public/plugins/alexanderzobnin-zabbix-app/img/screenshot-annotations.png',
|
||||
name: 'Annotations',
|
||||
},
|
||||
{
|
||||
path: 'public/plugins/alexanderzobnin-zabbix-app/img/screenshot-metric_editor.png',
|
||||
name: 'Metric Editor',
|
||||
},
|
||||
{
|
||||
path: 'public/plugins/alexanderzobnin-zabbix-app/img/screenshot-triggers.png',
|
||||
name: 'Triggers',
|
||||
},
|
||||
],
|
||||
version: '4.2.2',
|
||||
updated: '2021-08-25',
|
||||
},
|
||||
latestVersion: '',
|
||||
hasUpdate: false,
|
||||
defaultNavUrl: '/plugins/alexanderzobnin-zabbix-app/',
|
||||
category: '',
|
||||
state: '',
|
||||
signature: 'valid',
|
||||
signatureType: 'community',
|
||||
signatureOrg: 'Alexander Zobnin',
|
||||
} as LocalPlugin;
|
82
public/app/features/plugins/admin/__mocks__/mockHelpers.ts
Normal file
82
public/app/features/plugins/admin/__mocks__/mockHelpers.ts
Normal file
@ -0,0 +1,82 @@
|
||||
import { setBackendSrv } from '@grafana/runtime';
|
||||
import { PluginsState } from 'app/types';
|
||||
import { API_ROOT, GRAFANA_API_ROOT } from '../constants';
|
||||
import { CatalogPlugin, LocalPlugin, RemotePlugin, Version } from '../types';
|
||||
import remotePluginMock from './remotePlugin.mock';
|
||||
import localPluginMock from './localPlugin.mock';
|
||||
import catalogPluginMock from './catalogPlugin.mock';
|
||||
|
||||
// Returns a sample mock for a CatalogPlugin plugin with the possibility to extend it
|
||||
export const getCatalogPluginMock = (overrides?: Partial<CatalogPlugin>) => ({ ...catalogPluginMock, ...overrides });
|
||||
|
||||
// Returns a sample mock for a local (installed) plugin with the possibility to extend it
|
||||
export const getLocalPluginMock = (overrides?: Partial<LocalPlugin>) => ({ ...localPluginMock, ...overrides });
|
||||
|
||||
// Returns a sample mock for a remote plugin with the possibility to extend it
|
||||
export const getRemotePluginMock = (overrides?: Partial<RemotePlugin>) => ({ ...remotePluginMock, ...overrides });
|
||||
|
||||
// Returns a mock for the Redux store state of plugins
|
||||
export const getPluginsStateMock = (plugins: CatalogPlugin[] = []): PluginsState => ({
|
||||
// @ts-ignore - We don't need the rest of the properties here as we are using the "new" reducer (public/app/features/plugins/admin/state/reducer.ts)
|
||||
items: {
|
||||
ids: plugins.map(({ id }) => id),
|
||||
entities: plugins.reduce((prev, current) => ({ ...prev, [current.id]: current }), {}),
|
||||
},
|
||||
requests: {
|
||||
'plugins/fetchAll': {
|
||||
status: 'Fulfilled',
|
||||
},
|
||||
'plugins/fetchDetails': {
|
||||
status: 'Fulfilled',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Mocks a plugin by considering what needs to be mocked from GCOM and what needs to be mocked locally (local Grafana API)
|
||||
export const mockPluginApis = ({
|
||||
remote: remoteOverride,
|
||||
local: localOverride,
|
||||
versions,
|
||||
}: {
|
||||
remote?: Partial<RemotePlugin>;
|
||||
local?: Partial<LocalPlugin>;
|
||||
versions?: Version[];
|
||||
}) => {
|
||||
const remote = getRemotePluginMock(remoteOverride);
|
||||
const local = getLocalPluginMock(localOverride);
|
||||
const original = jest.requireActual('@grafana/runtime');
|
||||
const originalBackendSrv = original.getBackendSrv();
|
||||
|
||||
setBackendSrv({
|
||||
...originalBackendSrv,
|
||||
get: (path: string) => {
|
||||
// Mock GCOM plugins (remote) if necessary
|
||||
if (remote && path === `${GRAFANA_API_ROOT}/plugins`) {
|
||||
return Promise.resolve({ items: [remote] });
|
||||
}
|
||||
|
||||
// Mock GCOM single plugin page (remote) if necessary
|
||||
if (remote && path === `${GRAFANA_API_ROOT}/plugins/${remote.slug}`) {
|
||||
return Promise.resolve(remote);
|
||||
}
|
||||
|
||||
// Mock versions
|
||||
if (versions && path === `${GRAFANA_API_ROOT}/plugins/${remote.slug}/versions`) {
|
||||
return Promise.resolve({ items: versions });
|
||||
}
|
||||
|
||||
// Mock local plugin settings (installed) if necessary
|
||||
if (local && path === `${API_ROOT}/${local.id}/settings`) {
|
||||
return Promise.resolve(local);
|
||||
}
|
||||
|
||||
// Mock local plugin listing (of necessary)
|
||||
if (local && path === API_ROOT) {
|
||||
return Promise.resolve([local]);
|
||||
}
|
||||
|
||||
// Fall back to the original .get() in other cases
|
||||
return originalBackendSrv.get(path);
|
||||
},
|
||||
});
|
||||
};
|
@ -0,0 +1,48 @@
|
||||
import { PluginSignatureType, PluginType } from '@grafana/data';
|
||||
import { RemotePlugin } from '../types';
|
||||
|
||||
// Copied from /api/gnet/plugins/alexanderzobnin-zabbix-app
|
||||
export default {
|
||||
createdAt: '2016-04-06T20:23:41.000Z',
|
||||
description: 'Zabbix plugin for Grafana',
|
||||
downloads: 33645089,
|
||||
featured: 180,
|
||||
id: 74,
|
||||
typeId: 1,
|
||||
typeName: 'Application',
|
||||
internal: false,
|
||||
links: [],
|
||||
name: 'Zabbix',
|
||||
orgId: 13056,
|
||||
orgName: 'Alexander Zobnin',
|
||||
orgSlug: 'alexanderzobnin',
|
||||
orgUrl: 'https://github.com/alexanderzobnin',
|
||||
url: 'https://github.com/alexanderzobnin/grafana-zabbix',
|
||||
verified: false,
|
||||
downloadSlug: 'alexanderzobnin-zabbix-app',
|
||||
packages: {},
|
||||
popularity: 0.2111,
|
||||
signatureType: PluginSignatureType.community,
|
||||
slug: 'alexanderzobnin-zabbix-app',
|
||||
status: 'active',
|
||||
typeCode: PluginType.app,
|
||||
updatedAt: '2021-05-18T14:53:01.000Z',
|
||||
version: '4.1.5',
|
||||
versionStatus: 'active',
|
||||
versionSignatureType: PluginSignatureType.community,
|
||||
versionSignedByOrg: 'alexanderzobnin',
|
||||
versionSignedByOrgName: 'Alexander Zobnin',
|
||||
userId: 0,
|
||||
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',
|
||||
grafanaVersion: '7.3',
|
||||
plugins: [],
|
||||
},
|
||||
info: {
|
||||
links: [],
|
||||
},
|
||||
},
|
||||
} as RemotePlugin;
|
@ -1,7 +1,16 @@
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { API_ROOT, GRAFANA_API_ROOT } from './constants';
|
||||
import { PluginDetails, Org, LocalPlugin, RemotePlugin, CatalogPlugin, CatalogPluginDetails } from './types';
|
||||
import { mergeLocalsAndRemotes, mergeLocalAndRemote } from './helpers';
|
||||
import {
|
||||
PluginDetails,
|
||||
Org,
|
||||
LocalPlugin,
|
||||
RemotePlugin,
|
||||
CatalogPlugin,
|
||||
CatalogPluginDetails,
|
||||
Version,
|
||||
PluginVersion,
|
||||
} from './types';
|
||||
|
||||
export async function getCatalogPlugins(): Promise<CatalogPlugin[]> {
|
||||
const [localPlugins, remotePlugins] = await Promise.all([getLocalPlugins(), getRemotePlugins()]);
|
||||
@ -69,10 +78,13 @@ async function getRemotePlugin(id: string, isInstalled: boolean): Promise<Remote
|
||||
}
|
||||
}
|
||||
|
||||
async function getPluginVersions(id: string): Promise<any[]> {
|
||||
async function getPluginVersions(id: string): Promise<Version[]> {
|
||||
try {
|
||||
const versions = await getBackendSrv().get(`${GRAFANA_API_ROOT}/plugins/${id}/versions`);
|
||||
return versions.items;
|
||||
const versions: { items: PluginVersion[] } = await getBackendSrv().get(
|
||||
`${GRAFANA_API_ROOT}/plugins/${id}/versions`
|
||||
);
|
||||
|
||||
return (versions.items || []).map(({ version, createdAt }) => ({ version, createdAt }));
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
|
@ -3,38 +3,22 @@ import { Router } from 'react-router-dom';
|
||||
import { render, RenderResult, waitFor, within } from '@testing-library/react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { locationService } from '@grafana/runtime';
|
||||
import { PluginSignatureStatus, PluginSignatureType, PluginType } from '@grafana/data';
|
||||
import BrowsePage from './Browse';
|
||||
import { PluginType } from '@grafana/data';
|
||||
import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
import { LocalPlugin, RemotePlugin, PluginAdminRoutes } from '../types';
|
||||
import { API_ROOT, GRAFANA_API_ROOT } from '../constants';
|
||||
import { PluginAdminRoutes, CatalogPlugin } from '../types';
|
||||
import { getCatalogPluginMock, getPluginsStateMock } from '../__mocks__';
|
||||
import BrowsePage from './Browse';
|
||||
|
||||
// Mock the config to enable the plugin catalog
|
||||
jest.mock('@grafana/runtime', () => {
|
||||
const original = jest.requireActual('@grafana/runtime');
|
||||
return {
|
||||
...original,
|
||||
getBackendSrv: () => ({
|
||||
get: (path: string) => {
|
||||
switch (path) {
|
||||
case `${GRAFANA_API_ROOT}/plugins`:
|
||||
return Promise.resolve({ items: remote });
|
||||
case API_ROOT:
|
||||
return Promise.resolve(installed);
|
||||
default:
|
||||
return Promise.reject();
|
||||
}
|
||||
},
|
||||
}),
|
||||
config: {
|
||||
...original.config,
|
||||
pluginAdminEnabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
return { ...original, pluginAdminEnabled: true };
|
||||
});
|
||||
|
||||
function setup(path = '/plugins'): RenderResult {
|
||||
const store = configureStore();
|
||||
const renderBrowse = (path = '/plugins', plugins: CatalogPlugin[] = []): RenderResult => {
|
||||
const store = configureStore({ plugins: getPluginsStateMock(plugins) });
|
||||
locationService.push(path);
|
||||
const props = getRouteComponentProps({
|
||||
route: { routeName: PluginAdminRoutes.Home } as any,
|
||||
@ -47,108 +31,142 @@ function setup(path = '/plugins'): RenderResult {
|
||||
</Router>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
describe('Browse list of plugins', () => {
|
||||
describe('when filtering', () => {
|
||||
it('should list installed plugins by default', async () => {
|
||||
const { queryByText } = setup('/plugins');
|
||||
const { queryByText } = renderBrowse('/plugins', [
|
||||
getCatalogPluginMock({ id: 'plugin-1', name: 'Plugin 1', isInstalled: true }),
|
||||
getCatalogPluginMock({ id: 'plugin-2', name: 'Plugin 2', isInstalled: true }),
|
||||
getCatalogPluginMock({ id: 'plugin-3', name: 'Plugin 3', isInstalled: true }),
|
||||
getCatalogPluginMock({ id: 'plugin-4', name: 'Plugin 4', isInstalled: false }),
|
||||
]);
|
||||
|
||||
await waitFor(() => queryByText('Installed'));
|
||||
await waitFor(() => expect(queryByText('Plugin 1')).toBeInTheDocument());
|
||||
expect(queryByText('Plugin 1')).toBeInTheDocument();
|
||||
expect(queryByText('Plugin 2')).toBeInTheDocument();
|
||||
expect(queryByText('Plugin 3')).toBeInTheDocument();
|
||||
|
||||
for (const plugin of installed) {
|
||||
expect(queryByText(plugin.name)).toBeInTheDocument();
|
||||
}
|
||||
|
||||
for (const plugin of remote) {
|
||||
expect(queryByText(plugin.name)).toBeNull();
|
||||
}
|
||||
expect(queryByText('Plugin 4')).toBeNull();
|
||||
});
|
||||
|
||||
it('should list all plugins (except core plugins) when filtering by all', async () => {
|
||||
const { queryByText } = setup('/plugins?filterBy=all&filterByType=all');
|
||||
const { queryByText } = renderBrowse('/plugins?filterBy=all&filterByType=all', [
|
||||
getCatalogPluginMock({ id: 'plugin-1', name: 'Plugin 1', isInstalled: true }),
|
||||
getCatalogPluginMock({ id: 'plugin-2', name: 'Plugin 2', isInstalled: false }),
|
||||
getCatalogPluginMock({ id: 'plugin-3', name: 'Plugin 3', isInstalled: true }),
|
||||
getCatalogPluginMock({ id: 'plugin-4', name: 'Plugin 4', isInstalled: true, isCore: true }),
|
||||
]);
|
||||
|
||||
await waitFor(() => expect(queryByText('Diagram')).toBeInTheDocument());
|
||||
for (const plugin of remote) {
|
||||
expect(queryByText(plugin.name)).toBeInTheDocument();
|
||||
}
|
||||
await waitFor(() => expect(queryByText('Plugin 1')).toBeInTheDocument());
|
||||
expect(queryByText('Plugin 2')).toBeInTheDocument();
|
||||
expect(queryByText('Plugin 3')).toBeInTheDocument();
|
||||
|
||||
expect(queryByText('Alert Manager')).not.toBeInTheDocument();
|
||||
// Core plugins should not be listed
|
||||
expect(queryByText('Plugin 4')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should list installed plugins (including core plugins) when filtering by installed', async () => {
|
||||
const { queryByText } = setup('/plugins?filterBy=installed');
|
||||
const { queryByText } = renderBrowse('/plugins?filterBy=installed', [
|
||||
getCatalogPluginMock({ id: 'plugin-1', name: 'Plugin 1', isInstalled: true }),
|
||||
getCatalogPluginMock({ id: 'plugin-2', name: 'Plugin 2', isInstalled: false }),
|
||||
getCatalogPluginMock({ id: 'plugin-3', name: 'Plugin 3', isInstalled: true }),
|
||||
getCatalogPluginMock({ id: 'plugin-4', name: 'Plugin 4', isInstalled: true, isCore: true }),
|
||||
]);
|
||||
|
||||
await waitFor(() => queryByText('Installed'));
|
||||
await waitFor(() => expect(queryByText('Plugin 1')).toBeInTheDocument());
|
||||
expect(queryByText('Plugin 3')).toBeInTheDocument();
|
||||
expect(queryByText('Plugin 4')).toBeInTheDocument();
|
||||
|
||||
for (const plugin of installed) {
|
||||
expect(queryByText(plugin.name)).toBeInTheDocument();
|
||||
}
|
||||
|
||||
for (const plugin of remote) {
|
||||
expect(queryByText(plugin.name)).not.toBeInTheDocument();
|
||||
}
|
||||
// Not showing not installed plugins
|
||||
expect(queryByText('Plugin 2')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should list enterprise plugins', async () => {
|
||||
const { queryByText } = setup('/plugins?filterBy=all&q=wavefront');
|
||||
it('should list enterprise plugins when querying for them', async () => {
|
||||
const { queryByText } = renderBrowse('/plugins?filterBy=all&q=wavefront', [
|
||||
getCatalogPluginMock({ id: 'wavefront', name: 'Wavefront', isInstalled: true, isEnterprise: true }),
|
||||
getCatalogPluginMock({ id: 'plugin-2', name: 'Plugin 2', isInstalled: true, isCore: true }),
|
||||
getCatalogPluginMock({ id: 'plugin-3', name: 'Plugin 3', isInstalled: true }),
|
||||
]);
|
||||
|
||||
await waitFor(() => expect(queryByText('Wavefront')).toBeInTheDocument());
|
||||
|
||||
// Should not show plugins that don't match the query
|
||||
expect(queryByText('Plugin 2')).not.toBeInTheDocument();
|
||||
expect(queryByText('Plugin 3')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should list only datasource plugins when filtering by datasource', async () => {
|
||||
const { queryByText } = setup('/plugins?filterBy=all&filterByType=datasource');
|
||||
const { queryByText } = renderBrowse('/plugins?filterBy=all&filterByType=datasource', [
|
||||
getCatalogPluginMock({ id: 'plugin-1', name: 'Plugin 1', type: PluginType.app }),
|
||||
getCatalogPluginMock({ id: 'plugin-2', name: 'Plugin 2', type: PluginType.datasource }),
|
||||
getCatalogPluginMock({ id: 'plugin-3', name: 'Plugin 3', type: PluginType.panel }),
|
||||
]);
|
||||
|
||||
await waitFor(() => expect(queryByText('Wavefront')).toBeInTheDocument());
|
||||
await waitFor(() => expect(queryByText('Plugin 2')).toBeInTheDocument());
|
||||
|
||||
expect(queryByText('Alert Manager')).not.toBeInTheDocument();
|
||||
expect(queryByText('Diagram')).not.toBeInTheDocument();
|
||||
expect(queryByText('Zabbix')).not.toBeInTheDocument();
|
||||
expect(queryByText('ACE.SVG')).not.toBeInTheDocument();
|
||||
// Other plugin types shouldn't be shown
|
||||
expect(queryByText('Plugin 1')).not.toBeInTheDocument();
|
||||
expect(queryByText('Plugin 3')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should list only panel plugins when filtering by panel', async () => {
|
||||
const { queryByText } = setup('/plugins?filterBy=all&filterByType=panel');
|
||||
const { queryByText } = renderBrowse('/plugins?filterBy=all&filterByType=panel', [
|
||||
getCatalogPluginMock({ id: 'plugin-1', name: 'Plugin 1', type: PluginType.app }),
|
||||
getCatalogPluginMock({ id: 'plugin-2', name: 'Plugin 2', type: PluginType.datasource }),
|
||||
getCatalogPluginMock({ id: 'plugin-3', name: 'Plugin 3', type: PluginType.panel }),
|
||||
]);
|
||||
|
||||
await waitFor(() => expect(queryByText('Diagram')).toBeInTheDocument());
|
||||
expect(queryByText('ACE.SVG')).toBeInTheDocument();
|
||||
await waitFor(() => expect(queryByText('Plugin 3')).toBeInTheDocument());
|
||||
|
||||
expect(queryByText('Wavefront')).not.toBeInTheDocument();
|
||||
expect(queryByText('Alert Manager')).not.toBeInTheDocument();
|
||||
expect(queryByText('Zabbix')).not.toBeInTheDocument();
|
||||
// Other plugin types shouldn't be shown
|
||||
expect(queryByText('Plugin 1')).not.toBeInTheDocument();
|
||||
expect(queryByText('Plugin 2')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should list only app plugins when filtering by app', async () => {
|
||||
const { queryByText } = setup('/plugins?filterBy=all&filterByType=app');
|
||||
const { queryByText } = renderBrowse('/plugins?filterBy=all&filterByType=app', [
|
||||
getCatalogPluginMock({ id: 'plugin-1', name: 'Plugin 1', type: PluginType.app }),
|
||||
getCatalogPluginMock({ id: 'plugin-2', name: 'Plugin 2', type: PluginType.datasource }),
|
||||
getCatalogPluginMock({ id: 'plugin-3', name: 'Plugin 3', type: PluginType.panel }),
|
||||
]);
|
||||
|
||||
await waitFor(() => expect(queryByText('Zabbix')).toBeInTheDocument());
|
||||
await waitFor(() => expect(queryByText('Plugin 1')).toBeInTheDocument());
|
||||
|
||||
expect(queryByText('Wavefront')).not.toBeInTheDocument();
|
||||
expect(queryByText('Alert Manager')).not.toBeInTheDocument();
|
||||
expect(queryByText('Diagram')).not.toBeInTheDocument();
|
||||
expect(queryByText('ACE.SVG')).not.toBeInTheDocument();
|
||||
// Other plugin types shouldn't be shown
|
||||
expect(queryByText('Plugin 2')).not.toBeInTheDocument();
|
||||
expect(queryByText('Plugin 3')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
describe('when searching', () => {
|
||||
it('should only list plugins matching search', async () => {
|
||||
const { queryByText } = setup('/plugins?filterBy=all&q=zabbix');
|
||||
const { queryByText } = renderBrowse('/plugins?filterBy=all&q=zabbix', [
|
||||
getCatalogPluginMock({ id: 'zabbix', name: 'Zabbix' }),
|
||||
getCatalogPluginMock({ id: 'plugin-2', name: 'Plugin 2' }),
|
||||
getCatalogPluginMock({ id: 'plugin-3', name: 'Plugin 3' }),
|
||||
]);
|
||||
|
||||
await waitFor(() => expect(queryByText('Zabbix')).toBeInTheDocument());
|
||||
|
||||
expect(queryByText('Wavefront')).not.toBeInTheDocument();
|
||||
expect(queryByText('Alert Manager')).not.toBeInTheDocument();
|
||||
expect(queryByText('Diagram')).not.toBeInTheDocument();
|
||||
expect(queryByText('Redis Application')).not.toBeInTheDocument();
|
||||
// Other plugin types shouldn't be shown
|
||||
expect(queryByText('Plugin 2')).not.toBeInTheDocument();
|
||||
expect(queryByText('Plugin 3')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when sorting', () => {
|
||||
it('should sort plugins by name in ascending alphabetical order', async () => {
|
||||
const { findByTestId } = setup('/plugins?filterBy=all');
|
||||
const { findByTestId } = renderBrowse('/plugins?filterBy=all', [
|
||||
getCatalogPluginMock({ id: 'wavefront', name: 'Wavefront' }),
|
||||
getCatalogPluginMock({ id: 'redis-application', name: 'Redis Application' }),
|
||||
getCatalogPluginMock({ id: 'zabbix', name: 'Zabbix' }),
|
||||
getCatalogPluginMock({ id: 'diagram', name: 'Diagram' }),
|
||||
getCatalogPluginMock({ id: 'acesvg', name: 'ACE.SVG' }),
|
||||
]);
|
||||
|
||||
const pluginList = await findByTestId('plugin-list');
|
||||
const pluginHeadings = within(pluginList).queryAllByRole('heading');
|
||||
|
||||
expect(pluginHeadings.map((heading) => heading.innerHTML)).toStrictEqual([
|
||||
'ACE.SVG',
|
||||
'Diagram',
|
||||
@ -159,11 +177,16 @@ describe('Browse list of plugins', () => {
|
||||
});
|
||||
|
||||
it('should sort plugins by name in descending alphabetical order', async () => {
|
||||
const { findByTestId } = setup('/plugins?filterBy=all&sortBy=nameDesc');
|
||||
const { findByTestId } = renderBrowse('/plugins?filterBy=all&sortBy=nameDesc', [
|
||||
getCatalogPluginMock({ id: 'wavefront', name: 'Wavefront' }),
|
||||
getCatalogPluginMock({ id: 'redis-application', name: 'Redis Application' }),
|
||||
getCatalogPluginMock({ id: 'zabbix', name: 'Zabbix' }),
|
||||
getCatalogPluginMock({ id: 'diagram', name: 'Diagram' }),
|
||||
getCatalogPluginMock({ id: 'acesvg', name: 'ACE.SVG' }),
|
||||
]);
|
||||
|
||||
const pluginList = await findByTestId('plugin-list');
|
||||
const pluginHeadings = within(pluginList).queryAllByRole('heading');
|
||||
|
||||
expect(pluginHeadings.map((heading) => heading.innerHTML)).toStrictEqual([
|
||||
'Zabbix',
|
||||
'Wavefront',
|
||||
@ -174,11 +197,16 @@ describe('Browse list of plugins', () => {
|
||||
});
|
||||
|
||||
it('should sort plugins by date in ascending updated order', async () => {
|
||||
const { findByTestId } = setup('/plugins?filterBy=all&sortBy=updated');
|
||||
const { findByTestId } = renderBrowse('/plugins?filterBy=all&sortBy=updated', [
|
||||
getCatalogPluginMock({ id: '1', name: 'Wavefront', updatedAt: '2021-04-01T00:00:00.000Z' }),
|
||||
getCatalogPluginMock({ id: '2', name: 'Redis Application', updatedAt: '2021-02-01T00:00:00.000Z' }),
|
||||
getCatalogPluginMock({ id: '3', name: 'Zabbix', updatedAt: '2021-01-01T00:00:00.000Z' }),
|
||||
getCatalogPluginMock({ id: '4', name: 'Diagram', updatedAt: '2021-05-01T00:00:00.000Z' }),
|
||||
getCatalogPluginMock({ id: '5', name: 'ACE.SVG', updatedAt: '2021-02-01T00:00:00.000Z' }),
|
||||
]);
|
||||
|
||||
const pluginList = await findByTestId('plugin-list');
|
||||
const pluginHeadings = within(pluginList).queryAllByRole('heading');
|
||||
|
||||
expect(pluginHeadings.map((heading) => heading.innerHTML)).toStrictEqual([
|
||||
'Diagram',
|
||||
'Wavefront',
|
||||
@ -189,26 +217,36 @@ describe('Browse list of plugins', () => {
|
||||
});
|
||||
|
||||
it('should sort plugins by date in ascending published order', async () => {
|
||||
const { findByTestId } = setup('/plugins?filterBy=all&sortBy=published');
|
||||
const { findByTestId } = renderBrowse('/plugins?filterBy=all&sortBy=published', [
|
||||
getCatalogPluginMock({ id: '1', name: 'Wavefront', publishedAt: '2021-04-01T00:00:00.000Z' }),
|
||||
getCatalogPluginMock({ id: '2', name: 'Redis Application', publishedAt: '2021-02-01T00:00:00.000Z' }),
|
||||
getCatalogPluginMock({ id: '3', name: 'Zabbix', publishedAt: '2021-01-01T00:00:00.000Z' }),
|
||||
getCatalogPluginMock({ id: '4', name: 'Diagram', publishedAt: '2021-05-01T00:00:00.000Z' }),
|
||||
getCatalogPluginMock({ id: '5', name: 'ACE.SVG', publishedAt: '2021-02-01T00:00:00.000Z' }),
|
||||
]);
|
||||
|
||||
const pluginList = await findByTestId('plugin-list');
|
||||
const pluginHeadings = within(pluginList).queryAllByRole('heading');
|
||||
|
||||
expect(pluginHeadings.map((heading) => heading.innerHTML)).toStrictEqual([
|
||||
'Diagram',
|
||||
'Wavefront',
|
||||
'Redis Application',
|
||||
'ACE.SVG',
|
||||
'Wavefront',
|
||||
'Zabbix',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should sort plugins by number of downloads in ascending order', async () => {
|
||||
const { findByTestId } = setup('/plugins?filterBy=all&sortBy=downloads');
|
||||
const { findByTestId } = renderBrowse('/plugins?filterBy=all&sortBy=downloads', [
|
||||
getCatalogPluginMock({ id: '1', name: 'Wavefront', downloads: 30 }),
|
||||
getCatalogPluginMock({ id: '2', name: 'Redis Application', downloads: 10 }),
|
||||
getCatalogPluginMock({ id: '3', name: 'Zabbix', downloads: 50 }),
|
||||
getCatalogPluginMock({ id: '4', name: 'Diagram', downloads: 20 }),
|
||||
getCatalogPluginMock({ id: '5', name: 'ACE.SVG', downloads: 40 }),
|
||||
]);
|
||||
|
||||
const pluginList = await findByTestId('plugin-list');
|
||||
const pluginHeadings = within(pluginList).queryAllByRole('heading');
|
||||
|
||||
expect(pluginHeadings.map((heading) => heading.innerHTML)).toStrictEqual([
|
||||
'Zabbix',
|
||||
'ACE.SVG',
|
||||
@ -219,215 +257,3 @@ describe('Browse list of plugins', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const installed: LocalPlugin[] = [
|
||||
{
|
||||
name: 'Alert Manager',
|
||||
type: PluginType.datasource,
|
||||
id: 'alertmanager',
|
||||
enabled: true,
|
||||
pinned: false,
|
||||
info: {
|
||||
author: {
|
||||
name: 'Prometheus alertmanager',
|
||||
url: 'https://grafana.com',
|
||||
},
|
||||
description: '',
|
||||
links: [
|
||||
{
|
||||
name: 'Learn more',
|
||||
url: 'https://prometheus.io/docs/alerting/latest/alertmanager/',
|
||||
},
|
||||
],
|
||||
logos: {
|
||||
small: 'public/app/plugins/datasource/alertmanager/img/logo.svg',
|
||||
large: 'public/app/plugins/datasource/alertmanager/img/logo.svg',
|
||||
},
|
||||
build: {},
|
||||
screenshots: null,
|
||||
version: '',
|
||||
updated: '',
|
||||
},
|
||||
latestVersion: '',
|
||||
hasUpdate: false,
|
||||
defaultNavUrl: '/plugins/alertmanager/',
|
||||
category: '',
|
||||
state: 'alpha',
|
||||
signature: PluginSignatureStatus.internal,
|
||||
signatureType: PluginSignatureType.core,
|
||||
signatureOrg: '',
|
||||
},
|
||||
{
|
||||
name: 'Diagram',
|
||||
type: PluginType.panel,
|
||||
id: 'jdbranham-diagram-panel',
|
||||
enabled: true,
|
||||
pinned: false,
|
||||
info: {
|
||||
author: { name: 'Jeremy Branham', url: 'https://savantly.net' },
|
||||
description: 'Display diagrams and charts with colored metric indicators',
|
||||
links: [
|
||||
{
|
||||
name: 'Project site',
|
||||
url: 'https://github.com/jdbranham/grafana-diagram',
|
||||
},
|
||||
{
|
||||
name: 'Apache License',
|
||||
url: 'https://github.com/jdbranham/grafana-diagram/blob/master/LICENSE',
|
||||
},
|
||||
],
|
||||
logos: {
|
||||
small: 'public/plugins/jdbranham-diagram-panel/img/logo.svg',
|
||||
large: 'public/plugins/jdbranham-diagram-panel/img/logo.svg',
|
||||
},
|
||||
build: {},
|
||||
screenshots: [],
|
||||
version: '1.7.3',
|
||||
updated: '2021-07-20',
|
||||
},
|
||||
latestVersion: '1.7.3',
|
||||
hasUpdate: true,
|
||||
defaultNavUrl: '/plugins/jdbranham-diagram-panel/',
|
||||
category: '',
|
||||
state: '',
|
||||
signature: PluginSignatureStatus.missing,
|
||||
signatureType: PluginSignatureType.core,
|
||||
signatureOrg: '',
|
||||
},
|
||||
{
|
||||
name: 'Redis Application',
|
||||
type: PluginType.app,
|
||||
id: 'redis-app',
|
||||
enabled: false,
|
||||
pinned: false,
|
||||
info: {
|
||||
author: {
|
||||
name: 'RedisGrafana',
|
||||
url: 'https://redisgrafana.github.io',
|
||||
},
|
||||
description: 'Provides Application pages and custom panels for Redis Data Source.',
|
||||
links: [
|
||||
{ name: 'Website', url: 'https://redisgrafana.github.io' },
|
||||
{
|
||||
name: 'License',
|
||||
url: 'https://github.com/RedisGrafana/grafana-redis-app/blob/master/LICENSE',
|
||||
},
|
||||
],
|
||||
logos: {
|
||||
small: 'public/plugins/redis-app/img/logo.svg',
|
||||
large: 'public/plugins/redis-app/img/logo.svg',
|
||||
},
|
||||
build: {},
|
||||
screenshots: [],
|
||||
version: '2.0.1',
|
||||
updated: '2021-07-07',
|
||||
},
|
||||
latestVersion: '2.0.1',
|
||||
hasUpdate: false,
|
||||
defaultNavUrl: '/plugins/redis-app/',
|
||||
category: '',
|
||||
state: '',
|
||||
signature: PluginSignatureStatus.valid,
|
||||
signatureType: PluginSignatureType.commercial,
|
||||
signatureOrg: 'RedisGrafana',
|
||||
},
|
||||
];
|
||||
|
||||
const remote: RemotePlugin[] = [
|
||||
{
|
||||
status: 'active',
|
||||
id: 74,
|
||||
typeId: 1,
|
||||
typeName: 'Application',
|
||||
typeCode: PluginType.app,
|
||||
slug: 'alexanderzobnin-zabbix-app',
|
||||
name: 'Zabbix',
|
||||
description: 'Zabbix plugin for Grafana',
|
||||
version: '4.1.5',
|
||||
versionStatus: 'active',
|
||||
versionSignatureType: PluginSignatureType.community,
|
||||
versionSignedByOrg: 'alexanderzobnin',
|
||||
versionSignedByOrgName: 'Alexander Zobnin',
|
||||
userId: 0,
|
||||
orgId: 13056,
|
||||
orgName: 'Alexander Zobnin',
|
||||
orgSlug: 'alexanderzobnin',
|
||||
orgUrl: 'https://github.com/alexanderzobnin',
|
||||
url: 'https://github.com/alexanderzobnin/grafana-zabbix',
|
||||
createdAt: '2016-04-06T20:23:41.000Z',
|
||||
updatedAt: '2021-05-18T14:53:01.000Z',
|
||||
downloads: 34387994,
|
||||
verified: false,
|
||||
featured: 180,
|
||||
internal: false,
|
||||
downloadSlug: 'alexanderzobnin-zabbix-app',
|
||||
popularity: 0.2019,
|
||||
signatureType: PluginSignatureType.community,
|
||||
packages: {},
|
||||
links: [],
|
||||
},
|
||||
{
|
||||
status: 'enterprise',
|
||||
id: 658,
|
||||
typeId: 2,
|
||||
typeName: 'Data Source',
|
||||
typeCode: PluginType.datasource,
|
||||
slug: 'grafana-wavefront-datasource',
|
||||
name: 'Wavefront',
|
||||
description: 'Wavefront Datasource',
|
||||
version: '1.0.8',
|
||||
versionStatus: 'active',
|
||||
versionSignatureType: PluginSignatureType.grafana,
|
||||
versionSignedByOrg: 'grafana',
|
||||
versionSignedByOrgName: 'Grafana Labs',
|
||||
userId: 0,
|
||||
orgId: 5000,
|
||||
orgName: 'Grafana Labs',
|
||||
orgSlug: 'grafana',
|
||||
orgUrl: 'https://grafana.org',
|
||||
url: 'https://github.com/grafana/wavefront-datasource/',
|
||||
createdAt: '2020-09-01T13:02:57.000Z',
|
||||
updatedAt: '2021-07-12T18:41:03.000Z',
|
||||
downloads: 7818,
|
||||
verified: false,
|
||||
featured: 0,
|
||||
internal: false,
|
||||
downloadSlug: 'grafana-wavefront-datasource',
|
||||
popularity: 0.0107,
|
||||
signatureType: PluginSignatureType.grafana,
|
||||
packages: {},
|
||||
links: [],
|
||||
},
|
||||
{
|
||||
status: 'active',
|
||||
id: 659,
|
||||
typeId: 3,
|
||||
typeName: 'Panel',
|
||||
typeCode: PluginType.panel,
|
||||
slug: 'aceiot-svg-panel',
|
||||
name: 'ACE.SVG',
|
||||
description: 'SVG Visualization Panel',
|
||||
version: '0.0.10',
|
||||
versionStatus: 'active',
|
||||
versionSignatureType: PluginSignatureType.community,
|
||||
versionSignedByOrg: 'aceiot',
|
||||
versionSignedByOrgName: 'Andrew Rodgers',
|
||||
userId: 0,
|
||||
orgId: 409764,
|
||||
orgName: 'Andrew Rodgers',
|
||||
orgSlug: 'aceiot',
|
||||
orgUrl: '',
|
||||
url: 'https://github.com/ACE-IoT-Solutions/ace-svg-react',
|
||||
createdAt: '2020-09-01T14:46:44.000Z',
|
||||
updatedAt: '2021-06-28T14:01:36.000Z',
|
||||
downloads: 101569,
|
||||
verified: false,
|
||||
featured: 0,
|
||||
internal: false,
|
||||
downloadSlug: 'aceiot-svg-panel',
|
||||
popularity: 0.0134,
|
||||
signatureType: PluginSignatureType.community,
|
||||
packages: {},
|
||||
links: [],
|
||||
},
|
||||
];
|
||||
|
@ -3,93 +3,41 @@ import { Provider } from 'react-redux';
|
||||
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 { configureStore } from 'app/store/configureStore';
|
||||
import PluginDetailsPage from './PluginDetails';
|
||||
import { API_ROOT, GRAFANA_API_ROOT } from '../constants';
|
||||
import { LocalPlugin, RemotePlugin } from '../types';
|
||||
import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps';
|
||||
import { CatalogPlugin } from '../types';
|
||||
import { mockPluginApis, getCatalogPluginMock, getPluginsStateMock } from '../__mocks__';
|
||||
|
||||
// Mock the config to enable the plugin catalog
|
||||
jest.mock('@grafana/runtime', () => {
|
||||
const original = jest.requireActual('@grafana/runtime');
|
||||
const mockedRuntime = { ...original };
|
||||
|
||||
return {
|
||||
...original,
|
||||
getBackendSrv: () => ({
|
||||
get: (path: string) => {
|
||||
switch (path) {
|
||||
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(),
|
||||
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(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' }));
|
||||
case `${GRAFANA_API_ROOT}/plugins`:
|
||||
return Promise.resolve({
|
||||
items: [
|
||||
remotePlugin({ slug: 'not-installed' }),
|
||||
remotePlugin({ slug: 'installed' }),
|
||||
remotePlugin({ slug: 'has-update', version: '2.0.0' }),
|
||||
remotePlugin({ slug: 'enterprise', status: 'enterprise' }),
|
||||
],
|
||||
});
|
||||
default:
|
||||
return Promise.reject();
|
||||
}
|
||||
},
|
||||
}),
|
||||
config: {
|
||||
...original.config,
|
||||
bootData: {
|
||||
...original.config.bootData,
|
||||
user: {
|
||||
...original.config.bootData.user,
|
||||
isGrafanaAdmin: true,
|
||||
},
|
||||
},
|
||||
buildInfo: {
|
||||
...original.config.buildInfo,
|
||||
version: 'v7.5.0',
|
||||
},
|
||||
pluginAdminEnabled: true,
|
||||
},
|
||||
};
|
||||
mockedRuntime.config.bootData.user.isGrafanaAdmin = true;
|
||||
mockedRuntime.config.buildInfo.version = 'v8.1.0';
|
||||
mockedRuntime.config.pluginAdminEnabled = true;
|
||||
|
||||
return mockedRuntime;
|
||||
});
|
||||
|
||||
const renderPluginDetails = (pluginOverride: Partial<CatalogPlugin>): RenderResult => {
|
||||
const plugin = getCatalogPluginMock(pluginOverride);
|
||||
const { id } = plugin;
|
||||
const props = getRouteComponentProps({ match: { params: { pluginId: id }, isExact: true, url: '', path: '' } });
|
||||
const store = configureStore({
|
||||
plugins: getPluginsStateMock([plugin]),
|
||||
});
|
||||
|
||||
function setup(pluginId: string): RenderResult {
|
||||
const props = getRouteComponentProps({ match: { params: { pluginId }, isExact: true, url: '', path: '' } });
|
||||
const store = configureStore();
|
||||
return render(
|
||||
<Provider store={store}>
|
||||
<PluginDetailsPage {...props} />
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
describe('Plugin details page', () => {
|
||||
const id = 'my-plugin';
|
||||
let dateNow: any;
|
||||
|
||||
beforeAll(() => {
|
||||
@ -104,15 +52,50 @@ describe('Plugin details page', () => {
|
||||
dateNow.mockRestore();
|
||||
});
|
||||
|
||||
it('should display an overview (plugin readme) by default', async () => {
|
||||
const { queryByText } = setup('not-installed');
|
||||
// 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 props = getRouteComponentProps({ match: { params: { pluginId: id }, isExact: true, url: '', path: '' } });
|
||||
const store = configureStore();
|
||||
const { queryByText } = render(
|
||||
<Provider store={store}>
|
||||
<PluginDetailsPage {...props} />
|
||||
</Provider>
|
||||
);
|
||||
|
||||
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');
|
||||
it('should display an overview (plugin readme) by default', async () => {
|
||||
const { queryByText } = renderPluginDetails({ id });
|
||||
|
||||
await waitFor(() => expect(queryByText(/licensed under the apache 2.0 license/i)).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('should display version history in case it is available', async () => {
|
||||
const { queryByText, getByText, getByRole } = renderPluginDetails({
|
||||
id,
|
||||
details: {
|
||||
links: [],
|
||||
versions: [
|
||||
{
|
||||
version: '1.0.0',
|
||||
createdAt: '2016-04-06T20:23:41.000Z',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
// Check if version information is available
|
||||
await waitFor(() => expect(queryByText(/version history/i)).toBeInTheDocument());
|
||||
|
||||
// Go to the versions tab
|
||||
userEvent.click(getByText(/version history/i));
|
||||
expect(
|
||||
getByRole('columnheader', {
|
||||
@ -136,35 +119,42 @@ describe('Plugin details page', () => {
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display install button for a plugin that isn't installed", async () => {
|
||||
const { queryByRole } = setup('not-installed');
|
||||
it("should display an install button for a plugin that isn't installed", async () => {
|
||||
const { queryByRole } = renderPluginDetails({ id, isInstalled: false });
|
||||
|
||||
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');
|
||||
it('should display an uninstall button for an already installed plugin', async () => {
|
||||
const { queryByRole } = renderPluginDetails({ id, isInstalled: true });
|
||||
|
||||
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');
|
||||
const { queryByRole } = renderPluginDetails({ id, isInstalled: true, hasUpdate: true });
|
||||
|
||||
// Displays an "update" button
|
||||
await waitFor(() => expect(queryByRole('button', { name: /update/i })).toBeInTheDocument());
|
||||
|
||||
// Does not display "install" and "uninstall" buttons
|
||||
expect(queryByRole('button', { name: /install/i })).toBeInTheDocument();
|
||||
expect(queryByRole('button', { name: /uninstall/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display install button for enterprise plugins if license is valid', async () => {
|
||||
it('should display an install button for enterprise plugins if license is valid', async () => {
|
||||
config.licenseInfo.hasValidLicense = true;
|
||||
const { queryByRole } = setup('enterprise');
|
||||
|
||||
const { queryByRole } = renderPluginDetails({ id, isInstalled: false, isEnterprise: true });
|
||||
|
||||
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');
|
||||
|
||||
const { queryByRole, queryByText } = renderPluginDetails({ id, isInstalled: true, isEnterprise: true });
|
||||
|
||||
await waitFor(() => expect(queryByRole('button', { name: /install/i })).not.toBeInTheDocument());
|
||||
expect(queryByText(/no valid Grafana Enterprise license detected/i)).toBeInTheDocument();
|
||||
@ -172,126 +162,50 @@ describe('Plugin details page', () => {
|
||||
});
|
||||
|
||||
it('should not display install / uninstall buttons for core plugins', async () => {
|
||||
const { queryByRole } = setup('core');
|
||||
const { queryByRole } = renderPluginDetails({ id, isInstalled: true, isCore: true });
|
||||
|
||||
await waitFor(() => expect(queryByRole('button', { name: /update/i })).not.toBeInTheDocument());
|
||||
await waitFor(() => expect(queryByRole('button', { name: /(un)?install/i })).not.toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('should display install link with pluginAdminExternalManageEnabled true', async () => {
|
||||
it('should display install link with `config.pluginAdminExternalManageEnabled` set to true', async () => {
|
||||
config.pluginAdminExternalManageEnabled = true;
|
||||
const { queryByRole } = setup('not-installed');
|
||||
|
||||
const { queryByRole } = renderPluginDetails({ id, isInstalled: false });
|
||||
|
||||
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 () => {
|
||||
it('should display uninstall link for an installed plugin with `config.pluginAdminExternalManageEnabled` set to true', async () => {
|
||||
config.pluginAdminExternalManageEnabled = true;
|
||||
const { queryByRole } = setup('installed');
|
||||
|
||||
const { queryByRole } = renderPluginDetails({ id, isInstalled: true });
|
||||
|
||||
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 () => {
|
||||
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 } = setup('has-update');
|
||||
|
||||
const { queryByRole } = renderPluginDetails({ id, isInstalled: true, hasUpdate: true });
|
||||
|
||||
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');
|
||||
const { queryByText } = renderPluginDetails({
|
||||
id,
|
||||
details: {
|
||||
pluginDependencies: [],
|
||||
grafanaDependency: '>=8.0.0',
|
||||
links: [],
|
||||
},
|
||||
});
|
||||
|
||||
// Wait for the dependencies part to be loaded
|
||||
await waitFor(() => expect(queryByText(/dependencies:/i)).toBeInTheDocument());
|
||||
|
||||
expect(queryByText('Grafana >=7.3.0')).toBeInTheDocument();
|
||||
expect(queryByText('Grafana >=8.0.0')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
function remotePlugin(plugin: Partial<RemotePlugin> = {}): RemotePlugin {
|
||||
return {
|
||||
createdAt: '2016-04-06T20:23:41.000Z',
|
||||
description: 'Zabbix plugin for Grafana',
|
||||
downloads: 33645089,
|
||||
featured: 180,
|
||||
id: 74,
|
||||
typeId: 1,
|
||||
typeName: 'Application',
|
||||
internal: false,
|
||||
links: [],
|
||||
name: 'Zabbix',
|
||||
orgId: 13056,
|
||||
orgName: 'Alexander Zobnin',
|
||||
orgSlug: 'alexanderzobnin',
|
||||
orgUrl: 'https://github.com/alexanderzobnin',
|
||||
url: 'https://github.com/alexanderzobnin/grafana-zabbix',
|
||||
verified: false,
|
||||
downloadSlug: 'alexanderzobnin-zabbix-app',
|
||||
packages: {},
|
||||
popularity: 0.2111,
|
||||
signatureType: PluginSignatureType.community,
|
||||
slug: 'alexanderzobnin-zabbix-app',
|
||||
status: 'active',
|
||||
typeCode: PluginType.app,
|
||||
updatedAt: '2021-05-18T14:53:01.000Z',
|
||||
version: '4.1.5',
|
||||
versionStatus: 'active',
|
||||
versionSignatureType: PluginSignatureType.community,
|
||||
versionSignedByOrg: 'alexanderzobnin',
|
||||
versionSignedByOrgName: 'Alexander Zobnin',
|
||||
userId: 0,
|
||||
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',
|
||||
grafanaVersion: '7.3',
|
||||
plugins: [],
|
||||
},
|
||||
info: {
|
||||
links: [],
|
||||
},
|
||||
},
|
||||
...plugin,
|
||||
};
|
||||
}
|
||||
|
||||
function localPlugin(plugin: Partial<LocalPlugin> = {}): LocalPlugin {
|
||||
return {
|
||||
name: 'Akumuli',
|
||||
type: PluginType.datasource,
|
||||
id: 'akumuli-datasource',
|
||||
enabled: true,
|
||||
pinned: false,
|
||||
info: {
|
||||
author: {
|
||||
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: {},
|
||||
screenshots: null,
|
||||
version: '1.3.12',
|
||||
updated: '2019-12-19',
|
||||
},
|
||||
latestVersion: '1.3.12',
|
||||
hasUpdate: false,
|
||||
defaultNavUrl: '/plugins/akumuli-datasource/',
|
||||
category: '',
|
||||
state: '',
|
||||
signature: PluginSignatureStatus.valid,
|
||||
signatureType: PluginSignatureType.core,
|
||||
signatureOrg: 'Grafana Labs',
|
||||
...plugin,
|
||||
};
|
||||
}
|
||||
|
@ -220,3 +220,21 @@ export type ReducerState = PluginsState & {
|
||||
|
||||
// TODO<remove when the "plugin_admin_enabled" feature flag is removed>
|
||||
export type PluginCatalogStoreState = StoreState & { plugins: ReducerState };
|
||||
|
||||
// The data that we receive when fetching "/api/gnet/plugins/<plugin>/versions"
|
||||
export type PluginVersion = {
|
||||
id: number;
|
||||
pluginId: number;
|
||||
pluginSlug: string;
|
||||
version: string;
|
||||
url: string;
|
||||
commit: string;
|
||||
description: string;
|
||||
createdAt: string;
|
||||
updatedAt?: string;
|
||||
downloads: number;
|
||||
verified: boolean;
|
||||
status: string;
|
||||
downloadSlug: string;
|
||||
links: Array<{ rel: string; href: string }>;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user