aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-web/src/main/js/app/utils/startReactApp.tsx147
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreate.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/GitHub-it.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/components/NewCodeDefinitionSelection.tsx35
-rw-r--r--server/sonar-web/src/main/js/helpers/testReactTestingUtils.tsx32
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties2
6 files changed, 136 insertions, 86 deletions
diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx
index 1b800f04a37..985c80ab56b 100644
--- a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx
+++ b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx
@@ -24,7 +24,12 @@ import * as React from 'react';
import { render } from 'react-dom';
import { Helmet, HelmetProvider } from 'react-helmet-async';
import { IntlShape, RawIntlProvider } from 'react-intl';
-import { BrowserRouter, Route, Routes } from 'react-router-dom';
+import {
+ Route,
+ RouterProvider,
+ createBrowserRouter,
+ createRoutesFromElements,
+} from 'react-router-dom';
import accountRoutes from '../../apps/account/routes';
import auditLogsRoutes from '../../apps/audit-logs/routes';
import backgroundTasksRoutes from '../../apps/background-tasks/routes';
@@ -174,6 +179,75 @@ function renderRedirects() {
);
}
+const router = createBrowserRouter(
+ createRoutesFromElements(
+ <>
+ {renderRedirects()}
+
+ <Route path="formatting/help" element={<FormattingHelp />} />
+
+ <Route element={<SimpleContainer />}>{maintenanceRoutes()}</Route>
+
+ <Route element={<MigrationContainer />}>
+ {sessionsRoutes()}
+
+ <Route path="/" element={<App />}>
+ <Route index element={<Landing />} />
+
+ <Route element={<GlobalContainer />}>
+ {accountRoutes()}
+
+ {codingRulesRoutes()}
+
+ <Route path="extension/:pluginKey/:extensionKey" element={<GlobalPageExtension />} />
+
+ {globalIssuesRoutes()}
+
+ {projectsRoutes()}
+
+ {qualityGatesRoutes()}
+ {qualityProfilesRoutes()}
+
+ <Route path="portfolios" element={<PortfoliosPage />} />
+
+ <Route path="sonarlint/auth" element={<SonarLintConnection />} />
+
+ {webAPIRoutes()}
+ {webAPIRoutesV2()}
+
+ {renderComponentRoutes()}
+
+ {renderAdminRoutes()}
+ </Route>
+ <Route
+ // We don't want this route to have any menu.
+ // That is why we can not have it under the accountRoutes
+ path="account/reset_password"
+ element={<ResetPassword />}
+ />
+
+ <Route
+ // We don't want this route to have any menu. This is why we define it here
+ // rather than under the admin routes.
+ path="admin/change_admin_password"
+ element={<ChangeAdminPasswordApp />}
+ />
+
+ <Route
+ // We don't want this route to have any menu. This is why we define it here
+ // rather than under the admin routes.
+ path="admin/plugin_risk_consent"
+ element={<PluginRiskConsent />}
+ />
+ <Route path="not_found" element={<NotFound />} />
+ <Route path="*" element={<NotFound />} />
+ </Route>
+ </Route>
+ </>,
+ ),
+ { basename: getBaseUrl() },
+);
+
const queryClient = new QueryClient();
export default function startReactApp(
@@ -195,75 +269,8 @@ export default function startReactApp(
<ThemeProvider theme={lightTheme}>
<QueryClientProvider client={queryClient}>
<GlobalMessagesContainer />
- <BrowserRouter basename={getBaseUrl()}>
- <Helmet titleTemplate={translate('page_title.template.default')} />
- <Routes>
- {renderRedirects()}
-
- <Route path="formatting/help" element={<FormattingHelp />} />
-
- <Route element={<SimpleContainer />}>{maintenanceRoutes()}</Route>
-
- <Route element={<MigrationContainer />}>
- {sessionsRoutes()}
-
- <Route path="/" element={<App />}>
- <Route index element={<Landing />} />
-
- <Route element={<GlobalContainer />}>
- {accountRoutes()}
-
- {codingRulesRoutes()}
-
- <Route
- path="extension/:pluginKey/:extensionKey"
- element={<GlobalPageExtension />}
- />
-
- {globalIssuesRoutes()}
-
- {projectsRoutes()}
-
- {qualityGatesRoutes()}
- {qualityProfilesRoutes()}
-
- <Route path="portfolios" element={<PortfoliosPage />} />
-
- <Route path="sonarlint/auth" element={<SonarLintConnection />} />
-
- {webAPIRoutes()}
- {webAPIRoutesV2()}
-
- {renderComponentRoutes()}
-
- {renderAdminRoutes()}
- </Route>
- <Route
- // We don't want this route to have any menu.
- // That is why we can not have it under the accountRoutes
- path="account/reset_password"
- element={<ResetPassword />}
- />
-
- <Route
- // We don't want this route to have any menu. This is why we define it here
- // rather than under the admin routes.
- path="admin/change_admin_password"
- element={<ChangeAdminPasswordApp />}
- />
-
- <Route
- // We don't want this route to have any menu. This is why we define it here
- // rather than under the admin routes.
- path="admin/plugin_risk_consent"
- element={<PluginRiskConsent />}
- />
- <Route path="not_found" element={<NotFound />} />
- <Route path="*" element={<NotFound />} />
- </Route>
- </Route>
- </Routes>
- </BrowserRouter>
+ <Helmet titleTemplate={translate('page_title.template.default')} />
+ <RouterProvider router={router} />
</QueryClientProvider>
</ThemeProvider>
</RawIntlProvider>
diff --git a/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreate.tsx
index 6572243a44a..0fec8dd39a0 100644
--- a/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreate.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreate.tsx
@@ -55,7 +55,7 @@ interface State {
selectedAlmInstance?: AlmSettingsInstance;
}
-const REPOSITORY_PAGE_SIZE = 20;
+const REPOSITORY_PAGE_SIZE = 50;
export default class GitHubProjectCreate extends React.Component<Props, State> {
mounted = false;
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitHub-it.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitHub-it.tsx
index c80ae871168..5d74c34d0a2 100644
--- a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitHub-it.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitHub-it.tsx
@@ -235,7 +235,7 @@ it('should show search filter when the authentication is successful', async () =
almSetting: 'conf-github-2',
organization: 'org-1',
page: 1,
- pageSize: 20,
+ pageSize: 50,
query: 'search',
});
});
@@ -263,7 +263,7 @@ it('should have load more', async () => {
almSetting: 'conf-github-2',
organization: 'org-1',
page: 2,
- pageSize: 20,
+ pageSize: 50,
query: '',
});
expect(loadMore).not.toBeInTheDocument();
diff --git a/server/sonar-web/src/main/js/apps/create/project/components/NewCodeDefinitionSelection.tsx b/server/sonar-web/src/main/js/apps/create/project/components/NewCodeDefinitionSelection.tsx
index e49ae22d118..521fb01226c 100644
--- a/server/sonar-web/src/main/js/apps/create/project/components/NewCodeDefinitionSelection.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/components/NewCodeDefinitionSelection.tsx
@@ -22,7 +22,7 @@ import { omit } from 'lodash';
import * as React from 'react';
import { useEffect } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
-import { useNavigate } from 'react-router-dom';
+import { useNavigate, unstable_usePrompt as usePrompt } from 'react-router-dom';
import NewCodeDefinitionSelector from '../../../../components/new-code-definition/NewCodeDefinitionSelector';
import { useDocUrl } from '../../../../helpers/docs';
import { addGlobalSuccessMessage } from '../../../../helpers/globalMessages';
@@ -36,6 +36,10 @@ import {
import { NewCodeDefinitiondWithCompliance } from '../../../../types/new-code-definition';
import { ImportProjectParam } from '../CreateProjectPage';
+const listener = (event: BeforeUnloadEvent) => {
+ event.returnValue = true;
+};
+
interface Props {
importProjects: ImportProjectParam;
}
@@ -49,6 +53,10 @@ export default function NewCodeDefinitionSelection(props: Props) {
const intl = useIntl();
const navigate = useNavigate();
const getDocUrl = useDocUrl();
+ usePrompt({
+ when: isLoading,
+ message: translate('onboarding.create_project.please_dont_leave'),
+ });
const projectCount = importProjects.projects.length;
const isMultipleProjects = projectCount > 1;
@@ -77,6 +85,14 @@ export default function NewCodeDefinitionSelection(props: Props) {
}
}, [data, projectCount, mutateCount, reset, intl, navigate]);
+ React.useEffect(() => {
+ if (isLoading) {
+ window.addEventListener('beforeunload', listener);
+ }
+
+ return () => window.removeEventListener('beforeunload', listener);
+ }, [isLoading]);
+
const handleProjectCreation = () => {
if (selectedDefinition) {
importProjects.projects.forEach((p) => {
@@ -131,10 +147,8 @@ export default function NewCodeDefinitionSelection(props: Props) {
</FlagMessage>
)}
- <div className="sw-mt-10 sw-mb-8">
- <ButtonSecondary className="sw-mr-2" onClick={() => navigate(-1)}>
- {translate('back')}
- </ButtonSecondary>
+ <div className="sw-mt-10 sw-mb-8 sw-flex sw-gap-2 sw-items-center">
+ <ButtonSecondary onClick={() => navigate(-1)}>{translate('back')}</ButtonSecondary>
<ButtonPrimary
onClick={handleProjectCreation}
disabled={!selectedDefinition?.isCompliant || isLoading}
@@ -151,6 +165,17 @@ export default function NewCodeDefinitionSelection(props: Props) {
/>
<Spinner className="sw-ml-2" loading={isLoading} />
</ButtonPrimary>
+ {isLoading && (
+ <FlagMessage variant="warning">
+ <FormattedMessage
+ id="onboarding.create_project.import_in_progress"
+ values={{
+ count: projectCount - mutateCount,
+ total: projectCount,
+ }}
+ />
+ </FlagMessage>
+ )}
</div>
</div>
);
diff --git a/server/sonar-web/src/main/js/helpers/testReactTestingUtils.tsx b/server/sonar-web/src/main/js/helpers/testReactTestingUtils.tsx
index 4b21b5df3fe..efea2369189 100644
--- a/server/sonar-web/src/main/js/helpers/testReactTestingUtils.tsx
+++ b/server/sonar-web/src/main/js/helpers/testReactTestingUtils.tsx
@@ -24,7 +24,16 @@ import { omit } from 'lodash';
import * as React from 'react';
import { HelmetProvider } from 'react-helmet-async';
import { IntlProvider, ReactIntlErrorCode } from 'react-intl';
-import { MemoryRouter, Outlet, Route, Routes, parsePath } from 'react-router-dom';
+import {
+ MemoryRouter,
+ Outlet,
+ Route,
+ RouterProvider,
+ Routes,
+ createMemoryRouter,
+ createRoutesFromElements,
+ parsePath,
+} from 'react-router-dom';
import AdminContext from '../app/components/AdminContext';
import GlobalMessagesContainer from '../app/components/GlobalMessagesContainer';
import AppStateContextProvider from '../app/components/app-state/AppStateContextProvider';
@@ -189,9 +198,21 @@ function renderRoutedApp(
}: RenderContext = {},
): RenderResult {
const path = parsePath(navigateTo);
- path.pathname = `/${path.pathname}`;
+ if (!path.pathname?.startsWith('/')) {
+ path.pathname = `/${path.pathname}`;
+ }
const queryClient = new QueryClient();
+ const router = createMemoryRouter(
+ createRoutesFromElements(
+ <>
+ {children}
+ <Route path="*" element={<CatchAll />} />
+ </>,
+ ),
+ { initialEntries: [path] },
+ );
+
return render(
<HelmetProvider context={{}}>
<IntlWrapper>
@@ -203,12 +224,7 @@ function renderRoutedApp(
<IndexationContextProvider>
<QueryClientProvider client={queryClient}>
<GlobalMessagesContainer />
- <MemoryRouter initialEntries={[path]}>
- <Routes>
- {children}
- <Route path="*" element={<CatchAll />} />
- </Routes>
- </MemoryRouter>
+ <RouterProvider router={router} />
</QueryClientProvider>
</IndexationContextProvider>
</AppStateContextProvider>
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index 463c1e7b682..6d2d013165a 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -4193,6 +4193,8 @@ onboarding.create_project.bitbucket.title=Bitbucket Server project onboarding
onboarding.create_project.bitbucket.subtitle=Import projects from one of your Bitbucket server workspaces
onboarding.create_project.x_repositories_selected={count} {count, plural, one {repository} other {repositories}} selected
onboarding.create_project.x_repository_created={count} {count, plural, one {repository} other {repositories}} will be created as a project on SonarQube
+onboarding.create_project.please_dont_leave=If you leave the page the import could fail. Are you sure you want to leave?
+onboarding.create_project.import_in_progress={count} of {total} projects is imported. Please do not close this page until the import is done.
onboarding.create_project.new_code_definition.title=Set up project for Clean as You Code
onboarding.create_x_project.new_code_definition.title=Set up {count, plural, one {project} other {# projects}} for Clean as You Code