filesChanged.tsx 4.9 KB

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