suspectCommits.tsx 4.0 KB

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