Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

GitlabProjectCreate.tsx 9.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  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 { orderBy } from 'lodash';
  22. import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
  23. import { getGitlabProjects } from '../../../../api/alm-integrations';
  24. import { useLocation, useRouter } from '../../../../components/hoc/withRouter';
  25. import { GitlabProject } from '../../../../types/alm-integration';
  26. import { AlmInstanceBase } from '../../../../types/alm-settings';
  27. import { DopSetting } from '../../../../types/dop-translation';
  28. import { Paging } from '../../../../types/types';
  29. import { ImportProjectParam } from '../CreateProjectPage';
  30. import MonorepoProjectCreate from '../monorepo/MonorepoProjectCreate';
  31. import { CreateProjectModes } from '../types';
  32. import GitlabPersonalAccessTokenForm from './GItlabPersonalAccessTokenForm';
  33. import GitlabProjectCreateRenderer from './GitlabProjectCreateRenderer';
  34. interface Props {
  35. canAdmin: boolean;
  36. isLoadingBindings: boolean;
  37. onProjectSetupDone: (importProjects: ImportProjectParam) => void;
  38. dopSettings: DopSetting[];
  39. }
  40. const REPOSITORY_PAGE_SIZE = 50;
  41. const REPOSITORY_SEARCH_DEBOUNCE_TIME = 250;
  42. export default function GitlabProjectCreate(props: Readonly<Props>) {
  43. const { canAdmin, dopSettings, isLoadingBindings, onProjectSetupDone } = props;
  44. const repositorySearchDebounceId = useRef<NodeJS.Timeout | undefined>();
  45. const [isLoadingRepositories, setIsLoadingRepositories] = useState(false);
  46. const [isLoadingMoreRepositories, setIsLoadingMoreRepositories] = useState(false);
  47. const [repositories, setRepositories] = useState<GitlabProject[]>([]);
  48. const [repositoryPaging, setRepositoryPaging] = useState<Paging>({
  49. pageSize: REPOSITORY_PAGE_SIZE,
  50. total: 0,
  51. pageIndex: 1,
  52. });
  53. const [searchQuery, setSearchQuery] = useState('');
  54. const [selectedDopSetting, setSelectedDopSetting] = useState<DopSetting>();
  55. const [selectedRepository, setSelectedRepository] = useState<GitlabProject>();
  56. const [resetPersonalAccessToken, setResetPersonalAccessToken] = useState<boolean>(false);
  57. const [showPersonalAccessTokenForm, setShowPersonalAccessTokenForm] = useState<boolean>(true);
  58. const location = useLocation();
  59. const router = useRouter();
  60. const isMonorepoSetup = location.query?.mono === 'true';
  61. const hasDopSettings = useMemo(() => {
  62. if (dopSettings === undefined) {
  63. return false;
  64. }
  65. return dopSettings.length > 0;
  66. }, [dopSettings]);
  67. const repositoryOptions = useMemo(() => {
  68. return repositories.map(transformToOption);
  69. }, [repositories]);
  70. const fetchProjects = useCallback(
  71. (pageIndex = 1, query?: string) => {
  72. if (!selectedDopSetting) {
  73. return Promise.resolve(undefined);
  74. }
  75. // eslint-disable-next-line local-rules/no-api-imports
  76. return getGitlabProjects({
  77. almSetting: selectedDopSetting.key,
  78. page: pageIndex,
  79. pageSize: REPOSITORY_PAGE_SIZE,
  80. query,
  81. });
  82. },
  83. [selectedDopSetting],
  84. );
  85. const fetchInitialData = useCallback(() => {
  86. if (!showPersonalAccessTokenForm) {
  87. setIsLoadingRepositories(true);
  88. fetchProjects()
  89. .then((result) => {
  90. if (result?.projects) {
  91. setIsLoadingRepositories(false);
  92. setRepositories(
  93. isMonorepoSetup
  94. ? orderBy(result.projects, [(res) => res.name.toLowerCase()], ['asc'])
  95. : result.projects,
  96. );
  97. setRepositoryPaging(result.projectsPaging);
  98. } else {
  99. setIsLoadingRepositories(false);
  100. }
  101. })
  102. .catch(() => {
  103. setResetPersonalAccessToken(true);
  104. setShowPersonalAccessTokenForm(true);
  105. });
  106. }
  107. }, [fetchProjects, isMonorepoSetup, showPersonalAccessTokenForm]);
  108. const cleanUrl = useCallback(() => {
  109. delete location.query.resetPat;
  110. router.replace(location);
  111. }, [location, router]);
  112. const handlePersonalAccessTokenCreated = useCallback(() => {
  113. cleanUrl();
  114. setShowPersonalAccessTokenForm(false);
  115. setResetPersonalAccessToken(false);
  116. fetchInitialData();
  117. }, [cleanUrl, fetchInitialData]);
  118. const handleImportRepository = useCallback(
  119. (gitlabProjectId: string) => {
  120. if (selectedDopSetting) {
  121. onProjectSetupDone({
  122. almSetting: selectedDopSetting.key,
  123. creationMode: CreateProjectModes.GitLab,
  124. monorepo: false,
  125. projects: [{ gitlabProjectId }],
  126. });
  127. }
  128. },
  129. [onProjectSetupDone, selectedDopSetting],
  130. );
  131. const handleLoadMore = useCallback(async () => {
  132. setIsLoadingMoreRepositories(true);
  133. const result = await fetchProjects(repositoryPaging.pageIndex + 1, searchQuery);
  134. if (result?.projects) {
  135. setRepositoryPaging(result ? result.projectsPaging : repositoryPaging);
  136. setRepositories(result ? [...repositories, ...result.projects] : repositories);
  137. }
  138. setIsLoadingMoreRepositories(false);
  139. }, [fetchProjects, repositories, repositoryPaging, searchQuery]);
  140. const handleSelectRepository = useCallback(
  141. (repositoryKey: string) => {
  142. setSelectedRepository(repositories.find(({ id }) => id === repositoryKey));
  143. },
  144. [repositories],
  145. );
  146. const onSelectDopSetting = useCallback((setting: DopSetting | undefined) => {
  147. setSelectedDopSetting(setting);
  148. setShowPersonalAccessTokenForm(true);
  149. setRepositories([]);
  150. setSearchQuery('');
  151. }, []);
  152. const onSelectedAlmInstanceChange = useCallback(
  153. (instance: AlmInstanceBase) => {
  154. onSelectDopSetting(dopSettings.find((dopSetting) => dopSetting.key === instance.key));
  155. },
  156. [dopSettings, onSelectDopSetting],
  157. );
  158. useEffect(() => {
  159. if (dopSettings.length > 0) {
  160. setSelectedDopSetting(dopSettings[0]);
  161. return;
  162. }
  163. setSelectedDopSetting(undefined);
  164. // eslint-disable-next-line react-hooks/exhaustive-deps
  165. }, [hasDopSettings]);
  166. useEffect(() => {
  167. if (selectedDopSetting) {
  168. fetchInitialData();
  169. }
  170. }, [fetchInitialData, selectedDopSetting]);
  171. useEffect(() => {
  172. repositorySearchDebounceId.current = setTimeout(async () => {
  173. const result = await fetchProjects(1, searchQuery);
  174. if (result?.projects) {
  175. setRepositories(orderBy(result.projects, [(res) => res.name.toLowerCase()], ['asc']));
  176. setRepositoryPaging(result.projectsPaging);
  177. }
  178. }, REPOSITORY_SEARCH_DEBOUNCE_TIME);
  179. return () => {
  180. clearTimeout(repositorySearchDebounceId.current);
  181. };
  182. // eslint-disable-next-line react-hooks/exhaustive-deps
  183. }, [searchQuery]);
  184. return isMonorepoSetup ? (
  185. <MonorepoProjectCreate
  186. canAdmin={canAdmin}
  187. dopSettings={dopSettings}
  188. error={false}
  189. loadingBindings={isLoadingBindings}
  190. loadingOrganizations={false}
  191. loadingRepositories={isLoadingRepositories}
  192. onProjectSetupDone={onProjectSetupDone}
  193. onSearchRepositories={setSearchQuery}
  194. onSelectDopSetting={onSelectDopSetting}
  195. onSelectRepository={handleSelectRepository}
  196. personalAccessTokenComponent={
  197. !isLoadingRepositories &&
  198. selectedDopSetting && (
  199. <GitlabPersonalAccessTokenForm
  200. almSetting={selectedDopSetting}
  201. resetPat={resetPersonalAccessToken}
  202. onPersonalAccessTokenCreated={handlePersonalAccessTokenCreated}
  203. />
  204. )
  205. }
  206. repositoryOptions={repositoryOptions}
  207. repositorySearchQuery={searchQuery}
  208. selectedDopSetting={selectedDopSetting}
  209. selectedRepository={selectedRepository ? transformToOption(selectedRepository) : undefined}
  210. showPersonalAccessToken={showPersonalAccessTokenForm || Boolean(location.query.resetPat)}
  211. />
  212. ) : (
  213. <GitlabProjectCreateRenderer
  214. almInstances={dopSettings.map((dopSetting) => ({
  215. alm: dopSetting.type,
  216. key: dopSetting.key,
  217. url: dopSetting.url,
  218. }))}
  219. canAdmin={canAdmin}
  220. loading={isLoadingRepositories || isLoadingBindings}
  221. loadingMore={isLoadingMoreRepositories}
  222. onImport={handleImportRepository}
  223. onLoadMore={handleLoadMore}
  224. onPersonalAccessTokenCreated={handlePersonalAccessTokenCreated}
  225. onSearch={setSearchQuery}
  226. onSelectedAlmInstanceChange={onSelectedAlmInstanceChange}
  227. projects={repositories}
  228. projectsPaging={repositoryPaging}
  229. resetPat={resetPersonalAccessToken || Boolean(location.query.resetPat)}
  230. searching={isLoadingRepositories}
  231. searchQuery={searchQuery}
  232. selectedAlmInstance={
  233. selectedDopSetting && {
  234. alm: selectedDopSetting.type,
  235. key: selectedDopSetting.key,
  236. url: selectedDopSetting.url,
  237. }
  238. }
  239. showPersonalAccessTokenForm={showPersonalAccessTokenForm || Boolean(location.query.resetPat)}
  240. />
  241. );
  242. }
  243. function transformToOption({ id, name }: GitlabProject): LabelValueSelectOption<string> {
  244. return { value: id, label: name };
  245. }