resourcesAndPossibleSolutions.tsx 3.5 KB

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