userFeedbackEmpty.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import {useEffect} from 'react';
  2. import styled from '@emotion/styled';
  3. import * as Sentry from '@sentry/react';
  4. import emptyStateImg from 'sentry-images/spot/feedback-empty-state.svg';
  5. import {Button} from 'sentry/components/button';
  6. import ButtonBar from 'sentry/components/buttonBar';
  7. import EmptyStateWarning from 'sentry/components/emptyStateWarning';
  8. import OnboardingPanel from 'sentry/components/onboardingPanel';
  9. import {t} from 'sentry/locale';
  10. import {trackAnalytics} from 'sentry/utils/analytics';
  11. import useOrganization from 'sentry/utils/useOrganization';
  12. import useProjects from 'sentry/utils/useProjects';
  13. type Props = {
  14. projectIds?: string[];
  15. };
  16. export function UserFeedbackEmpty({projectIds}: Props) {
  17. const {projects, initiallyLoaded} = useProjects();
  18. const loadingProjects = !initiallyLoaded;
  19. const organization = useOrganization();
  20. const selectedProjects = projectIds?.length
  21. ? projects.filter(({id}) => projectIds.includes(id))
  22. : projects;
  23. const hasAnyFeedback = selectedProjects.some(({hasUserReports}) => hasUserReports);
  24. useEffect(() => {
  25. window.sentryEmbedCallback = function (embed) {
  26. // Mock the embed's submit xhr to always be successful
  27. // NOTE: this will not have errors if the form is empty
  28. embed.submit = function (_body) {
  29. this._submitInProgress = true;
  30. setTimeout(() => {
  31. this._submitInProgress = false;
  32. this.onSuccess();
  33. }, 500);
  34. };
  35. };
  36. if (hasAnyFeedback === false) {
  37. // send to reload only due to higher event volume
  38. trackAnalytics('user_feedback.viewed', {
  39. organization,
  40. projects: projectIds?.join(',') || '',
  41. });
  42. }
  43. return () => {
  44. window.sentryEmbedCallback = null;
  45. };
  46. }, [hasAnyFeedback, organization, projectIds]);
  47. function trackAnalyticsInternal(
  48. eventKey: 'user_feedback.docs_clicked' | 'user_feedback.dialog_opened'
  49. ) {
  50. trackAnalytics(eventKey, {
  51. organization,
  52. projects: selectedProjects?.join(','),
  53. });
  54. }
  55. // Show no user reports if waiting for projects to load or if there is no feedback
  56. if (loadingProjects || hasAnyFeedback !== false) {
  57. return (
  58. <EmptyStateWarning>
  59. <p>{t('Sorry, no user reports match your filters.')}</p>
  60. </EmptyStateWarning>
  61. );
  62. }
  63. // Show landing page after projects have loaded and it is confirmed no projects have feedback
  64. return (
  65. <OnboardingPanel
  66. data-test-id="user-feedback-empty"
  67. image={<img src={emptyStateImg} />}
  68. >
  69. <h3>{t('What do users think?')}</h3>
  70. <p>
  71. {t(
  72. `You can't read minds. At least we hope not. Ask users for feedback on the impact of their crashes or bugs and you shall receive.`
  73. )}
  74. </p>
  75. <ButtonList gap={1}>
  76. <Button
  77. external
  78. priority="primary"
  79. onClick={() => trackAnalyticsInternal('user_feedback.docs_clicked')}
  80. href="https://docs.sentry.io/product/user-feedback/"
  81. >
  82. {t('Read the docs')}
  83. </Button>
  84. <Button
  85. onClick={() => {
  86. Sentry.showReportDialog({
  87. // should never make it to the Sentry API, but just in case, use throwaway id
  88. eventId: '00000000000000000000000000000000',
  89. });
  90. trackAnalyticsInternal('user_feedback.dialog_opened');
  91. }}
  92. >
  93. {t('See an example')}
  94. </Button>
  95. </ButtonList>
  96. </OnboardingPanel>
  97. );
  98. }
  99. const ButtonList = styled(ButtonBar)`
  100. grid-template-columns: repeat(auto-fit, minmax(130px, max-content));
  101. `;