commitAuthorBreakdown.tsx 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import styled from '@emotion/styled';
  2. import UserAvatar from 'sentry/components/avatar/userAvatar';
  3. import {Button} from 'sentry/components/button';
  4. import Collapsible from 'sentry/components/collapsible';
  5. import DeprecatedAsyncComponent from 'sentry/components/deprecatedAsyncComponent';
  6. import * as SidebarSection from 'sentry/components/sidebarSection';
  7. import {t, tn} from 'sentry/locale';
  8. import {space} from 'sentry/styles/space';
  9. import {Commit, User} from 'sentry/types';
  10. import {percent} from 'sentry/utils';
  11. import {userDisplayName} from 'sentry/utils/formatters';
  12. type GroupedAuthorCommits = {
  13. [key: string]: {author: User | undefined; commitCount: number};
  14. };
  15. type Props = {
  16. orgId: string;
  17. projectSlug: string;
  18. version: string;
  19. } & DeprecatedAsyncComponent['props'];
  20. type State = {
  21. commits: Commit[];
  22. } & DeprecatedAsyncComponent['state'];
  23. class CommitAuthorBreakdown extends DeprecatedAsyncComponent<Props, State> {
  24. shouldReload = true;
  25. getEndpoints(): ReturnType<DeprecatedAsyncComponent['getEndpoints']> {
  26. const {orgId, projectSlug, version} = this.props;
  27. const commitsEndpoint = `/projects/${orgId}/${projectSlug}/releases/${encodeURIComponent(
  28. version
  29. )}/commits/`;
  30. return [['commits', commitsEndpoint]];
  31. }
  32. componentDidUpdate(prevProps: Props) {
  33. if (prevProps.version !== this.props.version) {
  34. this.remountComponent();
  35. }
  36. }
  37. getDisplayPercent(authorCommitCount: number): string {
  38. const {commits} = this.state;
  39. const calculatedPercent = Math.round(percent(authorCommitCount, commits.length));
  40. return `${calculatedPercent < 1 ? '<1' : calculatedPercent}%`;
  41. }
  42. renderBody() {
  43. // group commits by author
  44. const groupedAuthorCommits = this.state.commits?.reduce<GroupedAuthorCommits>(
  45. (authorCommitsAccumulator, commit) => {
  46. const email = commit.author?.email ?? 'unknown';
  47. if (authorCommitsAccumulator.hasOwnProperty(email)) {
  48. authorCommitsAccumulator[email].commitCount += 1;
  49. } else {
  50. authorCommitsAccumulator[email] = {
  51. commitCount: 1,
  52. author: commit.author,
  53. };
  54. }
  55. return authorCommitsAccumulator;
  56. },
  57. {}
  58. );
  59. // sort authors by number of commits
  60. const sortedAuthorsByNumberOfCommits = Object.values(groupedAuthorCommits).sort(
  61. (a, b) => b.commitCount - a.commitCount
  62. );
  63. if (!sortedAuthorsByNumberOfCommits.length) {
  64. return null;
  65. }
  66. return (
  67. <SidebarSection.Wrap>
  68. <SidebarSection.Title>{t('Commit Author Breakdown')}</SidebarSection.Title>
  69. <SidebarSection.Content>
  70. <Collapsible
  71. expandButton={({onExpand, numberOfHiddenItems}) => (
  72. <Button priority="link" onClick={onExpand}>
  73. {tn(
  74. 'Show %s collapsed author',
  75. 'Show %s collapsed authors',
  76. numberOfHiddenItems
  77. )}
  78. </Button>
  79. )}
  80. >
  81. {sortedAuthorsByNumberOfCommits.map(({commitCount, author}, index) => (
  82. <AuthorLine key={author?.email ?? index}>
  83. <UserAvatar user={author} size={20} hasTooltip />
  84. <AuthorName>{userDisplayName(author || {}, false)}</AuthorName>
  85. <Commits>{tn('%s commit', '%s commits', commitCount)}</Commits>
  86. <Percent>{this.getDisplayPercent(commitCount)}</Percent>
  87. </AuthorLine>
  88. ))}
  89. </Collapsible>
  90. </SidebarSection.Content>
  91. </SidebarSection.Wrap>
  92. );
  93. }
  94. }
  95. const AuthorLine = styled('div')`
  96. display: inline-grid;
  97. grid-template-columns: 30px 2fr 1fr 40px;
  98. width: 100%;
  99. margin-bottom: ${space(1)};
  100. font-size: ${p => p.theme.fontSizeMedium};
  101. `;
  102. const AuthorName = styled('div')`
  103. color: ${p => p.theme.textColor};
  104. ${p => p.theme.overflowEllipsis};
  105. `;
  106. const Commits = styled('div')`
  107. color: ${p => p.theme.subText};
  108. text-align: right;
  109. `;
  110. const Percent = styled('div')`
  111. min-width: 40px;
  112. text-align: right;
  113. `;
  114. export default CommitAuthorBreakdown;