projectUserFeedback.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import {RouteComponentProps} from 'react-router';
  2. import styled from '@emotion/styled';
  3. import * as Sentry from '@sentry/react';
  4. import Access from 'sentry/components/acl/access';
  5. import Button from 'sentry/components/button';
  6. import Form from 'sentry/components/forms/form';
  7. import JsonForm from 'sentry/components/forms/jsonForm';
  8. import formGroups from 'sentry/data/forms/userFeedback';
  9. import {t} from 'sentry/locale';
  10. import space from 'sentry/styles/space';
  11. import routeTitleGen from 'sentry/utils/routeTitle';
  12. import AsyncView from 'sentry/views/asyncView';
  13. import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
  14. import TextBlock from 'sentry/views/settings/components/text/textBlock';
  15. type RouteParams = {
  16. orgId: string;
  17. projectId: string;
  18. };
  19. type Props = RouteComponentProps<RouteParams, {}>;
  20. class ProjectUserFeedbackSettings extends AsyncView<Props> {
  21. submitTimeout: number | undefined = undefined;
  22. componentDidMount() {
  23. window.sentryEmbedCallback = function (embed) {
  24. // Mock the embed's submit xhr to always be successful
  25. // NOTE: this will not have errors if the form is empty
  26. embed.submit = function (_body) {
  27. this._submitInProgress = true;
  28. window.setTimeout(() => {
  29. this._submitInProgress = false;
  30. this.onSuccess();
  31. }, 500);
  32. };
  33. };
  34. }
  35. componentWillUnmount() {
  36. window.sentryEmbedCallback = null;
  37. }
  38. getEndpoints(): ReturnType<AsyncView['getEndpoints']> {
  39. const {orgId, projectId} = this.props.params;
  40. return [
  41. ['keyList', `/projects/${orgId}/${projectId}/keys/`],
  42. ['project', `/projects/${orgId}/${projectId}/`],
  43. ];
  44. }
  45. getTitle(): string {
  46. const {projectId} = this.props.params;
  47. return routeTitleGen(t('User Feedback'), projectId, false);
  48. }
  49. handleClick = () => {
  50. Sentry.showReportDialog({
  51. // should never make it to the Sentry API, but just in case, use throwaway id
  52. eventId: '00000000000000000000000000000000',
  53. });
  54. };
  55. renderBody() {
  56. const {orgId, projectId} = this.props.params;
  57. return (
  58. <div>
  59. <SettingsPageHeader title={t('User Feedback')} />
  60. <TextBlock>
  61. {t(
  62. `Don't rely on stack traces and graphs alone to understand
  63. the cause and impact of errors. Enable User Feedback to collect
  64. your users' comments when they encounter a crash or bug.`
  65. )}
  66. </TextBlock>
  67. <TextBlock>
  68. {t(
  69. `When configured, your users will be presented with a dialog prompting
  70. them for additional information. That information will get attached to
  71. the issue in Sentry.`
  72. )}
  73. </TextBlock>
  74. <ButtonList>
  75. <Button external href="https://docs.sentry.io/product/user-feedback/">
  76. {t('Read the docs')}
  77. </Button>
  78. <Button priority="primary" onClick={this.handleClick}>
  79. {t('Open the report dialog')}
  80. </Button>
  81. </ButtonList>
  82. <Form
  83. saveOnBlur
  84. apiMethod="PUT"
  85. apiEndpoint={`/projects/${orgId}/${projectId}/`}
  86. initialData={this.state.project.options}
  87. >
  88. <Access access={['project:write']}>
  89. {({hasAccess}) => <JsonForm disabled={!hasAccess} forms={formGroups} />}
  90. </Access>
  91. </Form>
  92. </div>
  93. );
  94. }
  95. }
  96. const ButtonList = styled('div')`
  97. display: inline-grid;
  98. grid-auto-flow: column;
  99. gap: ${space(1)};
  100. margin-bottom: ${space(2)};
  101. `;
  102. export default ProjectUserFeedbackSettings;