filesChanged.tsx 6.2 KB

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