diff --git a/.github/ISSUE_TEMPLATE/report-abuse.md b/.github/ISSUE_TEMPLATE/report-abuse.md
new file mode 100644
index 00000000..75740e87
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/report-abuse.md
@@ -0,0 +1,14 @@
+---
+name: Report abuse
+about: Report abuse or inappropriate content
+title: "[ABUSE-REPORT] Repository or package name"
+labels: abuse report
+assignees: ''
+
+---
+
+**Describe the problem**
+Please explain why are you reporting this content.
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/charts/artifact-hub/Chart.yaml b/charts/artifact-hub/Chart.yaml
index b833650d..5ac69c4f 100644
--- a/charts/artifact-hub/Chart.yaml
+++ b/charts/artifact-hub/Chart.yaml
@@ -2,7 +2,7 @@ apiVersion: v2
name: artifact-hub
description: Artifact Hub is a web-based application that enables finding, installing, and publishing Kubernetes packages.
type: application
-version: 1.3.1-1
+version: 1.3.1-2
appVersion: 1.3.0
kubeVersion: ">= 1.14.0-0"
home: https://artifacthub.io
diff --git a/charts/artifact-hub/templates/hub_secret.yaml b/charts/artifact-hub/templates/hub_secret.yaml
index 7a648029..dd31add9 100644
--- a/charts/artifact-hub/templates/hub_secret.yaml
+++ b/charts/artifact-hub/templates/hub_secret.yaml
@@ -85,6 +85,7 @@ stringData:
openGraphImage: {{ .Values.hub.theme.images.openGraphImage | quote }}
shortcutIcon: {{ .Values.hub.theme.images.shortcutIcon | quote }}
websiteLogo: {{ .Values.hub.theme.images.websiteLogo | quote }}
+ reportURL: {{ .Values.hub.theme.reportURL | quote }}
sampleQueries:
{{- range .Values.hub.theme.sampleQueries }}
- name: {{ .name }}
diff --git a/charts/artifact-hub/values-production.yaml b/charts/artifact-hub/values-production.yaml
index d732b2c9..a1b21794 100644
--- a/charts/artifact-hub/values-production.yaml
+++ b/charts/artifact-hub/values-production.yaml
@@ -58,6 +58,7 @@ hub:
enabled: true
redirectURL: https://artifacthub.io/oauth/google/callback
theme:
+ reportURL: https://github.com/artifacthub/hub/issues/new?labels=abuse+report&template=report-abuse.md
sampleQueries:
- name: OLM operators for databases
queryString: "kind=3&ts_query_web=database"
diff --git a/charts/artifact-hub/values.schema.json b/charts/artifact-hub/values.schema.json
index c21ca930..d246bd08 100644
--- a/charts/artifact-hub/values.schema.json
+++ b/charts/artifact-hub/values.schema.json
@@ -588,6 +588,11 @@
},
"required": ["appleTouchIcon192", "appleTouchIcon512", "openGraphImage", "shortcutIcon", "websiteLogo"]
},
+ "reportURL": {
+ "title": "Abuse report URL",
+ "description": "URL to report abuses.",
+ "type": "string"
+ },
"sampleQueries": {
"title": "Sample search queries used in home and no results found pages",
"type": "array",
diff --git a/internal/handlers/static/handlers.go b/internal/handlers/static/handlers.go
index ed061ec1..94e3f92d 100644
--- a/internal/handlers/static/handlers.go
+++ b/internal/handlers/static/handlers.go
@@ -156,6 +156,7 @@ func (h *Handlers) Index(w http.ResponseWriter, r *http.Request) {
"oidcAuth": h.cfg.IsSet("server.oauth.oidc"),
"openGraphImage": openGraphImage,
"primaryColor": h.cfg.GetString("theme.colors.primary"),
+ "reportURL": h.cfg.GetString("theme.reportURL"),
"secondaryColor": h.cfg.GetString("theme.colors.secondary"),
"shortcutIcon": h.cfg.GetString("theme.images.shortcutIcon"),
"siteName": h.cfg.GetString("theme.siteName"),
diff --git a/web/public/index.html b/web/public/index.html
index f59fcd23..aa17b1f6 100644
--- a/web/public/index.html
+++ b/web/public/index.html
@@ -19,6 +19,7 @@
+
<% } else { %>
{{ .title }}
@@ -34,6 +35,7 @@
+
<% } %>
diff --git a/web/src/layout/package/MoreActionsButton.module.css b/web/src/layout/package/MoreActionsButton.module.css
index 50f4c4e3..7126606c 100644
--- a/web/src/layout/package/MoreActionsButton.module.css
+++ b/web/src/layout/package/MoreActionsButton.module.css
@@ -18,6 +18,10 @@
right: 0.3rem !important;
}
+.icon {
+ margin-top: 2px;
+}
+
@media only screen and (max-width: 767.98px) {
.dropdown {
width: 18rem;
diff --git a/web/src/layout/package/MoreActionsButton.test.tsx b/web/src/layout/package/MoreActionsButton.test.tsx
index b98a969c..8c021401 100644
--- a/web/src/layout/package/MoreActionsButton.test.tsx
+++ b/web/src/layout/package/MoreActionsButton.test.tsx
@@ -20,6 +20,9 @@ jest.mock('react-router-dom', () => ({
}),
}));
+const openMock = jest.fn();
+window.open = openMock;
+
describe('MoreActionsButton', () => {
afterEach(() => {
jest.resetAllMocks();
@@ -78,5 +81,104 @@ describe('MoreActionsButton', () => {
},
});
});
+
+ describe('report abuse', () => {
+ it('opens url', () => {
+ Object.defineProperty(document, 'querySelector', {
+ value: (selector: any) => {
+ switch (selector) {
+ case `meta[name='artifacthub:reportURL']`:
+ return {
+ getAttribute: () => 'http://test.com',
+ };
+ default:
+ return false;
+ }
+ },
+ writable: true,
+ });
+
+ render();
+
+ const dropdown = screen.getByRole('menu');
+ expect(dropdown).toBeInTheDocument();
+ expect(dropdown).not.toHaveClass('show');
+
+ const btn = screen.getByRole('button', { name: 'Open menu' });
+ expect(btn).toBeInTheDocument();
+
+ userEvent.click(btn);
+
+ expect(dropdown).toHaveClass('show');
+
+ const reportURLBtn = screen.getByRole('button', { name: 'Open report abuse url' });
+ expect(reportURLBtn).toBeInTheDocument();
+
+ userEvent.click(reportURLBtn);
+
+ expect(openMock).toHaveBeenCalledTimes(1);
+ expect(openMock).toHaveBeenCalledWith('http://test.com', '_blank');
+ });
+
+ it('does not render when url is undefined', () => {
+ Object.defineProperty(document, 'querySelector', {
+ value: (selector: any) => {
+ switch (selector) {
+ case `meta[name='artifacthub:reportURL']`:
+ return {
+ getAttribute: () => null,
+ };
+ default:
+ return false;
+ }
+ },
+ writable: true,
+ });
+
+ render();
+
+ const dropdown = screen.getByRole('menu');
+ expect(dropdown).toBeInTheDocument();
+ expect(dropdown).not.toHaveClass('show');
+
+ const btn = screen.getByRole('button', { name: 'Open menu' });
+ expect(btn).toBeInTheDocument();
+
+ userEvent.click(btn);
+
+ expect(dropdown).toHaveClass('show');
+ expect(screen.queryByRole('button', { name: 'Open report abuse url' })).toBeNull();
+ });
+ });
+
+ it('does not render when url is an empty string', () => {
+ Object.defineProperty(document, 'querySelector', {
+ value: (selector: any) => {
+ switch (selector) {
+ case `meta[name='artifacthub:reportURL']`:
+ return {
+ getAttribute: () => '',
+ };
+ default:
+ return false;
+ }
+ },
+ writable: true,
+ });
+
+ render();
+
+ const dropdown = screen.getByRole('menu');
+ expect(dropdown).toBeInTheDocument();
+ expect(dropdown).not.toHaveClass('show');
+
+ const btn = screen.getByRole('button', { name: 'Open menu' });
+ expect(btn).toBeInTheDocument();
+
+ userEvent.click(btn);
+
+ expect(dropdown).toHaveClass('show');
+ expect(screen.queryByRole('button', { name: 'Open report abuse url' })).toBeNull();
+ });
});
});
diff --git a/web/src/layout/package/MoreActionsButton.tsx b/web/src/layout/package/MoreActionsButton.tsx
index 127b2ec3..39da3bff 100644
--- a/web/src/layout/package/MoreActionsButton.tsx
+++ b/web/src/layout/package/MoreActionsButton.tsx
@@ -1,10 +1,13 @@
import classnames from 'classnames';
+import { isNull } from 'lodash';
import React, { useEffect, useRef, useState } from 'react';
import { BiCode } from 'react-icons/bi';
+import { GoStop } from 'react-icons/go';
import { HiDotsVertical } from 'react-icons/hi';
import useOutsideClick from '../../hooks/useOutsideClick';
import { SearchFiltersURL } from '../../types';
+import getMetaTag from '../../utils/getMetaTag';
import styles from './MoreActionsButton.module.css';
import WidgetModal from './WidgetModal';
@@ -21,6 +24,7 @@ const MoreActionsButton = (props: Props) => {
const [openStatus, setOpenStatus] = useState(false);
const [visibleWidget, setVisibleWidget] = useState(props.visibleWidget);
const [currentPkgId, setCurrentPkgId] = useState(props.packageId);
+ const reportURL = getMetaTag('reportURL');
const ref = useRef(null);
useOutsideClick([ref], openStatus, () => setOpenStatus(false));
@@ -69,10 +73,28 @@ const MoreActionsButton = (props: Props) => {
aria-label="Open embed widget modal"
>
+
+ {!isNull(reportURL) && reportURL !== '' && (
+
+ )}
diff --git a/web/src/layout/package/__snapshots__/MoreActionsButton.test.tsx.snap b/web/src/layout/package/__snapshots__/MoreActionsButton.test.tsx.snap
index 85f790a8..4315116c 100644
--- a/web/src/layout/package/__snapshots__/MoreActionsButton.test.tsx.snap
+++ b/web/src/layout/package/__snapshots__/MoreActionsButton.test.tsx.snap
@@ -48,7 +48,7 @@ exports[`MoreActionsButton creates snapshot 1`] = `
class="d-flex flex-row align-items-center"
>