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): SCR-20240311-nuse The new page looks like this: SCR-20240311-numj 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')} + + } + /> + +
+ + {({hasAccess}) => } + +
+
+ ); +} + +export default ProjectUserFeedbackProcessingSettings;