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 | | `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 | | `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 | | `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 | | `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 | | `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 | | `dashboardSceneForViewers` | Enables dashboard rendering using Scenes for viewer roles | Yes |
@@ -120,7 +120,6 @@ export interface FeatureToggles {
lokiStructuredMetadata?: boolean; lokiStructuredMetadata?: boolean;
teamHttpHeaders?: boolean; teamHttpHeaders?: boolean;
cachingOptimizeSerializationMemoryUsage?: boolean; cachingOptimizeSerializationMemoryUsage?: boolean;
managedPluginsInstall?: boolean;
prometheusPromQAIL?: boolean; prometheusPromQAIL?: boolean;
prometheusCodeModeMetricNamesSearch?: boolean; prometheusCodeModeMetricNamesSearch?: boolean;
addFieldFromCalculationStatFunctions?: 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)
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) { 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/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)) 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 preInstalledPlugin bool
} }
tcs := []testCase{ 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: true},
{expectedCode: http.StatusNotFound, permissionOrg: globalOrg, permissions: canInstall, pluginAdminEnabled: false, pluginAdminExternalManageEnabled: false}, {expectedCode: http.StatusNotFound, permissionOrg: globalOrg, permissions: canInstall, pluginAdminEnabled: false, pluginAdminExternalManageEnabled: false},
{expectedCode: http.StatusForbidden, permissionOrg: globalOrg, permissions: cannotInstall, pluginAdminEnabled: true, pluginAdminExternalManageEnabled: false}, {expectedCode: http.StatusForbidden, permissionOrg: globalOrg, permissions: cannotInstall, pluginAdminEnabled: true, pluginAdminExternalManageEnabled: false},
-8
View File
@@ -765,14 +765,6 @@ var (
Owner: grafanaOperatorExperienceSquad, Owner: grafanaOperatorExperienceSquad,
FrontendOnly: false, 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", Name: "prometheusPromQAIL",
Description: "Prometheus and AI/ML to assist users in creating a query", 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 lokiStructuredMetadata,GA,@grafana/observability-logs,false,false,false
teamHttpHeaders,preview,@grafana/identity-access-team,false,false,false teamHttpHeaders,preview,@grafana/identity-access-team,false,false,false
cachingOptimizeSerializationMemoryUsage,experimental,@grafana/grafana-operator-experience-squad,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 prometheusPromQAIL,experimental,@grafana/oss-big-tent,false,false,true
prometheusCodeModeMetricNamesSearch,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 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. // 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" FlagCachingOptimizeSerializationMemoryUsage = "cachingOptimizeSerializationMemoryUsage"
// FlagManagedPluginsInstall
// Install managed plugins directly from plugins catalog
FlagManagedPluginsInstall = "managedPluginsInstall"
// FlagPrometheusPromQAIL // FlagPrometheusPromQAIL
// Prometheus and AI/ML to assist users in creating a query // Prometheus and AI/ML to assist users in creating a query
FlagPrometheusPromQAIL = "prometheusPromQAIL" FlagPrometheusPromQAIL = "prometheusPromQAIL"
@@ -2651,6 +2651,7 @@
"name": "managedPluginsInstall", "name": "managedPluginsInstall",
"resourceVersion": "1720021873452", "resourceVersion": "1720021873452",
"creationTimestamp": "2023-10-18T13:17:03Z", "creationTimestamp": "2023-10-18T13:17:03Z",
"deletionTimestamp": "2025-02-11T11:13:57Z",
"annotations": { "annotations": {
"grafana.app/updatedTimestamp": "2024-07-03 15:51:13.452477 +0000 UTC" "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}, Grants: []string{ac.RoleGrafanaAdmin},
} }
if !cfg.PluginAdminEnabled || if !cfg.PluginAdminEnabled {
(cfg.PluginAdminExternalManageEnabled && !features.IsEnabledGlobally(featuremgmt.FlagManagedPluginsInstall)) {
PluginsMaintainer.Grants = []string{} PluginsMaintainer.Grants = []string{}
} }
@@ -37,10 +37,8 @@ const plugin: CatalogPlugin = {
}; };
describe('GetStartedWithDataSource', () => { describe('GetStartedWithDataSource', () => {
const oldFeatureTogglesManagedPluginsInstall = config.featureToggles.managedPluginsInstall;
const oldPluginAdminExternalManageEnabled = config.pluginAdminExternalManageEnabled; const oldPluginAdminExternalManageEnabled = config.pluginAdminExternalManageEnabled;
config.featureToggles.managedPluginsInstall = true;
config.pluginAdminExternalManageEnabled = true; config.pluginAdminExternalManageEnabled = true;
const contextSrv = new ContextSrv(); const contextSrv = new ContextSrv();
@@ -51,11 +49,10 @@ describe('GetStartedWithDataSource', () => {
setContextSrv(contextSrv); setContextSrv(contextSrv);
afterAll(() => { afterAll(() => {
config.featureToggles.managedPluginsInstall = oldFeatureTogglesManagedPluginsInstall;
config.pluginAdminExternalManageEnabled = oldPluginAdminExternalManageEnabled; 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( render(
<TestProvider> <TestProvider>
<GetStartedWithDataSource plugin={{ ...plugin, isFullyInstalled: false }} /> <GetStartedWithDataSource plugin={{ ...plugin, isFullyInstalled: false }} />
@@ -67,7 +64,7 @@ describe('GetStartedWithDataSource', () => {
expect(el).toBeDisabled(); 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( render(
<TestProvider> <TestProvider>
<GetStartedWithDataSource plugin={{ ...plugin, isFullyInstalled: true }} /> <GetStartedWithDataSource plugin={{ ...plugin, isFullyInstalled: true }} />
@@ -30,8 +30,7 @@ export function GetStartedWithDataSource({ plugin }: Props): React.ReactElement
return null; return null;
} }
const disabledButton = const disabledButton = config.pluginAdminExternalManageEnabled && !plugin.isFullyInstalled;
config.featureToggles.managedPluginsInstall && config.pluginAdminExternalManageEnabled && !plugin.isFullyInstalled;
return ( return (
<Button <Button
@@ -13,10 +13,7 @@ type Props = {
}; };
export function GetStartedWithPlugin({ plugin }: Props): ReactElement | null { export function GetStartedWithPlugin({ plugin }: Props): ReactElement | null {
const isInstalled = const isInstalled = config.pluginAdminExternalManageEnabled ? plugin.isFullyInstalled : plugin.isInstalled;
config.featureToggles.managedPluginsInstall && config.pluginAdminExternalManageEnabled
? plugin.isFullyInstalled
: plugin.isInstalled;
if (!isInstalled || plugin.isDisabled) { if (!isInstalled || plugin.isDisabled) {
return null; 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', () => { describe('update button on managed instance', () => {
const oldFeatureTogglesManagedPluginsInstall = config.featureToggles.managedPluginsInstall;
const oldPluginAdminExternalManageEnabled = config.pluginAdminExternalManageEnabled; const oldPluginAdminExternalManageEnabled = config.pluginAdminExternalManageEnabled;
beforeAll(() => { beforeAll(() => {
config.featureToggles.managedPluginsInstall = true;
config.pluginAdminExternalManageEnabled = true; config.pluginAdminExternalManageEnabled = true;
}); });
afterAll(() => { afterAll(() => {
config.featureToggles.managedPluginsInstall = oldFeatureTogglesManagedPluginsInstall;
config.pluginAdminExternalManageEnabled = oldPluginAdminExternalManageEnabled; config.pluginAdminExternalManageEnabled = oldPluginAdminExternalManageEnabled;
}); });
@@ -199,16 +196,13 @@ describe('InstallControlsButton', () => {
}); });
describe('uninstall button on managed instance', () => { describe('uninstall button on managed instance', () => {
const oldFeatureTogglesManagedPluginsInstall = config.featureToggles.managedPluginsInstall;
const oldPluginAdminExternalManageEnabled = config.pluginAdminExternalManageEnabled; const oldPluginAdminExternalManageEnabled = config.pluginAdminExternalManageEnabled;
beforeAll(() => { beforeAll(() => {
config.featureToggles.managedPluginsInstall = true;
config.pluginAdminExternalManageEnabled = true; config.pluginAdminExternalManageEnabled = true;
}); });
afterAll(() => { afterAll(() => {
config.featureToggles.managedPluginsInstall = oldFeatureTogglesManagedPluginsInstall;
config.pluginAdminExternalManageEnabled = oldPluginAdminExternalManageEnabled; config.pluginAdminExternalManageEnabled = oldPluginAdminExternalManageEnabled;
}); });
@@ -5,7 +5,6 @@ import { AppEvents } from '@grafana/data';
import { config, locationService, reportInteraction } from '@grafana/runtime'; import { config, locationService, reportInteraction } from '@grafana/runtime';
import { Button, ConfirmModal, Stack } from '@grafana/ui'; import { Button, ConfirmModal, Stack } from '@grafana/ui';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import configCore from 'app/core/config';
import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { useQueryParams } from 'app/core/hooks/useQueryParams';
import { removePluginFromNavTree } from 'app/core/reducers/navBarTree'; import { removePluginFromNavTree } from 'app/core/reducers/navBarTree';
import { useDispatch } from 'app/types'; import { useDispatch } from 'app/types';
@@ -72,7 +71,7 @@ export function InstallControlsButton({
const result = await install(plugin.id, latestCompatibleVersion?.version); const result = await install(plugin.id, latestCompatibleVersion?.version);
if (!errorInstalling && !('error' in result)) { if (!errorInstalling && !('error' in result)) {
let successMessage = `Installed ${plugin.name}`; let successMessage = `Installed ${plugin.name}`;
if (config.pluginAdminExternalManageEnabled && configCore.featureToggles.managedPluginsInstall) { if (config.pluginAdminExternalManageEnabled) {
successMessage = 'Install requested, this may take a few minutes.'; successMessage = 'Install requested, this may take a few minutes.';
} }
@@ -98,7 +97,7 @@ export function InstallControlsButton({
} }
let successMessage = `Uninstalled ${plugin.name}`; let successMessage = `Uninstalled ${plugin.name}`;
if (config.pluginAdminExternalManageEnabled && configCore.featureToggles.managedPluginsInstall) { if (config.pluginAdminExternalManageEnabled) {
successMessage = 'Uninstall requested, this may take a few minutes.'; successMessage = 'Uninstall requested, this may take a few minutes.';
} }
@@ -154,10 +153,7 @@ export function InstallControlsButton({
} }
if (pluginStatus === PluginStatus.UPDATE) { if (pluginStatus === PluginStatus.UPDATE) {
const disableUpdate = const disableUpdate = config.pluginAdminExternalManageEnabled ? plugin.isUpdatingFromInstance : isInstalling;
config.pluginAdminExternalManageEnabled && configCore.featureToggles.managedPluginsInstall
? plugin.isUpdatingFromInstance
: isInstalling;
return ( return (
<Stack alignItems="flex-start" width="auto" height="auto"> <Stack alignItems="flex-start" width="auto" height="auto">
@@ -181,7 +177,7 @@ export function InstallControlsButton({
} }
function shouldDisableUninstall(isUninstalling: boolean, plugin: CatalogPlugin) { function shouldDisableUninstall(isUninstalling: boolean, plugin: CatalogPlugin) {
if (config.pluginAdminExternalManageEnabled && config.featureToggles.managedPluginsInstall) { if (config.pluginAdminExternalManageEnabled) {
return plugin.isUninstallingFromInstance || !plugin.isFullyInstalled || plugin.isUpdatingFromInstance; return plugin.isUninstallingFromInstance || !plugin.isFullyInstalled || plugin.isUpdatingFromInstance;
} }
@@ -2,13 +2,10 @@ import { css } from '@emotion/css';
import { useState } from 'react'; import { useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { config } from '@grafana/runtime';
import { Icon, Stack, useStyles2 } from '@grafana/ui'; import { Icon, Stack, useStyles2 } from '@grafana/ui';
import configCore from 'app/core/config';
import { GetStartedWithPlugin } from '../components/GetStartedWithPlugin'; import { GetStartedWithPlugin } from '../components/GetStartedWithPlugin';
import { InstallControlsButton } from '../components/InstallControls'; import { InstallControlsButton } from '../components/InstallControls';
import { ExternallyManagedButton } from '../components/InstallControls/ExternallyManagedButton';
import { getLatestCompatibleVersion, hasInstallControlWarning, isInstallControlsEnabled } from '../helpers'; import { getLatestCompatibleVersion, hasInstallControlWarning, isInstallControlsEnabled } from '../helpers';
import { useIsRemotePluginsAvailable } from '../state/hooks'; import { useIsRemotePluginsAvailable } from '../state/hooks';
import { CatalogPlugin, PluginStatus } from '../types'; import { CatalogPlugin, PluginStatus } from '../types';
@@ -28,7 +25,6 @@ export const PluginActions = ({ plugin }: Props) => {
} }
const hasInstallWarning = hasInstallControlWarning(plugin, isRemotePluginsAvailable, latestCompatibleVersion); const hasInstallWarning = hasInstallControlWarning(plugin, isRemotePluginsAvailable, latestCompatibleVersion);
const isExternallyManaged = config.pluginAdminExternalManageEnabled;
const pluginStatus = plugin.isInstalled const pluginStatus = plugin.isInstalled
? plugin.hasUpdate ? plugin.hasUpdate
? PluginStatus.UPDATE ? PluginStatus.UPDATE
@@ -41,23 +37,13 @@ export const PluginActions = ({ plugin }: Props) => {
<Stack direction="column"> <Stack direction="column">
<Stack alignItems="center"> <Stack alignItems="center">
{!isInstallControlsDisabled && ( {!isInstallControlsDisabled && (
<> <InstallControlsButton
{isExternallyManaged && !hasInstallWarning && !configCore.featureToggles.managedPluginsInstall ? ( plugin={plugin}
<ExternallyManagedButton latestCompatibleVersion={latestCompatibleVersion}
pluginId={plugin.id} pluginStatus={pluginStatus}
pluginStatus={pluginStatus} setNeedReload={setNeedReload}
angularDetected={plugin.angularDetected} hasInstallWarning={hasInstallWarning}
/> />
) : (
<InstallControlsButton
plugin={plugin}
latestCompatibleVersion={latestCompatibleVersion}
pluginStatus={pluginStatus}
setNeedReload={setNeedReload}
hasInstallWarning={hasInstallWarning}
/>
)}
</>
)} )}
<GetStartedWithPlugin plugin={plugin} /> <GetStartedWithPlugin plugin={plugin} />
</Stack> </Stack>
@@ -97,7 +97,7 @@ export const UpdateAllModal = ({ isOpen, onDismiss, isLoading, plugins }: Props)
setInProgress(true); setInProgress(true);
// in cloud the requests need to be sync // in cloud the requests need to be sync
if (config.pluginAdminExternalManageEnabled && config.featureToggles.managedPluginsInstall) { if (config.pluginAdminExternalManageEnabled) {
for (let plugin of plugins) { for (let plugin of plugins) {
if (selectedPlugins?.has(plugin.id)) { if (selectedPlugins?.has(plugin.id)) {
await install(plugin.id, plugin.latestVersion, true); 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')}`} {`${errorMap.size} ${t('plugins.catalog.update-all.error-status-text', 'failed - see error messages')}`}
</div> </div>
)} )}
{config.pluginAdminExternalManageEnabled && config.featureToggles.managedPluginsInstall && ( {config.pluginAdminExternalManageEnabled && (
<footer className={styles.footer}> <footer className={styles.footer}>
<Trans i18nKey="plugins.catalog.update-all.cloud-update-message"> <Trans i18nKey="plugins.catalog.update-all.cloud-update-message">
* It may take a few minutes for the plugins to be available for usage. * 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', () => { test('core plugins should be fullyInstalled in cloud', () => {
const corePluginId = 'plugin-core'; const corePluginId = 'plugin-core';
const oldFeatureTogglesManagedPluginsInstall = config.featureToggles.managedPluginsInstall;
const oldPluginAdminExternalManageEnabled = config.pluginAdminExternalManageEnabled; const oldPluginAdminExternalManageEnabled = config.pluginAdminExternalManageEnabled;
config.featureToggles.managedPluginsInstall = true;
config.pluginAdminExternalManageEnabled = true; config.pluginAdminExternalManageEnabled = true;
const merged = mergeLocalsAndRemotes({ const merged = mergeLocalsAndRemotes({
@@ -109,17 +107,14 @@ describe('Plugins/Helpers', () => {
expect(findMerged(corePluginId)?.isCore).toBe(true); expect(findMerged(corePluginId)?.isCore).toBe(true);
expect(findMerged(corePluginId)?.isFullyInstalled).toBe(true); expect(findMerged(corePluginId)?.isFullyInstalled).toBe(true);
config.featureToggles.managedPluginsInstall = oldFeatureTogglesManagedPluginsInstall;
config.pluginAdminExternalManageEnabled = oldPluginAdminExternalManageEnabled; config.pluginAdminExternalManageEnabled = oldPluginAdminExternalManageEnabled;
}); });
test('plugins should be fully installed if they are installed and it is provisioned', () => { test('plugins should be fully installed if they are installed and it is provisioned', () => {
const pluginId = 'plugin-1'; const pluginId = 'plugin-1';
const oldFeatureTogglesManagedPluginsInstall = config.featureToggles.managedPluginsInstall;
const oldPluginAdminExternalManageEnabled = config.pluginAdminExternalManageEnabled; const oldPluginAdminExternalManageEnabled = config.pluginAdminExternalManageEnabled;
config.featureToggles.managedPluginsInstall = true;
config.pluginAdminExternalManageEnabled = true; config.pluginAdminExternalManageEnabled = true;
const merged = mergeLocalsAndRemotes({ const merged = mergeLocalsAndRemotes({
@@ -133,15 +128,12 @@ describe('Plugins/Helpers', () => {
expect(findMerged(pluginId)).not.toBeUndefined(); expect(findMerged(pluginId)).not.toBeUndefined();
expect(findMerged(pluginId)?.isFullyInstalled).toBe(true); expect(findMerged(pluginId)?.isFullyInstalled).toBe(true);
config.featureToggles.managedPluginsInstall = oldFeatureTogglesManagedPluginsInstall;
config.pluginAdminExternalManageEnabled = oldPluginAdminExternalManageEnabled; config.pluginAdminExternalManageEnabled = oldPluginAdminExternalManageEnabled;
}); });
test('plugins should have update when instance version is different from remote version', () => { test('plugins should have update when instance version is different from remote version', () => {
const oldFeatureTogglesManagedPluginsInstall = config.featureToggles.managedPluginsInstall;
const oldPluginAdminExternalManageEnabled = config.pluginAdminExternalManageEnabled; const oldPluginAdminExternalManageEnabled = config.pluginAdminExternalManageEnabled;
config.featureToggles.managedPluginsInstall = true;
config.pluginAdminExternalManageEnabled = true; config.pluginAdminExternalManageEnabled = true;
const pluginId = 'plugin-1'; const pluginId = 'plugin-1';
@@ -162,7 +154,6 @@ describe('Plugins/Helpers', () => {
expect(findMerged(pluginId)).not.toBeUndefined(); expect(findMerged(pluginId)).not.toBeUndefined();
expect(findMerged(pluginId)?.hasUpdate).toBe(true); expect(findMerged(pluginId)?.hasUpdate).toBe(true);
config.featureToggles.managedPluginsInstall = oldFeatureTogglesManagedPluginsInstall;
config.pluginAdminExternalManageEnabled = oldPluginAdminExternalManageEnabled; 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 { PluginSignatureStatus, dateTimeParse, PluginError, PluginType, PluginErrorCode } from '@grafana/data';
import { config, featureEnabled } from '@grafana/runtime'; 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 { contextSrv } from 'app/core/core';
import { getBackendSrv } from 'app/core/services/backend_srv'; import { getBackendSrv } from 'app/core/services/backend_srv';
import { AccessControlAction } from 'app/types'; import { AccessControlAction } from 'app/types';
@@ -63,7 +63,7 @@ export function mergeLocalsAndRemotes({
const catalogPlugin = mergeLocalAndRemote(localCounterpart, remotePlugin, error); const catalogPlugin = mergeLocalAndRemote(localCounterpart, remotePlugin, error);
// for managed instances, check if plugin is installed, but not yet present in the current instance // 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 catalogPlugin.isFullyInstalled = catalogPlugin.isCore
? true ? true
: (instancesMap.has(remotePlugin.slug) || provisionedSet.has(remotePlugin.slug)) && catalogPlugin.isInstalled; : (instancesMap.has(remotePlugin.slug) || provisionedSet.has(remotePlugin.slug)) && catalogPlugin.isInstalled;
@@ -11,10 +11,8 @@ export const usePluginConfig = (plugin?: CatalogPlugin) => {
return null; return null;
} }
const isPluginInstalled = const isPluginInstalled = config.pluginAdminExternalManageEnabled ? plugin.isFullyInstalled : plugin.isInstalled;
config.pluginAdminExternalManageEnabled && config.featureToggles.managedPluginsInstall
? plugin.isFullyInstalled
: plugin.isInstalled;
if (isPluginInstalled && !plugin.isDisabled) { if (isPluginInstalled && !plugin.isDisabled) {
return loadPlugin(plugin.id); return loadPlugin(plugin.id);
} }
@@ -391,31 +391,6 @@ describe('Plugin details page', () => {
expect(await queryByRole('button', { name: /(un)?install/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 () => { it('should display alert with information about why the plugin is disabled', async () => {
const { queryByTestId } = renderPluginDetails({ const { queryByTestId } = renderPluginDetails({
id, id,
@@ -3,7 +3,6 @@ import { from, forkJoin, timeout, lastValueFrom, catchError, of } from 'rxjs';
import { PanelPlugin, PluginError } from '@grafana/data'; import { PanelPlugin, PluginError } from '@grafana/data';
import { config, getBackendSrv, isFetchError } from '@grafana/runtime'; import { config, getBackendSrv, isFetchError } from '@grafana/runtime';
import configCore from 'app/core/config';
import { importPanelPlugin } from 'app/features/plugins/importPanelPlugin'; import { importPanelPlugin } from 'app/features/plugins/importPanelPlugin';
import { StoreState, ThunkResult } from 'app/types'; 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}/fetchLocal/pending` });
thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchRemote/pending` }); thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchRemote/pending` });
const instance$ = const instance$ = config.pluginAdminExternalManageEnabled ? from(getInstancePlugins()) : of(undefined);
config.pluginAdminExternalManageEnabled && configCore.featureToggles.managedPluginsInstall const provisioned$ = config.pluginAdminExternalManageEnabled ? from(getProvisionedPlugins()) : of(undefined);
? from(getInstancePlugins())
: of(undefined);
const provisioned$ =
config.pluginAdminExternalManageEnabled && configCore.featureToggles.managedPluginsInstall
? from(getProvisionedPlugins())
: of(undefined);
const TIMEOUT = 500; const TIMEOUT = 500;
const pluginErrors$ = from(getPluginErrors()); const pluginErrors$ = from(getPluginErrors());
const local$ = from(getLocalPlugins()); const local$ = from(getLocalPlugins());