resourcesAndPossibleSolutions.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. import {css} from '@emotion/react';
  2. import styled from '@emotion/styled';
  3. import {AiSuggestedSolution} from 'sentry/components/events/aiSuggestedSolution';
  4. import {Autofix} from 'sentry/components/events/autofix';
  5. import {useAutofixSetup} from 'sentry/components/events/autofix/useAutofixSetup';
  6. import {Resources} from 'sentry/components/events/interfaces/performance/resources';
  7. import {t} from 'sentry/locale';
  8. import ConfigStore from 'sentry/stores/configStore';
  9. import {space} from 'sentry/styles/space';
  10. import {EntryType, type Event} from 'sentry/types/event';
  11. import type {Group} from 'sentry/types/group';
  12. import type {Project} from 'sentry/types/project';
  13. import {
  14. getConfigForIssueType,
  15. shouldShowCustomErrorResourceConfig,
  16. } from 'sentry/utils/issueTypeConfig';
  17. import {getRegionDataFromOrganization} from 'sentry/utils/regions';
  18. import useOrganization from 'sentry/utils/useOrganization';
  19. import {SectionKey} from 'sentry/views/issueDetails/streamline/context';
  20. import {InterimSection} from 'sentry/views/issueDetails/streamline/interimSection';
  21. import {useIsSampleEvent} from 'sentry/views/issueDetails/utils';
  22. type Props = {
  23. event: Event;
  24. group: Group;
  25. project: Project;
  26. };
  27. // Autofix requires the event to have stack trace frames in order to work correctly.
  28. function hasStacktraceWithFrames(event: Event) {
  29. for (const entry of event.entries) {
  30. if (entry.type === EntryType.EXCEPTION) {
  31. if (entry.data.values?.some(value => value.stacktrace?.frames?.length)) {
  32. return true;
  33. }
  34. }
  35. if (entry.type === EntryType.THREADS) {
  36. if (entry.data.values?.some(thread => thread.stacktrace?.frames?.length)) {
  37. return true;
  38. }
  39. }
  40. }
  41. return false;
  42. }
  43. // This section provides users with resources and possible solutions on how to resolve an issue
  44. export function ResourcesAndPossibleSolutions({event, project, group}: Props) {
  45. const organization = useOrganization();
  46. const config = getConfigForIssueType(group, project);
  47. const isSelfHostedErrorsOnly = ConfigStore.get('isSelfHostedErrorsOnly');
  48. const isSampleError = useIsSampleEvent();
  49. const {genAIConsent} = useAutofixSetup({
  50. groupId: group.id,
  51. });
  52. const displayAiAutofix =
  53. ((organization.features.includes('autofix') &&
  54. organization.features.includes('issue-details-autofix-ui')) ||
  55. genAIConsent) &&
  56. !shouldShowCustomErrorResourceConfig(group, project) &&
  57. config.autofix &&
  58. hasStacktraceWithFrames(event) &&
  59. !isSampleError;
  60. const displayAiSuggestedSolution =
  61. // Skip showing AI suggested solution if the issue has a custom resource
  62. config.aiSuggestedSolution &&
  63. organization.aiSuggestedSolution &&
  64. getRegionDataFromOrganization(organization)?.name !== 'de' &&
  65. !shouldShowCustomErrorResourceConfig(group, project) &&
  66. !displayAiAutofix &&
  67. !isSampleError;
  68. if (
  69. isSelfHostedErrorsOnly ||
  70. (!config.resources && !(displayAiSuggestedSolution || displayAiAutofix))
  71. ) {
  72. return null;
  73. }
  74. return (
  75. <Wrapper
  76. title={t('Resources and Possible Solutions')}
  77. configResources={!!config.resources}
  78. type={SectionKey.RESOURCES}
  79. >
  80. <Content>
  81. {config.resources && (
  82. <Resources
  83. eventPlatform={event.platform}
  84. groupId={group.id}
  85. configResources={config.resources}
  86. />
  87. )}
  88. {displayAiSuggestedSolution && (
  89. <AiSuggestedSolution event={event} projectSlug={project.slug} />
  90. )}
  91. {displayAiAutofix && <Autofix event={event} group={group} />}
  92. </Content>
  93. </Wrapper>
  94. );
  95. }
  96. const Content = styled('div')`
  97. display: flex;
  98. flex-direction: column;
  99. gap: ${space(2)};
  100. `;
  101. const Wrapper = styled(InterimSection)<{configResources: boolean}>`
  102. @media (min-width: ${p => p.theme.breakpoints.xlarge}) {
  103. ${p =>
  104. !p.configResources &&
  105. css`
  106. && {
  107. padding-top: ${space(3)};
  108. }
  109. `}
  110. }
  111. `;