Plugins: Disable version installation for specific plugin types (#98597)
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { PluginType } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
|
||||
import { getCatalogPluginMock } from '../__mocks__';
|
||||
import { PluginTabIds } from '../types';
|
||||
|
||||
import { PluginDetailsBody } from './PluginDetailsBody';
|
||||
|
||||
function renderWithStore(component: JSX.Element) {
|
||||
const store = configureStore();
|
||||
|
||||
return render(<Provider store={store}>{component}</Provider>);
|
||||
}
|
||||
|
||||
describe('PluginDetailsBody', () => {
|
||||
const tcs = [
|
||||
{
|
||||
name: 'renderer type plugin',
|
||||
plugin: {
|
||||
type: PluginType.renderer,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'secrets manager type plugin',
|
||||
plugin: {
|
||||
type: PluginType.secretsmanager,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'enterprise plugin type without enterprise license',
|
||||
plugin: {
|
||||
isEnterprise: true,
|
||||
},
|
||||
changeConfig: () => {
|
||||
config.licenseInfo.enabledFeatures = {
|
||||
'enterprise-plugins': true,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'unpublished plugin',
|
||||
plugin: {
|
||||
isPublished: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'core plugin',
|
||||
plugin: {
|
||||
isCore: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'disabled plugin',
|
||||
plugin: {
|
||||
isDisabled: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'provisioned plugin',
|
||||
plugin: {
|
||||
isProvisioned: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'install controls disabled',
|
||||
changeConfig: () => {
|
||||
config.pluginAdminEnabled = false;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
tcs.forEach((tc) => {
|
||||
it(`should render disable version installation for ${tc.name}`, async () => {
|
||||
if (tc.changeConfig) {
|
||||
tc.changeConfig();
|
||||
}
|
||||
const plugin = getCatalogPluginMock({ ...tc.plugin });
|
||||
await act(async () => {
|
||||
renderWithStore(
|
||||
<PluginDetailsBody
|
||||
plugin={plugin}
|
||||
info={[]}
|
||||
queryParams={{}}
|
||||
pageId={PluginTabIds.VERSIONS}
|
||||
showDetails={false}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const installSpans = screen.getAllByText('Install');
|
||||
|
||||
installSpans.forEach((span) => {
|
||||
const button = span.closest('button');
|
||||
expect(button).toHaveAttribute('aria-disabled', 'true');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -9,6 +9,7 @@ import { CellProps, Column, InteractiveTable, Stack, useStyles2 } from '@grafana
|
||||
import { Changelog } from '../components/Changelog';
|
||||
import { PluginDetailsPanel } from '../components/PluginDetailsPanel';
|
||||
import { VersionList } from '../components/VersionList';
|
||||
import { shouldDisablePluginInstall } from '../helpers';
|
||||
import { usePluginConfig } from '../hooks/usePluginConfig';
|
||||
import { CatalogPlugin, Permission, PluginTabIds } from '../types';
|
||||
|
||||
@@ -64,6 +65,7 @@ export function PluginDetailsBody({ plugin, queryParams, pageId, info, showDetai
|
||||
pluginId={plugin.id}
|
||||
versions={plugin.details?.versions}
|
||||
installedVersion={plugin.installedVersion}
|
||||
disableInstallation={shouldDisablePluginInstall(plugin)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PluginSignatureBadge, Stack } from '@grafana/ui';
|
||||
|
||||
import { isPluginUpdateable } from '../helpers';
|
||||
import { isPluginUpdatable } from '../helpers';
|
||||
import { CatalogPlugin } from '../types';
|
||||
|
||||
import {
|
||||
@@ -18,7 +18,7 @@ type PluginBadgeType = {
|
||||
|
||||
export function PluginListItemBadges({ plugin }: PluginBadgeType) {
|
||||
// Currently renderer plugins are not supported by the catalog due to complications related to installation / update / uninstall.
|
||||
const canUpdate = isPluginUpdateable(plugin);
|
||||
const canUpdate = isPluginUpdatable(plugin);
|
||||
if (plugin.isEnterprise) {
|
||||
return (
|
||||
<Stack height="auto" wrap="wrap">
|
||||
|
||||
@@ -22,7 +22,7 @@ describe('VersionList', () => {
|
||||
},
|
||||
];
|
||||
|
||||
renderWithStore(<VersionList pluginId={''} versions={versions} />);
|
||||
renderWithStore(<VersionList pluginId={''} versions={versions} disableInstallation={false} />);
|
||||
const installElements = screen.getAllByText('Install');
|
||||
expect(installElements).toHaveLength(versions.length);
|
||||
});
|
||||
@@ -52,7 +52,12 @@ describe('VersionList', () => {
|
||||
const installedVersionIndex = 1;
|
||||
|
||||
renderWithStore(
|
||||
<VersionList pluginId={''} versions={versions} installedVersion={versions[installedVersionIndex].version} />
|
||||
<VersionList
|
||||
pluginId={''}
|
||||
versions={versions}
|
||||
installedVersion={versions[installedVersionIndex].version}
|
||||
disableInstallation={false}
|
||||
/>
|
||||
);
|
||||
expect(screen.getAllByText('Installed')).toHaveLength(1);
|
||||
expect(screen.getAllByText('Downgrade')).toHaveLength(1);
|
||||
|
||||
@@ -14,9 +14,10 @@ interface Props {
|
||||
pluginId: string;
|
||||
versions?: Version[];
|
||||
installedVersion?: string;
|
||||
disableInstallation: boolean;
|
||||
}
|
||||
|
||||
export const VersionList = ({ pluginId, versions = [], installedVersion }: Props) => {
|
||||
export const VersionList = ({ pluginId, versions = [], installedVersion, disableInstallation }: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
const latestCompatibleVersion = getLatestCompatibleVersion(versions);
|
||||
|
||||
@@ -58,6 +59,10 @@ export const VersionList = ({ pluginId, versions = [], installedVersion }: Props
|
||||
tooltip = 'This plugin version is not compatible with the current Grafana version';
|
||||
}
|
||||
|
||||
if (disableInstallation) {
|
||||
tooltip = `This plugin can't be managed through the Plugin Catalog`;
|
||||
}
|
||||
|
||||
return (
|
||||
<tr key={version.version}>
|
||||
{/* Version number */}
|
||||
@@ -77,7 +82,14 @@ export const VersionList = ({ pluginId, versions = [], installedVersion }: Props
|
||||
latestCompatibleVersion={latestCompatibleVersion?.version}
|
||||
installedVersion={installedVersion}
|
||||
onConfirmInstallation={onInstallClick}
|
||||
disabled={isInstalledVersion || isInstalling || !canInstall || !version.isCompatible || !canInstall}
|
||||
disabled={
|
||||
isInstalledVersion ||
|
||||
isInstalling ||
|
||||
!canInstall ||
|
||||
!version.isCompatible ||
|
||||
!canInstall ||
|
||||
disableInstallation
|
||||
}
|
||||
tooltip={tooltip}
|
||||
/>
|
||||
</td>
|
||||
|
||||
@@ -431,41 +431,49 @@ export function filterByKeyword(plugins: CatalogPlugin[], query: string) {
|
||||
return idxs.map((id) => getId(dataArray[id]));
|
||||
}
|
||||
|
||||
export function isPluginUpdateable(plugin: CatalogPlugin) {
|
||||
function isPluginModifiable(plugin: CatalogPlugin) {
|
||||
if (
|
||||
plugin.isProvisioned || //provisioned plugins cannot be modified
|
||||
plugin.isCore || //core plugins cannot be modified
|
||||
plugin.type === PluginType.renderer || // currently renderer plugins are not supported by the catalog due to complications related to installation / update / uninstall
|
||||
plugin.isPreinstalled.withVersion || // Preinstalled plugins (with specified version) cannot be modified
|
||||
plugin.isManaged // Managed plugins cannot be modified
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isPluginUpdatable(plugin: CatalogPlugin) {
|
||||
if (!isPluginModifiable(plugin)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there is no update available, the plugin cannot be updated
|
||||
if (!plugin.hasUpdate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Provisioned plugins cannot be updated
|
||||
if (plugin.isProvisioned) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Core plugins cannot be updated
|
||||
if (plugin.isCore) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Currently renderer plugins are not supported by the catalog due to complications related to installation / update / uninstall.
|
||||
if (plugin.type === PluginType.renderer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Preinstalled plugins (with specified version) cannot be updated
|
||||
if (plugin.isPreinstalled.withVersion) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the plugin is currently being updated, it should not be updated
|
||||
if (plugin.isUpdatingFromInstance) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Managed plugins cannot be updated
|
||||
if (plugin.isManaged) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function shouldDisablePluginInstall(plugin: CatalogPlugin) {
|
||||
if (
|
||||
!isPluginModifiable(plugin) ||
|
||||
plugin.type === PluginType.secretsmanager ||
|
||||
(plugin.isEnterprise && !featureEnabled('enterprise.plugins')) ||
|
||||
!plugin.isPublished ||
|
||||
plugin.isDisabled ||
|
||||
!isInstallControlsEnabled()
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useEffect, useMemo } from 'react';
|
||||
import { PluginError, PluginType } from '@grafana/data';
|
||||
import { useDispatch, useSelector } from 'app/types';
|
||||
|
||||
import { sortPlugins, Sorters, isPluginUpdateable } from '../helpers';
|
||||
import { sortPlugins, Sorters, isPluginUpdatable } from '../helpers';
|
||||
import { CatalogPlugin } from '../types';
|
||||
|
||||
import { fetchAll, fetchDetails, fetchRemotePlugins, install, uninstall, fetchAllLocal, unsetInstall } from './actions';
|
||||
@@ -37,7 +37,7 @@ export const useGetAll = (filters: PluginFilters, sortBy: Sorters = Sorters.name
|
||||
export const useGetUpdatable = () => {
|
||||
const { isLoading } = useFetchStatus();
|
||||
const { plugins: installed } = useGetAll({ isInstalled: true });
|
||||
const updatablePlugins = installed.filter(isPluginUpdateable);
|
||||
const updatablePlugins = installed.filter(isPluginUpdatable);
|
||||
return {
|
||||
isLoading,
|
||||
updatablePlugins,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { debounce } from 'lodash';
|
||||
import { PluginError, PluginType, unEscapeStringFromRegex } from '@grafana/data';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
|
||||
import { filterByKeyword, isPluginUpdateable } from '../helpers';
|
||||
import { filterByKeyword, isPluginUpdatable } from '../helpers';
|
||||
import { RequestStatus, PluginCatalogStoreState } from '../types';
|
||||
|
||||
import { pluginsAdapter } from './reducer';
|
||||
@@ -69,7 +69,7 @@ export const selectPlugins = (filters: PluginFilters) =>
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filters.hasUpdate !== undefined && (plugin.hasUpdate !== filters.hasUpdate || !isPluginUpdateable(plugin))) {
|
||||
if (filters.hasUpdate !== undefined && (plugin.hasUpdate !== filters.hasUpdate || !isPluginUpdatable(plugin))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user