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.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  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 { LabelValueSelectOption } from 'design-system';
  21. import React, { useCallback, useMemo, useState } from 'react';
  22. import { searchForBitbucketCloudRepositories } from '../../../../api/alm-integrations';
  23. import { useLocation } from '../../../../components/hoc/withRouter';
  24. import { BitbucketCloudRepository } from '../../../../types/alm-integration';
  25. import { AlmKeys } from '../../../../types/alm-settings';
  26. import { DopSetting } from '../../../../types/dop-translation';
  27. import { ImportProjectParam } from '../CreateProjectPage';
  28. import { REPOSITORY_PAGE_SIZE } from '../constants';
  29. import MonorepoProjectCreate from '../monorepo/MonorepoProjectCreate';
  30. import { CreateProjectModes } from '../types';
  31. import { useProjectCreate } from '../useProjectCreate';
  32. import { useProjectRepositorySearch } from '../useProjectRepositorySearch';
  33. import BitbucketCloudPersonalAccessTokenForm from './BitbucketCloudPersonalAccessTokenForm';
  34. import BitbucketCloudProjectCreateRenderer from './BitbucketCloudProjectCreateRender';
  35. interface Props {
  36. dopSettings: DopSetting[];
  37. isLoadingBindings: boolean;
  38. onProjectSetupDone: (importProjects: ImportProjectParam) => void;
  39. }
  40. export default function BitbucketCloudProjectCreate(props: Readonly<Props>) {
  41. const { dopSettings, isLoadingBindings, onProjectSetupDone } = props;
  42. const [isLastPage, setIsLastPage] = useState<boolean>(true);
  43. const [projectsPaging, setProjectsPaging] = useState<{ pageIndex: number; pageSize: number }>({
  44. pageIndex: 1,
  45. pageSize: REPOSITORY_PAGE_SIZE,
  46. });
  47. const {
  48. handlePersonalAccessTokenCreated,
  49. handleSelectRepository,
  50. isInitialized,
  51. isLoadingRepositories,
  52. isLoadingMoreRepositories,
  53. isMonorepoSetup,
  54. onSelectedAlmInstanceChange,
  55. onSelectDopSetting,
  56. repositories,
  57. resetLoading,
  58. resetPersonalAccessToken,
  59. searchQuery,
  60. selectedDopSetting,
  61. selectedRepository,
  62. setIsInitialized,
  63. setRepositories,
  64. setResetPersonalAccessToken,
  65. setSearchQuery,
  66. setShowPersonalAccessTokenForm,
  67. showPersonalAccessTokenForm,
  68. } = useProjectCreate<BitbucketCloudRepository, undefined>(
  69. AlmKeys.BitbucketCloud,
  70. dopSettings,
  71. ({ slug }) => slug,
  72. REPOSITORY_PAGE_SIZE,
  73. );
  74. const location = useLocation();
  75. const repositoryOptions = useMemo(() => repositories?.map(transformToOption), [repositories]);
  76. const fetchRepositories = useCallback(
  77. (_orgKey?: string, query = '', pageIndex = 1, more = false) => {
  78. if (!selectedDopSetting || showPersonalAccessTokenForm) {
  79. return Promise.resolve();
  80. }
  81. resetLoading(true, more);
  82. // eslint-disable-next-line local-rules/no-api-imports
  83. return searchForBitbucketCloudRepositories(
  84. selectedDopSetting.key,
  85. query,
  86. REPOSITORY_PAGE_SIZE,
  87. pageIndex,
  88. )
  89. .then((result) => {
  90. resetLoading(false, more);
  91. if (result) {
  92. setIsLastPage(result.isLastPage);
  93. setIsInitialized(true);
  94. }
  95. if (result?.repositories) {
  96. setRepositories(
  97. more && repositories && repositories.length > 0
  98. ? [...repositories, ...result.repositories]
  99. : result.repositories,
  100. );
  101. }
  102. })
  103. .catch(() => {
  104. resetLoading(false, more);
  105. setResetPersonalAccessToken(true);
  106. setShowPersonalAccessTokenForm(true);
  107. });
  108. },
  109. [
  110. repositories,
  111. resetLoading,
  112. selectedDopSetting,
  113. showPersonalAccessTokenForm,
  114. setIsInitialized,
  115. setIsLastPage,
  116. setRepositories,
  117. setResetPersonalAccessToken,
  118. setShowPersonalAccessTokenForm,
  119. ],
  120. );
  121. const handleLoadMore = useCallback(() => {
  122. const page = projectsPaging.pageIndex + 1;
  123. setProjectsPaging((paging) => ({
  124. pageIndex: page,
  125. pageSize: paging.pageSize,
  126. }));
  127. fetchRepositories(undefined, searchQuery, page, true);
  128. }, [fetchRepositories, projectsPaging, searchQuery, setProjectsPaging]);
  129. const handleImportRepository = useCallback(
  130. (repositorySlug: string) => {
  131. if (selectedDopSetting) {
  132. onProjectSetupDone({
  133. creationMode: CreateProjectModes.BitbucketCloud,
  134. almSetting: selectedDopSetting.key,
  135. monorepo: false,
  136. projects: [{ repositorySlug }],
  137. });
  138. }
  139. },
  140. [onProjectSetupDone, selectedDopSetting],
  141. );
  142. const { isSearching, onSearch } = useProjectRepositorySearch(
  143. AlmKeys.BitbucketCloud,
  144. fetchRepositories,
  145. isInitialized,
  146. selectedDopSetting,
  147. undefined,
  148. setSearchQuery,
  149. showPersonalAccessTokenForm,
  150. );
  151. return isMonorepoSetup ? (
  152. <MonorepoProjectCreate
  153. dopSettings={dopSettings}
  154. error={false}
  155. loadingBindings={isLoadingBindings}
  156. loadingOrganizations={false}
  157. loadingRepositories={isLoadingRepositories}
  158. onProjectSetupDone={onProjectSetupDone}
  159. onSearchRepositories={onSearch}
  160. onSelectDopSetting={onSelectDopSetting}
  161. onSelectRepository={handleSelectRepository}
  162. personalAccessTokenComponent={
  163. !isLoadingRepositories &&
  164. selectedDopSetting && (
  165. <BitbucketCloudPersonalAccessTokenForm
  166. almSetting={selectedDopSetting}
  167. resetPat={resetPersonalAccessToken}
  168. onPersonalAccessTokenCreated={handlePersonalAccessTokenCreated}
  169. />
  170. )
  171. }
  172. repositoryOptions={repositoryOptions}
  173. repositorySearchQuery={searchQuery}
  174. selectedDopSetting={selectedDopSetting}
  175. selectedRepository={selectedRepository ? transformToOption(selectedRepository) : undefined}
  176. showPersonalAccessToken={showPersonalAccessTokenForm || Boolean(location.query.resetPat)}
  177. />
  178. ) : (
  179. <BitbucketCloudProjectCreateRenderer
  180. isLastPage={isLastPage}
  181. selectedAlmInstance={
  182. selectedDopSetting
  183. ? {
  184. alm: selectedDopSetting.type,
  185. key: selectedDopSetting.key,
  186. url: selectedDopSetting.url,
  187. }
  188. : undefined
  189. }
  190. almInstances={dopSettings?.map((instance) => ({
  191. alm: instance.type,
  192. key: instance.key,
  193. url: instance.url,
  194. }))}
  195. loadingMore={isLoadingMoreRepositories}
  196. loading={isLoadingRepositories || isLoadingBindings}
  197. onImport={handleImportRepository}
  198. onLoadMore={handleLoadMore}
  199. onPersonalAccessTokenCreated={handlePersonalAccessTokenCreated}
  200. onSearch={onSearch}
  201. onSelectedAlmInstanceChange={onSelectedAlmInstanceChange}
  202. repositories={repositories}
  203. searching={isSearching}
  204. searchQuery={searchQuery}
  205. resetPat={resetPersonalAccessToken || Boolean(location.query.resetPat)}
  206. showPersonalAccessTokenForm={showPersonalAccessTokenForm || Boolean(location.query.resetPat)}
  207. />
  208. );
  209. }
  210. function transformToOption({
  211. name,
  212. slug,
  213. }: BitbucketCloudRepository): LabelValueSelectOption<string> {
  214. return { value: slug, label: name };
  215. }