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.

CreateProjectPage.tsx 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  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 classNames from 'classnames';
  21. import { LargeCenteredLayout } from 'design-system';
  22. import * as React from 'react';
  23. import { Helmet } from 'react-helmet-async';
  24. import { getAlmSettings } from '../../../api/alm-settings';
  25. import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
  26. import withAvailableFeatures, {
  27. WithAvailableFeaturesProps,
  28. } from '../../../app/components/available-features/withAvailableFeatures';
  29. import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
  30. import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
  31. import { translate } from '../../../helpers/l10n';
  32. import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings';
  33. import { AppState } from '../../../types/appstate';
  34. import { Feature } from '../../../types/features';
  35. import AlmBindingDefinitionForm from '../../settings/components/almIntegration/AlmBindingDefinitionForm';
  36. import AzureProjectCreate from './Azure/AzureProjectCreate';
  37. import BitbucketCloudProjectCreate from './BitbucketCloud/BitbucketCloudProjectCreate';
  38. import BitbucketProjectCreate from './BitbucketServer/BitbucketProjectCreate';
  39. import CreateProjectModeSelection from './CreateProjectModeSelection';
  40. import GitHubProjectCreate from './Github/GitHubProjectCreate';
  41. import GitlabProjectCreate from './Gitlab/GitlabProjectCreate';
  42. import NewCodeDefinitionSelection from './components/NewCodeDefinitionSelection';
  43. import ManualProjectCreate from './manual/ManualProjectCreate';
  44. import { CreateProjectModes } from './types';
  45. export interface CreateProjectPageProps extends WithAvailableFeaturesProps {
  46. appState: AppState;
  47. location: Location;
  48. router: Router;
  49. }
  50. interface State {
  51. azureSettings: AlmSettingsInstance[];
  52. bitbucketSettings: AlmSettingsInstance[];
  53. bitbucketCloudSettings: AlmSettingsInstance[];
  54. githubSettings: AlmSettingsInstance[];
  55. gitlabSettings: AlmSettingsInstance[];
  56. loading: boolean;
  57. creatingAlmDefinition?: AlmKeys;
  58. importProjects?: ImportProjectParam;
  59. redirectTo: string;
  60. }
  61. const PROJECT_MODE_FOR_ALM_KEY = {
  62. [AlmKeys.Azure]: CreateProjectModes.AzureDevOps,
  63. [AlmKeys.BitbucketCloud]: CreateProjectModes.BitbucketCloud,
  64. [AlmKeys.BitbucketServer]: CreateProjectModes.BitbucketServer,
  65. [AlmKeys.GitHub]: CreateProjectModes.GitHub,
  66. [AlmKeys.GitLab]: CreateProjectModes.GitLab,
  67. };
  68. export type ImportProjectParam =
  69. | {
  70. creationMode: CreateProjectModes.AzureDevOps;
  71. almSetting: string;
  72. projects: {
  73. projectName: string;
  74. repositoryName: string;
  75. }[];
  76. }
  77. | {
  78. creationMode: CreateProjectModes.BitbucketCloud;
  79. almSetting: string;
  80. projects: {
  81. repositorySlug: string;
  82. }[];
  83. }
  84. | {
  85. creationMode: CreateProjectModes.BitbucketServer;
  86. almSetting: string;
  87. projects: {
  88. repositorySlug: string;
  89. projectKey: string;
  90. }[];
  91. }
  92. | {
  93. creationMode: CreateProjectModes.GitHub;
  94. almSetting: string;
  95. projects: {
  96. repositoryKey: string;
  97. }[];
  98. }
  99. | {
  100. creationMode: CreateProjectModes.GitLab;
  101. almSetting: string;
  102. projects: {
  103. gitlabProjectId: string;
  104. }[];
  105. }
  106. | {
  107. creationMode: CreateProjectModes.Manual;
  108. projects: {
  109. project: string;
  110. name: string;
  111. mainBranch: string;
  112. }[];
  113. }
  114. | {
  115. creationMode: CreateProjectModes.Monorepo;
  116. devOpsPlatformSettingId: string;
  117. monorepo: boolean;
  118. projects: {
  119. projectKey: string;
  120. projectName: string;
  121. }[];
  122. repositoryIdentifier: string;
  123. };
  124. export class CreateProjectPage extends React.PureComponent<CreateProjectPageProps, State> {
  125. mounted = false;
  126. state: State = {
  127. azureSettings: [],
  128. bitbucketSettings: [],
  129. bitbucketCloudSettings: [],
  130. githubSettings: [],
  131. gitlabSettings: [],
  132. loading: true,
  133. redirectTo: this.props.location.state?.from || '/projects',
  134. };
  135. componentDidMount() {
  136. this.mounted = true;
  137. this.cleanQueryParameters();
  138. this.fetchAlmBindings();
  139. }
  140. componentWillUnmount() {
  141. this.mounted = false;
  142. }
  143. cleanQueryParameters() {
  144. const { location, router } = this.props;
  145. if (location.query?.setncd === 'true') {
  146. // Timeout is required to force the refresh of the URL
  147. setTimeout(() => {
  148. location.query.setncd = undefined;
  149. router.replace(location);
  150. }, 0);
  151. }
  152. }
  153. fetchAlmBindings = () => {
  154. this.setState({ loading: true });
  155. return getAlmSettings()
  156. .then((almSettings) => {
  157. if (this.mounted) {
  158. this.setState({
  159. azureSettings: almSettings.filter((s) => s.alm === AlmKeys.Azure),
  160. bitbucketSettings: almSettings.filter((s) => s.alm === AlmKeys.BitbucketServer),
  161. bitbucketCloudSettings: almSettings.filter((s) => s.alm === AlmKeys.BitbucketCloud),
  162. githubSettings: almSettings.filter((s) => s.alm === AlmKeys.GitHub),
  163. gitlabSettings: almSettings.filter((s) => s.alm === AlmKeys.GitLab),
  164. loading: false,
  165. });
  166. }
  167. })
  168. .catch(() => {
  169. if (this.mounted) {
  170. this.setState({ loading: false });
  171. }
  172. });
  173. };
  174. handleModeSelect = (mode: CreateProjectModes) => {
  175. const { router, location } = this.props;
  176. router.push({
  177. pathname: location.pathname,
  178. query: { mode },
  179. });
  180. };
  181. handleModeConfig = (alm: AlmKeys) => {
  182. this.setState({ creatingAlmDefinition: alm });
  183. };
  184. handleProjectSetupDone = (importProjects: ImportProjectParam) => {
  185. const { location, router } = this.props;
  186. this.setState({ importProjects });
  187. location.query.setncd = 'true';
  188. router.push(location);
  189. };
  190. handleOnCancelCreation = () => {
  191. this.setState({ creatingAlmDefinition: undefined });
  192. };
  193. handleAfterSubmit = async () => {
  194. let { creatingAlmDefinition: createdAlmDefinition } = this.state;
  195. this.setState({ creatingAlmDefinition: undefined });
  196. await this.fetchAlmBindings();
  197. if (this.mounted && createdAlmDefinition) {
  198. const { bitbucketCloudSettings } = this.state;
  199. if (createdAlmDefinition === AlmKeys.BitbucketServer && bitbucketCloudSettings.length > 0) {
  200. createdAlmDefinition = AlmKeys.BitbucketCloud;
  201. }
  202. this.handleModeSelect(PROJECT_MODE_FOR_ALM_KEY[createdAlmDefinition]);
  203. }
  204. };
  205. renderProjectCreation(mode?: CreateProjectModes) {
  206. const {
  207. appState: { canAdmin },
  208. location,
  209. router,
  210. } = this.props;
  211. const {
  212. azureSettings,
  213. bitbucketSettings,
  214. bitbucketCloudSettings,
  215. githubSettings,
  216. gitlabSettings,
  217. loading,
  218. redirectTo,
  219. } = this.state;
  220. const branchSupportEnabled = this.props.hasFeature(Feature.BranchSupport);
  221. switch (mode) {
  222. case CreateProjectModes.AzureDevOps: {
  223. return (
  224. <AzureProjectCreate
  225. canAdmin={!!canAdmin}
  226. loadingBindings={loading}
  227. location={location}
  228. router={router}
  229. almInstances={azureSettings}
  230. onProjectSetupDone={this.handleProjectSetupDone}
  231. />
  232. );
  233. }
  234. case CreateProjectModes.BitbucketServer: {
  235. return (
  236. <BitbucketProjectCreate
  237. canAdmin={!!canAdmin}
  238. almInstances={bitbucketSettings}
  239. loadingBindings={loading}
  240. location={location}
  241. router={router}
  242. onProjectSetupDone={this.handleProjectSetupDone}
  243. />
  244. );
  245. }
  246. case CreateProjectModes.BitbucketCloud: {
  247. return (
  248. <BitbucketCloudProjectCreate
  249. canAdmin={!!canAdmin}
  250. loadingBindings={loading}
  251. location={location}
  252. onProjectSetupDone={this.handleProjectSetupDone}
  253. router={router}
  254. almInstances={bitbucketCloudSettings}
  255. />
  256. );
  257. }
  258. case CreateProjectModes.GitHub: {
  259. return (
  260. <GitHubProjectCreate
  261. canAdmin={!!canAdmin}
  262. loadingBindings={loading}
  263. location={location}
  264. onProjectSetupDone={this.handleProjectSetupDone}
  265. router={router}
  266. almInstances={githubSettings}
  267. />
  268. );
  269. }
  270. case CreateProjectModes.GitLab: {
  271. return (
  272. <GitlabProjectCreate
  273. canAdmin={!!canAdmin}
  274. loadingBindings={loading}
  275. location={location}
  276. router={router}
  277. almInstances={gitlabSettings}
  278. onProjectSetupDone={this.handleProjectSetupDone}
  279. />
  280. );
  281. }
  282. case CreateProjectModes.Manual: {
  283. return (
  284. <ManualProjectCreate
  285. branchesEnabled={branchSupportEnabled}
  286. onProjectSetupDone={this.handleProjectSetupDone}
  287. onClose={() => this.props.router.push({ pathname: redirectTo })}
  288. />
  289. );
  290. }
  291. default: {
  292. const almCounts = {
  293. [AlmKeys.Azure]: azureSettings.length,
  294. [AlmKeys.BitbucketServer]: bitbucketSettings.length,
  295. [AlmKeys.BitbucketCloud]: bitbucketCloudSettings.length,
  296. [AlmKeys.GitHub]: githubSettings.length,
  297. [AlmKeys.GitLab]: gitlabSettings.length,
  298. };
  299. return (
  300. <CreateProjectModeSelection
  301. almCounts={almCounts}
  302. loadingBindings={loading}
  303. onConfigMode={this.handleModeConfig}
  304. />
  305. );
  306. }
  307. }
  308. }
  309. render() {
  310. const { location } = this.props;
  311. const { creatingAlmDefinition, importProjects, redirectTo } = this.state;
  312. const mode: CreateProjectModes | undefined = location.query?.mode;
  313. const isProjectSetupDone = location.query?.setncd === 'true';
  314. const gridLayoutStyle = mode ? 'sw-col-start-2 sw-col-span-10' : 'sw-col-span-12';
  315. const pageTitle = mode
  316. ? translate(`onboarding.create_project.${mode}.title`)
  317. : translate('onboarding.create_project.select_method');
  318. return (
  319. <LargeCenteredLayout
  320. id="create-project"
  321. className="sw-pt-8 sw-grid sw-gap-x-12 sw-gap-y-6 sw-grid-cols-12"
  322. >
  323. <div className={gridLayoutStyle}>
  324. <Helmet title={pageTitle} titleTemplate="%s" />
  325. <A11ySkipTarget anchor="create_project_main" />
  326. <div className={classNames({ 'sw-hidden': isProjectSetupDone })}>
  327. {this.renderProjectCreation(mode)}
  328. </div>
  329. {importProjects !== undefined && isProjectSetupDone && (
  330. <NewCodeDefinitionSelection
  331. importProjects={importProjects}
  332. onClose={() => this.props.router.push({ pathname: redirectTo })}
  333. redirectTo={redirectTo}
  334. />
  335. )}
  336. {creatingAlmDefinition && (
  337. <AlmBindingDefinitionForm
  338. alm={creatingAlmDefinition}
  339. onCancel={this.handleOnCancelCreation}
  340. afterSubmit={this.handleAfterSubmit}
  341. enforceValidation
  342. />
  343. )}
  344. </div>
  345. </LargeCenteredLayout>
  346. );
  347. }
  348. }
  349. export default withRouter(withAvailableFeatures(withAppStateContext(CreateProjectPage)));