index.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. import {useEffect} from 'react';
  2. import type {RouteComponentProps} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import * as Sentry from '@sentry/react';
  5. import Access from 'sentry/components/acl/access';
  6. import {Button, LinkButton} from 'sentry/components/button';
  7. import Form from 'sentry/components/forms/form';
  8. import JsonForm from 'sentry/components/forms/jsonForm';
  9. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  10. import formGroups from 'sentry/data/forms/userFeedback';
  11. import {t} from 'sentry/locale';
  12. import {space} from 'sentry/styles/space';
  13. import type {Organization} from 'sentry/types/organization';
  14. import type {Project} from 'sentry/types/project';
  15. import withOrganization from 'sentry/utils/withOrganization';
  16. import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
  17. import TextBlock from 'sentry/views/settings/components/text/textBlock';
  18. import PermissionAlert from 'sentry/views/settings/project/permissionAlert';
  19. type RouteParams = {
  20. projectId: string;
  21. };
  22. type Props = RouteComponentProps<RouteParams, {}> & {
  23. organization: Organization;
  24. project: Project;
  25. };
  26. function ProjectUserFeedback({organization, project, params: {projectId}}: Props) {
  27. const handleClick = () => {
  28. Sentry.showReportDialog({
  29. // should never make it to the Sentry API, but just in case, use throwaway id
  30. eventId: '00000000000000000000000000000000',
  31. });
  32. };
  33. // We need this mock here, otherwise the demo crash modal report will send to Sentry.
  34. // We also need to unset window.sentryEmbedCallback, otherwise if we get a legit crash modal in our app this code would gobble it up.
  35. useEffect(() => {
  36. window.sentryEmbedCallback = function (embed) {
  37. // Mock the embed's submit xhr to always be successful
  38. // NOTE: this will not have errors if the form is empty
  39. embed.submit = function (_body) {
  40. this._submitInProgress = true;
  41. window.setTimeout(() => {
  42. this._submitInProgress = false;
  43. this.onSuccess();
  44. }, 500);
  45. };
  46. };
  47. return () => {
  48. window.sentryEmbedCallback = null;
  49. };
  50. }, []);
  51. return (
  52. <SentryDocumentTitle title={t('User Feedback')} projectSlug={project.slug}>
  53. <SettingsPageHeader
  54. title={t('User Feedback')}
  55. action={
  56. <ButtonList>
  57. <LinkButton href="https://docs.sentry.io/product/user-feedback/">
  58. {t('Read the Docs')}
  59. </LinkButton>
  60. <Button priority="primary" onClick={handleClick}>
  61. {t('Open the Crash Report Modal')}
  62. </Button>
  63. </ButtonList>
  64. }
  65. />
  66. <TextBlock>
  67. {t(
  68. `Don't rely on stack traces and graphs alone to understand
  69. the cause and impact of errors. Enable the User Feedback Widget to collect
  70. your users' comments at anytime, or enable the Crash Report Modal to collect additional context only when an error occurs.`
  71. )}
  72. </TextBlock>
  73. <PermissionAlert project={project} />
  74. <Form
  75. saveOnBlur
  76. apiMethod="PUT"
  77. apiEndpoint={`/projects/${organization.slug}/${projectId}/`}
  78. initialData={project.options}
  79. >
  80. <Access access={['project:write']} project={project}>
  81. {({hasAccess}) => <JsonForm disabled={!hasAccess} forms={formGroups} />}
  82. </Access>
  83. </Form>
  84. </SentryDocumentTitle>
  85. );
  86. }
  87. const ButtonList = styled('div')`
  88. display: inline-grid;
  89. grid-auto-flow: column;
  90. gap: ${space(1)};
  91. `;
  92. export default withOrganization(ProjectUserFeedback);