From 46e6377e49c1aace7ad034ae14f86707048f13bf Mon Sep 17 00:00:00 2001
From: Michelle Zhang <56095982+michellewzhang@users.noreply.github.com>
Date: Thu, 21 Mar 2024 11:10:17 -0700
Subject: [PATCH] feat(feedback): add spam detection to project settings
(#66740)
Create a User Feedback section under the Processing header, to add a
spam detection toggle in project settings. We already have a User
Feedback section under SDK Setup, but I didn't think that spam fit in
with SDK setup. The result is that we now have two User Feedback
sections in the settings (open to ideas for consolidating or renaming):
The new page looks like this:
Closes https://github.com/getsentry/sentry/issues/66731
---
.../app/data/forms/userFeedbackProcessing.tsx | 22 ++++++++
static/app/routes.tsx | 7 +++
.../project/navigationConfiguration.tsx | 5 ++
.../projectUserFeedbackProcessing.spec.tsx | 55 +++++++++++++++++++
.../project/projectUserFeedbackProcessing.tsx | 52 ++++++++++++++++++
5 files changed, 141 insertions(+)
create mode 100644 static/app/data/forms/userFeedbackProcessing.tsx
create mode 100644 static/app/views/settings/project/projectUserFeedbackProcessing.spec.tsx
create mode 100644 static/app/views/settings/project/projectUserFeedbackProcessing.tsx
diff --git a/static/app/data/forms/userFeedbackProcessing.tsx b/static/app/data/forms/userFeedbackProcessing.tsx
new file mode 100644
index 00000000000000..0ef5d11f9ee8ba
--- /dev/null
+++ b/static/app/data/forms/userFeedbackProcessing.tsx
@@ -0,0 +1,22 @@
+import type {JsonFormObject} from 'sentry/components/forms/types';
+
+export const route = '/settings/:orgId/projects/:projectId/user-feedback-processing/';
+
+const formGroups: JsonFormObject[] = [
+ {
+ title: 'Settings',
+ fields: [
+ {
+ name: 'sentry:feedback_ai_spam_detection',
+ type: 'boolean',
+
+ // additional data/props that is related to rendering of form field rather than data
+ label: 'Enable Spam Detection',
+ help: 'Toggles whether or not to enable auto spam detection in User Feedback.',
+ getData: data => ({options: data}),
+ },
+ ],
+ },
+];
+
+export default formGroups;
diff --git a/static/app/routes.tsx b/static/app/routes.tsx
index de85d602c16576..dd749bab061d7e 100644
--- a/static/app/routes.tsx
+++ b/static/app/routes.tsx
@@ -564,6 +564,13 @@ function buildRoutes() {
name={t('Replays')}
component={make(() => import('sentry/views/settings/project/projectReplays'))}
/>
+ import('sentry/views/settings/project/projectUserFeedbackProcessing')
+ )}
+ />
!!organization?.features?.includes('session-replay-ui'),
},
+ {
+ path: `${pathPrefix}/user-feedback-processing/`,
+ title: t('User Feedback'),
+ show: () => !!organization?.features?.includes('user-feedback-ui'),
+ },
],
},
{
diff --git a/static/app/views/settings/project/projectUserFeedbackProcessing.spec.tsx b/static/app/views/settings/project/projectUserFeedbackProcessing.spec.tsx
new file mode 100644
index 00000000000000..dc84b5fc24c254
--- /dev/null
+++ b/static/app/views/settings/project/projectUserFeedbackProcessing.spec.tsx
@@ -0,0 +1,55 @@
+import {ProjectFixture} from 'sentry-fixture/project';
+
+import {initializeOrg} from 'sentry-test/initializeOrg';
+import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
+
+import ProjectUserFeedbackProcessing from 'sentry/views/settings/project/projectUserFeedbackProcessing';
+
+describe('ProjectUserFeedbackProcessing', function () {
+ const {routerProps, organization, project, routerContext} = initializeOrg();
+ const url = `/projects/${organization.slug}/${project.slug}/`;
+
+ beforeEach(function () {
+ MockApiClient.clearMockResponses();
+ MockApiClient.addMockResponse({
+ url,
+ method: 'GET',
+ body: ProjectFixture(),
+ });
+ MockApiClient.addMockResponse({
+ url: `${url}keys/`,
+ method: 'GET',
+ body: [],
+ });
+ });
+
+ it('can toggle spam detection', async function () {
+ render(
+ ,
+ {
+ context: routerContext,
+ }
+ );
+
+ const mock = MockApiClient.addMockResponse({
+ url,
+ method: 'PUT',
+ });
+
+ await userEvent.click(screen.getByRole('checkbox', {name: 'Enable Spam Detection'}));
+
+ expect(mock).toHaveBeenCalledWith(
+ url,
+ expect.objectContaining({
+ method: 'PUT',
+ data: {
+ options: {'sentry:feedback_ai_spam_detection': true},
+ },
+ })
+ );
+ });
+});
diff --git a/static/app/views/settings/project/projectUserFeedbackProcessing.tsx b/static/app/views/settings/project/projectUserFeedbackProcessing.tsx
new file mode 100644
index 00000000000000..8f0c2d83b32ac4
--- /dev/null
+++ b/static/app/views/settings/project/projectUserFeedbackProcessing.tsx
@@ -0,0 +1,52 @@
+import type {RouteComponentProps} from 'react-router';
+
+import Access from 'sentry/components/acl/access';
+import {Button} from 'sentry/components/button';
+import Form from 'sentry/components/forms/form';
+import JsonForm from 'sentry/components/forms/jsonForm';
+import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
+import formGroups from 'sentry/data/forms/userFeedbackProcessing';
+import {t} from 'sentry/locale';
+import type {Organization, Project} from 'sentry/types';
+import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
+import PermissionAlert from 'sentry/views/settings/project/permissionAlert';
+
+type RouteParams = {
+ projectId: string;
+};
+type Props = RouteComponentProps & {
+ organization: Organization;
+ project: Project;
+};
+
+function ProjectUserFeedbackProcessingSettings({
+ project,
+ organization,
+ params: {projectId},
+}: Props) {
+ return (
+
+
+ {t('Read the docs')}
+
+ }
+ />
+
+
+
+ );
+}
+
+export default ProjectUserFeedbackProcessingSettings;