Browse Source

feat(replays): add replays rage click issue creation toggle (#64241)

- [x] creates replay subsection on project settings, under 'processing'
- [x] adds a toggle to control a project option for rage click issue
creation


Depends on creating project option that the toggle hooks up to. creating
PR now so we can iterate on copy. Also need to start thinking about what
the docs section for rage click issues could look like. for now just
links to
[here](https://docs.sentry.io/product/session-replay/replay-page-and-filters/)
which mentions rage clicks.
<img width="1185" alt="Screenshot 2024-01-30 at 3 10 19 PM"
src="https://github.com/getsentry/sentry/assets/1976777/715b453c-2684-422a-8f70-29ce3aca6fc7">
Josh Ferge 1 year ago
parent
commit
332cfc7702

+ 22 - 0
static/app/data/forms/replay.tsx

@@ -0,0 +1,22 @@
+import type {JsonFormObject} from 'sentry/components/forms/types';
+
+export const route = '/settings/:orgId/projects/:projectId/replays/';
+
+const formGroups: JsonFormObject[] = [
+  {
+    title: 'Settings',
+    fields: [
+      {
+        name: 'sentry:replay_rage_click_issues',
+        type: 'boolean',
+
+        // additional data/props that is related to rendering of form field rather than data
+        label: 'Create Rage Click Issues',
+        help: 'Toggles whether or not to create Session Replay Rage Click Issues',
+        getData: data => ({options: data}),
+      },
+    ],
+  },
+];
+
+export default formGroups;

+ 5 - 0
static/app/routes.tsx

@@ -580,6 +580,11 @@ function buildRoutes() {
           )}
         />
       </Route>
+      <Route
+        path="replays/"
+        name={t('Replays')}
+        component={make(() => import('sentry/views/settings/project/projectReplays'))}
+      />
 
       <Route path="source-maps/" name={t('Source Maps')}>
         <IndexRoute

+ 5 - 0
static/app/views/settings/project/navigationConfiguration.tsx

@@ -121,6 +121,11 @@ export default function getConfiguration({
               organization?.features?.includes('ddm-ui')
             ),
         },
+        {
+          path: `${pathPrefix}/replays/`,
+          title: t('Replays'),
+          show: () => !!organization?.features?.includes('session-replay-ui'),
+        },
       ],
     },
     {

+ 80 - 0
static/app/views/settings/project/projectReplays.tsx

@@ -0,0 +1,80 @@
+import type {RouteComponentProps} from 'react-router';
+import styled from '@emotion/styled';
+
+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 formGroups from 'sentry/data/forms/replay';
+import {t} from 'sentry/locale';
+import {space} from 'sentry/styles/space';
+import type {Organization, Project} from 'sentry/types';
+import routeTitleGen from 'sentry/utils/routeTitle';
+import DeprecatedAsyncView from 'sentry/views/deprecatedAsyncView';
+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;
+};
+
+class ProjectUserFeedbackSettings extends DeprecatedAsyncView<Props> {
+  submitTimeout: number | undefined = undefined;
+
+  getEndpoints(): ReturnType<DeprecatedAsyncView['getEndpoints']> {
+    const {organization} = this.props;
+    const {projectId} = this.props.params;
+    return [['project', `/projects/${organization.slug}/${projectId}/`]];
+  }
+
+  getTitle(): string {
+    const {projectId} = this.props.params;
+    return routeTitleGen(t('Replays'), projectId, false);
+  }
+
+  renderBody() {
+    const {organization, project} = this.props;
+    const {projectId} = this.props.params;
+
+    return (
+      <div>
+        <SettingsPageHeader
+          title={t('Replays')}
+          action={
+            <ButtonList>
+              <Button
+                external
+                href="https://docs.sentry.io/product/session-replay/replay-page-and-filters/"
+              >
+                {t('Read the docs')}
+              </Button>
+            </ButtonList>
+          }
+        />
+        <PermissionAlert project={project} />
+        <Form
+          saveOnBlur
+          apiMethod="PUT"
+          apiEndpoint={`/projects/${organization.slug}/${projectId}/`}
+          initialData={this.state.project.options}
+        >
+          <Access access={['project:write']} project={project}>
+            {({hasAccess}) => <JsonForm disabled={!hasAccess} forms={formGroups} />}
+          </Access>
+        </Form>
+      </div>
+    );
+  }
+}
+
+export default ProjectUserFeedbackSettings;
+
+const ButtonList = styled('div')`
+  display: inline-grid;
+  grid-auto-flow: column;
+  gap: ${space(1)};
+`;