Skip to content

Commit

Permalink
feat(feedback): add spam detection to project settings (#66740)
Browse files Browse the repository at this point in the history
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):
<img width="190" alt="SCR-20240311-nuse"
src="https://github.com/getsentry/sentry/assets/56095982/d702ebe0-6b29-4f4f-bd16-b631bace6d26">

The new page looks like this:
<img width="978" alt="SCR-20240311-numj"
src="https://github.com/getsentry/sentry/assets/56095982/6cc13770-129e-40bb-b074-8a63b51eec8e">

Closes #66731
  • Loading branch information
michellewzhang authored Mar 21, 2024
1 parent 3f7e560 commit 46e6377
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 0 deletions.
22 changes: 22 additions & 0 deletions static/app/data/forms/userFeedbackProcessing.tsx
Original file line number Diff line number Diff line change
@@ -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;
7 changes: 7 additions & 0 deletions static/app/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,13 @@ function buildRoutes() {
name={t('Replays')}
component={make(() => import('sentry/views/settings/project/projectReplays'))}
/>
<Route
path="user-feedback-processing/"
name={t('User Feedback')}
component={make(
() => import('sentry/views/settings/project/projectUserFeedbackProcessing')
)}
/>

<Route path="source-maps/" name={t('Source Maps')}>
<IndexRoute
Expand Down
5 changes: 5 additions & 0 deletions static/app/views/settings/project/navigationConfiguration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ export default function getConfiguration({
title: t('Replays'),
show: () => !!organization?.features?.includes('session-replay-ui'),
},
{
path: `${pathPrefix}/user-feedback-processing/`,
title: t('User Feedback'),
show: () => !!organization?.features?.includes('user-feedback-ui'),
},
],
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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(
<ProjectUserFeedbackProcessing
{...routerProps}
organization={organization}
project={project}
/>,
{
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},
},
})
);
});
});
Original file line number Diff line number Diff line change
@@ -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<RouteParams, {}> & {
organization: Organization;
project: Project;
};

function ProjectUserFeedbackProcessingSettings({
project,
organization,
params: {projectId},
}: Props) {
return (
<SentryDocumentTitle title={t('User Feedback')} projectSlug={project.slug}>
<SettingsPageHeader
title={t('User Feedback')}
action={
<Button external href="https://docs.sentry.io/product/user-feedback/">
{t('Read the docs')}
</Button>
}
/>
<PermissionAlert project={project} />
<Form
saveOnBlur
apiMethod="PUT"
apiEndpoint={`/projects/${organization.slug}/${projectId}/`}
initialData={project.options}
>
<Access access={['project:write']} project={project}>
{({hasAccess}) => <JsonForm disabled={!hasAccess} forms={formGroups} />}
</Access>
</Form>
</SentryDocumentTitle>
);
}

export default ProjectUserFeedbackProcessingSettings;

0 comments on commit 46e6377

Please sign in to comment.