Plugins: Fix better UX for disabled Angular plugins (#101333)

* Feat: better UX for Angular plugins

* Chore: fix i18n

* Update public/app/features/plugins/admin/components/PluginDetailsDisabledError.tsx

Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com>

* Update public/app/features/plugins/admin/components/PluginDetailsDisabledError.tsx

Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com>

* Chore: fixes after PR feedback

* Test: testing uninstall in cloud

* Chore: fix weird merge

* Chore: fix test import

* Chore: comment out an expec

* Chore: revert test of uninstall on cloud

* Chore: adds tooltip and removes admin message

* Trigger build

* Chore: fix for cloud

* Trigger build

---------

Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com>
pull/102258/head
Hugo Häggmark 2 months ago committed by GitHub
parent bb881f38bb
commit 16ca230898
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 10
      .betterer.results
  2. 2
      apps/dashboard/pkg/apis/dashboard_manifest.go
  3. 18
      public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx
  4. 277
      public/app/features/plugins/admin/components/PluginActions.test.tsx
  5. 59
      public/app/features/plugins/admin/components/PluginActions.tsx
  6. 128
      public/app/features/plugins/admin/components/PluginDetailsDisabledError.tsx
  7. 37
      public/app/features/plugins/admin/helpers.test.ts
  8. 12
      public/app/features/plugins/admin/helpers.ts
  9. 13
      public/locales/en-US/grafana.json

@ -5066,16 +5066,6 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"]
],
"public/app/features/plugins/admin/components/PluginDetailsDisabledError.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "6"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "7"]
],
"public/app/features/plugins/admin/components/PluginDetailsHeaderDependencies.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],

