filesChanged.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. import {Fragment} from 'react';
  2. import {RouteComponentProps} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import {Location} from 'history';
  5. import FileChange from 'sentry/components/fileChange';
  6. import {Body, Main} from 'sentry/components/layouts/thirds';
  7. import LoadingIndicator from 'sentry/components/loadingIndicator';
  8. import Pagination from 'sentry/components/pagination';
  9. import {Panel, PanelBody, PanelHeader} from 'sentry/components/panels';
  10. import {t, tn} from 'sentry/locale';
  11. import {CommitFile, Organization, Project, Repository} from 'sentry/types';
  12. import {formatVersion} from 'sentry/utils/formatters';
  13. import routeTitleGen from 'sentry/utils/routeTitle';
  14. import AsyncView from 'sentry/views/asyncView';
  15. import {getFilesByRepository, getQuery, getReposToRender} from '../utils';
  16. import EmptyState from './emptyState';
  17. import RepositorySwitcher from './repositorySwitcher';
  18. import withReleaseRepos from './withReleaseRepos';
  19. type Props = RouteComponentProps<{orgId: string; release: string}, {}> & {
  20. location: Location;
  21. orgSlug: Organization['slug'];
  22. projectSlug: Project['slug'];
  23. release: string;
  24. releaseRepos: Repository[];
  25. activeReleaseRepo?: Repository;
  26. } & AsyncView['props'];
  27. type State = {
  28. fileList: CommitFile[];
  29. } & AsyncView['state'];
  30. class FilesChanged extends AsyncView<Props, State> {
  31. getTitle() {
  32. const {params, projectSlug} = this.props;
  33. const {orgId} = params;
  34. return routeTitleGen(
  35. t('Files Changed - Release %s', formatVersion(params.release)),
  36. orgId,
  37. false,
  38. projectSlug
  39. );
  40. }
  41. getDefaultState(): State {
  42. return {
  43. ...super.getDefaultState(),
  44. fileList: [],
  45. };
  46. }
  47. componentDidUpdate(prevProps: Props, prevState: State) {
  48. if (prevProps.activeReleaseRepo?.name !== this.props.activeReleaseRepo?.name) {
  49. this.remountComponent();
  50. return;
  51. }
  52. super.componentDidUpdate(prevProps, prevState);
  53. }
  54. getEndpoints(): ReturnType<AsyncView['getEndpoints']> {
  55. const {activeReleaseRepo: activeRepository, location, release, orgSlug} = this.props;
  56. const query = getQuery({location, activeRepository});
  57. return [
  58. [
  59. 'fileList',
  60. `/organizations/${orgSlug}/releases/${encodeURIComponent(release)}/commitfiles/`,
  61. {query},
  62. ],
  63. ];
  64. }
  65. renderLoading() {
  66. return this.renderBody();
  67. }
  68. renderContent() {
  69. const {fileList, fileListPageLinks, loading} = this.state;
  70. const {activeReleaseRepo} = this.props;
  71. if (loading) {
  72. return <LoadingIndicator />;
  73. }
  74. if (!fileList.length) {
  75. return (
  76. <EmptyState>
  77. {!activeReleaseRepo
  78. ? t('There are no changed files associated with this release.')
  79. : t(
  80. 'There are no changed files associated with this release in the %s repository.',
  81. activeReleaseRepo.name
  82. )}
  83. </EmptyState>
  84. );
  85. }
  86. const filesByRepository = getFilesByRepository(fileList);
  87. const reposToRender = getReposToRender(Object.keys(filesByRepository));
  88. return (
  89. <Fragment>
  90. {reposToRender.map(repoName => {
  91. const repoData = filesByRepository[repoName];
  92. const files = Object.keys(repoData);
  93. const fileCount = files.length;
  94. return (
  95. <Panel key={repoName}>
  96. <PanelHeader>
  97. <span>{repoName}</span>
  98. <span>{tn('%s file changed', '%s files changed', fileCount)}</span>
  99. </PanelHeader>
  100. <PanelBody>
  101. {files.map(filename => {
  102. const {authors} = repoData[filename];
  103. return (
  104. <StyledFileChange
  105. key={filename}
  106. filename={filename}
  107. authors={Object.values(authors)}
  108. />
  109. );
  110. })}
  111. </PanelBody>
  112. </Panel>
  113. );
  114. })}
  115. <Pagination pageLinks={fileListPageLinks} />
  116. </Fragment>
  117. );
  118. }
  119. renderBody() {
  120. const {activeReleaseRepo, releaseRepos, router, location} = this.props;
  121. return (
  122. <Fragment>
  123. {releaseRepos.length > 1 && (
  124. <RepositorySwitcher
  125. repositories={releaseRepos}
  126. activeRepository={activeReleaseRepo}
  127. location={location}
  128. router={router}
  129. />
  130. )}
  131. {this.renderContent()}
  132. </Fragment>
  133. );
  134. }
  135. renderComponent() {
  136. return (
  137. <Body>
  138. <Main fullWidth>{super.renderComponent()}</Main>
  139. </Body>
  140. );
  141. }
  142. }
  143. export default withReleaseRepos(FilesChanged);
  144. const StyledFileChange = styled(FileChange)`
  145. border-radius: 0;
  146. border-left: none;
  147. border-right: none;
  148. border-top: none;
  149. :last-child {
  150. border: none;
  151. border-radius: 0;
  152. }
  153. `;