commitRow.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import {useCallback} from 'react';
  2. import styled from '@emotion/styled';
  3. import * as Sentry from '@sentry/react';
  4. import {openInviteMembersModal} from 'sentry/actionCreators/modal';
  5. import UserAvatar from 'sentry/components/avatar/userAvatar';
  6. import CommitLink from 'sentry/components/commitLink';
  7. import {Hovercard} from 'sentry/components/hovercard';
  8. import Link from 'sentry/components/links/link';
  9. import {PanelItem} from 'sentry/components/panels';
  10. import TextOverflow from 'sentry/components/textOverflow';
  11. import TimeSince from 'sentry/components/timeSince';
  12. import {IconWarning} from 'sentry/icons';
  13. import {t, tct} from 'sentry/locale';
  14. import space from 'sentry/styles/space';
  15. import {Commit} from 'sentry/types';
  16. function formatCommitMessage(message: string | null) {
  17. if (!message) {
  18. return t('No message provided');
  19. }
  20. return message.split(/\n/)[0];
  21. }
  22. interface CommitRowProps {
  23. commit: Commit;
  24. className?: string;
  25. customAvatar?: React.ReactNode;
  26. }
  27. function CommitRow({commit, customAvatar, className}: CommitRowProps) {
  28. const handleInviteClick = useCallback(() => {
  29. if (!commit.author?.email) {
  30. Sentry.captureException(
  31. new Error(`Commit author has no email or id, invite flow is broken.`)
  32. );
  33. return;
  34. }
  35. openInviteMembersModal({
  36. initialData: [
  37. {
  38. emails: new Set([commit.author.email]),
  39. },
  40. ],
  41. source: 'suspect_commit',
  42. });
  43. }, [commit.author]);
  44. return (
  45. <PanelItem key={commit.id} className={className} data-test-id="commit-row">
  46. {customAvatar ? (
  47. customAvatar
  48. ) : commit.author && commit.author.id === undefined ? (
  49. <AvatarWrapper>
  50. <Hovercard
  51. body={
  52. <EmailWarning>
  53. {tct(
  54. 'The email [actorEmail] is not a member of your organization. [inviteUser:Invite] them or link additional emails in [accountSettings:account settings].',
  55. {
  56. actorEmail: <strong>{commit.author.email}</strong>,
  57. accountSettings: <StyledLink to="/settings/account/emails/" />,
  58. inviteUser: <StyledLink to="" onClick={handleInviteClick} />,
  59. }
  60. )}
  61. </EmailWarning>
  62. }
  63. >
  64. <UserAvatar size={36} user={commit.author} />
  65. <EmailWarningIcon data-test-id="email-warning">
  66. <IconWarning size="xs" />
  67. </EmailWarningIcon>
  68. </Hovercard>
  69. </AvatarWrapper>
  70. ) : (
  71. <AvatarWrapper>
  72. <UserAvatar size={36} user={commit.author} />
  73. </AvatarWrapper>
  74. )}
  75. <CommitMessage>
  76. <Message>{formatCommitMessage(commit.message)}</Message>
  77. <Meta>
  78. {tct('[author] committed [timeago]', {
  79. author: <strong>{commit.author?.name ?? t('Unknown author')}</strong>,
  80. timeago: <TimeSince date={commit.dateCreated} />,
  81. })}
  82. </Meta>
  83. </CommitMessage>
  84. <div>
  85. <CommitLink commitId={commit.id} repository={commit.repository} />
  86. </div>
  87. </PanelItem>
  88. );
  89. }
  90. const AvatarWrapper = styled('div')`
  91. position: relative;
  92. align-self: flex-start;
  93. margin-right: ${space(2)};
  94. `;
  95. const EmailWarning = styled('div')`
  96. font-size: ${p => p.theme.fontSizeSmall};
  97. line-height: 1.4;
  98. margin: -4px;
  99. `;
  100. const StyledLink = styled(Link)`
  101. color: ${p => p.theme.textColor};
  102. border-bottom: 1px dotted ${p => p.theme.textColor};
  103. &:hover {
  104. color: ${p => p.theme.textColor};
  105. }
  106. `;
  107. const EmailWarningIcon = styled('span')`
  108. position: absolute;
  109. bottom: -6px;
  110. right: -7px;
  111. line-height: 12px;
  112. border-radius: 50%;
  113. border: 1px solid ${p => p.theme.background};
  114. background: ${p => p.theme.yellow200};
  115. padding: 1px 2px 3px 2px;
  116. `;
  117. const CommitMessage = styled('div')`
  118. flex: 1;
  119. flex-direction: column;
  120. min-width: 0;
  121. margin-right: ${space(2)};
  122. `;
  123. const Message = styled(TextOverflow)`
  124. font-size: 15px;
  125. line-height: 1.1;
  126. font-weight: bold;
  127. `;
  128. const Meta = styled(TextOverflow)`
  129. font-size: 13px;
  130. line-height: 1.5;
  131. margin: 0;
  132. color: ${p => p.theme.subText};
  133. `;
  134. const StyledCommitRow = styled(CommitRow)`
  135. align-items: center;
  136. `;
  137. export {StyledCommitRow as CommitRow};