@ -11,8 +11,6 @@ import (
"github.com/grafana/grafana-app-sdk/app"
)
var ()
var appManifestData = app.ManifestData{
AppName: "dashboard",
Group: "dashboard.grafana.app",

@ -9,6 +9,7 @@ import { useQueryParams } from 'app/core/hooks/useQueryParams';
import { removePluginFromNavTree } from 'app/core/reducers/navBarTree';
import { useDispatch } from 'app/types';
import { isDisabledAngularPlugin } from '../../helpers';
import {
useInstallStatus,
useUninstallStatus,
@ -118,7 +119,10 @@ export function InstallControlsButton({
}
};
let disableUninstall = shouldDisableUninstall(isUninstalling, plugin);
let disableUninstall = shouldDisableUninstall(isUninstalling, plugin) ?? false;
const uninstallTooltip = isDisabledAngularPlugin(plugin)
? 'To uninstall this plugin, upgrade to a compatible version first, then uninstall it.'
: '';
let uninstallTitle = '';
if (plugin.isPreinstalled.found) {
@ -137,7 +141,13 @@ export function InstallControlsButton({
onConfirm={onUninstall}
onDismiss={hideConfirmModal}
/>
<Button variant="destructive" disabled={disableUninstall} onClick={showConfirmModal} title={uninstallTitle}>
<Button
variant="destructive"
disabled={disableUninstall}
onClick={showConfirmModal}
title={uninstallTitle}
tooltip={uninstallTooltip}
>
{uninstallBtnText}
</Button>
</>
@ -179,6 +189,10 @@ export function InstallControlsButton({
}
function shouldDisableUninstall(isUninstalling: boolean, plugin: CatalogPlugin) {
if (isDisabledAngularPlugin(plugin)) {
return true;
}
if (config.pluginAdminExternalManageEnabled) {
return plugin.isUninstallingFromInstance || !plugin.isFullyInstalled || plugin.isUpdatingFromInstance;
}

@ -0,0 +1,277 @@
import { render, screen } from 'test/test-utils';
import { PluginErrorCode, PluginSignatureStatus, PluginSignatureType } from '@grafana/data';
import * as helpers from '../helpers';
import * as hooks from '../state/hooks';
import { initialState } from '../state/reducer';
import { CatalogPlugin, PluginStatus, ReducerState, Version } from '../types';
import { getInstallControlsDisabled, getPluginStatus, PluginActions } from './PluginActions';
describe('PluginActions', () => {
let plugins: ReducerState;
beforeEach(() => {
plugins = { ...initialState };
jest.spyOn(helpers, 'isInstallControlsEnabled').mockReturnValue(true);
jest.spyOn(helpers, 'hasInstallControlWarning').mockReturnValue(false);
jest.spyOn(hooks, 'useIsRemotePluginsAvailable').mockReturnValue(true);
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('render', () => {
it('should render nothing when no plugin is provided', () => {
render(<PluginActions />, { preloadedState: { plugins } });
expect(screen.queryByRole('button')).not.toBeInTheDocument();
});
it('should render install button for non-installed plugin', () => {
render(<PluginActions plugin={createPluginStub()} />, { preloadedState: { plugins } });
expect(screen.getByRole('button', { name: /install/i })).toBeInTheDocument();
});
it('should render uninstall button for installed plugin', () => {
const installedPlugin = createPluginStub({ isInstalled: true });
render(<PluginActions plugin={installedPlugin} />, { preloadedState: { plugins } });
expect(screen.getByRole('button', { name: /uninstall/i })).toBeInTheDocument();
});
it('should render update button for plugin with update', () => {
const pluginWithUpdate = createPluginStub({ isInstalled: true, hasUpdate: true });
render(<PluginActions plugin={pluginWithUpdate} />, { preloadedState: { plugins } });
expect(screen.getByRole('button', { name: /update/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /uninstall/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /uninstall/i })).toHaveAttribute('aria-disabled', 'false');
});
it('should not render install controls for core plugins', () => {
const corePlugin = createPluginStub({ isCore: true });
render(<PluginActions plugin={corePlugin} />, { preloadedState: { plugins } });
expect(screen.queryByRole('button', { name: /install|uninstall|update/i })).not.toBeInTheDocument();
});
it('should not render install controls for disabled plugins', () => {
const disabledPlugin = createPluginStub({ isDisabled: true });
render(<PluginActions plugin={disabledPlugin} />, { preloadedState: { plugins } });
expect(screen.queryByRole('button', { name: /install|uninstall|update/i })).not.toBeInTheDocument();
});
it('should not render install controls for provisioned plugins', () => {
const provisionedPlugin = createPluginStub({ isProvisioned: true });
render(<PluginActions plugin={provisionedPlugin} />, { preloadedState: { plugins } });
expect(screen.queryByRole('button', { name: /install|uninstall|update/i })).not.toBeInTheDocument();
});
it('should not render install controls when install controls are disabled', () => {
jest.spyOn(helpers, 'isInstallControlsEnabled').mockReturnValue(false);
render(<PluginActions plugin={createPluginStub()} />, { preloadedState: { plugins } });
expect(screen.queryByRole('button', { name: /install|uninstall|update/i })).not.toBeInTheDocument();
});
it('should render install controls when there is an installed disabled angular plugin with a non-angular version available', async () => {
jest.spyOn(helpers, 'getLatestCompatibleVersion').mockReturnValue(createVersion({ angularDetected: false }));
const disabledAngularPlugin = createPluginStub({
isInstalled: true,
isDisabled: true,
error: PluginErrorCode.angular,
});
render(<PluginActions plugin={disabledAngularPlugin} />, { preloadedState: { plugins } });
expect(screen.getByRole('button', { name: /update/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /uninstall/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /uninstall/i })).toHaveAttribute('aria-disabled', 'true');
});
it('should not render install controls when there is an installed disabled angular plugin with no non-angular version available', () => {
jest.spyOn(helpers, 'getLatestCompatibleVersion').mockReturnValue(createVersion({ angularDetected: true }));
const disabledAngularPlugin = createPluginStub({
isInstalled: true,
isDisabled: true,
error: PluginErrorCode.angular,
});
render(<PluginActions plugin={disabledAngularPlugin} />, { preloadedState: { plugins } });
expect(screen.queryByRole('button', { name: /install|uninstall|update/i })).not.toBeInTheDocument();
});
});
describe('getPluginStatus', () => {
describe('regular plugins', () => {
it('should return INSTALL for non-installed plugins', () => {
const plugin = createPluginStub({ isInstalled: false });
expect(getPluginStatus(plugin, undefined)).toBe(PluginStatus.INSTALL);
});
it('should return UPDATE for installed plugins with updates', () => {
const plugin = createPluginStub({ isInstalled: true, hasUpdate: true });
expect(getPluginStatus(plugin, undefined)).toBe(PluginStatus.UPDATE);
});
it('should return UNINSTALL for installed plugins without updates', () => {
const plugin = createPluginStub({ isInstalled: true, hasUpdate: false });
expect(getPluginStatus(plugin, undefined)).toBe(PluginStatus.UNINSTALL);
});
});
describe('angular plugins', () => {
it('should return INSTALL for non-installed angular plugins', () => {
const plugin = createPluginStub({
isInstalled: false,
error: PluginErrorCode.angular,
});
expect(getPluginStatus(plugin, undefined)).toBe(PluginStatus.INSTALL);
});
it('should return UPDATE for installed angular plugins with non-angular version available', () => {
const plugin = createPluginStub({
isInstalled: true,
error: PluginErrorCode.angular,
});
const latestVersion = createVersion({ angularDetected: false });
expect(getPluginStatus(plugin, latestVersion)).toBe(PluginStatus.UPDATE);
});
it('should return UNINSTALL for installed angular plugins with only angular versions available', () => {
const plugin = createPluginStub({
isInstalled: true,
error: PluginErrorCode.angular,
});
const latestVersion = createVersion({ angularDetected: true });
expect(getPluginStatus(plugin, latestVersion)).toBe(PluginStatus.UNINSTALL);
});
it('should return UNINSTALL for installed angular plugins with no version info', () => {
const plugin = createPluginStub({
isInstalled: true,
error: PluginErrorCode.angular,
});
expect(getPluginStatus(plugin, undefined)).toBe(PluginStatus.UNINSTALL);
});
});
describe('disabled plugins', () => {
it('should handle disabled angular plugins', () => {
const plugin = createPluginStub({
isInstalled: true,
isDisabled: true,
error: PluginErrorCode.angular,
});
expect(getPluginStatus(plugin, undefined)).toBe(PluginStatus.UNINSTALL);
});
it('should handle disabled regular plugins', () => {
const plugin = createPluginStub({
isInstalled: true,
isDisabled: true,
});
expect(getPluginStatus(plugin, undefined)).toBe(PluginStatus.UNINSTALL);
});
});
});
describe('getInstallControlsDisabled', () => {
it('should return false for disabled angular plugins that have a non-angular version available', () => {
const plugin = createPluginStub({ isDisabled: true, error: PluginErrorCode.angular });
const latestVersion = createVersion({ angularDetected: false });
expect(getInstallControlsDisabled(plugin, latestVersion)).toBe(false);
});
it('should return true for disabled regular plugins', () => {
const plugin = createPluginStub({ isDisabled: true });
expect(getInstallControlsDisabled(plugin, undefined)).toBe(true);
});
it('should return true for core plugins', () => {
const plugin = createPluginStub({ isCore: true });
expect(getInstallControlsDisabled(plugin, undefined)).toBe(true);
});
it('should return true for provisioned plugins', () => {
const plugin = createPluginStub({ isProvisioned: true });
expect(getInstallControlsDisabled(plugin, undefined)).toBe(true);
});
it('should return false for regular plugins', () => {
const plugin = createPluginStub({});
expect(getInstallControlsDisabled(plugin, undefined)).toBe(false);
});
it('should return true when install controls are not enabled', () => {
jest.spyOn(helpers, 'isInstallControlsEnabled').mockReturnValue(false);
const plugin = createPluginStub({});
expect(getInstallControlsDisabled(plugin, undefined)).toBe(true);
});
});
});
function createPluginStub(overrides?: Partial<CatalogPlugin>): CatalogPlugin {
return {
name: 'Test Plugin',
id: 'test-plugin',
description: 'Test plugin',
isCore: false,
isInstalled: false,
isDisabled: false,
isProvisioned: false,
hasUpdate: false,
signature: PluginSignatureStatus.valid,
signatureType: PluginSignatureType.grafana,
signatureOrg: 'grafana',
info: {
logos: { small: '', large: '' },
keywords: [],
},
error: undefined,
downloads: 0,
popularity: 0,
orgName: 'Test Org',
publishedAt: '',
updatedAt: '',
isPublished: true,
isDev: false,
isEnterprise: false,
isDeprecated: false,
isManaged: false,
isPreinstalled: { found: false, withVersion: false },
...overrides,
};
}
function createVersion(overrides?: Partial<Version>): Version {
return {
version: '1.0.0',
createdAt: '',
updatedAt: '',
isCompatible: true,
grafanaDependency: '',
angularDetected: false,
...overrides,
};
}

@ -1,14 +1,20 @@
import { css } from '@emotion/css';
import { useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { GrafanaTheme2, PluginErrorCode } from '@grafana/data';
import { Icon, Stack, useStyles2 } from '@grafana/ui';
import { GetStartedWithPlugin } from '../components/GetStartedWithPlugin';
import { InstallControlsButton } from '../components/InstallControls';
import { getLatestCompatibleVersion, hasInstallControlWarning, isInstallControlsEnabled } from '../helpers';
import {
getLatestCompatibleVersion,
hasInstallControlWarning,
isDisabledAngularPlugin,
isInstallControlsEnabled,
isNonAngularVersion,
} from '../helpers';
import { useIsRemotePluginsAvailable } from '../state/hooks';
import { CatalogPlugin, PluginStatus } from '../types';
import { CatalogPlugin, PluginStatus, Version } from '../types';
interface Props {
plugin?: CatalogPlugin;
@ -25,13 +31,8 @@ export const PluginActions = ({ plugin }: Props) => {
}
const hasInstallWarning = hasInstallControlWarning(plugin, isRemotePluginsAvailable, latestCompatibleVersion);
const pluginStatus = plugin.isInstalled
? plugin.hasUpdate
? PluginStatus.UPDATE
: PluginStatus.UNINSTALL
: PluginStatus.INSTALL;
const isInstallControlsDisabled =
plugin.isCore || plugin.isDisabled || plugin.isProvisioned || !isInstallControlsEnabled();
const pluginStatus = getPluginStatus(plugin, latestCompatibleVersion);
const isInstallControlsDisabled = getInstallControlsDisabled(plugin, latestCompatibleVersion);
return (
<Stack direction="column">
@ -64,3 +65,41 @@ const getStyles = (theme: GrafanaTheme2) => {
}),
};
};
function getAngularPluginStatus(plugin: CatalogPlugin, latestCompatibleVersion: Version | undefined): PluginStatus {
if (!plugin.isInstalled) {
return PluginStatus.INSTALL;
}
if (isNonAngularVersion(latestCompatibleVersion)) {
return PluginStatus.UPDATE;
}
return PluginStatus.UNINSTALL;
}
function getPluginStatus(plugin: CatalogPlugin, latestCompatibleVersion: Version | undefined) {
if (plugin.error === PluginErrorCode.angular) {
return getAngularPluginStatus(plugin, latestCompatibleVersion);
}
if (!plugin.isInstalled) {
return PluginStatus.INSTALL;
}
if (plugin.hasUpdate) {
return PluginStatus.UPDATE;
}
return PluginStatus.UNINSTALL;
}
function getInstallControlsDisabled(plugin: CatalogPlugin, latestCompatibleVersion: Version | undefined) {
if (isDisabledAngularPlugin(plugin) && isNonAngularVersion(latestCompatibleVersion)) {
return false;
}
return plugin.isCore || plugin.isDisabled || plugin.isProvisioned || !isInstallControlsEnabled();
}
export { getPluginStatus, getInstallControlsDisabled };

@ -2,8 +2,10 @@ import { ReactElement } from 'react';
import { PluginErrorCode } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { Alert } from '@grafana/ui';
import { Alert, Stack } from '@grafana/ui';
import { t, Trans } from 'app/core/internationalization';
import { getLatestCompatibleVersion, isDisabledAngularPlugin, isNonAngularVersion } from '../helpers';
import { CatalogPlugin } from '../types';
type Props = {
@ -16,66 +18,122 @@ export function PluginDetailsDisabledError({ className, plugin }: Props): ReactE
return null;
}
const title = t('plugins.details.disabled-error.title', 'Plugin disabled');
const isLatestCompatibleNotAngular = isNonAngularVersion(getLatestCompatibleVersion(plugin?.details?.versions));
return (
<Alert
severity="error"
title="Plugin disabled"
className={className}
data-testid={selectors.pages.PluginPage.disabledInfo}
>
{renderDescriptionFromError(plugin.error)}
<p>Please contact your server administrator to get this resolved.</p>
<a
href="https://grafana.com/docs/grafana/latest/administration/cli/#plugins-commands"
className="external-link"
target="_blank"
rel="noreferrer"
>
Read more about managing plugins
</a>
<Alert severity="error" title={title} className={className} data-testid={selectors.pages.PluginPage.disabledInfo}>
{renderDescriptionFromError(plugin.error, plugin.id, isLatestCompatibleNotAngular)}
{!isDisabledAngularPlugin(plugin) && (
<p>
<Trans i18nKey="plugins.details.disabled-error.contact-server-admin">
Please contact your server administrator to get this resolved.
</Trans>
</p>
)}
<Stack direction="column" gap={1}>
<a
href="https://grafana.com/docs/grafana/latest/administration/cli/#plugins-commands"
className="external-link"
target="_blank"
rel="noreferrer"
>
<Trans i18nKey="plugins.details.disabled-error.manage-plugins-link">Read more about managing plugins</Trans>
</a>
{plugin.error === PluginErrorCode.angular && (
<a
href="https://grafana.com/docs/grafana/latest/developers/angular_deprecation/"
className="external-link"
target="_blank"
rel="noreferrer"
>
<Trans i18nKey="plugins.details.disabled-error.angular-deprecation-link">
Read more about angular deprecation
</Trans>
</a>
)}
</Stack>
</Alert>
);
}
function renderDescriptionFromError(error?: PluginErrorCode): ReactElement {
function renderDescriptionFromError(
error?: PluginErrorCode,
id?: string,
isLatestCompatibleNotAngular?: boolean
): ReactElement {
switch (error) {
case PluginErrorCode.modifiedSignature:
return (
<p>
Grafana Labs checks each plugin to verify that it has a valid digital signature. While doing this, we
discovered that the content of this plugin does not match its signature. We can not guarantee the trustworthy
of this plugin and have therefore disabled it. We recommend you to reinstall the plugin to make sure you are
running a verified version of this plugin.
<Trans i18nKey="plugins.details.disabled-error.modified-signature-text">
Grafana Labs checks each plugin to verify that it has a valid digital signature. While doing this, we
discovered that the content of this plugin does not match its signature. We can not guarantee the
trustworthy of this plugin and have therefore disabled it. We recommend you to reinstall the plugin to make
sure you are running a verified version of this plugin.
</Trans>
</p>
);
case PluginErrorCode.invalidSignature:
return (
<p>
Grafana Labs checks each plugin to verify that it has a valid digital signature. While doing this, we
discovered that it was invalid. We can not guarantee the trustworthy of this plugin and have therefore
disabled it. We recommend you to reinstall the plugin to make sure you are running a verified version of this
plugin.
<Trans i18nKey="plugins.details.disabled-error.invalid-signature-text">
Grafana Labs checks each plugin to verify that it has a valid digital signature. While doing this, we
discovered that it was invalid. We can not guarantee the trustworthy of this plugin and have therefore
disabled it. We recommend you to reinstall the plugin to make sure you are running a verified version of
this plugin.
</Trans>
</p>
);
case PluginErrorCode.missingSignature:
return (
<p>
Grafana Labs checks each plugin to verify that it has a valid digital signature. While doing this, we
discovered that there is no signature for this plugin. We can not guarantee the trustworthy of this plugin and
have therefore disabled it. We recommend you to reinstall the plugin to make sure you are running a verified
version of this plugin.
<Trans i18nKey="plugins.details.disabled-error.missing-signature-text">
Grafana Labs checks each plugin to verify that it has a valid digital signature. While doing this, we
discovered that there is no signature for this plugin. We can not guarantee the trustworthy of this plugin
and have therefore disabled it. We recommend you to reinstall the plugin to make sure you are running a
verified version of this plugin.
</Trans>
</p>
);
case PluginErrorCode.failedBackendStart:
return <p>This plugin failed to start. Server logs can provide more information.</p>;
return (
<p>
<Trans i18nKey="plugins.details.disabled-error.failed-backend-start-text">
This plugin failed to start. Server logs can provide more information.
</Trans>
</p>
);
case PluginErrorCode.angular:
// Error message already rendered by AngularDeprecationPluginNotice
return <></>;
if (isLatestCompatibleNotAngular) {
return (
<p>
<Trans i18nKey="plugins.details.disabled-error.angular-error-text">
This plugin has been disabled as Grafana no longer supports Angular based plugins. You can try updating
the plugin to the latest version to resolve this issue. You should then test to confirm it works as
expected.
</Trans>
</p>
);
}
return (
<p>
<Trans i18nKey="plugins.details.disabled-error.angular-error-text-no-non-angular-version">
This plugin has been disabled as Grafana no longer supports Angular based plugins. Unfortunately, the latest
version of this plugin still uses Angular so you need to wait for the plugin author to migrate to continue
using this plugin.
</Trans>
</p>
);
default:
return (
<p>
We failed to run this plugin due to an unkown reason and have therefore disabled it. We recommend you to
reinstall the plugin to make sure you are running a working version of this plugin.
<Trans i18nKey="plugins.details.disabled-error.unknown-error-text">
We failed to run this plugin due to an unkown reason and have therefore disabled it. We recommend you to
reinstall the plugin to make sure you are running a working version of this plugin.
</Trans>
</p>
);
}

@ -1,4 +1,4 @@
import { PluginSignatureStatus, PluginSignatureType, PluginType } from '@grafana/data';
import { PluginErrorCode, PluginSignatureStatus, PluginSignatureType, PluginType } from '@grafana/data';
import { config } from '@grafana/runtime';
import { getLocalPluginMock, getRemotePluginMock, getCatalogPluginMock } from './__mocks__';
@ -12,8 +12,10 @@ import {
Sorters,
isLocalPluginVisibleByConfig,
isRemotePluginVisibleByConfig,
isNonAngularVersion,
isDisabledAngularPlugin,
} from './helpers';
import { RemotePlugin, LocalPlugin, RemotePluginStatus } from './types';
import { RemotePlugin, LocalPlugin, RemotePluginStatus, Version, CatalogPlugin } from './types';
describe('Plugins/Helpers', () => {
let remotePlugin: RemotePlugin;
@ -876,4 +878,35 @@ describe('Plugins/Helpers', () => {
expect(isRemotePluginVisibleByConfig(plugin)).toBe(false);
});
});
describe('isNonAngularVersion()', () => {
test('should return TRUE if the version is not using angular', () => {
expect(isNonAngularVersion({ angularDetected: false } as Version)).toBe(true);
});
test('should return FALSE if the version is using angular', () => {
expect(isNonAngularVersion({ angularDetected: true } as Version)).toBe(false);
});
test('should return FALSE if the version is not set', () => {
expect(isNonAngularVersion(undefined)).toBe(false);
});
});
describe('isDisabledAngularPlugin', () => {
it('should return true for disabled angular plugins', () => {
const plugin = { isDisabled: true, error: PluginErrorCode.angular } as CatalogPlugin;
expect(isDisabledAngularPlugin(plugin)).toBe(true);
});
it('should return false for non-angular plugins', () => {
const plugin = { isDisabled: true, error: undefined } as CatalogPlugin;
expect(isDisabledAngularPlugin(plugin)).toBe(false);
});
it('should return false for plugins that are not disabled', () => {
const plugin = { isDisabled: false, error: undefined } as CatalogPlugin;
expect(isDisabledAngularPlugin(plugin)).toBe(false);
});
});
});

@ -480,3 +480,15 @@ export function shouldDisablePluginInstall(plugin: CatalogPlugin) {
return false;
}
export function isNonAngularVersion(version?: Version) {
if (!version) {
return false;
}
return version.angularDetected === false;
}
export function isDisabledAngularPlugin(plugin: CatalogPlugin) {
return plugin.isDisabled && plugin.error === PluginErrorCode.angular;
}

@ -3102,6 +3102,19 @@
"connections-tab": {
"description": "You currently have the following data sources configured for {{pluginName}}, click a tile to view the configuration details. You can find all of your data source connections in <4><0>Connections</0> - <3>Data sources</3>.</4>"
},
"disabled-error": {
"angular-deprecation-link": "Read more about angular deprecation",
"angular-error-text": "This plugin has been disabled as Grafana no longer supports Angular based plugins. You can try updating the plugin to the latest version to resolve this issue. You should then test to confirm it works as expected.",
"angular-error-text-no-non-angular-version": "This plugin has been disabled as Grafana no longer supports Angular based plugins. Unfortunately, the latest version of this plugin still uses Angular so you need to wait for the plugin author to migrate to continue using this plugin.",
"contact-server-admin": "Please contact your server administrator to get this resolved.",
"failed-backend-start-text": "This plugin failed to start. Server logs can provide more information.",
"invalid-signature-text": "Grafana Labs checks each plugin to verify that it has a valid digital signature. While doing this, we discovered that it was invalid. We can not guarantee the trustworthy of this plugin and have therefore disabled it. We recommend you to reinstall the plugin to make sure you are running a verified version of this plugin.",
"manage-plugins-link": "Read more about managing plugins",
"missing-signature-text": "Grafana Labs checks each plugin to verify that it has a valid digital signature. While doing this, we discovered that there is no signature for this plugin. We can not guarantee the trustworthy of this plugin and have therefore disabled it. We recommend you to reinstall the plugin to make sure you are running a verified version of this plugin.",
"modified-signature-text": "Grafana Labs checks each plugin to verify that it has a valid digital signature. While doing this, we discovered that the content of this plugin does not match its signature. We can not guarantee the trustworthy of this plugin and have therefore disabled it. We recommend you to reinstall the plugin to make sure you are running a verified version of this plugin.",
"title": "Plugin disabled",
"unknown-error-text": "We failed to run this plugin due to an unkown reason and have therefore disabled it. We recommend you to reinstall the plugin to make sure you are running a working version of this plugin."
},
"labels": {
"contactGrafanaLabs": "Contact Grafana Labs",
"customLinks": "Custom links ",

Loading…
Cancel
Save