Plugins: Ensure only updateable plugins can be updated via the UI (#93205)

* only updateable plugins can be updated via the UI

* add missing state

* Apply levi brains

Co-authored-by: Levente Balogh <balogh.levente.hu@gmail.com>

* apply same style throughout

---------

Co-authored-by: Levente Balogh <balogh.levente.hu@gmail.com>
This commit is contained in:
Will Browne 2024-09-12 13:51:50 +01:00 committed by GitHub
parent 3d9ae801cb
commit 3f8d127410
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 48 additions and 22 deletions

View File

@ -1,6 +1,6 @@
import { PluginType } from '@grafana/data';
import { PluginSignatureBadge, Stack } from '@grafana/ui'; import { PluginSignatureBadge, Stack } from '@grafana/ui';
import { isPluginUpdateable } from '../helpers';
import { CatalogPlugin } from '../types'; import { CatalogPlugin } from '../types';
import { import {
@ -18,15 +18,13 @@ type PluginBadgeType = {
export function PluginListItemBadges({ plugin }: PluginBadgeType) { export function PluginListItemBadges({ plugin }: PluginBadgeType) {
// Currently renderer plugins are not supported by the catalog due to complications related to installation / update / uninstall. // Currently renderer plugins are not supported by the catalog due to complications related to installation / update / uninstall.
const hasUpdate = plugin.hasUpdate && !plugin.isCore && plugin.type !== PluginType.renderer; const canUpdate = isPluginUpdateable(plugin);
if (plugin.isEnterprise) { if (plugin.isEnterprise) {
return ( return (
<Stack height="auto" wrap="wrap"> <Stack height="auto" wrap="wrap">
<PluginEnterpriseBadge plugin={plugin} /> <PluginEnterpriseBadge plugin={plugin} />
{plugin.isDisabled && <PluginDisabledBadge error={plugin.error} />} {plugin.isDisabled && <PluginDisabledBadge error={plugin.error} />}
{hasUpdate && !plugin.isManaged && !plugin.isPreinstalled.withVersion && ( {canUpdate && <PluginUpdateAvailableBadge plugin={plugin} />}
<PluginUpdateAvailableBadge plugin={plugin} />
)}
{plugin.angularDetected && <PluginAngularBadge />} {plugin.angularDetected && <PluginAngularBadge />}
</Stack> </Stack>
); );
@ -38,9 +36,7 @@ export function PluginListItemBadges({ plugin }: PluginBadgeType) {
{plugin.isDisabled && <PluginDisabledBadge error={plugin.error} />} {plugin.isDisabled && <PluginDisabledBadge error={plugin.error} />}
{plugin.isDeprecated && <PluginDeprecatedBadge />} {plugin.isDeprecated && <PluginDeprecatedBadge />}
{plugin.isInstalled && <PluginInstalledBadge />} {plugin.isInstalled && <PluginInstalledBadge />}
{hasUpdate && !plugin.isManaged && !plugin.isPreinstalled.withVersion && ( {canUpdate && <PluginUpdateAvailableBadge plugin={plugin} />}
<PluginUpdateAvailableBadge plugin={plugin} />
)}
{plugin.angularDetected && <PluginAngularBadge />} {plugin.angularDetected && <PluginAngularBadge />}
</Stack> </Stack>
); );

View File

@ -425,3 +425,42 @@ export function filterByKeyword(plugins: CatalogPlugin[], query: string) {
} }
return idxs.map((id) => getId(dataArray[id])); return idxs.map((id) => getId(dataArray[id]));
} }
export function isPluginUpdateable(plugin: CatalogPlugin) {
// 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;
}

View File

@ -3,7 +3,7 @@ import { useEffect, useMemo } from 'react';
import { PluginError, PluginType } from '@grafana/data'; import { PluginError, PluginType } from '@grafana/data';
import { useDispatch, useSelector } from 'app/types'; import { useDispatch, useSelector } from 'app/types';
import { sortPlugins, Sorters } from '../helpers'; import { sortPlugins, Sorters, isPluginUpdateable } from '../helpers';
import { CatalogPlugin } from '../types'; import { CatalogPlugin } from '../types';
import { fetchAll, fetchDetails, fetchRemotePlugins, install, uninstall, fetchAllLocal, unsetInstall } from './actions'; import { fetchAll, fetchDetails, fetchRemotePlugins, install, uninstall, fetchAllLocal, unsetInstall } from './actions';
@ -36,7 +36,8 @@ export const useGetAll = (filters: PluginFilters, sortBy: Sorters = Sorters.name
export const useGetUpdatable = () => { export const useGetUpdatable = () => {
const { isLoading } = useFetchStatus(); const { isLoading } = useFetchStatus();
const updatablePlugins = useSelector(selectPlugins({ isInstalled: true, hasUpdate: true })); const { plugins: installed } = useGetAll({ isInstalled: true });
const updatablePlugins = installed.filter(isPluginUpdateable);
return { return {
isLoading, isLoading,
updatablePlugins, updatablePlugins,

View File

@ -3,7 +3,7 @@ import { createSelector } from '@reduxjs/toolkit';
import { PluginError, PluginType, unEscapeStringFromRegex } from '@grafana/data'; import { PluginError, PluginType, unEscapeStringFromRegex } from '@grafana/data';
import { reportInteraction } from '@grafana/runtime'; import { reportInteraction } from '@grafana/runtime';
import { filterByKeyword } from '../helpers'; import { filterByKeyword, isPluginUpdateable } from '../helpers';
import { RequestStatus, PluginCatalogStoreState } from '../types'; import { RequestStatus, PluginCatalogStoreState } from '../types';
import { pluginsAdapter } from './reducer'; import { pluginsAdapter } from './reducer';
@ -61,17 +61,7 @@ export const selectPlugins = (filters: PluginFilters) =>
return false; return false;
} }
if (filters.hasUpdate !== undefined && plugin.hasUpdate !== filters.hasUpdate) { if (filters.hasUpdate !== undefined && (plugin.hasUpdate !== filters.hasUpdate || !isPluginUpdateable(plugin))) {
return false;
}
// plugins not controlled by the user should not be shown as updatable
if (
filters.hasUpdate !== undefined &&
filters.hasUpdate &&
plugin.hasUpdate &&
(plugin.isCore || plugin.isManaged || plugin.isProvisioned || plugin.isUpdatingFromInstance)
) {
return false; return false;
} }