Add option to report abuse (#1608)

Closes #1580

Signed-off-by: Sergio Castaño Arteaga <tegioz@icloud.com>
Signed-off-by: Cintia Sanchez Garcia <cynthiasg@icloud.com>
Co-authored-by: Sergio Castaño Arteaga <tegioz@icloud.com>
Co-authored-by: Cintia Sanchez Garcia <cynthiasg@icloud.com>
This commit is contained in:
Sergio C. Arteaga 2021-10-12 17:52:12 +02:00 committed by GitHub
parent 81adbb28fb
commit f86585a80d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 155 additions and 3 deletions

14
.github/ISSUE_TEMPLATE/report-abuse.md vendored Normal file
View File

@ -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.

View File

@ -2,7 +2,7 @@ apiVersion: v2
name: artifact-hub name: artifact-hub
description: Artifact Hub is a web-based application that enables finding, installing, and publishing Kubernetes packages. description: Artifact Hub is a web-based application that enables finding, installing, and publishing Kubernetes packages.
type: application type: application
version: 1.3.1-1 version: 1.3.1-2
appVersion: 1.3.0 appVersion: 1.3.0
kubeVersion: ">= 1.14.0-0" kubeVersion: ">= 1.14.0-0"
home: https://artifacthub.io home: https://artifacthub.io

View File

@ -85,6 +85,7 @@ stringData:
openGraphImage: {{ .Values.hub.theme.images.openGraphImage | quote }} openGraphImage: {{ .Values.hub.theme.images.openGraphImage | quote }}
shortcutIcon: {{ .Values.hub.theme.images.shortcutIcon | quote }} shortcutIcon: {{ .Values.hub.theme.images.shortcutIcon | quote }}
websiteLogo: {{ .Values.hub.theme.images.websiteLogo | quote }} websiteLogo: {{ .Values.hub.theme.images.websiteLogo | quote }}
reportURL: {{ .Values.hub.theme.reportURL | quote }}
sampleQueries: sampleQueries:
{{- range .Values.hub.theme.sampleQueries }} {{- range .Values.hub.theme.sampleQueries }}
- name: {{ .name }} - name: {{ .name }}

View File

@ -58,6 +58,7 @@ hub:
enabled: true enabled: true
redirectURL: https://artifacthub.io/oauth/google/callback redirectURL: https://artifacthub.io/oauth/google/callback
theme: theme:
reportURL: https://github.com/artifacthub/hub/issues/new?labels=abuse+report&template=report-abuse.md
sampleQueries: sampleQueries:
- name: OLM operators for databases - name: OLM operators for databases
queryString: "kind=3&ts_query_web=database" queryString: "kind=3&ts_query_web=database"

View File

@ -588,6 +588,11 @@
}, },
"required": ["appleTouchIcon192", "appleTouchIcon512", "openGraphImage", "shortcutIcon", "websiteLogo"] "required": ["appleTouchIcon192", "appleTouchIcon512", "openGraphImage", "shortcutIcon", "websiteLogo"]
}, },
"reportURL": {
"title": "Abuse report URL",
"description": "URL to report abuses.",
"type": "string"
},
"sampleQueries": { "sampleQueries": {
"title": "Sample search queries used in home and no results found pages", "title": "Sample search queries used in home and no results found pages",
"type": "array", "type": "array",

View File

@ -156,6 +156,7 @@ func (h *Handlers) Index(w http.ResponseWriter, r *http.Request) {
"oidcAuth": h.cfg.IsSet("server.oauth.oidc"), "oidcAuth": h.cfg.IsSet("server.oauth.oidc"),
"openGraphImage": openGraphImage, "openGraphImage": openGraphImage,
"primaryColor": h.cfg.GetString("theme.colors.primary"), "primaryColor": h.cfg.GetString("theme.colors.primary"),
"reportURL": h.cfg.GetString("theme.reportURL"),
"secondaryColor": h.cfg.GetString("theme.colors.secondary"), "secondaryColor": h.cfg.GetString("theme.colors.secondary"),
"shortcutIcon": h.cfg.GetString("theme.images.shortcutIcon"), "shortcutIcon": h.cfg.GetString("theme.images.shortcutIcon"),
"siteName": h.cfg.GetString("theme.siteName"), "siteName": h.cfg.GetString("theme.siteName"),

View File

@ -19,6 +19,7 @@
<meta name="artifacthub:googleAuth" content="true" /> <meta name="artifacthub:googleAuth" content="true" />
<meta name="artifacthub:oidcAuth" content="false" /> <meta name="artifacthub:oidcAuth" content="false" />
<meta name="artifacthub:sampleQueries" content='[{"name":"OLM operators for databases","queryString":"kind=3\u0026ts_query_web=database"},{"name":"Helm Charts provided by Bitnami","queryString":"kind=0\u0026org=bitnami"},{"name":"Packages of any kind related to etcd","queryString":"ts_query_web=etcd"},{"name":"Falco rules for CVE","queryString":"kind=1\u0026ts_query_web=cve"},{"name":"OLM operators in the monitoring category","queryString":"kind=3\u0026ts_query=monitoring"},{"name":"Packages from verified publishers","queryString":"verified_publisher=true"},{"name":"Official Prometheus packages","queryString":"ts_query_web=prometheus\u0026official=true"},{"name":"Operators with auto pilot capabilities","queryString":"capabilities=auto+pilot"},{"name":"Helm Charts in the storage category","queryString":"kind=0\u0026ts_query=storage"},{"name":"Packages with Apache-2.0 license","queryString":"license=Apache-2.0"},{"name":"OPA policies with MIT license","queryString":"kind=2\u0026license=MIT"},{"name":"Helm plugins","queryString":"kind=6"},{"name":"Kubectl plugins","queryString":"kind=5"},{"name":"Tekton tasks","queryString":"kind=7"}]' /> <meta name="artifacthub:sampleQueries" content='[{"name":"OLM operators for databases","queryString":"kind=3\u0026ts_query_web=database"},{"name":"Helm Charts provided by Bitnami","queryString":"kind=0\u0026org=bitnami"},{"name":"Packages of any kind related to etcd","queryString":"ts_query_web=etcd"},{"name":"Falco rules for CVE","queryString":"kind=1\u0026ts_query_web=cve"},{"name":"OLM operators in the monitoring category","queryString":"kind=3\u0026ts_query=monitoring"},{"name":"Packages from verified publishers","queryString":"verified_publisher=true"},{"name":"Official Prometheus packages","queryString":"ts_query_web=prometheus\u0026official=true"},{"name":"Operators with auto pilot capabilities","queryString":"capabilities=auto+pilot"},{"name":"Helm Charts in the storage category","queryString":"kind=0\u0026ts_query=storage"},{"name":"Packages with Apache-2.0 license","queryString":"license=Apache-2.0"},{"name":"OPA policies with MIT license","queryString":"kind=2\u0026license=MIT"},{"name":"Helm plugins","queryString":"kind=6"},{"name":"Kubectl plugins","queryString":"kind=5"},{"name":"Tekton tasks","queryString":"kind=7"}]' />
<meta name="artifacthub:reportURL" content="https://github.com/artifacthub/hub/issues/new?labels=abuse+report&template=report-abuse.md" />
<% } else { %> <% } else { %>
<title>{{ .title }}</title> <title>{{ .title }}</title>
<meta name="description" content="{{ .description }}" /> <meta name="description" content="{{ .description }}" />
@ -34,6 +35,7 @@
<meta name="artifacthub:gaTrackingID" content="{{ .gaTrackingID }}" /> <meta name="artifacthub:gaTrackingID" content="{{ .gaTrackingID }}" />
<meta name="artifacthub:motd" content="{{ .motd }}" /> <meta name="artifacthub:motd" content="{{ .motd }}" />
<meta name="artifacthub:motdSeverity" content="{{ .motdSeverity }}" /> <meta name="artifacthub:motdSeverity" content="{{ .motdSeverity }}" />
<meta name="artifacthub:reportURL" content="{{ .reportURL }}" />
<% } %> <% } %>
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:title" content="{{ .title }}" /> <meta property="og:title" content="{{ .title }}" />

View File

@ -18,6 +18,10 @@
right: 0.3rem !important; right: 0.3rem !important;
} }
.icon {
margin-top: 2px;
}
@media only screen and (max-width: 767.98px) { @media only screen and (max-width: 767.98px) {
.dropdown { .dropdown {
width: 18rem; width: 18rem;

View File

@ -20,6 +20,9 @@ jest.mock('react-router-dom', () => ({
}), }),
})); }));
const openMock = jest.fn();
window.open = openMock;
describe('MoreActionsButton', () => { describe('MoreActionsButton', () => {
afterEach(() => { afterEach(() => {
jest.resetAllMocks(); 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(<MoreActionsButton {...defaultProps} />);
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(<MoreActionsButton {...defaultProps} />);
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(<MoreActionsButton {...defaultProps} />);
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();
});
}); });
}); });

