releaseContext.tsx 5.6 KB

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