releaseContext.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. import {useEffect} from 'react';
  2. import styled from '@emotion/styled';
  3. import AvatarList from 'sentry/components/avatar/avatarList';
  4. import {QuickContextCommitRow} from 'sentry/components/discover/quickContextCommitRow';
  5. import {DataSection} from 'sentry/components/events/styles';
  6. import {Panel} from 'sentry/components/panels';
  7. import TimeSince from 'sentry/components/timeSince';
  8. import {IconNot} from 'sentry/icons';
  9. import {t, tct} from 'sentry/locale';
  10. import ConfigStore from 'sentry/stores/configStore';
  11. import {space} from 'sentry/styles/space';
  12. import {ReleaseWithHealth, User} from 'sentry/types';
  13. import {trackAnalytics} from 'sentry/utils/analytics';
  14. import {useApiQuery} from 'sentry/utils/queryClient';
  15. import {NoContext} from './quickContextWrapper';
  16. import {
  17. ContextBody,
  18. ContextContainer,
  19. ContextHeader,
  20. ContextRow,
  21. ContextTitle,
  22. Wrapper,
  23. } from './styles';
  24. import {BaseContextProps, ContextType, tenSecondInMs} from './utils';
  25. function ReleaseContext(props: BaseContextProps) {
  26. const {dataRow, organization} = props;
  27. const {isLoading, isError, data} = useApiQuery<ReleaseWithHealth>(
  28. [`/organizations/${organization.slug}/releases/${dataRow.release}/`],
  29. {
  30. staleTime: tenSecondInMs,
  31. }
  32. );
  33. useEffect(() => {
  34. trackAnalytics('discover_v2.quick_context_hover_contexts', {
  35. organization,
  36. contextType: ContextType.RELEASE,
  37. });
  38. }, [organization]);
  39. const getCommitAuthorTitle = () => {
  40. const user = ConfigStore.get('user');
  41. const commitCount = data?.commitCount || 0;
  42. let authorsCount = data?.authors?.length || 0;
  43. const userInAuthors =
  44. data &&
  45. data.authors.length >= 1 &&
  46. data.authors.find((author: User) => author.id && user.id && author.id === user.id);
  47. if (userInAuthors) {
  48. authorsCount = authorsCount - 1;
  49. return authorsCount !== 1 && commitCount !== 1
  50. ? tct('[commitCount] commits by you and [authorsCount] others', {
  51. commitCount,
  52. authorsCount,
  53. })
  54. : commitCount !== 1
  55. ? tct('[commitCount] commits by you and 1 other', {
  56. commitCount,
  57. })
  58. : authorsCount !== 1
  59. ? tct('1 commit by you and [authorsCount] others', {
  60. authorsCount,
  61. })
  62. : t('1 commit by you and 1 other');
  63. }
  64. return (
  65. data &&
  66. (authorsCount !== 1 && commitCount !== 1
  67. ? tct('[commitCount] commits by [authorsCount] authors', {
  68. commitCount,
  69. authorsCount,
  70. })
  71. : commitCount !== 1
  72. ? tct('[commitCount] commits by 1 author', {
  73. commitCount,
  74. })
  75. : authorsCount !== 1
  76. ? tct('1 commit by [authorsCount] authors', {
  77. authorsCount,
  78. })
  79. : t('1 commit by 1 author'))
  80. );
  81. };
  82. const renderReleaseAuthors = () => {
  83. return (
  84. data && (
  85. <ReleaseContextContainer data-test-id="quick-context-release-details-container">
  86. <ContextHeader data-test-id="quick-context-release-author-header">
  87. <ContextTitle>{getCommitAuthorTitle()}</ContextTitle>
  88. </ContextHeader>
  89. <ContextBody>
  90. {data.commitCount === 0 ? (
  91. <IconNot color="gray500" size="md" />
  92. ) : (
  93. <StyledAvatarList users={data.authors} maxVisibleAvatars={10} />
  94. )}
  95. </ContextBody>
  96. </ReleaseContextContainer>
  97. )
  98. );
  99. };
  100. const renderLastCommit = () =>
  101. data &&
  102. data.lastCommit && (
  103. <ReleaseContextContainer data-test-id="quick-context-release-last-commit-container">
  104. <ContextHeader>
  105. <ContextTitle>{t('Last Commit')}</ContextTitle>
  106. </ContextHeader>
  107. <DataSection>
  108. <Panel>
  109. <QuickContextCommitRow commit={data.lastCommit} />
  110. </Panel>
  111. </DataSection>
  112. </ReleaseContextContainer>
  113. );
  114. const renderReleaseDetails = () =>
  115. data && (
  116. <ReleaseContextContainer data-test-id="quick-context-release-issues-and-authors-container">
  117. <ContextRow>
  118. <div>
  119. <ContextHeader>
  120. <ContextTitle>{t('Created')}</ContextTitle>
  121. </ContextHeader>
  122. <ReleaseBody>
  123. <TimeSince date={data.dateCreated} />
  124. </ReleaseBody>
  125. </div>
  126. <div>
  127. <ContextHeader>
  128. <ContextTitle>{t('Last Event')}</ContextTitle>
  129. </ContextHeader>
  130. <ReleaseBody>
  131. {data.lastEvent ? <TimeSince date={data.lastEvent} /> : '\u2014'}
  132. </ReleaseBody>
  133. </div>
  134. <div>
  135. <ContextHeader>
  136. <ContextTitle>{t('New Issues')}</ContextTitle>
  137. </ContextHeader>
  138. <ContextBody>{data.newGroups}</ContextBody>
  139. </div>
  140. </ContextRow>
  141. </ReleaseContextContainer>
  142. );
  143. if (isLoading || isError) {
  144. return <NoContext isLoading={isLoading} />;
  145. }
  146. return (
  147. <Wrapper data-test-id="quick-context-hover-body">
  148. {renderReleaseDetails()}
  149. {renderReleaseAuthors()}
  150. {renderLastCommit()}
  151. </Wrapper>
  152. );
  153. }
  154. const StyledAvatarList = styled(AvatarList)`
  155. margin: 0 ${space(0.75)};
  156. `;
  157. const ReleaseContextContainer = styled(ContextContainer)`
  158. ${Panel} {
  159. margin: 0;
  160. border: none;
  161. box-shadow: none;
  162. }
  163. ${DataSection} {
  164. padding: 0;
  165. }
  166. & + & {
  167. margin-top: ${space(2)};
  168. }
  169. `;
  170. const ReleaseBody = styled(ContextBody)<{}>`
  171. font-size: 13px;
  172. color: ${p => p.theme.subText};
  173. `;
  174. export default ReleaseContext;