View File

@ -1,10 +1,13 @@
import classnames from 'classnames'; import classnames from 'classnames';
import { isNull } from 'lodash';
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { BiCode } from 'react-icons/bi'; import { BiCode } from 'react-icons/bi';
import { GoStop } from 'react-icons/go';
import { HiDotsVertical } from 'react-icons/hi'; import { HiDotsVertical } from 'react-icons/hi';
import useOutsideClick from '../../hooks/useOutsideClick'; import useOutsideClick from '../../hooks/useOutsideClick';
import { SearchFiltersURL } from '../../types'; import { SearchFiltersURL } from '../../types';
import getMetaTag from '../../utils/getMetaTag';
import styles from './MoreActionsButton.module.css'; import styles from './MoreActionsButton.module.css';
import WidgetModal from './WidgetModal'; import WidgetModal from './WidgetModal';
@ -21,6 +24,7 @@ const MoreActionsButton = (props: Props) => {
const [openStatus, setOpenStatus] = useState(false); const [openStatus, setOpenStatus] = useState(false);
const [visibleWidget, setVisibleWidget] = useState<boolean>(props.visibleWidget); const [visibleWidget, setVisibleWidget] = useState<boolean>(props.visibleWidget);
const [currentPkgId, setCurrentPkgId] = useState<string>(props.packageId); const [currentPkgId, setCurrentPkgId] = useState<string>(props.packageId);
const reportURL = getMetaTag('reportURL');
const ref = useRef(null); const ref = useRef(null);
useOutsideClick([ref], openStatus, () => setOpenStatus(false)); useOutsideClick([ref], openStatus, () => setOpenStatus(false));
@ -69,10 +73,28 @@ const MoreActionsButton = (props: Props) => {
aria-label="Open embed widget modal" aria-label="Open embed widget modal"
> >
<div className="d-flex flex-row align-items-center"> <div className="d-flex flex-row align-items-center">
<BiCode className="mr-2" /> <BiCode className={`mr-2 position-relative ${styles.icon}`} />
<div>Embed widget</div> <div>Embed widget</div>
</div> </div>
</button> </button>
{!isNull(reportURL) && reportURL !== '' && (
<button
className="dropdown-item btn btn-sm rounded-0 text-dark"
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
window.open(reportURL, '_blank');
setOpenStatus(false);
}}
aria-label="Open report abuse url"
>
<div className="d-flex flex-row align-items-center">
<GoStop className={`mr-2 position-relative ${styles.icon}`} />
<div>Report abuse</div>
</div>
</button>
)}
</div> </div>
</div> </div>

View File

@ -48,7 +48,7 @@ exports[`MoreActionsButton creates snapshot 1`] = `
class="d-flex flex-row align-items-center" class="d-flex flex-row align-items-center"
> >
<svg <svg
class="mr-2" class="mr-2 position-relative icon"
fill="currentColor" fill="currentColor"
height="1em" height="1em"
stroke="currentColor" stroke="currentColor"