commits.tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import {Fragment, useContext} from 'react';
  2. import styled from '@emotion/styled';
  3. import * as Layout from 'sentry/components/layouts/thirds';
  4. import LoadingError from 'sentry/components/loadingError';
  5. import LoadingIndicator from 'sentry/components/loadingIndicator';
  6. import Pagination from 'sentry/components/pagination';
  7. import Panel from 'sentry/components/panels/panel';
  8. import PanelBody from 'sentry/components/panels/panelBody';
  9. import PanelHeader from 'sentry/components/panels/panelHeader';
  10. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  11. import {t} from 'sentry/locale';
  12. import {space} from 'sentry/styles/space';
  13. import type {Commit, Repository} from 'sentry/types/integrations';
  14. import type {Organization} from 'sentry/types/organization';
  15. import type {Project} from 'sentry/types/project';
  16. import {useApiQuery} from 'sentry/utils/queryClient';
  17. import routeTitleGen from 'sentry/utils/routeTitle';
  18. import {useLocation} from 'sentry/utils/useLocation';
  19. import useOrganization from 'sentry/utils/useOrganization';
  20. import {useParams} from 'sentry/utils/useParams';
  21. import {useReleaseRepositories} from 'sentry/utils/useReleaseRepositories';
  22. import {useRepositories} from 'sentry/utils/useRepositories';
  23. import {formatVersion} from 'sentry/utils/versions/formatVersion';
  24. import {ReleaseContext} from 'sentry/views/releases/detail';
  25. import {ReleaseCommit} from 'sentry/views/releases/detail/commitsAndFiles/releaseCommit';
  26. import {getCommitsByRepository, getQuery, getReposToRender} from '../utils';
  27. import {EmptyState, NoReleaseRepos, NoRepositories} from './emptyState';
  28. import RepositorySwitcher from './repositorySwitcher';
  29. interface CommitsProps {
  30. organization: Organization;
  31. projectSlug: Project['slug'];
  32. releaseRepos: Repository[];
  33. }
  34. function CommitsList({organization, releaseRepos, projectSlug}: CommitsProps) {
  35. const location = useLocation();
  36. const params = useParams<{release: string}>();
  37. const activeReleaseRepo =
  38. releaseRepos.find(repo => repo.name === location.query.activeRepo) ?? releaseRepos[0];
  39. const query = getQuery({location, activeRepository: activeReleaseRepo});
  40. const {
  41. data: commitList = [],
  42. isPending: isLoadingCommitList,
  43. error: commitListError,
  44. refetch,
  45. getResponseHeader,
  46. } = useApiQuery<Commit[]>(
  47. [
  48. `/projects/${organization.slug}/${projectSlug}/releases/${encodeURIComponent(
  49. params.release
  50. )}/commits/`,
  51. {query},
  52. ],
  53. {
  54. staleTime: Infinity,
  55. }
  56. );
  57. const commitsByRepository = getCommitsByRepository(commitList);
  58. const reposToRender = getReposToRender(Object.keys(commitsByRepository));
  59. const activeRepoName: string | undefined = activeReleaseRepo
  60. ? activeReleaseRepo.name
  61. : reposToRender[0];
  62. return (
  63. <Layout.Body>
  64. <Layout.Main fullWidth>
  65. <SentryDocumentTitle
  66. title={routeTitleGen(
  67. t('Commits - Release %s', formatVersion(params.release)),
  68. organization.slug,
  69. false,
  70. projectSlug
  71. )}
  72. />
  73. {releaseRepos.length > 1 && (
  74. <Actions>
  75. <RepositorySwitcher
  76. repositories={releaseRepos}
  77. activeRepository={activeReleaseRepo}
  78. />
  79. </Actions>
  80. )}
  81. {commitListError && <LoadingError onRetry={refetch} />}
  82. {isLoadingCommitList ? (
  83. <LoadingIndicator />
  84. ) : commitList.length && activeRepoName ? (
  85. <Fragment>
  86. <Panel>
  87. <PanelHeader>{activeRepoName}</PanelHeader>
  88. <PanelBody>
  89. {commitsByRepository[activeRepoName]?.map(commit => (
  90. <ReleaseCommit key={commit.id} commit={commit} />
  91. ))}
  92. </PanelBody>
  93. </Panel>
  94. <Pagination pageLinks={getResponseHeader?.('Link')} />
  95. </Fragment>
  96. ) : (
  97. <EmptyState>
  98. {activeReleaseRepo
  99. ? t(
  100. 'There are no commits associated with this release in the %s repository.',
  101. activeReleaseRepo.name
  102. )
  103. : t('There are no commits associated with this release.')}
  104. </EmptyState>
  105. )}
  106. </Layout.Main>
  107. </Layout.Body>
  108. );
  109. }
  110. function Commits() {
  111. const organization = useOrganization();
  112. const params = useParams<{release: string}>();
  113. const releaseContext = useContext(ReleaseContext);
  114. const {
  115. data: repositories,
  116. isLoading: isLoadingRepositories,
  117. isError: isRepositoriesError,
  118. refetch: refetchRepositories,
  119. } = useRepositories({
  120. orgSlug: organization.slug,
  121. });
  122. const {
  123. data: releaseRepos,
  124. isLoading: isLoadingReleaseRepos,
  125. isError: isReleaseReposError,
  126. refetch: refetchReleaseRepos,
  127. } = useReleaseRepositories({
  128. orgSlug: organization.slug,
  129. projectSlug: releaseContext.project.slug,
  130. release: params.release,
  131. options: {
  132. enabled: !!releaseContext.project.slug,
  133. },
  134. });
  135. if (isLoadingReleaseRepos || isLoadingRepositories) {
  136. return <LoadingIndicator />;
  137. }
  138. if (isRepositoriesError || isReleaseReposError) {
  139. return (
  140. <LoadingError
  141. onRetry={() => {
  142. refetchRepositories();
  143. refetchReleaseRepos();
  144. }}
  145. />
  146. );
  147. }
  148. const noReleaseReposFound = !releaseRepos?.length;
  149. if (noReleaseReposFound) {
  150. return <NoReleaseRepos />;
  151. }
  152. const noRepositoryOrgRelatedFound = !repositories?.length;
  153. if (noRepositoryOrgRelatedFound) {
  154. return <NoRepositories orgSlug={organization.slug} />;
  155. }
  156. return (
  157. <CommitsList
  158. releaseRepos={releaseRepos}
  159. organization={organization}
  160. projectSlug={releaseContext.project.slug}
  161. />
  162. );
  163. }
  164. const Actions = styled('div')`
  165. margin-bottom: ${space(2)};
  166. `;
  167. export default Commits;