filesChanged.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  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, 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<{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, orgSlug, projectSlug} = this.props;
  33. return routeTitleGen(
  34. t('Files Changed - Release %s', formatVersion(params.release)),
  35. orgSlug,
  36. false,
  37. projectSlug
  38. );
  39. }
  40. getDefaultState(): State {
  41. return {
  42. ...super.getDefaultState(),
  43. fileList: [],
  44. };
  45. }
  46. componentDidUpdate(prevProps: Props, prevState: State) {
  47. if (prevProps.activeReleaseRepo?.name !== this.props.activeReleaseRepo?.name) {
  48. this.remountComponent();
  49. return;
  50. }
  51. super.componentDidUpdate(prevProps, prevState);
  52. }
  53. getEndpoints(): ReturnType<AsyncView['getEndpoints']> {
  54. const {activeReleaseRepo: activeRepository, location, release, orgSlug} = this.props;
  55. const query = getQuery({location, activeRepository});
  56. return [
  57. [
  58. 'fileList',
  59. `/organizations/${orgSlug}/releases/${encodeURIComponent(release)}/commitfiles/`,
  60. {query},
  61. ],
  62. ];
  63. }
  64. renderLoading() {
  65. return this.renderBody();
  66. }
  67. renderContent() {
  68. const {fileList, fileListPageLinks, loading} = this.state;
  69. const {activeReleaseRepo} = this.props;
  70. if (loading) {
  71. return <LoadingIndicator />;
  72. }
  73. if (!fileList.length) {
  74. return (
  75. <EmptyState>
  76. {!activeReleaseRepo
  77. ? t('There are no changed files associated with this release.')
  78. : t(
  79. 'There are no changed files associated with this release in the %s repository.',
  80. activeReleaseRepo.name
  81. )}
  82. </EmptyState>
  83. );
  84. }
  85. const filesByRepository = getFilesByRepository(fileList);
  86. const reposToRender = getReposToRender(Object.keys(filesByRepository));
  87. return (
  88. <Fragment>
  89. {reposToRender.map(repoName => {
  90. const repoData = filesByRepository[repoName];
  91. const files = Object.keys(repoData);
  92. const fileCount = files.length;
  93. return (
  94. <Panel key={repoName}>
  95. <PanelHeader>
  96. <span>{repoName}</span>
  97. <span>{tn('%s file changed', '%s files changed', fileCount)}</span>
  98. </PanelHeader>
  99. <PanelBody>
  100. {files.map(filename => {
  101. const {authors} = repoData[filename];
  102. return (
  103. <StyledFileChange
  104. key={filename}
  105. filename={filename}
  106. authors={Object.values(authors)}
  107. />
  108. );
  109. })}
  110. </PanelBody>
  111. </Panel>
  112. );
  113. })}
  114. <Pagination pageLinks={fileListPageLinks} />
  115. </Fragment>
  116. );
  117. }
  118. renderBody() {
  119. const {activeReleaseRepo, releaseRepos, router, location} = this.props;
  120. return (
  121. <Fragment>
  122. {releaseRepos.length > 1 && (
  123. <RepositorySwitcher
  124. repositories={releaseRepos}
  125. activeRepository={activeReleaseRepo}
  126. location={location}
  127. router={router}
  128. />
  129. )}
  130. {this.renderContent()}
  131. </Fragment>
  132. );
  133. }
  134. renderComponent() {
  135. return (
  136. <Layout.Body>
  137. <Layout.Main fullWidth>{super.renderComponent()}</Layout.Main>
  138. </Layout.Body>
  139. );
  140. }
  141. }
  142. export default withReleaseRepos(FilesChanged);
  143. const StyledFileChange = styled(FileChange)`
  144. border-radius: 0;
  145. border-left: none;
  146. border-right: none;
  147. border-top: none;
  148. :last-child {
  149. border: none;
  150. border-radius: 0;
  151. }
  152. `;