diff --git a/.betterer.results b/.betterer.results
index 89b413d6023..8db179df663 100644
--- a/.betterer.results
+++ b/.betterer.results
@@ -5501,7 +5501,8 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with ", "5"]
],
"public/app/features/plugins/admin/components/PluginDetailsPanel.tsx:5381": [
- [0, 0, 0, "\'@grafana/runtime/src/components/PluginPage\' import is restricted from being used by a pattern. Import from the public export instead.", "0"]
+ [0, 0, 0, "\'@grafana/runtime/src/components/PluginPage\' import is restricted from being used by a pattern. Import from the public export instead.", "0"],
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "1"]
],
"public/app/features/plugins/admin/components/PluginDetailsSignature.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"],
diff --git a/public/app/features/plugins/admin/api.ts b/public/app/features/plugins/admin/api.ts
index 36d49c2dbd7..f79968d47ae 100644
--- a/public/app/features/plugins/admin/api.ts
+++ b/public/app/features/plugins/admin/api.ts
@@ -37,6 +37,8 @@ export async function getPluginDetails(id: string): Promise {
it('should render report abuse section for non-core plugins', () => {
render();
expect(screen.getByText('Report a concern')).toBeInTheDocument();
- expect(screen.getByText('Contact Grafana Labs')).toBeInTheDocument();
});
it('should not render report abuse section for core plugins', () => {
@@ -117,6 +116,6 @@ describe('PluginDetailsPanel', () => {
it('should respect custom width prop', () => {
render();
const panel = screen.getByTestId('plugin-details-panel');
- expect(panel).toHaveStyle({ maxWidth: '300px' });
+ expect(panel).toHaveStyle({ width: '300px' });
});
});
diff --git a/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx b/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx
index 95b963097b7..7d7612dc814 100644
--- a/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx
+++ b/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx
@@ -1,8 +1,22 @@
import { css } from '@emotion/css';
+import { useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
+import { reportInteraction } from '@grafana/runtime';
import { PageInfoItem } from '@grafana/runtime/src/components/PluginPage';
-import { Stack, Text, LinkButton, Box, TextLink, useStyles2 } from '@grafana/ui';
+import {
+ Stack,
+ Text,
+ LinkButton,
+ Box,
+ TextLink,
+ CollapsableSection,
+ Tooltip,
+ Icon,
+ Modal,
+ Button,
+ useStyles2,
+} from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { formatDate } from 'app/core/internationalization/dates';
@@ -16,73 +30,203 @@ type Props = {
export function PluginDetailsPanel(props: Props): React.ReactElement | null {
const { pluginExtentionsInfo, plugin, width = '250px' } = props;
+ const [reportAbuseModalOpen, setReportAbuseModalOpen] = useState(false);
+
+ const normalizeURL = (url: string | undefined) => url?.replace(/\/$/, '');
+
+ const customLinks = plugin.details?.links?.filter((link) => {
+ const customLinksFiltered = ![plugin.url, plugin.details?.licenseUrl, plugin.details?.documentationUrl]
+ .map(normalizeURL)
+ .includes(normalizeURL(link.url));
+ return customLinksFiltered;
+ });
+ const shouldRenderLinks = plugin.url || plugin.details?.licenseUrl || plugin.details?.documentationUrl;
+
const styles = useStyles2(getStyles);
- return (
-
-
-
- {pluginExtentionsInfo.map((infoItem, index) => {
- return (
-
- {infoItem.label + ':'}
- {infoItem.value}
-
- );
- })}
- {plugin.updatedAt && (
-
-
- Last updated:
- {' '}
- {formatDate(new Date(plugin.updatedAt), { day: 'numeric', month: 'short', year: 'numeric' })}
-
- )}
- {plugin?.details?.lastCommitDate && (
-
-
- Last commit date:
- {' '}
-
- {formatDate(new Date(plugin.details.lastCommitDate), {
- day: 'numeric',
- month: 'short',
- year: 'numeric',
- })}
-
-
- )}
-
-
+ const onClickReportConcern = (pluginId: string) => {
+ setReportAbuseModalOpen(true);
+ reportInteraction('plugin_detail_report_concern', {
+ plugin_id: pluginId,
+ });
+ };
- {plugin?.details?.links && plugin.details?.links?.length > 0 && (
+ return (
+ <>
+
-
- Links
-
- {plugin.details.links.map((link, index) => (
-
- {link.name}
-
- ))}
+ {pluginExtentionsInfo.map((infoItem, index) => {
+ return (
+
+ {infoItem.label + ':'}
+ {infoItem.value}
+
+ );
+ })}
+ {plugin.updatedAt && (
+
+
+ Last updated:
+ {' '}
+
+ {formatDate(new Date(plugin.updatedAt), { day: 'numeric', month: 'short', year: 'numeric' })}
+
+
+ )}
+ {plugin?.details?.lastCommitDate && (
+
+
+ Last commit date:
+ {' '}
+
+ {formatDate(new Date(plugin.details.lastCommitDate), {
+ day: 'numeric',
+ month: 'short',
+ year: 'numeric',
+ })}
+
+
+ )}
- )}
-
- {!plugin?.isCore && (
-
-
-
- Report a concern
+ {shouldRenderLinks && (
+ <>
+
+
+ {plugin.url && (
+
+ Repository
+
+ )}
+ {plugin.raiseAnIssueUrl && (
+
+ Raise an issue
+
+ )}
+ {plugin.details?.licenseUrl && (
+
+ License
+
+ )}
+ {plugin.details?.documentationUrl && (
+
+ Documentation
+
+ )}
+
+
+ >
+ )}
+ {customLinks && customLinks?.length > 0 && (
+
+
+
+ Custom links
+
+
+ These links are provided by the plugin developer to offer additional, developer-specific
+ resources and information
+
+ }
+ placement="right-end"
+ >
+
+
+
+ }
+ >
+
+ {customLinks.map((link, index) => (
+
+ {link.name}
+
+ ))}
+
+
+
+ )}
+ {!plugin?.isCore && (
+
+
+
+ Report a concern
+
+
+ Report issues related to malicious or harmful plugins directly to Grafana Labs.
+
+ }
+ placement="right-end"
+ >
+
+
+
+ }
+ >
+
+
+
+
+
+ )}
+
+ {reportAbuseModalOpen && (
+ Report a plugin concern}
+ isOpen
+ onDismiss={() => setReportAbuseModalOpen(false)}
+ >
+
+
+
+ This feature is for reporting malicious or harmful behaviour within plugins. For plugin concerns, email
+ us at:{' '}
+
+ integrations@grafana.com
+
+
+
+ Note: For general plugin issues like bugs or feature requests, please contact the plugin author using
+ the provided links.{' '}
+
-
- Contact Grafana Labs
-
-
+
+
+
+
+
)}
-
+ >
);
}
diff --git a/public/app/features/plugins/admin/helpers.test.ts b/public/app/features/plugins/admin/helpers.test.ts
index b47c5bf774b..b1b109f2ca5 100644
--- a/public/app/features/plugins/admin/helpers.test.ts
+++ b/public/app/features/plugins/admin/helpers.test.ts
@@ -217,6 +217,7 @@ describe('Plugins/Helpers', () => {
updatedAt: '2021-05-18T14:53:01.000Z',
isFullyInstalled: false,
angularDetected: false,
+ url: 'https://github.com/alexanderzobnin/grafana-zabbix',
});
});
@@ -354,6 +355,7 @@ describe('Plugins/Helpers', () => {
installedVersion: '4.2.2',
isFullyInstalled: true,
angularDetected: false,
+ url: 'https://github.com/alexanderzobnin/grafana-zabbix',
});
});
diff --git a/public/app/features/plugins/admin/helpers.ts b/public/app/features/plugins/admin/helpers.ts
index 172a32e088f..2f7814cc729 100644
--- a/public/app/features/plugins/admin/helpers.ts
+++ b/public/app/features/plugins/admin/helpers.ts
@@ -121,6 +121,8 @@ export function mapRemoteToCatalog(plugin: RemotePlugin, error?: PluginError): C
signatureType,
versionSignatureType,
versionSignedByOrgName,
+ url,
+ raiseAnIssueUrl,
} = plugin;
const isDisabled = !!error || isDisabledSecretsPlugin(typeCode);
@@ -158,6 +160,8 @@ export function mapRemoteToCatalog(plugin: RemotePlugin, error?: PluginError): C
angularDetected,
isFullyInstalled: isDisabled,
latestVersion: plugin.version,
+ url,
+ raiseAnIssueUrl,
};
}
@@ -174,6 +178,7 @@ export function mapLocalToCatalog(plugin: LocalPlugin, error?: PluginError): Cat
hasUpdate,
accessControl,
angularDetected,
+ raiseAnIssueUrl,
} = plugin;
const isDisabled = !!error || isDisabledSecretsPlugin(type);
@@ -208,6 +213,7 @@ export function mapLocalToCatalog(plugin: LocalPlugin, error?: PluginError): Cat
isFullyInstalled: true,
iam: plugin.iam,
latestVersion: plugin.latestVersion,
+ raiseAnIssueUrl,
};
}
@@ -271,6 +277,8 @@ export function mapToCatalogPlugin(local?: LocalPlugin, remote?: RemotePlugin, e
isFullyInstalled: Boolean(local) || isDisabled,
iam: local?.iam,
latestVersion: local?.latestVersion || remote?.version || '',
+ url: remote?.url || '',
+ raiseAnIssueUrl: remote?.raiseAnIssueUrl || local?.raiseAnIssueUrl,
};
}
diff --git a/public/app/features/plugins/admin/types.ts b/public/app/features/plugins/admin/types.ts
index 031f6c1543c..f3d5783ae3c 100644
--- a/public/app/features/plugins/admin/types.ts
+++ b/public/app/features/plugins/admin/types.ts
@@ -64,6 +64,8 @@ export interface CatalogPlugin extends WithAccessControlMetadata {
isUpdatingFromInstance?: boolean;
iam?: IdentityAccessManagement;
isProvisioned?: boolean;
+ url?: string;
+ raiseAnIssueUrl?: string;
}
export interface CatalogPluginDetails {
@@ -79,6 +81,8 @@ export interface CatalogPluginDetails {
iam?: IdentityAccessManagement;
changelog?: string;
lastCommitDate?: string;
+ licenseUrl?: string;
+ documentationUrl?: string;
signatureType?: PluginSignatureType;
signature?: PluginSignatureStatus;
}
@@ -143,6 +147,9 @@ export type RemotePlugin = {
versionStatus: string;
angularDetected?: boolean;
lastCommitDate?: string;
+ licenseUrl?: string;
+ documentationUrl?: string;
+ raiseAnIssueUrl?: string;
};
// The available status codes on GCOM are available here:
@@ -190,6 +197,7 @@ export type LocalPlugin = WithAccessControlMetadata & {
dependencies: PluginDependencies;
angularDetected: boolean;
iam?: IdentityAccessManagement;
+ raiseAnIssueUrl?: string;
};
interface IdentityAccessManagement {
diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json
index 2c90bf29511..b7ea5026077 100644
--- a/public/locales/en-US/grafana.json
+++ b/public/locales/en-US/grafana.json
@@ -2781,17 +2781,30 @@
},
"labels": {
"contactGrafanaLabs": "Contact Grafana Labs",
+ "customLinks": "Custom links ",
+ "customLinksTooltip": "These links are provided by the plugin developer to offer additional, developer-specific resources and information",
"dependencies": "Dependencies",
+ "documentation": "Documentation",
"downloads": "Downloads",
"from": "From",
"installedVersion": "Installed Version",
"lastCommitDate": "Last commit date:",
"latestVersion": "Latest Version",
- "links": "Links ",
+ "license": "License",
+ "raiseAnIssue": "Raise an issue",
"reportAbuse": "Report a concern ",
+ "reportAbuseTooltip": "Report issues related to malicious or harmful plugins directly to Grafana Labs.",
+ "repository": "Repository",
"signature": "Signature",
"status": "Status",
"updatedAt": "Last updated:"
+ },
+ "modal": {
+ "cancel": "Cancel",
+ "copyEmail": "Copy email address",
+ "description": "This feature is for reporting malicious or harmful behaviour within plugins. For plugin concerns, email us at: ",
+ "node": "Note: For general plugin issues like bugs or feature requests, please contact the plugin author using the provided links. ",
+ "title": "Report a plugin concern"
}
},
"empty-state": {
diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json
index 61b47f9f9a5..9543a62f07e 100644
--- a/public/locales/pseudo-LOCALE/grafana.json
+++ b/public/locales/pseudo-LOCALE/grafana.json
@@ -2781,17 +2781,30 @@
},
"labels": {
"contactGrafanaLabs": "Cőʼnŧäčŧ Ğřäƒäʼnä Ŀäþş",
+ "customLinks": "Cūşŧőm ľįʼnĸş ",
+ "customLinksTooltip": "Ŧĥęşę ľįʼnĸş äřę přővįđęđ þy ŧĥę pľūģįʼn đęvęľőpęř ŧő őƒƒęř äđđįŧįőʼnäľ, đęvęľőpęř-şpęčįƒįč řęşőūřčęş äʼnđ įʼnƒőřmäŧįőʼn",
"dependencies": "Đępęʼnđęʼnčįęş",
+ "documentation": "Đőčūmęʼnŧäŧįőʼn",
"downloads": "Đőŵʼnľőäđş",
"from": "Fřőm",
"installedVersion": "Ĩʼnşŧäľľęđ Vęřşįőʼn",
"lastCommitDate": "Ŀäşŧ čőmmįŧ đäŧę:",
"latestVersion": "Ŀäŧęşŧ Vęřşįőʼn",
- "links": "Ŀįʼnĸş ",
+ "license": "Ŀįčęʼnşę",
+ "raiseAnIssue": "Ŗäįşę äʼn įşşūę",
"reportAbuse": "Ŗępőřŧ ä čőʼnčęřʼn ",
+ "reportAbuseTooltip": "Ŗępőřŧ įşşūęş řęľäŧęđ ŧő mäľįčįőūş őř ĥäřmƒūľ pľūģįʼnş đįřęčŧľy ŧő Ğřäƒäʼnä Ŀäþş.",
+ "repository": "Ŗępőşįŧőřy",
"signature": "Ŝįģʼnäŧūřę",
"status": "Ŝŧäŧūş",
"updatedAt": "Ŀäşŧ ūpđäŧęđ:"
+ },
+ "modal": {
+ "cancel": "Cäʼnčęľ",
+ "copyEmail": "Cőpy ęmäįľ äđđřęşş",
+ "description": "Ŧĥįş ƒęäŧūřę įş ƒőř řępőřŧįʼnģ mäľįčįőūş őř ĥäřmƒūľ þęĥävįőūř ŵįŧĥįʼn pľūģįʼnş. Főř pľūģįʼn čőʼnčęřʼnş, ęmäįľ ūş äŧ: ",
+ "node": "Ńőŧę: Főř ģęʼnęřäľ pľūģįʼn įşşūęş ľįĸę þūģş őř ƒęäŧūřę řęqūęşŧş, pľęäşę čőʼnŧäčŧ ŧĥę pľūģįʼn äūŧĥőř ūşįʼnģ ŧĥę přővįđęđ ľįʼnĸş. ",
+ "title": "Ŗępőřŧ ä pľūģįʼn čőʼnčęřʼn"
}
},
"empty-state": {