eventCause.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. import {Fragment, JSXElementConstructor, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import flatMap from 'lodash/flatMap';
  4. import uniqBy from 'lodash/uniqBy';
  5. import {CommitRowProps} from 'sentry/components/commitRow';
  6. import {CauseHeader, DataSection} from 'sentry/components/events/styles';
  7. import {Panel} from 'sentry/components/panels';
  8. import {IconAdd, IconSubtract} from 'sentry/icons';
  9. import {t} from 'sentry/locale';
  10. import space from 'sentry/styles/space';
  11. import {AvatarProject, Commit, Group, IssueCategory} from 'sentry/types';
  12. import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
  13. import useRouteAnalyticsParams from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams';
  14. import useCommitters from 'sentry/utils/useCommitters';
  15. import useOrganization from 'sentry/utils/useOrganization';
  16. interface Props {
  17. commitRow: JSXElementConstructor<CommitRowProps>;
  18. eventId: string;
  19. project: AvatarProject;
  20. group?: Group;
  21. }
  22. export function EventCause({group, eventId, project, commitRow: CommitRow}: Props) {
  23. const organization = useOrganization();
  24. const [isExpanded, setIsExpanded] = useState(false);
  25. const {data, isLoading} = useCommitters({
  26. eventId,
  27. projectSlug: project.slug,
  28. });
  29. const committers = data?.committers ?? [];
  30. useRouteAnalyticsParams({
  31. fetching: isLoading,
  32. num_suspect_commits: committers.length,
  33. });
  34. function getUniqueCommitsWithAuthors() {
  35. // Get a list of commits with author information attached
  36. const commitsWithAuthors = flatMap(committers, ({commits, author}) =>
  37. commits.map(commit => ({
  38. ...commit,
  39. author,
  40. }))
  41. );
  42. // Remove duplicate commits
  43. return uniqBy(commitsWithAuthors, commit => commit.id);
  44. }
  45. if (!committers.length) {
  46. return null;
  47. }
  48. const handlePullRequestClick = () => {
  49. trackAdvancedAnalyticsEvent('issue_details.suspect_commits.pull_request_clicked', {
  50. organization,
  51. project_id: parseInt(project.id as string, 10),
  52. group_id: parseInt(group?.id as string, 10),
  53. issue_category: group?.issueCategory ?? IssueCategory.ERROR,
  54. });
  55. };
  56. const handleCommitClick = (commit: Commit) => {
  57. trackAdvancedAnalyticsEvent('issue_details.suspect_commits.commit_clicked', {
  58. organization,
  59. project_id: parseInt(project.id as string, 10),
  60. group_id: parseInt(group?.id as string, 10),
  61. issue_category: group?.issueCategory ?? IssueCategory.ERROR,
  62. has_pull_request: commit.pullRequest?.id !== undefined,
  63. });
  64. };
  65. const commits = getUniqueCommitsWithAuthors();
  66. return (
  67. <DataSection>
  68. <CauseHeader>
  69. <h3 data-test-id="event-cause">
  70. {t('Suspect Commits')} ({commits.length})
  71. </h3>
  72. {commits.length > 1 && (
  73. <ExpandButton onClick={() => setIsExpanded(!isExpanded)}>
  74. {isExpanded ? (
  75. <Fragment>
  76. {t('Show less')} <IconSubtract isCircled size="md" />
  77. </Fragment>
  78. ) : (
  79. <Fragment>
  80. {t('Show more')} <IconAdd isCircled size="md" />
  81. </Fragment>
  82. )}
  83. </ExpandButton>
  84. )}
  85. </CauseHeader>
  86. <Panel>
  87. {commits.slice(0, isExpanded ? 100 : 1).map(commit => (
  88. <CommitRow
  89. key={commit.id}
  90. commit={commit}
  91. onCommitClick={handleCommitClick}
  92. onPullRequestClick={handlePullRequestClick}
  93. />
  94. ))}
  95. </Panel>
  96. </DataSection>
  97. );
  98. }
  99. const ExpandButton = styled('button')`
  100. display: flex;
  101. align-items: center;
  102. gap: ${space(0.5)};
  103. `;