You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

BitbucketCloudProjectCreate.tsx 7.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. import * as React from 'react';
  21. import { searchForBitbucketCloudRepositories } from '../../../../api/alm-integrations';
  22. import { Location, Router } from '../../../../components/hoc/withRouter';
  23. import { BitbucketCloudRepository } from '../../../../types/alm-integration';
  24. import { AlmSettingsInstance } from '../../../../types/alm-settings';
  25. import { Paging } from '../../../../types/types';
  26. import { ImportProjectParam } from '../CreateProjectPage';
  27. import { BITBUCKET_CLOUD_PROJECTS_PAGESIZE } from '../constants';
  28. import { CreateProjectModes } from '../types';
  29. import BitbucketCloudProjectCreateRenderer from './BitbucketCloudProjectCreateRender';
  30. interface Props {
  31. canAdmin: boolean;
  32. almInstances: AlmSettingsInstance[];
  33. loadingBindings: boolean;
  34. location: Location;
  35. router: Router;
  36. onProjectSetupDone: (importProjects: ImportProjectParam) => void;
  37. }
  38. interface State {
  39. isLastPage?: boolean;
  40. loading: boolean;
  41. loadingMore: boolean;
  42. projectsPaging: Omit<Paging, 'total'>;
  43. resetPat: boolean;
  44. repositories: BitbucketCloudRepository[];
  45. searching: boolean;
  46. searchQuery: string;
  47. selectedAlmInstance: AlmSettingsInstance;
  48. showPersonalAccessTokenForm: boolean;
  49. }
  50. export default class BitbucketCloudProjectCreate extends React.PureComponent<Props, State> {
  51. mounted = false;
  52. constructor(props: Props) {
  53. super(props);
  54. this.state = {
  55. // For now, we only handle a single instance. So we always use the first
  56. // one from the list.
  57. loading: false,
  58. loadingMore: false,
  59. resetPat: false,
  60. projectsPaging: { pageIndex: 1, pageSize: BITBUCKET_CLOUD_PROJECTS_PAGESIZE },
  61. repositories: [],
  62. searching: false,
  63. searchQuery: '',
  64. selectedAlmInstance: props.almInstances[0],
  65. showPersonalAccessTokenForm: true,
  66. };
  67. }
  68. componentDidMount() {
  69. this.mounted = true;
  70. }
  71. componentDidUpdate(prevProps: Props) {
  72. if (prevProps.almInstances.length === 0 && this.props.almInstances.length > 0) {
  73. this.setState({ selectedAlmInstance: this.props.almInstances[0] }, () => {
  74. this.fetchData().catch(() => {
  75. /* noop */
  76. });
  77. });
  78. }
  79. }
  80. handlePersonalAccessTokenCreated = () => {
  81. this.cleanUrl();
  82. this.setState({ loading: true, showPersonalAccessTokenForm: false }, () => {
  83. this.fetchData()
  84. .then(() => this.setState({ loading: false }))
  85. .catch(() => {
  86. /* noop */
  87. });
  88. });
  89. };
  90. cleanUrl = () => {
  91. const { location, router } = this.props;
  92. delete location.query.resetPat;
  93. router.replace(location);
  94. };
  95. async fetchData(more = false) {
  96. const {
  97. selectedAlmInstance,
  98. searchQuery,
  99. projectsPaging: { pageIndex, pageSize },
  100. showPersonalAccessTokenForm,
  101. } = this.state;
  102. if (selectedAlmInstance && !showPersonalAccessTokenForm) {
  103. const { isLastPage, repositories } = await searchForBitbucketCloudRepositories(
  104. selectedAlmInstance.key,
  105. searchQuery,
  106. pageSize,
  107. pageIndex,
  108. ).catch(() => {
  109. this.handleError();
  110. return { isLastPage: undefined, repositories: undefined };
  111. });
  112. if (this.mounted && isLastPage !== undefined && repositories !== undefined) {
  113. if (more) {
  114. this.setState((state) => ({
  115. isLastPage,
  116. repositories: [...state.repositories, ...repositories],
  117. }));
  118. } else {
  119. this.setState({ isLastPage, repositories });
  120. }
  121. }
  122. }
  123. }
  124. handleError = () => {
  125. if (this.mounted) {
  126. this.setState({
  127. projectsPaging: { pageIndex: 1, pageSize: BITBUCKET_CLOUD_PROJECTS_PAGESIZE },
  128. repositories: [],
  129. resetPat: true,
  130. showPersonalAccessTokenForm: true,
  131. });
  132. }
  133. return undefined;
  134. };
  135. handleSearch = (searchQuery: string) => {
  136. this.setState(
  137. {
  138. searching: true,
  139. projectsPaging: { pageIndex: 1, pageSize: BITBUCKET_CLOUD_PROJECTS_PAGESIZE },
  140. searchQuery,
  141. },
  142. () => {
  143. this.fetchData().then(
  144. () => {
  145. if (this.mounted) {
  146. this.setState({ searching: false });
  147. }
  148. },
  149. () => {
  150. /* noop */
  151. },
  152. );
  153. },
  154. );
  155. };
  156. handleLoadMore = () => {
  157. this.setState(
  158. (state) => ({
  159. loadingMore: true,
  160. projectsPaging: {
  161. pageIndex: state.projectsPaging.pageIndex + 1,
  162. pageSize: state.projectsPaging.pageSize,
  163. },
  164. }),
  165. () => {
  166. this.fetchData(true).then(
  167. () => {
  168. if (this.mounted) {
  169. this.setState({ loadingMore: false });
  170. }
  171. },
  172. () => {
  173. /* noop */
  174. },
  175. );
  176. },
  177. );
  178. };
  179. handleImport = (repositorySlug: string) => {
  180. const { selectedAlmInstance } = this.state;
  181. if (selectedAlmInstance) {
  182. this.props.onProjectSetupDone({
  183. creationMode: CreateProjectModes.BitbucketCloud,
  184. almSetting: selectedAlmInstance.key,
  185. monorepo: false,
  186. projects: [
  187. {
  188. repositorySlug,
  189. },
  190. ],
  191. });
  192. }
  193. };
  194. onSelectedAlmInstanceChange = (instance: AlmSettingsInstance) => {
  195. this.setState({
  196. selectedAlmInstance: instance,
  197. showPersonalAccessTokenForm: true,
  198. resetPat: false,
  199. searching: false,
  200. searchQuery: '',
  201. projectsPaging: { pageIndex: 1, pageSize: BITBUCKET_CLOUD_PROJECTS_PAGESIZE },
  202. });
  203. };
  204. render() {
  205. const { canAdmin, loadingBindings, location, almInstances } = this.props;
  206. const {
  207. isLastPage = true,
  208. selectedAlmInstance,
  209. loading,
  210. loadingMore,
  211. repositories,
  212. showPersonalAccessTokenForm,
  213. resetPat,
  214. searching,
  215. searchQuery,
  216. } = this.state;
  217. return (
  218. <BitbucketCloudProjectCreateRenderer
  219. isLastPage={isLastPage}
  220. selectedAlmInstance={selectedAlmInstance}
  221. almInstances={almInstances}
  222. canAdmin={canAdmin}
  223. loadingMore={loadingMore}
  224. loading={loading || loadingBindings}
  225. onImport={this.handleImport}
  226. onLoadMore={this.handleLoadMore}
  227. onPersonalAccessTokenCreated={this.handlePersonalAccessTokenCreated}
  228. onSearch={this.handleSearch}
  229. onSelectedAlmInstanceChange={this.onSelectedAlmInstanceChange}
  230. repositories={repositories}
  231. searching={searching}
  232. searchQuery={searchQuery}
  233. resetPat={resetPat || Boolean(location.query.resetPat)}
  234. showPersonalAccessTokenForm={
  235. showPersonalAccessTokenForm || Boolean(location.query.resetPat)
  236. }
  237. />
  238. );
  239. }
  240. }