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

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