solutionsSection.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import {useRef} from 'react';
  2. import styled from '@emotion/styled';
  3. import Color from 'color';
  4. import {Button} from 'sentry/components/button';
  5. import {AutofixDrawer} from 'sentry/components/events/autofix/autofixDrawer';
  6. import {useGroupSummary} from 'sentry/components/events/autofix/groupSummary';
  7. import useDrawer from 'sentry/components/globalDrawer';
  8. import Placeholder from 'sentry/components/placeholder';
  9. import {IconSeer} from 'sentry/icons';
  10. import {t} from 'sentry/locale';
  11. import {space} from 'sentry/styles/space';
  12. import type {Event} from 'sentry/types/event';
  13. import type {Group} from 'sentry/types/group';
  14. import type {Project} from 'sentry/types/project';
  15. import {getConfigForIssueType} from 'sentry/utils/issueTypeConfig';
  16. import {singleLineRenderer} from 'sentry/utils/marked';
  17. import {SidebarSectionTitle} from 'sentry/views/issueDetails/streamline/sidebar';
  18. const isSummaryEnabled = (hasGenAIConsent: boolean, hasIssueSummary: boolean) => {
  19. return hasGenAIConsent && hasIssueSummary;
  20. };
  21. export default function SolutionsSection({
  22. group,
  23. project,
  24. event,
  25. }: {
  26. event: Event | undefined;
  27. group: Group;
  28. project: Project;
  29. }) {
  30. const {openDrawer} = useDrawer();
  31. const openButtonRef = useRef<HTMLButtonElement>(null);
  32. const openSolutionsDrawer = () => {
  33. if (!event) {
  34. return;
  35. }
  36. openDrawer(() => <AutofixDrawer group={group} project={project} event={event} />, {
  37. ariaLabel: t('Sentry AI drawer'),
  38. shouldCloseOnInteractOutside: element => {
  39. const viewAllButton = openButtonRef.current;
  40. if (
  41. viewAllButton?.contains(element) ||
  42. document.getElementById('sentry-feedback')?.contains(element)
  43. ) {
  44. return false;
  45. }
  46. return true;
  47. },
  48. transitionProps: {stiffness: 1000},
  49. });
  50. };
  51. const {data, isPending, hasGenAIConsent} = useGroupSummary(
  52. group.id,
  53. group.issueCategory
  54. );
  55. const issueTypeConfig = getConfigForIssueType(group, group.project);
  56. return (
  57. <div>
  58. <TitleWrapper>
  59. <IconSeer size="md" />
  60. <SidebarSectionTitle
  61. style={{marginTop: 0, marginLeft: space(1), marginBottom: 0}}
  62. >
  63. {t('Sentry AI')}
  64. </SidebarSectionTitle>
  65. </TitleWrapper>
  66. {isPending &&
  67. isSummaryEnabled(hasGenAIConsent, issueTypeConfig.issueSummary.enabled) && (
  68. <Placeholder height="60px" width="95%" style={{marginBottom: space(1)}} />
  69. )}
  70. {isSummaryEnabled(hasGenAIConsent, issueTypeConfig.issueSummary.enabled) &&
  71. data && (
  72. <Summary
  73. dangerouslySetInnerHTML={{
  74. __html: singleLineRenderer(data.whatsWrong?.replaceAll('**', '') ?? ''),
  75. }}
  76. />
  77. )}
  78. {!hasGenAIConsent && !isPending && (
  79. <Summary>
  80. Use Sentry AI to explore potential root causes and fixes for this issue.
  81. </Summary>
  82. )}
  83. {hasGenAIConsent && (
  84. <StyledButton
  85. ref={openButtonRef}
  86. onClick={() => openSolutionsDrawer()}
  87. analyticsEventKey="ai.drawer_opened"
  88. analyticsEventName="Sentry AI: Drawer Opened"
  89. analyticsParams={{group_id: group.id}}
  90. >
  91. {t('Explore Sentry AI')}
  92. </StyledButton>
  93. )}
  94. {!hasGenAIConsent && (
  95. <SetupButton
  96. ref={openButtonRef}
  97. onClick={() => openSolutionsDrawer()}
  98. analyticsEventKey="ai.get_started_clicked"
  99. analyticsEventName="Sentry AI: Get Started Clicked"
  100. analyticsParams={{group_id: group.id}}
  101. aria-label={t('Get started with Sentry AI')}
  102. >
  103. {!isPending ? (
  104. t('Get started with Sentry AI')
  105. ) : (
  106. <Placeholder height="24px" width="75%" />
  107. )}
  108. </SetupButton>
  109. )}
  110. </div>
  111. );
  112. }
  113. const Summary = styled('div')`
  114. margin-bottom: ${space(1)};
  115. word-wrap: break-word;
  116. overflow-wrap: break-word;
  117. max-width: 100%;
  118. `;
  119. const StyledButton = styled(Button)`
  120. width: 100%;
  121. background: linear-gradient(
  122. to right,
  123. ${p => p.theme.background},
  124. ${p => Color(p.theme.error).alpha(0.15).string()}
  125. );
  126. color: ${p => p.theme.errorText};
  127. `;
  128. const SetupButton = styled(Button)`
  129. width: 100%;
  130. `;
  131. const TitleWrapper = styled('div')`
  132. display: flex;
  133. align-items: center;
  134. margin-bottom: ${space(1)};
  135. `;