Renderer plugins cannot be managed by the Plugin Catalog.
;
+ return (
+ Secrets manager plugins cannot be managed by the Plugin Catalog.
;
+ return (
+ This is a development build of the plugin and can't be uninstalled.
+
- The install controls have been disabled because the Grafana server cannot access grafana.com.
-
+
diff --git a/public/app/features/plugins/admin/components/PluginDetailsHeaderSignature.tsx b/public/app/features/plugins/admin/components/PluginDetailsHeaderSignature.tsx
index 375bb27171a..5314def26a6 100644
--- a/public/app/features/plugins/admin/components/PluginDetailsHeaderSignature.tsx
+++ b/public/app/features/plugins/admin/components/PluginDetailsHeaderSignature.tsx
@@ -1,13 +1,11 @@
import { css } from '@emotion/css';
import * as React from 'react';
-import { GrafanaTheme2, PluginSignatureStatus } from '@grafana/data';
+import { GrafanaTheme2 } from '@grafana/data';
import { PluginSignatureBadge, useStyles2 } from '@grafana/ui';
import { CatalogPlugin } from '../types';
-import { PluginSignatureDetailsBadge } from './PluginSignatureDetailsBadge';
-
type Props = {
plugin: CatalogPlugin;
};
@@ -15,7 +13,6 @@ type Props = {
// Designed to show plugin signature information in the header on the plugin's details page
export function PluginDetailsHeaderSignature({ plugin }: Props): React.ReactElement {
const styles = useStyles2(getStyles);
- const isSignatureValid = plugin.signature === PluginSignatureStatus.valid;
return (
@@ -25,12 +22,12 @@ export function PluginDetailsHeaderSignature({ plugin }: Props): React.ReactElem
rel="noreferrer"
className={styles.link}
>
-
+
-
- {isSignatureValid && (
-
- )}
);
}
diff --git a/public/app/features/plugins/admin/components/PluginDetailsPage.tsx b/public/app/features/plugins/admin/components/PluginDetailsPage.tsx
index c2dbbea44f3..47d03e5c5fd 100644
--- a/public/app/features/plugins/admin/components/PluginDetailsPage.tsx
+++ b/public/app/features/plugins/admin/components/PluginDetailsPage.tsx
@@ -12,6 +12,7 @@ import { AngularDeprecationPluginNotice } from '../../angularDeprecation/Angular
import { Loader } from '../components/Loader';
import { PluginDetailsBody } from '../components/PluginDetailsBody';
import { PluginDetailsDisabledError } from '../components/PluginDetailsDisabledError';
+import { PluginDetailsRightPanel } from '../components/PluginDetailsRightPanel';
import { PluginDetailsSignature } from '../components/PluginDetailsSignature';
import { usePluginDetailsTabs } from '../hooks/usePluginDetailsTabs';
import { usePluginPageExtensions } from '../hooks/usePluginPageExtensions';
@@ -72,26 +73,31 @@ export function PluginDetailsPage({
);
}
+ const conditionalProps = !config.featureToggles.pluginsDetailsRightPanel ? { info: info } : {};
+
return (
-
-
-
- {plugin.angularDetected && (
-
- )}
-
-
-
-
-
-
+
+
+
+
+ {plugin.angularDetected && (
+
+ )}
+
+
+
+
+
+
+ {config.featureToggles.pluginsDetailsRightPanel && }
+
);
}
diff --git a/public/app/features/plugins/admin/components/PluginDetailsRightPanel.tsx b/public/app/features/plugins/admin/components/PluginDetailsRightPanel.tsx
new file mode 100644
index 00000000000..61303e61e33
--- /dev/null
+++ b/public/app/features/plugins/admin/components/PluginDetailsRightPanel.tsx
@@ -0,0 +1,55 @@
+import { PageInfoItem } from '@grafana/runtime/src/components/PluginPage';
+import { TextLink, Stack, Text } from '@grafana/ui';
+import { Trans } from 'app/core/internationalization';
+import { formatDate } from 'app/core/internationalization/dates';
+
+import { CatalogPlugin } from '../types';
+
+type Props = {
+ info: PageInfoItem[];
+ plugin: CatalogPlugin;
+};
+
+export function PluginDetailsRightPanel(props: Props): React.ReactElement | null {
+ const { info, plugin } = props;
+
+ return (
+
+ {info.map((infoItem, index) => {
+ return (
+
+ {infoItem.label + ':'}
+ {infoItem.value}
+
+ );
+ })}
+
+ {plugin.updatedAt && (
+
+
+ Last updated:
+ {' '}
+ {formatDate(new Date(plugin.updatedAt))}
+
+ )}
+
+ {plugin?.details?.links && plugin.details?.links?.length > 0 && (
+
+ {plugin.details.links.map((link, index) => (
+
+
+ {link.name}
+
+
+ ))}
+
+ )}
+
+ {!plugin?.isCore && (
+
+ Report Abuse
+
+ )}
+
+ );
+}
diff --git a/public/app/features/plugins/admin/components/PluginSubtitle.tsx b/public/app/features/plugins/admin/components/PluginSubtitle.tsx
index ee1f42c18e8..53e604bd110 100644
--- a/public/app/features/plugins/admin/components/PluginSubtitle.tsx
+++ b/public/app/features/plugins/admin/components/PluginSubtitle.tsx
@@ -2,6 +2,7 @@ import { css } from '@emotion/css';
import { Fragment } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
+import { config } from '@grafana/runtime';
import { Alert, useStyles2 } from '@grafana/ui';
import { InstallControlsWarning } from '../components/InstallControls';
@@ -35,7 +36,7 @@ export const PluginSubtitle = ({ plugin }: Props) => {
)}
{plugin?.description && {plugin?.description}
}
- {plugin?.details?.links && plugin.details.links.length > 0 && (
+ {!config.featureToggles.pluginsDetailsRightPanel && !!plugin?.details?.links?.length && (
{plugin.details.links.map((link, index) => (
diff --git a/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx b/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx
index 5ff258b5916..3173e5df198 100644
--- a/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx
+++ b/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx
@@ -35,6 +35,15 @@ export const usePluginDetailsTabs = (plugin?: CatalogPlugin, pageId?: PluginTabI
active: PluginTabIds.VERSIONS === currentPageId,
});
}
+ if (isPublished && plugin?.details?.changelog) {
+ navModelChildren.push({
+ text: PluginTabLabels.CHANGELOG,
+ id: PluginTabIds.CHANGELOG,
+ icon: 'rocket',
+ url: `${pathname}?page=${PluginTabIds.CHANGELOG}`,
+ active: PluginTabIds.CHANGELOG === currentPageId,
+ });
+ }
// Not extending the tabs with the config pages if the plugin is not installed
if (!pluginConfig) {
diff --git a/public/app/features/plugins/admin/hooks/usePluginInfo.tsx b/public/app/features/plugins/admin/hooks/usePluginInfo.tsx
index e50b393e022..54ccebbb21a 100644
--- a/public/app/features/plugins/admin/hooks/usePluginInfo.tsx
+++ b/public/app/features/plugins/admin/hooks/usePluginInfo.tsx
@@ -1,6 +1,7 @@
import { css } from '@emotion/css';
import { GrafanaTheme2, PluginSignatureType } from '@grafana/data';
+import { t } from 'app/core/internationalization';
import { PageInfoItem } from '../../../../core/components/Page/types';
import { PluginDisabledBadge } from '../components/Badges';
@@ -27,12 +28,12 @@ export const usePluginInfo = (plugin?: CatalogPlugin): PageInfoItem[] => {
if (Boolean(version)) {
if (plugin.isManaged) {
info.push({
- label: 'Version',
+ label: t('plugins.details.labels.version', 'Version'),
value: 'Managed by Grafana',
});
} else {
info.push({
- label: 'Version',
+ label: t('plugins.details.labels.version', 'Version'),
value: version,
});
}
@@ -40,7 +41,7 @@ export const usePluginInfo = (plugin?: CatalogPlugin): PageInfoItem[] => {
if (Boolean(plugin.orgName)) {
info.push({
- label: 'From',
+ label: t('plugins.details.labels.from', 'From'),
value: plugin.orgName,
});
}
@@ -51,7 +52,7 @@ export const usePluginInfo = (plugin?: CatalogPlugin): PageInfoItem[] => {
plugin.signatureType === PluginSignatureType.commercial;
if (showDownloads && Boolean(plugin.downloads > 0)) {
info.push({
- label: 'Downloads',
+ label: t('plugins.details.labels.downloads', 'Downloads'),
value: new Intl.NumberFormat().format(plugin.downloads),
});
}
@@ -65,20 +66,20 @@ export const usePluginInfo = (plugin?: CatalogPlugin): PageInfoItem[] => {
if (!hasNoDependencyInfo) {
info.push({
- label: 'Dependencies',
+ label: t('plugins.details.labels.dependencies', 'Dependencies'),
value: ,
});
}
if (plugin.isDisabled) {
info.push({
- label: 'Status',
+ label: t('plugins.details.labels.status', 'Status'),
value: ,
});
}
info.push({
- label: 'Signature',
+ label: t('plugins.details.labels.signature', 'Signature'),
value: ,
});
diff --git a/public/app/features/plugins/admin/pages/PluginDetails.test.tsx b/public/app/features/plugins/admin/pages/PluginDetails.test.tsx
index e13a855336b..931d20cb16a 100644
--- a/public/app/features/plugins/admin/pages/PluginDetails.test.tsx
+++ b/public/app/features/plugins/admin/pages/PluginDetails.test.tsx
@@ -215,7 +215,7 @@ describe('Plugin details page', () => {
it('should display a "Signed" badge if the plugin signature is verified', async () => {
const { queryByText } = renderPluginDetails({ id, signature: PluginSignatureStatus.valid });
- expect(await queryByText('Signed')).toBeInTheDocument();
+ expect(await queryByText('community')).toBeInTheDocument();
});
it('should display a "Missing signature" badge if the plugin signature is missing', async () => {
@@ -880,4 +880,42 @@ describe('Plugin details page', () => {
expect(queryByText('Add new data source')).toBeNull();
});
});
+
+ describe('Display plugin details right panel', () => {
+ beforeAll(() => {
+ mockUserPermissions({
+ isAdmin: true,
+ isDataSourceEditor: false,
+ isOrgAdmin: true,
+ });
+ config.featureToggles.pluginsDetailsRightPanel = true;
+ });
+
+ afterAll(() => {
+ config.featureToggles.pluginsDetailsRightPanel = false;
+ });
+
+ it('should display Last updated and report abuse information', async () => {
+ const id = 'right-panel-test-plugin';
+ const updatedAt = '2023-10-26T16:54:55.000Z';
+ const { queryByText } = renderPluginDetails({ id, updatedAt });
+ expect(queryByText('Last updated:')).toBeVisible();
+ expect(queryByText('10/26/2023')).toBeVisible();
+ expect(queryByText('Report Abuse')).toBeVisible();
+ });
+
+ it('should not display Last updated if there is no updated At data', async () => {
+ const id = 'right-panel-test-plugin';
+ const updatedAt = undefined;
+ const { queryByText } = renderPluginDetails({ id, updatedAt });
+ expect(queryByText('Last updated:')).toBeNull();
+ });
+
+ it('should not display Report Abuse if the plugin is Core', async () => {
+ const id = 'right-panel-test-plugin';
+ const isCore = true;
+ const { queryByText } = renderPluginDetails({ id, isCore });
+ expect(queryByText('Report Abuse')).toBeNull();
+ });
+ });
});
diff --git a/public/app/features/plugins/admin/types.ts b/public/app/features/plugins/admin/types.ts
index aa6b41c7737..0991c0921bb 100644
--- a/public/app/features/plugins/admin/types.ts
+++ b/public/app/features/plugins/admin/types.ts
@@ -80,6 +80,7 @@ export interface CatalogPluginDetails {
pluginDependencies?: PluginDependencies['plugins'];
statusContext?: string;
iam?: IdentityAccessManagement;
+ changelog?: string;
}
export interface CatalogPluginInfo {
@@ -91,6 +92,7 @@ export interface CatalogPluginInfo {
}
export type RemotePlugin = {
+ changelog: string;
createdAt: string;
description: string;
downloads: number;
@@ -252,6 +254,7 @@ export enum PluginTabLabels {
DASHBOARDS = 'Dashboards',
USAGE = 'Usage',
IAM = 'IAM',
+ CHANGELOG = 'Changelog',
}
export enum PluginTabIds {
@@ -261,6 +264,7 @@ export enum PluginTabIds {
DASHBOARDS = 'dashboards',
USAGE = 'usage',
IAM = 'iam',
+ CHANGELOG = 'changelog',
}
export enum RequestStatus {
diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json
index f5a96871f45..2cefb4996dd 100644
--- a/public/locales/en-US/grafana.json
+++ b/public/locales/en-US/grafana.json
@@ -1656,6 +1656,18 @@
}
},
"plugins": {
+ "details": {
+ "labels": {
+ "dependencies": "Dependencies",
+ "downloads": "Downloads",
+ "from": "From",
+ "reportAbuse": "Report Abuse",
+ "signature": "Signature",
+ "status": "Status",
+ "updatedAt": "Last updated: ",
+ "version": "Version"
+ }
+ },
"empty-state": {
"message": "No plugins found"
}
diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json
index 729d80a2982..3ca86e26862 100644
--- a/public/locales/pseudo-LOCALE/grafana.json
+++ b/public/locales/pseudo-LOCALE/grafana.json
@@ -1656,6 +1656,18 @@
}
},
"plugins": {
+ "details": {
+ "labels": {
+ "dependencies": "Đępęʼnđęʼnčįęş",
+ "downloads": "Đőŵʼnľőäđş",
+ "from": "Fřőm",
+ "reportAbuse": "Ŗępőřŧ Åþūşę",
+ "signature": "Ŝįģʼnäŧūřę",
+ "status": "Ŝŧäŧūş",
+ "updatedAt": "Ŀäşŧ ūpđäŧęđ: ",
+ "version": "Vęřşįőʼn"
+ }
+ },
"empty-state": {
"message": "Ńő pľūģįʼnş ƒőūʼnđ"
}