Plugins: Remove managedPluginsInstall feature toggle (#100416)

* Plugins: Remove managedPluginsInstall feature toggle

Co-authored-by: Will Browne <wbrowne@users.noreply.github.com>
This commit is contained in:
Hugo Kiyodi Oshiro
2025-02-17 16:07:41 +01:00
committed by GitHub
co-authored by Will Browne
parent 39b424b8a7
commit 29afe7d2cc
24 changed files with 27 additions and 210 deletions
@@ -49,7 +49,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
| `kubernetesPlaylists` | Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s | Yes |
| `recoveryThreshold` | Enables feature recovery threshold (aka hysteresis) for threshold server-side expression | Yes |
| `lokiStructuredMetadata` | Enables the loki data source to request structured metadata from the Loki server | Yes |
| `managedPluginsInstall` | Install managed plugins directly from plugins catalog | Yes |
| `addFieldFromCalculationStatFunctions` | Add cumulative and window functions to the add field from calculation transformation | Yes |
| `annotationPermissionUpdate` | Change the way annotation permissions work by scoping them to folders and dashboards. | Yes |
| `dashboardSceneForViewers` | Enables dashboard rendering using Scenes for viewer roles | Yes |
@@ -120,7 +120,6 @@ export interface FeatureToggles {
lokiStructuredMetadata?: boolean;
teamHttpHeaders?: boolean;
cachingOptimizeSerializationMemoryUsage?: boolean;
managedPluginsInstall?: boolean;
prometheusPromQAIL?: boolean;
prometheusCodeModeMetricNamesSearch?: boolean;
addFieldFromCalculationStatFunctions?: boolean;
+1 -1
View File
@@ -424,7 +424,7 @@ func (hs *HTTPServer) registerRoutes() {
apiRoute.Any("/plugin-proxy/:pluginId/*", requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow), authorize(ac.EvalPermission(pluginaccesscontrol.ActionAppAccess, pluginIDScope)), checkAppEnabled(hs.pluginStore, hs.PluginSettings), hs.ProxyPluginRequest)
apiRoute.Any("/plugin-proxy/:pluginId", requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow), authorize(ac.EvalPermission(pluginaccesscontrol.ActionAppAccess, pluginIDScope)), checkAppEnabled(hs.pluginStore, hs.PluginSettings), hs.ProxyPluginRequest)
if hs.Cfg.PluginAdminEnabled && (hs.Features.IsEnabledGlobally(featuremgmt.FlagManagedPluginsInstall) || !hs.Cfg.PluginAdminExternalManageEnabled) {
if hs.Cfg.PluginAdminEnabled {
apiRoute.Group("/plugins", func(pluginRoute routing.RouteRegister) {
pluginRoute.Post("/:pluginId/install", authorizeInOrg(ac.UseGlobalOrSingleOrg(hs.Cfg), ac.EvalPermission(pluginaccesscontrol.ActionInstall)), routing.Wrap(hs.InstallPlugin))
pluginRoute.Post("/:pluginId/uninstall", authorizeInOrg(ac.UseGlobalOrSingleOrg(hs.Cfg), ac.EvalPermission(pluginaccesscontrol.ActionInstall)), routing.Wrap(hs.UninstallPlugin))
+1 -1
View File
@@ -73,7 +73,7 @@ func Test_PluginsInstallAndUninstall(t *testing.T) {
preInstalledPlugin bool
}
tcs := []testCase{
{expectedCode: http.StatusNotFound, permissionOrg: globalOrg, permissions: canInstall, pluginAdminEnabled: true, pluginAdminExternalManageEnabled: true},
{expectedCode: http.StatusOK, permissionOrg: globalOrg, permissions: canInstall, pluginAdminEnabled: true, pluginAdminExternalManageEnabled: true},
{expectedCode: http.StatusNotFound, permissionOrg: globalOrg, permissions: canInstall, pluginAdminEnabled: false, pluginAdminExternalManageEnabled: true},
{expectedCode: http.StatusNotFound, permissionOrg: globalOrg, permissions: canInstall, pluginAdminEnabled: false, pluginAdminExternalManageEnabled: false},
{expectedCode: http.StatusForbidden, permissionOrg: globalOrg, permissions: cannotInstall, pluginAdminEnabled: true, pluginAdminExternalManageEnabled: false},
-8
View File
@@ -765,14 +765,6 @@ var (
Owner: grafanaOperatorExperienceSquad,
FrontendOnly: false,
},
{
Name: "managedPluginsInstall",
Description: "Install managed plugins directly from plugins catalog",
Stage: FeatureStageGeneralAvailability,
RequiresDevMode: false,
Owner: grafanaPluginsPlatformSquad,
Expression: "true", // enabled by default
},
{
Name: "prometheusPromQAIL",
Description: "Prometheus and AI/ML to assist users in creating a query",
-1
View File
@@ -101,7 +101,6 @@ recoveryThreshold,GA,@grafana/alerting-squad,false,true,false
lokiStructuredMetadata,GA,@grafana/observability-logs,false,false,false
teamHttpHeaders,preview,@grafana/identity-access-team,false,false,false
cachingOptimizeSerializationMemoryUsage,experimental,@grafana/grafana-operator-experience-squad,false,false,false
managedPluginsInstall,GA,@grafana/plugins-platform-backend,false,false,false
prometheusPromQAIL,experimental,@grafana/oss-big-tent,false,false,true
prometheusCodeModeMetricNamesSearch,experimental,@grafana/oss-big-tent,false,false,true
addFieldFromCalculationStatFunctions,GA,@grafana/dataviz-squad,false,false,true
1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
101 lokiStructuredMetadata GA @grafana/observability-logs false false false
102 teamHttpHeaders preview @grafana/identity-access-team false false false
103 cachingOptimizeSerializationMemoryUsage experimental @grafana/grafana-operator-experience-squad false false false
managedPluginsInstall GA @grafana/plugins-platform-backend false false false
104 prometheusPromQAIL experimental @grafana/oss-big-tent false false true
105 prometheusCodeModeMetricNamesSearch experimental @grafana/oss-big-tent false false true
106 addFieldFromCalculationStatFunctions GA @grafana/dataviz-squad false false true
-4
View File
@@ -415,10 +415,6 @@ const (
// If enabled, the caching backend gradually serializes query responses for the cache, comparing against the configured `[caching]max_value_mb` value as it goes. This can can help prevent Grafana from running out of memory while attempting to cache very large query responses.
FlagCachingOptimizeSerializationMemoryUsage = "cachingOptimizeSerializationMemoryUsage"
// FlagManagedPluginsInstall
// Install managed plugins directly from plugins catalog
FlagManagedPluginsInstall = "managedPluginsInstall"
// FlagPrometheusPromQAIL
// Prometheus and AI/ML to assist users in creating a query
FlagPrometheusPromQAIL = "prometheusPromQAIL"
@@ -2651,6 +2651,7 @@
"name": "managedPluginsInstall",
"resourceVersion": "1720021873452",
"creationTimestamp": "2023-10-18T13:17:03Z",
"deletionTimestamp": "2025-02-11T11:13:57Z",
"annotations": {
"grafana.app/updatedTimestamp": "2024-07-03 15:51:13.452477 +0000 UTC"
}
@@ -82,8 +82,7 @@ func DeclareRBACRoles(service ac.Service, cfg *setting.Cfg, features featuremgmt
Grants: []string{ac.RoleGrafanaAdmin},
}
if !cfg.PluginAdminEnabled ||
(cfg.PluginAdminExternalManageEnabled && !features.IsEnabledGlobally(featuremgmt.FlagManagedPluginsInstall)) {
if !cfg.PluginAdminEnabled {
PluginsMaintainer.Grants = []string{}
}
@@ -37,10 +37,8 @@ const plugin: CatalogPlugin = {
};
describe('GetStartedWithDataSource', () => {
const oldFeatureTogglesManagedPluginsInstall = config.featureToggles.managedPluginsInstall;
const oldPluginAdminExternalManageEnabled = config.pluginAdminExternalManageEnabled;
config.featureToggles.managedPluginsInstall = true;
config.pluginAdminExternalManageEnabled = true;
const contextSrv = new ContextSrv();
@@ -51,11 +49,10 @@ describe('GetStartedWithDataSource', () => {
setContextSrv(contextSrv);
afterAll(() => {
config.featureToggles.managedPluginsInstall = oldFeatureTogglesManagedPluginsInstall;
config.pluginAdminExternalManageEnabled = oldPluginAdminExternalManageEnabled;
});
it('should disable button when managedPluginsInstall and pluginAdminExternalManaged are enabled, but plugin.isFullyInstalled is false', () => {
it('should disable button when pluginAdminExternalManaged is enabled, but plugin.isFullyInstalled is false', () => {
render(
<TestProvider>
<GetStartedWithDataSource plugin={{ ...plugin, isFullyInstalled: false }} />
@@ -67,7 +64,7 @@ describe('GetStartedWithDataSource', () => {
expect(el).toBeDisabled();
});
it('should disable button when managedPluginsInstall and pluginAdminExternalManaged are enabled, but plugin.isFullyInstalled is true', () => {
it('should disable button when pluginAdminExternalManaged enabled, but plugin.isFullyInstalled is true', () => {
render(
<TestProvider>
<GetStartedWithDataSource plugin={{ ...plugin, isFullyInstalled: true }} />
@@ -30,8 +30,7 @@ export function GetStartedWithDataSource({ plugin }: Props): React.ReactElement
return null;
}
const disabledButton =
config.featureToggles.managedPluginsInstall && config.pluginAdminExternalManageEnabled && !plugin.isFullyInstalled;
const disabledButton = config.pluginAdminExternalManageEnabled && !plugin.isFullyInstalled;
return (
<Button
@@ -13,10 +13,7 @@ type Props = {
};
export function GetStartedWithPlugin({ plugin }: Props): ReactElement | null {
const isInstalled =
config.featureToggles.managedPluginsInstall && config.pluginAdminExternalManageEnabled
? plugin.isFullyInstalled
: plugin.isInstalled;
const isInstalled = config.pluginAdminExternalManageEnabled ? plugin.isFullyInstalled : plugin.isInstalled;
if (!isInstalled || plugin.isDisabled) {
return null;
@@ -1,47 +0,0 @@
import { render, screen } from '@testing-library/react';
import { config } from '@grafana/runtime';
import { PluginStatus } from '../../types';
import { ExternallyManagedButton } from './ExternallyManagedButton';
function setup(opts: { angularSupportEnabled: boolean; angularDetected: boolean }) {
config.angularSupportEnabled = opts.angularSupportEnabled;
render(
<ExternallyManagedButton
pluginId={'some-plugin-id'}
angularDetected={opts.angularDetected}
pluginStatus={PluginStatus.INSTALL}
/>
);
}
describe('ExternallyManagedButton', () => {
let oldAngularSupportEnabled = config.angularSupportEnabled;
afterAll(() => {
config.angularSupportEnabled = oldAngularSupportEnabled;
});
describe.each([{ angularSupportEnabled: true }, { angularSupportEnabled: false }])(
'angular support is $angularSupportEnabled',
({ angularSupportEnabled }) => {
it.each([
{ angularDetected: true, expectEnabled: angularSupportEnabled },
{ angularDetected: false, expectEnabled: true },
])('angular detected is $angularDetected', ({ angularDetected, expectEnabled }) => {
setup({ angularSupportEnabled, angularDetected });
const el = screen.getByRole('link');
expect(el).toHaveTextContent(/install/i);
expect(el).toBeVisible();
const linkDisabledStyle = 'pointer-events: none';
if (expectEnabled) {
expect(el).not.toHaveStyle(linkDisabledStyle);
} else {
expect(el).toHaveStyle(linkDisabledStyle);
}
});
}
);
});
@@ -1,47 +0,0 @@
import { config } from '@grafana/runtime';
import { LinkButton, Stack } from '@grafana/ui';
import { getExternalManageLink } from '../../helpers';
import { PluginStatus } from '../../types';
type ExternallyManagedButtonProps = {
pluginId: string;
pluginStatus: PluginStatus;
angularDetected?: boolean;
};
export function ExternallyManagedButton({ pluginId, pluginStatus, angularDetected }: ExternallyManagedButtonProps) {
const externalManageLink = `${getExternalManageLink(pluginId)}/?tab=installation`;
if (pluginStatus === PluginStatus.UPDATE) {
return (
<Stack height="auto">
<LinkButton href={externalManageLink} target="_blank" rel="noopener noreferrer">
Update via grafana.com
</LinkButton>
<LinkButton variant="destructive" href={externalManageLink} target="_blank" rel="noopener noreferrer">
Uninstall via grafana.com
</LinkButton>
</Stack>
);
}
if (pluginStatus === PluginStatus.UNINSTALL) {
return (
<LinkButton variant="destructive" href={externalManageLink} target="_blank" rel="noopener noreferrer">
Uninstall via grafana.com
</LinkButton>
);
}
return (
<LinkButton
disabled={!config.angularSupportEnabled && angularDetected}
href={externalManageLink}
target="_blank"
rel="noopener noreferrer"
>
Install via grafana.com
</LinkButton>
);
}
@@ -124,16 +124,13 @@ describe('InstallControlsButton', () => {
});
describe('update button on managed instance', () => {
const oldFeatureTogglesManagedPluginsInstall = config.featureToggles.managedPluginsInstall;
const oldPluginAdminExternalManageEnabled = config.pluginAdminExternalManageEnabled;
beforeAll(() => {
config.featureToggles.managedPluginsInstall = true;
config.pluginAdminExternalManageEnabled = true;
});
afterAll(() => {
config.featureToggles.managedPluginsInstall = oldFeatureTogglesManagedPluginsInstall;
config.pluginAdminExternalManageEnabled = oldPluginAdminExternalManageEnabled;
});
@@ -199,16 +196,13 @@ describe('InstallControlsButton', () => {
});
describe('uninstall button on managed instance', () => {
const oldFeatureTogglesManagedPluginsInstall = config.featureToggles.managedPluginsInstall;
const oldPluginAdminExternalManageEnabled = config.pluginAdminExternalManageEnabled;
beforeAll(() => {
config.featureToggles.managedPluginsInstall = true;
config.pluginAdminExternalManageEnabled = true;
});
afterAll(() => {
config.featureToggles.managedPluginsInstall = oldFeatureTogglesManagedPluginsInstall;
config.pluginAdminExternalManageEnabled = oldPluginAdminExternalManageEnabled;
});
@@ -5,7 +5,6 @@ import { AppEvents } from '@grafana/data';
import { config, locationService, reportInteraction } from '@grafana/runtime';
import { Button, ConfirmModal, Stack } from '@grafana/ui';
import appEvents from 'app/core/app_events';
import configCore from 'app/core/config';
import { useQueryParams } from 'app/core/hooks/useQueryParams';
import { removePluginFromNavTree } from 'app/core/reducers/navBarTree';
import { useDispatch } from 'app/types';
@@ -72,7 +71,7 @@ export function InstallControlsButton({
const result = await install(plugin.id, latestCompatibleVersion?.version);
if (!errorInstalling && !('error' in result)) {
let successMessage = `Installed ${plugin.name}`;
if (config.pluginAdminExternalManageEnabled && configCore.featureToggles.managedPluginsInstall) {
if (config.pluginAdminExternalManageEnabled) {
successMessage = 'Install requested, this may take a few minutes.';
}
@@ -98,7 +97,7 @@ export function InstallControlsButton({
}
let successMessage = `Uninstalled ${plugin.name}`;
if (config.pluginAdminExternalManageEnabled && configCore.featureToggles.managedPluginsInstall) {
if (config.pluginAdminExternalManageEnabled) {
successMessage = 'Uninstall requested, this may take a few minutes.';
}
@@ -154,10 +153,7 @@ export function InstallControlsButton({
}
if (pluginStatus === PluginStatus.UPDATE) {
const disableUpdate =
config.pluginAdminExternalManageEnabled && configCore.featureToggles.managedPluginsInstall
? plugin.isUpdatingFromInstance
: isInstalling;
const disableUpdate = config.pluginAdminExternalManageEnabled ? plugin.isUpdatingFromInstance : isInstalling;
return (
<Stack alignItems="flex-start" width="auto" height="auto">
@@ -181,7 +177,7 @@ export function InstallControlsButton({
}
function shouldDisableUninstall(isUninstalling: boolean, plugin: CatalogPlugin) {
if (config.pluginAdminExternalManageEnabled && config.featureToggles.managedPluginsInstall) {
if (config.pluginAdminExternalManageEnabled) {
return plugin.isUninstallingFromInstance || !plugin.isFullyInstalled || plugin.isUpdatingFromInstance;
}
@@ -2,13 +2,10 @@ import { css } from '@emotion/css';
import { useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { config } from '@grafana/runtime';
import { Icon, Stack, useStyles2 } from '@grafana/ui';
import configCore from 'app/core/config';
import { GetStartedWithPlugin } from '../components/GetStartedWithPlugin';
import { InstallControlsButton } from '../components/InstallControls';
import { ExternallyManagedButton } from '../components/InstallControls/ExternallyManagedButton';
import { getLatestCompatibleVersion, hasInstallControlWarning, isInstallControlsEnabled } from '../helpers';
import { useIsRemotePluginsAvailable } from '../state/hooks';
import { CatalogPlugin, PluginStatus } from '../types';
@@ -28,7 +25,6 @@ export const PluginActions = ({ plugin }: Props) => {
}
const hasInstallWarning = hasInstallControlWarning(plugin, isRemotePluginsAvailable, latestCompatibleVersion);
const isExternallyManaged = config.pluginAdminExternalManageEnabled;
const pluginStatus = plugin.isInstalled
? plugin.hasUpdate
? PluginStatus.UPDATE
@@ -41,23 +37,13 @@ export const PluginActions = ({ plugin }: Props) => {
<Stack direction="column">
<Stack alignItems="center">
{!isInstallControlsDisabled && (
<>
{isExternallyManaged && !hasInstallWarning && !configCore.featureToggles.managedPluginsInstall ? (
<ExternallyManagedButton
pluginId={plugin.id}
pluginStatus={pluginStatus}
angularDetected={plugin.angularDetected}
/>
) : (
<InstallControlsButton
plugin={plugin}
latestCompatibleVersion={latestCompatibleVersion}
pluginStatus={pluginStatus}
setNeedReload={setNeedReload}
hasInstallWarning={hasInstallWarning}
/>
)}
</>
<InstallControlsButton
plugin={plugin}
latestCompatibleVersion={latestCompatibleVersion}
pluginStatus={pluginStatus}
setNeedReload={setNeedReload}
hasInstallWarning={hasInstallWarning}
/>
)}
<GetStartedWithPlugin plugin={plugin} />
</Stack>
@@ -97,7 +97,7 @@ export const UpdateAllModal = ({ isOpen, onDismiss, isLoading, plugins }: Props)
setInProgress(true);
// in cloud the requests need to be sync
if (config.pluginAdminExternalManageEnabled && config.featureToggles.managedPluginsInstall) {
if (config.pluginAdminExternalManageEnabled) {
for (let plugin of plugins) {
if (selectedPlugins?.has(plugin.id)) {
await install(plugin.id, plugin.latestVersion, true);
@@ -194,7 +194,7 @@ export const UpdateModalBody = ({
{`${errorMap.size} ${t('plugins.catalog.update-all.error-status-text', 'failed - see error messages')}`}
</div>
)}
{config.pluginAdminExternalManageEnabled && config.featureToggles.managedPluginsInstall && (
{config.pluginAdminExternalManageEnabled && (
<footer className={styles.footer}>
<Trans i18nKey="plugins.catalog.update-all.cloud-update-message">
* It may take a few minutes for the plugins to be available for usage.
@@ -92,10 +92,8 @@ describe('Plugins/Helpers', () => {
test('core plugins should be fullyInstalled in cloud', () => {
const corePluginId = 'plugin-core';
const oldFeatureTogglesManagedPluginsInstall = config.featureToggles.managedPluginsInstall;
const oldPluginAdminExternalManageEnabled = config.pluginAdminExternalManageEnabled;
config.featureToggles.managedPluginsInstall = true;
config.pluginAdminExternalManageEnabled = true;
const merged = mergeLocalsAndRemotes({
@@ -109,17 +107,14 @@ describe('Plugins/Helpers', () => {
expect(findMerged(corePluginId)?.isCore).toBe(true);
expect(findMerged(corePluginId)?.isFullyInstalled).toBe(true);
config.featureToggles.managedPluginsInstall = oldFeatureTogglesManagedPluginsInstall;
config.pluginAdminExternalManageEnabled = oldPluginAdminExternalManageEnabled;
});
test('plugins should be fully installed if they are installed and it is provisioned', () => {
const pluginId = 'plugin-1';
const oldFeatureTogglesManagedPluginsInstall = config.featureToggles.managedPluginsInstall;
const oldPluginAdminExternalManageEnabled = config.pluginAdminExternalManageEnabled;
config.featureToggles.managedPluginsInstall = true;
config.pluginAdminExternalManageEnabled = true;
const merged = mergeLocalsAndRemotes({
@@ -133,15 +128,12 @@ describe('Plugins/Helpers', () => {
expect(findMerged(pluginId)).not.toBeUndefined();
expect(findMerged(pluginId)?.isFullyInstalled).toBe(true);
config.featureToggles.managedPluginsInstall = oldFeatureTogglesManagedPluginsInstall;
config.pluginAdminExternalManageEnabled = oldPluginAdminExternalManageEnabled;
});
test('plugins should have update when instance version is different from remote version', () => {
const oldFeatureTogglesManagedPluginsInstall = config.featureToggles.managedPluginsInstall;
const oldPluginAdminExternalManageEnabled = config.pluginAdminExternalManageEnabled;
config.featureToggles.managedPluginsInstall = true;
config.pluginAdminExternalManageEnabled = true;
const pluginId = 'plugin-1';
@@ -162,7 +154,6 @@ describe('Plugins/Helpers', () => {
expect(findMerged(pluginId)).not.toBeUndefined();
expect(findMerged(pluginId)?.hasUpdate).toBe(true);
config.featureToggles.managedPluginsInstall = oldFeatureTogglesManagedPluginsInstall;
config.pluginAdminExternalManageEnabled = oldPluginAdminExternalManageEnabled;
});
});
+2 -2
View File
@@ -2,7 +2,7 @@ import uFuzzy from '@leeoniya/ufuzzy';
import { PluginSignatureStatus, dateTimeParse, PluginError, PluginType, PluginErrorCode } from '@grafana/data';
import { config, featureEnabled } from '@grafana/runtime';
import configCore, { Settings } from 'app/core/config';
import { Settings } from 'app/core/config';
import { contextSrv } from 'app/core/core';
import { getBackendSrv } from 'app/core/services/backend_srv';
import { AccessControlAction } from 'app/types';
@@ -63,7 +63,7 @@ export function mergeLocalsAndRemotes({
const catalogPlugin = mergeLocalAndRemote(localCounterpart, remotePlugin, error);
// for managed instances, check if plugin is installed, but not yet present in the current instance
if (configCore.featureToggles.managedPluginsInstall && config.pluginAdminExternalManageEnabled) {
if (config.pluginAdminExternalManageEnabled) {
catalogPlugin.isFullyInstalled = catalogPlugin.isCore
? true
: (instancesMap.has(remotePlugin.slug) || provisionedSet.has(remotePlugin.slug)) && catalogPlugin.isInstalled;
@@ -11,10 +11,8 @@ export const usePluginConfig = (plugin?: CatalogPlugin) => {
return null;
}
const isPluginInstalled =
config.pluginAdminExternalManageEnabled && config.featureToggles.managedPluginsInstall
? plugin.isFullyInstalled
: plugin.isInstalled;
const isPluginInstalled = config.pluginAdminExternalManageEnabled ? plugin.isFullyInstalled : plugin.isInstalled;
if (isPluginInstalled && !plugin.isDisabled) {
return loadPlugin(plugin.id);
}
@@ -391,31 +391,6 @@ describe('Plugin details page', () => {
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 { queryByTestId } = renderPluginDetails({
id,
@@ -3,7 +3,6 @@ import { from, forkJoin, timeout, lastValueFrom, catchError, of } from 'rxjs';
import { PanelPlugin, PluginError } from '@grafana/data';
import { config, getBackendSrv, isFetchError } from '@grafana/runtime';
import configCore from 'app/core/config';
import { importPanelPlugin } from 'app/features/plugins/importPanelPlugin';
import { StoreState, ThunkResult } from 'app/types';
@@ -28,14 +27,8 @@ export const fetchAll = createAsyncThunk(`${STATE_PREFIX}/fetchAll`, async (_, t
thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchLocal/pending` });
thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchRemote/pending` });
const instance$ =
config.pluginAdminExternalManageEnabled && configCore.featureToggles.managedPluginsInstall
? from(getInstancePlugins())
: of(undefined);
const provisioned$ =
config.pluginAdminExternalManageEnabled && configCore.featureToggles.managedPluginsInstall
? from(getProvisionedPlugins())
: of(undefined);
const instance$ = config.pluginAdminExternalManageEnabled ? from(getInstancePlugins()) : of(undefined);
const provisioned$ = config.pluginAdminExternalManageEnabled ? from(getProvisionedPlugins()) : of(undefined);
const TIMEOUT = 500;
const pluginErrors$ = from(getPluginErrors());
const local$ = from(getLocalPlugins());