userFeedbackEmpty.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  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 {trackAdhocEvent, trackAnalyticsEvent} 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 =
  21. projectIds && projectIds.length
  22. ? projects.filter(({id}) => projectIds.includes(id))
  23. : projects;
  24. const hasAnyFeedback = selectedProjects.some(({hasUserReports}) => hasUserReports);
  25. useEffect(() => {
  26. window.sentryEmbedCallback = function (embed) {
  27. // Mock the embed's submit xhr to always be successful
  28. // NOTE: this will not have errors if the form is empty
  29. embed.submit = function (_body) {
  30. this._submitInProgress = true;
  31. setTimeout(() => {
  32. this._submitInProgress = false;
  33. this.onSuccess();
  34. }, 500);
  35. };
  36. };
  37. if (hasAnyFeedback === false) {
  38. // send to reload only due to higher event volume
  39. trackAdhocEvent({
  40. eventKey: 'user_feedback.viewed',
  41. org_id: parseInt(organization.id, 10),
  42. projects: projectIds,
  43. });
  44. }
  45. return () => {
  46. window.sentryEmbedCallback = null;
  47. };
  48. }, [hasAnyFeedback, organization.id, projectIds]);
  49. function trackAnalytics({eventKey, eventName}: {eventKey: string; eventName: string}) {
  50. trackAnalyticsEvent({
  51. eventKey,
  52. eventName,
  53. organization_id: organization.id,
  54. projects: projectIds,
  55. });
  56. }
  57. // Show no user reports if waiting for projects to load or if there is no feedback
  58. if (loadingProjects || hasAnyFeedback !== false) {
  59. return (
  60. <EmptyStateWarning>
  61. <p>{t('Sorry, no user reports match your filters.')}</p>
  62. </EmptyStateWarning>
  63. );
  64. }
  65. // Show landing page after projects have loaded and it is confirmed no projects have feedback
  66. return (
  67. <OnboardingPanel
  68. data-test-id="user-feedback-empty"
  69. image={<img src={emptyStateImg} />}
  70. >
  71. <h3>{t('What do users think?')}</h3>
  72. <p>
  73. {t(
  74. `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.`
  75. )}
  76. </p>
  77. <ButtonList gap={1}>
  78. <Button
  79. external
  80. priority="primary"
  81. onClick={() =>
  82. trackAnalytics({
  83. eventKey: 'user_feedback.docs_clicked',
  84. eventName: 'User Feedback Docs Clicked',
  85. })
  86. }
  87. href="https://docs.sentry.io/product/user-feedback/"
  88. >
  89. {t('Read the docs')}
  90. </Button>
  91. <Button
  92. onClick={() => {
  93. Sentry.showReportDialog({
  94. // should never make it to the Sentry API, but just in case, use throwaway id
  95. eventId: '00000000000000000000000000000000',
  96. });
  97. trackAnalytics({
  98. eventKey: 'user_feedback.dialog_opened',
  99. eventName: 'User Feedback Dialog Opened',
  100. });
  101. }}
  102. >
  103. {t('See an example')}
  104. </Button>
  105. </ButtonList>
  106. </OnboardingPanel>
  107. );
  108. }
  109. const ButtonList = styled(ButtonBar)`
  110. grid-template-columns: repeat(auto-fit, minmax(130px, max-content));
  111. `;