diff --git a/public/app/features/plugins/admin/components/Badges/PluginAngularBadge.tsx b/public/app/features/plugins/admin/components/Badges/PluginAngularBadge.tsx
new file mode 100644
index 00000000000..9202826a9d3
--- /dev/null
+++ b/public/app/features/plugins/admin/components/Badges/PluginAngularBadge.tsx
@@ -0,0 +1,14 @@
+import React from 'react';
+
+import { Badge } from '@grafana/ui';
+
+export function PluginAngularBadge(): React.ReactElement {
+ return (
+
+ );
+}
diff --git a/public/app/features/plugins/admin/components/Badges/PluginUpdateAvailableBadge.tsx b/public/app/features/plugins/admin/components/Badges/PluginUpdateAvailableBadge.tsx
index f726ac2fc26..17fd2c452cf 100644
--- a/public/app/features/plugins/admin/components/Badges/PluginUpdateAvailableBadge.tsx
+++ b/public/app/features/plugins/admin/components/Badges/PluginUpdateAvailableBadge.tsx
@@ -1,7 +1,7 @@
import { css } from '@emotion/css';
import React from 'react';
-import { GrafanaTheme2, PluginType } from '@grafana/data';
+import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '@grafana/ui';
import { CatalogPlugin } from '../../types';
@@ -12,13 +12,7 @@ type Props = {
export function PluginUpdateAvailableBadge({ plugin }: Props): React.ReactElement | null {
const styles = useStyles2(getStyles);
-
- // Currently renderer plugins are not supported by the catalog due to complications related to installation / update / uninstall.
- if (plugin.hasUpdate && !plugin.isCore && plugin.type !== PluginType.renderer) {
- return
Update available!
;
- }
-
- return null;
+ return Update available!
;
}
export const getStyles = (theme: GrafanaTheme2) => {
diff --git a/public/app/features/plugins/admin/components/Badges/index.ts b/public/app/features/plugins/admin/components/Badges/index.ts
index 8a8e0a822e2..a93033c0591 100644
--- a/public/app/features/plugins/admin/components/Badges/index.ts
+++ b/public/app/features/plugins/admin/components/Badges/index.ts
@@ -2,3 +2,4 @@ export { PluginDisabledBadge } from './PluginDisabledBadge';
export { PluginInstalledBadge } from './PluginInstallBadge';
export { PluginEnterpriseBadge } from './PluginEnterpriseBadge';
export { PluginUpdateAvailableBadge } from './PluginUpdateAvailableBadge';
+export { PluginAngularBadge } from './PluginAngularBadge';
diff --git a/public/app/features/plugins/admin/components/InstallControls/ExternallyManagedButton.test.tsx b/public/app/features/plugins/admin/components/InstallControls/ExternallyManagedButton.test.tsx
new file mode 100644
index 00000000000..6fd067a9712
--- /dev/null
+++ b/public/app/features/plugins/admin/components/InstallControls/ExternallyManagedButton.test.tsx
@@ -0,0 +1,48 @@
+import { render, screen } from '@testing-library/react';
+import React from '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(
+
+ );
+}
+
+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);
+ }
+ });
+ }
+ );
+});
diff --git a/public/app/features/plugins/admin/components/InstallControls/ExternallyManagedButton.tsx b/public/app/features/plugins/admin/components/InstallControls/ExternallyManagedButton.tsx
index 651d46b132b..1bead53d3c7 100644
--- a/public/app/features/plugins/admin/components/InstallControls/ExternallyManagedButton.tsx
+++ b/public/app/features/plugins/admin/components/InstallControls/ExternallyManagedButton.tsx
@@ -1,5 +1,6 @@
import React from 'react';
+import { config } from '@grafana/runtime';
import { HorizontalGroup, LinkButton } from '@grafana/ui';
import { getExternalManageLink } from '../../helpers';
@@ -8,9 +9,10 @@ import { PluginStatus } from '../../types';
type ExternallyManagedButtonProps = {
pluginId: string;
pluginStatus: PluginStatus;
+ angularDetected?: boolean;
};
-export function ExternallyManagedButton({ pluginId, pluginStatus }: ExternallyManagedButtonProps) {
+export function ExternallyManagedButton({ pluginId, pluginStatus, angularDetected }: ExternallyManagedButtonProps) {
const externalManageLink = `${getExternalManageLink(pluginId)}/?tab=installation`;
if (pluginStatus === PluginStatus.UPDATE) {
@@ -35,7 +37,12 @@ export function ExternallyManagedButton({ pluginId, pluginStatus }: ExternallyMa
}
return (
-
+
Install via grafana.com
);
diff --git a/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.test.tsx b/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.test.tsx
new file mode 100644
index 00000000000..74ee6e929e5
--- /dev/null
+++ b/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.test.tsx
@@ -0,0 +1,72 @@
+import { render, screen } from '@testing-library/react';
+import React from 'react';
+import { TestProvider } from 'test/helpers/TestProvider';
+
+import { PluginSignatureStatus } from '@grafana/data';
+import { config } from '@grafana/runtime';
+
+import { CatalogPlugin, PluginStatus } from '../../types';
+
+import { InstallControlsButton } from './InstallControlsButton';
+
+const plugin: CatalogPlugin = {
+ description: 'The test plugin',
+ downloads: 5,
+ id: 'test-plugin',
+ info: {
+ logos: { small: '', large: '' },
+ },
+ name: 'Testing Plugin',
+ orgName: 'Test',
+ popularity: 0,
+ signature: PluginSignatureStatus.valid,
+ publishedAt: '2020-09-01',
+ updatedAt: '2021-06-28',
+ hasUpdate: false,
+ isInstalled: false,
+ isCore: false,
+ isDev: false,
+ isEnterprise: false,
+ isDisabled: false,
+ isPublished: true,
+};
+
+function setup(opts: { angularSupportEnabled: boolean; angularDetected: boolean }) {
+ config.angularSupportEnabled = opts.angularSupportEnabled;
+ render(
+
+
+
+ );
+}
+
+describe('InstallControlsButton', () => {
+ 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('button');
+ expect(el).toHaveTextContent(/install/i);
+ expect(el).toBeVisible();
+ if (expectEnabled) {
+ expect(el).toBeEnabled();
+ } else {
+ expect(el).toBeDisabled();
+ }
+ });
+ }
+ );
+});
diff --git a/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx b/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx
index 1169c4afbc7..86778817a8d 100644
--- a/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx
+++ b/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { AppEvents } from '@grafana/data';
-import { locationService } from '@grafana/runtime';
+import { config, locationService } from '@grafana/runtime';
import { Button, HorizontalGroup, ConfirmModal } from '@grafana/ui';
import appEvents from 'app/core/app_events';
import { useQueryParams } from 'app/core/hooks/useQueryParams';
@@ -17,7 +17,7 @@ type InstallControlsButtonProps = {
plugin: CatalogPlugin;
pluginStatus: PluginStatus;
latestCompatibleVersion?: Version;
- setNeedReload: (needReload: boolean) => void;
+ setNeedReload?: (needReload: boolean) => void;
};
export function InstallControlsButton({
@@ -58,7 +58,7 @@ export function InstallControlsButton({
if (!errorInstalling && !('error' in result)) {
appEvents.emit(AppEvents.alertSuccess, [`Installed ${plugin.name}`]);
if (plugin.type === 'app') {
- setNeedReload(true);
+ setNeedReload?.(true);
}
}
};
@@ -77,7 +77,7 @@ export function InstallControlsButton({
appEvents.emit(AppEvents.alertSuccess, [`Uninstalled ${plugin.name}`]);
if (plugin.type === 'app') {
dispatch(removePluginFromNavTree({ pluginID: plugin.id }));
- setNeedReload(false);
+ setNeedReload?.(false);
}
}
};
@@ -122,9 +122,9 @@ export function InstallControlsButton({
);
}
-
+ const shouldDisable = isInstalling || errorInstalling || (!config.angularSupportEnabled && plugin.angularDetected);
return (
-