aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/app
diff options
context:
space:
mode:
authorJeremy Davis <jeremy.davis@sonarsource.com>2022-06-13 11:39:21 +0200
committersonartech <sonartech@sonarsource.com>2022-06-28 20:02:53 +0000
commit54732569670fc345367062d5b20fcca83d9f7692 (patch)
tree8a3d86a9b76fbc056b74ac68ff8b38db9cee2cb1 /server/sonar-web/src/main/js/app
parent26675093303e38f1973f3ee9da5750aeeb2a5a5f (diff)
downloadsonarqube-54732569670fc345367062d5b20fcca83d9f7692.tar.gz
sonarqube-54732569670fc345367062d5b20fcca83d9f7692.zip
SONAR-16045 Migrate react-router to 6.3.0
Co-authored-by: Jeremy Davis <jeremy.davis@sonarsource.com> Co-authored-by: Guillaume Péoc'h <guillaume.peoch@sonarsource.com>
Diffstat (limited to 'server/sonar-web/src/main/js/app')
-rw-r--r--server/sonar-web/src/main/js/app/components/AdminContainer.tsx9
-rw-r--r--server/sonar-web/src/main/js/app/components/App.tsx3
-rw-r--r--server/sonar-web/src/main/js/app/components/ComponentContainer.tsx15
-rw-r--r--server/sonar-web/src/main/js/app/components/ComponentContainerNotFound.tsx2
-rw-r--r--server/sonar-web/src/main/js/app/components/GlobalContainer.tsx18
-rw-r--r--server/sonar-web/src/main/js/app/components/GlobalFooter.tsx2
-rw-r--r--server/sonar-web/src/main/js/app/components/Landing.tsx24
-rw-r--r--server/sonar-web/src/main/js/app/components/MigrationContainer.tsx32
-rw-r--r--server/sonar-web/src/main/js/app/components/NonAdminPagesContainer.tsx16
-rw-r--r--server/sonar-web/src/main/js/app/components/NotFound.tsx2
-rw-r--r--server/sonar-web/src/main/js/app/components/PluginRiskConsent.tsx11
-rw-r--r--server/sonar-web/src/main/js/app/components/ProjectAdminContainer.tsx17
-rw-r--r--server/sonar-web/src/main/js/app/components/SimpleContainer.tsx13
-rw-r--r--server/sonar-web/src/main/js/app/components/SimpleSessionsContainer.tsx9
-rw-r--r--server/sonar-web/src/main/js/app/components/StartupModal.tsx4
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx8
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/GlobalMessagesContainer-it.tsx (renamed from server/sonar-web/src/main/js/app/components/__tests__/GlobalMessagesContainer-it.ts)9
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/Landing-test.tsx19
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/NonAdminPagesContainer-test.tsx61
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/PluginRiskConsent-test.tsx11
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/ProjectAdminContainer-test.tsx23
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/StartupModal-test.tsx10
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/AdminContainer-test.tsx.snap10
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/App-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap54
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooter-test.tsx.snap10
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/NonAdminPagesContainer-test.tsx.snap21
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/ProjectAdminContainer-test.tsx.snap31
-rw-r--r--server/sonar-web/src/main/js/app/components/admin/withAdminPagesOutletContext.tsx (renamed from server/sonar-web/src/main/js/app/components/extensions/__tests__/PortfolioPage-test.tsx)27
-rw-r--r--server/sonar-web/src/main/js/app/components/componentContext/ComponentContext.ts (renamed from server/sonar-web/src/main/js/app/components/ComponentContext.tsx)16
-rw-r--r--server/sonar-web/src/main/js/app/components/componentContext/withComponentContext.tsx (renamed from server/sonar-web/src/main/js/app/components/__tests__/GlobalContainer-test.tsx)42
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/GlobalAdminPageExtension.tsx15
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/GlobalPageExtension.tsx23
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/PortfolioPage.tsx15
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/ProjectAdminPageExtension.tsx17
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/ProjectPageExtension.tsx28
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/__tests__/GlobalPageExtension-test.tsx69
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/__tests__/ProjectAdminPageExtension-test.tsx69
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/__tests__/ProjectPageExtension-test.tsx72
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/Extension-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/PortfolioPage-test.tsx.snap34
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/ProjectAdminPageExtension-test.tsx.snap50
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/ProjectPageExtension-test.tsx.snap54
-rw-r--r--server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx7
-rw-r--r--server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/IndexationNotificationRenderer-test.tsx.snap21
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/Breadcrumb.tsx2
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBgTaskNotif.tsx7
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavLicenseNotif.tsx2
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavProjectBindingErrorNotif.tsx2
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx119
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/Breadcrumb-test.tsx.snap6
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavLicenseNotif-test.tsx.snap4
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavProjectBindingErrorNotif-test.tsx.snap7
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/Menu-test.tsx.snap1043
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/branch-like/CurrentBranchLike.tsx2
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/branch-like/Menu.tsx8
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/Menu-test.tsx7
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/CurrentBranchLike-test.tsx.snap6
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/Menu-test.tsx.snap12
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/utils-test.ts5
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/utils.ts34
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/MetaQualityGate.tsx2
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/MetaQualityProfiles.tsx2
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/__snapshots__/MetaQualityProfiles-test.tsx.snap14
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/GlobalNavBranding.tsx2
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.tsx37
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.tsx4
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavMenu-test.tsx29
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavBranding-test.tsx.snap6
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavMenu-test.tsx.snap102
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavUser-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx70
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/settings/SystemRestartNotif.tsx8
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/settings/__tests__/SettingsNav-test.tsx4
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsNav-test.tsx.snap213
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SystemRestartNotif-test.tsx.snap8
-rw-r--r--server/sonar-web/src/main/js/app/components/search/Search.tsx14
-rw-r--r--server/sonar-web/src/main/js/app/components/search/SearchResult.tsx2
-rw-r--r--server/sonar-web/src/main/js/app/components/search/__tests__/Search-test.tsx16
-rw-r--r--server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.tsx.snap36
-rw-r--r--server/sonar-web/src/main/js/app/index.ts59
-rw-r--r--server/sonar-web/src/main/js/app/utils/NavigateWithParams.tsx46
-rw-r--r--server/sonar-web/src/main/js/app/utils/NavigateWithSearchAndHash.tsx31
-rw-r--r--server/sonar-web/src/main/js/app/utils/__tests__/NavigateWithParams-test.tsx51
-rw-r--r--server/sonar-web/src/main/js/app/utils/__tests__/handleRequiredAuthorization-test.ts32
-rw-r--r--server/sonar-web/src/main/js/app/utils/exportModulesAsGlobals.ts4
-rw-r--r--server/sonar-web/src/main/js/app/utils/handleRequiredAuthorization.ts9
-rw-r--r--server/sonar-web/src/main/js/app/utils/startReactApp.tsx414
88 files changed, 1376 insertions, 2114 deletions
diff --git a/server/sonar-web/src/main/js/app/components/AdminContainer.tsx b/server/sonar-web/src/main/js/app/components/AdminContainer.tsx
index f5ef383db99..de4a84124e4 100644
--- a/server/sonar-web/src/main/js/app/components/AdminContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/AdminContainer.tsx
@@ -19,11 +19,13 @@
*/
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
+import { Outlet } from 'react-router-dom';
import { getSettingsNavigation } from '../../api/nav';
import { getPendingPlugins } from '../../api/plugins';
import { getSystemStatus, waitSystemUPStatus } from '../../api/system';
import handleRequiredAuthorization from '../../app/utils/handleRequiredAuthorization';
import { translate } from '../../helpers/l10n';
+import { AdminPagesContext } from '../../types/admin';
import { AppState } from '../../types/appstate';
import { PendingPluginResult } from '../../types/plugins';
import { Extension, SysStatus } from '../../types/types';
@@ -33,7 +35,6 @@ import SettingsNav from './nav/settings/SettingsNav';
export interface AdminContainerProps {
appState: AppState;
- children: React.ReactElement;
}
interface State {
@@ -120,6 +121,8 @@ export class AdminContainer extends React.PureComponent<AdminContainerProps, Sta
const { pendingPlugins, systemStatus } = this.state;
const defaultTitle = translate('layout.settings');
+ const adminPagesContext: AdminPagesContext = { adminPages };
+
return (
<div>
<Helmet defaultTitle={defaultTitle} defer={false} titleTemplate={`%s - ${defaultTitle}`} />
@@ -137,9 +140,7 @@ export class AdminContainer extends React.PureComponent<AdminContainerProps, Sta
pendingPlugins,
systemStatus
}}>
- {React.cloneElement(this.props.children, {
- adminPages
- })}
+ <Outlet context={adminPagesContext} />
</AdminContext.Provider>
</div>
);
diff --git a/server/sonar-web/src/main/js/app/components/App.tsx b/server/sonar-web/src/main/js/app/components/App.tsx
index e7dcd7d0654..ca2b0ab0daf 100644
--- a/server/sonar-web/src/main/js/app/components/App.tsx
+++ b/server/sonar-web/src/main/js/app/components/App.tsx
@@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { Outlet } from 'react-router-dom';
import { lazyLoadComponent } from '../../components/lazyLoadComponent';
import { AppState } from '../../types/appstate';
import { GlobalSettingKeys } from '../../types/settings';
@@ -91,7 +92,7 @@ export class App extends React.PureComponent<Props> {
return (
<>
<PageTracker>{this.renderPreconnectLink()}</PageTracker>
- {this.props.children}
+ <Outlet />
<KeyboardShortcutsModal />
</>
);
diff --git a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
index a3d571fcb2e..4d6d2f3fac0 100644
--- a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
@@ -19,6 +19,7 @@
*/
import { differenceBy } from 'lodash';
import * as React from 'react';
+import { Outlet } from 'react-router-dom';
import { getProjectAlmBinding, validateProjectAlmBinding } from '../../api/alm-settings';
import { getBranches, getPullRequests } from '../../api/branches';
import { getAnalysisStatus, getTasksForComponent } from '../../api/ce';
@@ -46,16 +47,15 @@ import handleRequiredAuthorization from '../utils/handleRequiredAuthorization';
import withAppStateContext from './app-state/withAppStateContext';
import withBranchStatusActions from './branch-status/withBranchStatusActions';
import ComponentContainerNotFound from './ComponentContainerNotFound';
-import { ComponentContext } from './ComponentContext';
+import { ComponentContext } from './componentContext/ComponentContext';
import PageUnavailableDueToIndexation from './indexation/PageUnavailableDueToIndexation';
import ComponentNav from './nav/component/ComponentNav';
interface Props {
appState: AppState;
- children: React.ReactElement;
- location: Pick<Location, 'query' | 'pathname'>;
+ location: Location;
updateBranchStatus: (branchLike: BranchLike, component: string, status: Status) => void;
- router: Pick<Router, 'replace'>;
+ router: Router;
}
interface State {
@@ -448,8 +448,8 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
<i className="spinner" />
</div>
) : (
- <ComponentContext.Provider value={{ branchLike, component }}>
- {React.cloneElement(this.props.children, {
+ <ComponentContext.Provider
+ value={{
branchLike,
branchLikes,
component,
@@ -458,7 +458,8 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
onBranchesChange: this.handleBranchesChange,
onComponentChange: this.handleComponentChange,
projectBinding
- })}
+ }}>
+ <Outlet />
</ComponentContext.Provider>
)}
</div>
diff --git a/server/sonar-web/src/main/js/app/components/ComponentContainerNotFound.tsx b/server/sonar-web/src/main/js/app/components/ComponentContainerNotFound.tsx
index e10f29c2411..c6fb0f8c382 100644
--- a/server/sonar-web/src/main/js/app/components/ComponentContainerNotFound.tsx
+++ b/server/sonar-web/src/main/js/app/components/ComponentContainerNotFound.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { translate } from '../../helpers/l10n';
export default function ComponentContainerNotFound() {
diff --git a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
index 6bc280ca84f..f9f4cecc211 100644
--- a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
@@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { Outlet, useLocation } from 'react-router-dom';
import A11yProvider from '../../components/a11y/A11yProvider';
import A11ySkipLinks from '../../components/a11y/A11ySkipLinks';
import SuggestionsProvider from '../../components/embed-docs-modal/SuggestionsProvider';
@@ -33,15 +34,10 @@ import PromotionNotification from './promotion-notification/PromotionNotificatio
import StartupModal from './StartupModal';
import UpdateNotification from './update-notification/UpdateNotification';
-export interface Props {
- children: React.ReactNode;
- footer?: React.ReactNode;
- location: { pathname: string };
-}
-
-export default function GlobalContainer(props: Props) {
+export default function GlobalContainer() {
// it is important to pass `location` down to `GlobalNav` to trigger render on url change
- const { footer = <GlobalFooter /> } = props;
+ const location = useLocation();
+
return (
<SuggestionsProvider>
<A11yProvider>
@@ -55,10 +51,10 @@ export default function GlobalContainer(props: Props) {
<IndexationContextProvider>
<LanguagesContextProvider>
<MetricsContextProvider>
- <GlobalNav location={props.location} />
+ <GlobalNav location={location} />
<IndexationNotification />
<UpdateNotification dismissable={true} />
- {props.children}
+ <Outlet />
</MetricsContextProvider>
</LanguagesContextProvider>
</IndexationContextProvider>
@@ -67,7 +63,7 @@ export default function GlobalContainer(props: Props) {
</div>
<PromotionNotification />
</div>
- {footer}
+ <GlobalFooter />
</div>
</StartupModal>
</A11yProvider>
diff --git a/server/sonar-web/src/main/js/app/components/GlobalFooter.tsx b/server/sonar-web/src/main/js/app/components/GlobalFooter.tsx
index 9da659e6004..70d929392fd 100644
--- a/server/sonar-web/src/main/js/app/components/GlobalFooter.tsx
+++ b/server/sonar-web/src/main/js/app/components/GlobalFooter.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import InstanceMessage from '../../components/common/InstanceMessage';
import { Alert } from '../../components/ui/Alert';
import { getEdition } from '../../helpers/editions';
diff --git a/server/sonar-web/src/main/js/app/components/Landing.tsx b/server/sonar-web/src/main/js/app/components/Landing.tsx
index 13109175290..e2e2db052e5 100644
--- a/server/sonar-web/src/main/js/app/components/Landing.tsx
+++ b/server/sonar-web/src/main/js/app/components/Landing.tsx
@@ -18,30 +18,24 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { Router, withRouter } from '../../components/hoc/withRouter';
+import { Navigate, To } from 'react-router-dom';
import { getHomePageUrl } from '../../helpers/urls';
import { CurrentUser, isLoggedIn } from '../../types/users';
import withCurrentUserContext from './current-user/withCurrentUserContext';
export interface LandingProps {
currentUser: CurrentUser;
- router: Router;
}
-export class Landing extends React.PureComponent<LandingProps> {
- componentDidMount() {
- const { currentUser } = this.props;
- if (isLoggedIn(currentUser) && currentUser.homepage) {
- const homepage = getHomePageUrl(currentUser.homepage);
- this.props.router.replace(homepage);
- } else {
- this.props.router.replace('/projects');
- }
+export function Landing({ currentUser }: LandingProps) {
+ let redirectUrl: To;
+ if (isLoggedIn(currentUser) && currentUser.homepage) {
+ redirectUrl = getHomePageUrl(currentUser.homepage);
+ } else {
+ redirectUrl = '/projects';
}
- render() {
- return null;
- }
+ return <Navigate to={redirectUrl} replace={true} />;
}
-export default withRouter(withCurrentUserContext(Landing));
+export default withCurrentUserContext(Landing);
diff --git a/server/sonar-web/src/main/js/app/components/MigrationContainer.tsx b/server/sonar-web/src/main/js/app/components/MigrationContainer.tsx
index 6cdf95d5477..c11a011ad70 100644
--- a/server/sonar-web/src/main/js/app/components/MigrationContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/MigrationContainer.tsx
@@ -18,25 +18,23 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { WithRouterProps } from 'react-router';
+import { Navigate, Outlet, useLocation } from 'react-router-dom';
import { getSystemStatus } from '../../helpers/system';
-export default class MigrationContainer extends React.PureComponent<WithRouterProps> {
- componentDidMount() {
- if (getSystemStatus() !== 'UP') {
- this.props.router.push({
- pathname: '/maintenance',
- query: {
- return_to: window.location.pathname + window.location.search + window.location.hash
- }
- });
- }
- }
+export function MigrationContainer() {
+ const location = useLocation();
+
+ if (getSystemStatus() !== 'UP') {
+ const to = {
+ pathname: '/maintenance',
+ search: new URLSearchParams({
+ return_to: location.pathname + location.search + location.hash
+ }).toString()
+ };
- render() {
- if (getSystemStatus() !== 'UP') {
- return null;
- }
- return this.props.children;
+ return <Navigate to={to} />;
}
+ return <Outlet />;
}
+
+export default MigrationContainer;
diff --git a/server/sonar-web/src/main/js/app/components/NonAdminPagesContainer.tsx b/server/sonar-web/src/main/js/app/components/NonAdminPagesContainer.tsx
index 622a88610ae..0b1a924ab5f 100644
--- a/server/sonar-web/src/main/js/app/components/NonAdminPagesContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/NonAdminPagesContainer.tsx
@@ -18,25 +18,21 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { Outlet } from 'react-router-dom';
import { Alert } from '../../components/ui/Alert';
import { translate } from '../../helpers/l10n';
import { isApplication } from '../../types/component';
-import { Component } from '../../types/types';
+import { ComponentContext } from './componentContext/ComponentContext';
-export interface NonAdminPagesContainerProps {
- children: JSX.Element;
- component: Component;
-}
-
-export default function NonAdminPagesContainer(props: NonAdminPagesContainerProps) {
- const { children, component } = props;
+export default function NonAdminPagesContainer() {
+ const { component } = React.useContext(ComponentContext);
/*
* Catch Applications for which the user does not have access to all child projects
* and prevent displaying whatever page was requested.
* This doesn't apply to admin pages (those are not within this container)
*/
- if (isApplication(component.qualifier) && !component.canBrowseAllChildProjects) {
+ if (component && isApplication(component.qualifier) && !component.canBrowseAllChildProjects) {
return (
<div className="page page-limited display-flex-justify-center">
<Alert
@@ -51,5 +47,5 @@ export default function NonAdminPagesContainer(props: NonAdminPagesContainerProp
);
}
- return React.cloneElement(children, props);
+ return <Outlet />;
}
diff --git a/server/sonar-web/src/main/js/app/components/NotFound.tsx b/server/sonar-web/src/main/js/app/components/NotFound.tsx
index e476c137172..2c5546b4a4a 100644
--- a/server/sonar-web/src/main/js/app/components/NotFound.tsx
+++ b/server/sonar-web/src/main/js/app/components/NotFound.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { translate } from '../../helpers/l10n';
import SimpleContainer from './SimpleContainer';
diff --git a/server/sonar-web/src/main/js/app/components/PluginRiskConsent.tsx b/server/sonar-web/src/main/js/app/components/PluginRiskConsent.tsx
index 394fa4a10b3..9445a72706f 100644
--- a/server/sonar-web/src/main/js/app/components/PluginRiskConsent.tsx
+++ b/server/sonar-web/src/main/js/app/components/PluginRiskConsent.tsx
@@ -38,8 +38,15 @@ export interface PluginRiskConsentProps {
export function PluginRiskConsent(props: PluginRiskConsentProps) {
const { router, currentUser } = props;
- if (!hasGlobalPermission(currentUser, Permissions.Admin)) {
- router.replace('/');
+ const isAdmin = hasGlobalPermission(currentUser, Permissions.Admin);
+
+ React.useEffect(() => {
+ if (!isAdmin) {
+ router.replace('/');
+ }
+ }, [isAdmin, router]);
+
+ if (!isAdmin) {
return null;
}
diff --git a/server/sonar-web/src/main/js/app/components/ProjectAdminContainer.tsx b/server/sonar-web/src/main/js/app/components/ProjectAdminContainer.tsx
index b806154db88..bc718d9be32 100644
--- a/server/sonar-web/src/main/js/app/components/ProjectAdminContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/ProjectAdminContainer.tsx
@@ -18,23 +18,17 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { Outlet } from 'react-router-dom';
import A11ySkipTarget from '../../components/a11y/A11ySkipTarget';
-import { BranchLike } from '../../types/branch-like';
import { Component } from '../../types/types';
import handleRequiredAuthorization from '../utils/handleRequiredAuthorization';
+import withComponentContext from './componentContext/withComponentContext';
interface Props {
- children: JSX.Element;
- branchLike?: BranchLike;
- branchLikes: BranchLike[];
component: Component;
- isInProgress?: boolean;
- isPending?: boolean;
- onBranchesChange: () => void;
- onComponentChange: (changes: {}) => void;
}
-export default class ProjectAdminContainer extends React.PureComponent<Props> {
+export class ProjectAdminContainer extends React.PureComponent<Props> {
componentDidMount() {
this.checkPermissions();
}
@@ -59,12 +53,13 @@ export default class ProjectAdminContainer extends React.PureComponent<Props> {
return null;
}
- const { children, ...props } = this.props;
return (
<>
<A11ySkipTarget anchor="admin_main" />
- {React.cloneElement(children, props)}
+ <Outlet />
</>
);
}
}
+
+export default withComponentContext(ProjectAdminContainer);
diff --git a/server/sonar-web/src/main/js/app/components/SimpleContainer.tsx b/server/sonar-web/src/main/js/app/components/SimpleContainer.tsx
index 0a9e52d54dd..ad605d25770 100644
--- a/server/sonar-web/src/main/js/app/components/SimpleContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/SimpleContainer.tsx
@@ -18,20 +18,21 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { Outlet } from 'react-router-dom';
import NavBar from '../../components/ui/NavBar';
import { rawSizes } from '../theme';
import GlobalFooter from './GlobalFooter';
-interface Props {
- children?: React.ReactNode;
-}
-
-export default function SimpleContainer({ children }: Props) {
+/*
+ * We need to render either children or the Outlet,
+ * because this component is used both in the context of routes and as a regular container
+ */
+export default function SimpleContainer({ children }: { children?: React.ReactNode }) {
return (
<div className="global-container">
<div className="page-wrapper" id="container">
<NavBar className="navbar-global" height={rawSizes.globalNavHeightRaw} />
- {children}
+ {children !== undefined ? children : <Outlet />}
</div>
<GlobalFooter />
</div>
diff --git a/server/sonar-web/src/main/js/app/components/SimpleSessionsContainer.tsx b/server/sonar-web/src/main/js/app/components/SimpleSessionsContainer.tsx
index c9564386eaa..6efc8feff87 100644
--- a/server/sonar-web/src/main/js/app/components/SimpleSessionsContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/SimpleSessionsContainer.tsx
@@ -18,23 +18,20 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { Outlet } from 'react-router-dom';
import { lazyLoadComponent } from '../../components/lazyLoadComponent';
import GlobalFooter from './GlobalFooter';
const PageTracker = lazyLoadComponent(() => import('./PageTracker'));
-interface Props {
- children?: React.ReactNode;
-}
-
-export default function SimpleSessionsContainer({ children }: Props) {
+export default function SimpleSessionsContainer() {
return (
<>
<PageTracker />
<div className="global-container">
<div className="page-wrapper" id="container">
- {children}
+ <Outlet />
</div>
<GlobalFooter hideLoggedInInfo={true} />
</div>
diff --git a/server/sonar-web/src/main/js/app/components/StartupModal.tsx b/server/sonar-web/src/main/js/app/components/StartupModal.tsx
index 8bf0201d3b4..5e6bfb55512 100644
--- a/server/sonar-web/src/main/js/app/components/StartupModal.tsx
+++ b/server/sonar-web/src/main/js/app/components/StartupModal.tsx
@@ -42,8 +42,8 @@ interface StateProps {
type Props = {
children?: React.ReactNode;
- location: Pick<Location, 'pathname'>;
- router: Pick<Router, 'push'>;
+ location: Location;
+ router: Router;
appState: AppState;
};
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
index e9e3361093a..88f82cbab52 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
@@ -102,7 +102,7 @@ it('changes component', () => {
loading: false
});
- (wrapper.find(Inner).prop('onComponentChange') as Function)({ visibility: 'private' });
+ wrapper.instance().handleComponentChange({ visibility: 'private' });
expect(wrapper.state().component).toEqual({ qualifier: 'TRK', visibility: 'private' });
});
@@ -136,8 +136,6 @@ it("doesn't load branches portfolio", async () => {
expect(getPullRequests).not.toBeCalled();
expect(getComponentData).toBeCalledWith({ component: 'portfolioKey', branch: undefined });
expect(getComponentNavigation).toBeCalledWith({ component: 'portfolioKey', branch: undefined });
- wrapper.update();
- expect(wrapper.find(Inner).exists()).toBe(true);
});
it('updates branches on change', async () => {
@@ -153,7 +151,7 @@ it('updates branches on change', async () => {
}),
loading: false
});
- wrapper.find(Inner).prop<Function>('onBranchesChange')();
+ wrapper.instance().handleBranchesChange();
expect(getBranches).toBeCalledWith('projectKey');
expect(getPullRequests).toBeCalledWith('projectKey');
await waitAndUpdate(wrapper);
@@ -355,7 +353,7 @@ it('should redirect if the component is a portfolio', async () => {
router: mockRouter({ replace })
});
await waitAndUpdate(wrapper);
- expect(replace).toBeCalledWith({ pathname: '/portfolio', query: { id: componentKey } });
+ expect(replace).toBeCalledWith({ pathname: '/portfolio', search: `?id=${componentKey}` });
});
it('should display display the unavailable page if the component needs issue sync', async () => {
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/GlobalMessagesContainer-it.ts b/server/sonar-web/src/main/js/app/components/__tests__/GlobalMessagesContainer-it.tsx
index cc705e4d3b8..dca57e11428 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/GlobalMessagesContainer-it.ts
+++ b/server/sonar-web/src/main/js/app/components/__tests__/GlobalMessagesContainer-it.tsx
@@ -18,14 +18,19 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { screen } from '@testing-library/react';
+import React from 'react';
import { addGlobalErrorMessage, addGlobalSuccessMessage } from '../../../helpers/globalMessages';
import { renderComponentApp } from '../../../helpers/testReactTestingUtils';
+function NullComponent() {
+ return null;
+}
+
it('should display messages', () => {
jest.useFakeTimers();
- // we render anything, the GlobalMesasgeContainer is rendered independently from routing
- renderComponentApp('sonarqube', () => null);
+ // we render anything, the GlobalMessageContainer is rendered independently from routing
+ renderComponentApp('sonarqube', <NullComponent />);
addGlobalErrorMessage('This is an error');
addGlobalSuccessMessage('This was a triumph!');
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/Landing-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/Landing-test.tsx
index 437ef87cf29..13f854d9bfa 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/Landing-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/__tests__/Landing-test.tsx
@@ -19,9 +19,10 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { mockCurrentUser, mockLoggedInUser, mockRouter } from '../../../helpers/testMocks';
+import { Navigate } from 'react-router-dom';
+import { mockCurrentUser, mockLoggedInUser } from '../../../helpers/testMocks';
import { CurrentUser } from '../../../types/users';
-import { Landing } from '../Landing';
+import { Landing, LandingProps } from '../Landing';
it.each([
[mockCurrentUser(), '/projects'],
@@ -30,14 +31,12 @@ it.each([
mockLoggedInUser({ homepage: { type: 'ISSUES' } }),
expect.objectContaining({ pathname: '/issues' })
]
-])('should render correctly', (currentUser: CurrentUser, homepageUrl: string) => {
- const router = mockRouter();
- shallowRender({ router, currentUser });
- expect(router.replace).toHaveBeenCalledWith(homepageUrl);
+])('should render correctly', (currentUser: CurrentUser, expected: string) => {
+ const wrapper = shallowRender({ currentUser });
+
+ expect(wrapper.find(Navigate).props().to).toEqual(expected);
});
-function shallowRender(props: Partial<Landing['props']> = {}) {
- return shallow<Landing>(
- <Landing currentUser={mockCurrentUser()} router={mockRouter()} {...props} />
- );
+function shallowRender(props: Partial<LandingProps> = {}) {
+ return shallow<LandingProps>(<Landing currentUser={mockCurrentUser()} {...props} />);
}
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/NonAdminPagesContainer-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/NonAdminPagesContainer-test.tsx
index d6d98b7ab09..02302a5b180 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/NonAdminPagesContainer-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/__tests__/NonAdminPagesContainer-test.tsx
@@ -17,48 +17,41 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { shallow } from 'enzyme';
+import { render, screen } from '@testing-library/react';
import * as React from 'react';
+import { MemoryRouter, Route, Routes } from 'react-router-dom';
import { mockComponent } from '../../../helpers/mocks/component';
-import { ComponentQualifier } from '../../../types/component';
-import NonAdminPagesContainer, { NonAdminPagesContainerProps } from '../NonAdminPagesContainer';
+import { ComponentContextShape, ComponentQualifier } from '../../../types/component';
+import { Component } from '../../../types/types';
+import { ComponentContext } from '../componentContext/ComponentContext';
+import NonAdminPagesContainer from '../NonAdminPagesContainer';
function Child() {
- return <div />;
+ return <div>Test Child</div>;
}
-it('should render correctly', () => {
- expect(
- shallowRender()
- .find(Child)
- .exists()
- ).toBe(true);
-
- expect(
- shallowRender({
- component: mockComponent({
- qualifier: ComponentQualifier.Application,
- canBrowseAllChildProjects: true
- })
- })
- .find(Child)
- .exists()
- ).toBe(true);
-
- const wrapper = shallowRender({
- component: mockComponent({
- qualifier: ComponentQualifier.Application
- })
- });
+it('should render correctly for an user that does not have access to all children', () => {
+ renderNonAdminPagesContainer(
+ mockComponent({ qualifier: ComponentQualifier.Application, canBrowseAllChildProjects: false })
+ );
+ expect(screen.getByText('application.cannot_access_all_child_projects1')).toBeInTheDocument();
+});
- expect(wrapper.find(Child).exists()).toBe(false);
- expect(wrapper).toMatchSnapshot();
+it('should render correctly', () => {
+ renderNonAdminPagesContainer(mockComponent());
+ expect(screen.getByText('Test Child')).toBeInTheDocument();
});
-function shallowRender(props: Partial<NonAdminPagesContainerProps> = {}) {
- return shallow<NonAdminPagesContainerProps>(
- <NonAdminPagesContainer component={mockComponent()} {...props}>
- <Child />
- </NonAdminPagesContainer>
+function renderNonAdminPagesContainer(component: Component) {
+ return render(
+ <ComponentContext.Provider value={{ component } as ComponentContextShape}>
+ <MemoryRouter>
+ <Routes>
+ <Route element={<NonAdminPagesContainer />}>
+ <Route path="*" element={<Child />} />
+ </Route>
+ </Routes>
+ </MemoryRouter>
+ </ComponentContext.Provider>
);
}
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/PluginRiskConsent-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/PluginRiskConsent-test.tsx
index 480c3d79d8e..dca6ce7d236 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/PluginRiskConsent-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/__tests__/PluginRiskConsent-test.tsx
@@ -28,6 +28,17 @@ jest.mock('../../../api/settings', () => ({
setSimpleSettingValue: jest.fn().mockResolvedValue({})
}));
+jest.mock('react', () => {
+ return {
+ ...jest.requireActual('react'),
+ useEffect: jest.fn().mockImplementation(f => f())
+ };
+});
+
+afterAll(() => {
+ jest.clearAllMocks();
+});
+
it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
});
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/ProjectAdminContainer-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/ProjectAdminContainer-test.tsx
index 31013720ff4..445de3ac747 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/ProjectAdminContainer-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/__tests__/ProjectAdminContainer-test.tsx
@@ -21,18 +21,12 @@ import { mount, shallow } from 'enzyme';
import * as React from 'react';
import handleRequiredAuthorization from '../../../app/utils/handleRequiredAuthorization';
import { mockComponent } from '../../../helpers/mocks/component';
-import ProjectAdminContainer from '../ProjectAdminContainer';
+import { ProjectAdminContainer } from '../ProjectAdminContainer';
jest.mock('../../utils/handleRequiredAuthorization', () => {
return jest.fn();
});
-class ChildComponent extends React.Component {
- render() {
- return null;
- }
-}
-
it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});
@@ -42,13 +36,6 @@ it('should redirect for authorization if needed', () => {
expect(handleRequiredAuthorization).toBeCalled();
});
-it('should pass props to its children', () => {
- const child = shallowRender().find(ChildComponent);
- // No need to check all...
- expect(child.prop('component')).toBeDefined();
- expect(child.prop('onBranchesChange')).toBeDefined();
-});
-
function mountRender(props: Partial<ProjectAdminContainer['props']> = {}) {
return mount(createComponent(props));
}
@@ -60,12 +47,8 @@ function shallowRender(props: Partial<ProjectAdminContainer['props']> = {}) {
function createComponent(props: Partial<ProjectAdminContainer['props']> = {}) {
return (
<ProjectAdminContainer
- branchLikes={[]}
component={mockComponent({ configuration: { showSettings: true } })}
- onBranchesChange={jest.fn()}
- onComponentChange={jest.fn()}
- {...props}>
- <ChildComponent />
- </ProjectAdminContainer>
+ {...props}
+ />
);
}
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/StartupModal-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/StartupModal-test.tsx
index c6d82ab38e9..96a80a3e0af 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/StartupModal-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/__tests__/StartupModal-test.tsx
@@ -24,7 +24,7 @@ import { showLicense } from '../../../api/editions';
import { toShortNotSoISOString } from '../../../helpers/dates';
import { hasMessage } from '../../../helpers/l10n';
import { get, save } from '../../../helpers/storage';
-import { mockAppState } from '../../../helpers/testMocks';
+import { mockAppState, mockLocation, mockRouter } from '../../../helpers/testMocks';
import { waitAndUpdate } from '../../../helpers/testUtils';
import { EditionKey } from '../../../types/editions';
import { LoggedInUser } from '../../../types/users';
@@ -89,7 +89,7 @@ it('should render only the children', async () => {
getWrapper({
appState: mockAppState({ canAdmin: false }),
currentUser: { ...LOGGED_IN_USER },
- location: { pathname: '/documentation/' }
+ location: mockLocation({ pathname: '/documentation/' })
})
);
@@ -97,7 +97,7 @@ it('should render only the children', async () => {
getWrapper({
appState: mockAppState({ canAdmin: false }),
currentUser: { ...LOGGED_IN_USER },
- location: { pathname: '/create-organization' }
+ location: mockLocation({ pathname: '/create-organization' })
})
);
});
@@ -129,8 +129,8 @@ function getWrapper(props: Partial<StartupModal['props']> = {}) {
<StartupModal
appState={mockAppState({ edition: EditionKey.enterprise, canAdmin: true })}
currentUser={LOGGED_IN_USER}
- location={{ pathname: 'foo/bar' }}
- router={{ push: jest.fn() }}
+ location={mockLocation({ pathname: 'foo/bar' })}
+ router={mockRouter()}
{...props}>
<div />
</StartupModal>
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/AdminContainer-test.tsx.snap b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/AdminContainer-test.tsx.snap
index b36639ca82e..3e6df64fa42 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/AdminContainer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/AdminContainer-test.tsx.snap
@@ -9,7 +9,7 @@ exports[`should render correctly 1`] = `
prioritizeSeoTags={false}
titleTemplate="%s - layout.settings"
/>
- <SettingsNav
+ <WithLocation
extensions={Array []}
fetchPendingPlugins={[Function]}
fetchSystemStatus={[Function]}
@@ -36,8 +36,12 @@ exports[`should render correctly 1`] = `
}
}
>
- <div
- adminPages={Array []}
+ <Outlet
+ context={
+ Object {
+ "adminPages": Array [],
+ }
+ }
/>
</ContextProvider>
</div>
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/App-test.tsx.snap
index 8f592cd1ec7..d322409ceb1 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/App-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/App-test.tsx.snap
@@ -3,6 +3,7 @@
exports[`should render correctly: default 1`] = `
<Fragment>
<LazyComponentWrapper />
+ <Outlet />
<KeyboardShortcutsModal />
</Fragment>
`;
@@ -15,6 +16,7 @@ exports[`should render correctly: with gravatar 1`] = `
rel="preconnect"
/>
</LazyComponentWrapper>
+ <Outlet />
<KeyboardShortcutsModal />
</Fragment>
`;
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap
deleted file mode 100644
index 16b94edc8ff..00000000000
--- a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap
+++ /dev/null
@@ -1,54 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<SuggestionsProvider>
- <A11yProvider>
- <withCurrentUserContext(withRouter(withAppStateContext(StartupModal)))>
- <A11ySkipLinks />
- <div
- className="global-container"
- >
- <div
- className="page-wrapper"
- id="container"
- >
- <div
- className="page-container"
- >
- <BranchStatusContextProvider>
- <Workspace>
- <withAppStateContext(IndexationContextProvider)>
- <LanguagesContextProvider>
- <MetricsContextProvider>
- <withCurrentUserContext(GlobalNav)
- location={
- Object {
- "action": "PUSH",
- "hash": "",
- "key": "key",
- "pathname": "/path",
- "query": Object {},
- "search": "",
- "state": Object {},
- }
- }
- />
- <withCurrentUserContext(withIndexationContext(IndexationNotification)) />
- <withCurrentUserContext(withAppStateContext(UpdateNotification))
- dismissable={true}
- />
- <ChildComponent />
- </MetricsContextProvider>
- </LanguagesContextProvider>
- </withAppStateContext(IndexationContextProvider)>
- </Workspace>
- </BranchStatusContextProvider>
- </div>
- <withCurrentUserContext(PromotionNotification) />
- </div>
- <withAppStateContext(GlobalFooter) />
- </div>
- </withCurrentUserContext(withRouter(withAppStateContext(StartupModal)))>
- </A11yProvider>
-</SuggestionsProvider>
-`;
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooter-test.tsx.snap b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooter-test.tsx.snap
index d5450c04210..233f033bc31 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooter-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooter-test.tsx.snap
@@ -45,8 +45,6 @@ exports[`should display the sq version 1`] = `
className="page-footer-menu-item"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to="/documentation"
>
footer.documentation
@@ -67,8 +65,6 @@ exports[`should display the sq version 1`] = `
className="page-footer-menu-item"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to="/web_api"
>
footer.web_api
@@ -113,8 +109,6 @@ exports[`should not render the only logged in information 1`] = `
className="page-footer-menu-item"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to="/documentation"
>
footer.documentation
@@ -180,8 +174,6 @@ exports[`should render the only logged in information 1`] = `
className="page-footer-menu-item"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to="/documentation"
>
footer.documentation
@@ -202,8 +194,6 @@ exports[`should render the only logged in information 1`] = `
className="page-footer-menu-item"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to="/web_api"
>
footer.web_api
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/NonAdminPagesContainer-test.tsx.snap b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/NonAdminPagesContainer-test.tsx.snap
deleted file mode 100644
index ec24550e566..00000000000
--- a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/NonAdminPagesContainer-test.tsx.snap
+++ /dev/null
@@ -1,21 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<div
- className="page page-limited display-flex-justify-center"
->
- <Alert
- className="it__alert-no-access-all-child-project max-width-60 huge-spacer-top"
- display="block"
- variant="error"
- >
- <p>
- application.cannot_access_all_child_projects1
- </p>
- <br />
- <p>
- application.cannot_access_all_child_projects2
- </p>
- </Alert>
-</div>
-`;
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/ProjectAdminContainer-test.tsx.snap b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/ProjectAdminContainer-test.tsx.snap
index 90341d94f9a..c3f74fb0aa7 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/ProjectAdminContainer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/ProjectAdminContainer-test.tsx.snap
@@ -5,35 +5,6 @@ exports[`should render correctly 1`] = `
<A11ySkipTarget
anchor="admin_main"
/>
- <ChildComponent
- branchLikes={Array []}
- component={
- Object {
- "breadcrumbs": Array [],
- "configuration": Object {
- "showSettings": true,
- },
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": Object {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": Array [
- Object {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": Array [],
- }
- }
- onBranchesChange={[MockFunction]}
- onComponentChange={[MockFunction]}
- />
+ <Outlet />
</Fragment>
`;
diff --git a/server/sonar-web/src/main/js/app/components/extensions/__tests__/PortfolioPage-test.tsx b/server/sonar-web/src/main/js/app/components/admin/withAdminPagesOutletContext.tsx
index 1e1166e1807..961f4f93626 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/__tests__/PortfolioPage-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/admin/withAdminPagesOutletContext.tsx
@@ -17,25 +17,16 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { shallow } from 'enzyme';
import * as React from 'react';
-import { mockComponent } from '../../../../helpers/mocks/component';
-import { mockLocation, mockRouter } from '../../../../helpers/testMocks';
-import { PortfolioPage, PortfolioPageProps } from '../PortfolioPage';
+import { useOutletContext } from 'react-router-dom';
+import { AdminPagesContext } from '../../../types/admin';
-it('should render correctly', () => {
- expect(shallowRender()).toMatchSnapshot();
-});
+export default function withAdminPagesOutletContext(
+ WrappedComponent: React.ComponentType<AdminPagesContext>
+) {
+ return function WithAdminPagesOutletContext() {
+ const { adminPages } = useOutletContext<AdminPagesContext>();
-function shallowRender(props?: Partial<PortfolioPageProps>) {
- return shallow<PortfolioPageProps>(
- <PortfolioPage
- component={mockComponent()}
- location={mockLocation()}
- router={mockRouter()}
- routes={[]}
- params={{}}
- {...props}
- />
- );
+ return <WrappedComponent adminPages={adminPages} />;
+ };
}
diff --git a/server/sonar-web/src/main/js/app/components/ComponentContext.tsx b/server/sonar-web/src/main/js/app/components/componentContext/ComponentContext.ts
index f00f4dfc645..10db938858d 100644
--- a/server/sonar-web/src/main/js/app/components/ComponentContext.tsx
+++ b/server/sonar-web/src/main/js/app/components/componentContext/ComponentContext.ts
@@ -17,16 +17,12 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { noop } from 'lodash';
import * as React from 'react';
-import { BranchLike } from '../../types/branch-like';
-import { Component } from '../../types/types';
+import { ComponentContextShape } from '../../../types/component';
-interface ComponentContextType {
- branchLike: BranchLike | undefined;
- component: Component | undefined;
-}
-
-export const ComponentContext = React.createContext<ComponentContextType>({
- branchLike: undefined,
- component: undefined
+export const ComponentContext = React.createContext<ComponentContextShape>({
+ branchLikes: [],
+ onBranchesChange: noop,
+ onComponentChange: noop
});
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/GlobalContainer-test.tsx b/server/sonar-web/src/main/js/app/components/componentContext/withComponentContext.tsx
index e094b6512df..a9071e2ebb2 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/GlobalContainer-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/componentContext/withComponentContext.tsx
@@ -17,35 +17,25 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { shallow } from 'enzyme';
import * as React from 'react';
-import { mockLocation } from '../../../helpers/testMocks';
-import GlobalContainer, { Props } from '../GlobalContainer';
+import { getWrappedDisplayName } from '../../../components/hoc/utils';
+import { ComponentContextShape } from '../../../types/component';
+import { ComponentContext } from './ComponentContext';
-jest.mock('../../../components/embed-docs-modal/SuggestionsProvider', () => {
- class SuggestionsProvider extends React.Component {
- render() {
- return this.props.children;
- }
- }
-
- return SuggestionsProvider;
-});
+export default function withComponentContext<P extends Partial<ComponentContextShape>>(
+ WrappedComponent: React.ComponentType<P>
+) {
+ return class WithComponentContext extends React.PureComponent<
+ Omit<P, keyof ComponentContextShape>
+ > {
+ static displayName = getWrappedDisplayName(WrappedComponent, 'withComponentContext');
-it('should render correctly', () => {
- expect(shallowRender()).toMatchSnapshot();
-});
-
-function shallowRender(props: Partial<Props> = {}) {
- class ChildComponent extends React.Component {
render() {
- return null;
+ return (
+ <ComponentContext.Consumer>
+ {componentContext => <WrappedComponent {...componentContext} {...(this.props as P)} />}
+ </ComponentContext.Consumer>
+ );
}
- }
-
- return shallow(
- <GlobalContainer location={mockLocation()} {...props}>
- <ChildComponent />
- </GlobalContainer>
- );
+ };
}
diff --git a/server/sonar-web/src/main/js/app/components/extensions/GlobalAdminPageExtension.tsx b/server/sonar-web/src/main/js/app/components/extensions/GlobalAdminPageExtension.tsx
index 28974085d08..388aab27464 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/GlobalAdminPageExtension.tsx
+++ b/server/sonar-web/src/main/js/app/components/extensions/GlobalAdminPageExtension.tsx
@@ -18,20 +18,15 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { Extension as TypeExtension } from '../../../types/types';
+import { useOutletContext, useParams } from 'react-router-dom';
+import { AdminPagesContext } from '../../../types/admin';
import NotFound from '../NotFound';
import Extension from './Extension';
-interface Props {
- adminPages: TypeExtension[] | undefined;
- params: { extensionKey: string; pluginKey: string };
-}
+export default function GlobalAdminPageExtension() {
+ const { pluginKey, extensionKey } = useParams();
+ const { adminPages } = useOutletContext<AdminPagesContext>();
-export default function GlobalAdminPageExtension(props: Props) {
- const {
- params: { extensionKey, pluginKey },
- adminPages
- } = props;
const extension = (adminPages || []).find(p => p.key === `${pluginKey}/${extensionKey}`);
return extension ? <Extension extension={extension} /> : <NotFound withContainer={false} />;
}
diff --git a/server/sonar-web/src/main/js/app/components/extensions/GlobalPageExtension.tsx b/server/sonar-web/src/main/js/app/components/extensions/GlobalPageExtension.tsx
index 0c4206497c2..6216cf5f55e 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/GlobalPageExtension.tsx
+++ b/server/sonar-web/src/main/js/app/components/extensions/GlobalPageExtension.tsx
@@ -18,22 +18,33 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { useParams } from 'react-router-dom';
import { AppState } from '../../../types/appstate';
import withAppStateContext from '../app-state/withAppStateContext';
import NotFound from '../NotFound';
import Extension from './Extension';
-interface Props {
+export interface GlobalPageExtensionProps {
appState: AppState;
- params: { extensionKey: string; pluginKey: string };
+ params?: {
+ extensionKey: string;
+ pluginKey: string;
+ };
}
-function GlobalPageExtension(props: Props) {
+function GlobalPageExtension(props: GlobalPageExtensionProps) {
const {
- params: { extensionKey, pluginKey },
- appState: { globalPages }
+ appState: { globalPages },
+ params
} = props;
- const extension = (globalPages || []).find(p => p.key === `${pluginKey}/${extensionKey}`);
+ const { extensionKey, pluginKey } = useParams();
+
+ const fullKey =
+ params !== undefined
+ ? `${params.pluginKey}/${params.extensionKey}`
+ : `${pluginKey}/${extensionKey}`;
+
+ const extension = (globalPages || []).find(p => p.key === fullKey);
return extension ? <Extension extension={extension} /> : <NotFound withContainer={false} />;
}
diff --git a/server/sonar-web/src/main/js/app/components/extensions/PortfolioPage.tsx b/server/sonar-web/src/main/js/app/components/extensions/PortfolioPage.tsx
index 389c138c607..9925a233ebb 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/PortfolioPage.tsx
+++ b/server/sonar-web/src/main/js/app/components/extensions/PortfolioPage.tsx
@@ -18,23 +18,12 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { WithRouterProps } from 'react-router';
import withIndexationGuard from '../../../components/hoc/withIndexationGuard';
-import { Component } from '../../../types/types';
import { PageContext } from '../indexation/PageUnavailableDueToIndexation';
import ProjectPageExtension from './ProjectPageExtension';
-export interface PortfolioPageProps extends WithRouterProps {
- component: Component;
-}
-
-export function PortfolioPage({ component }: PortfolioPageProps) {
- return (
- <ProjectPageExtension
- component={component}
- params={{ pluginKey: 'governance', extensionKey: 'portfolio' }}
- />
- );
+export function PortfolioPage() {
+ return <ProjectPageExtension params={{ pluginKey: 'governance', extensionKey: 'portfolio' }} />;
}
export default withIndexationGuard(PortfolioPage, PageContext.Portfolios);
diff --git a/server/sonar-web/src/main/js/app/components/extensions/ProjectAdminPageExtension.tsx b/server/sonar-web/src/main/js/app/components/extensions/ProjectAdminPageExtension.tsx
index efc6ae1a1ae..58e16dc1093 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/ProjectAdminPageExtension.tsx
+++ b/server/sonar-web/src/main/js/app/components/extensions/ProjectAdminPageExtension.tsx
@@ -18,22 +18,17 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { Component } from '../../../types/types';
+import { useParams } from 'react-router-dom';
+import { ComponentContext } from '../componentContext/ComponentContext';
import NotFound from '../NotFound';
import Extension from './Extension';
-export interface ProjectAdminPageExtensionProps {
- component: Component;
- params: { extensionKey: string; pluginKey: string };
-}
-
-export default function ProjectAdminPageExtension(props: ProjectAdminPageExtensionProps) {
- const {
- component,
- params: { extensionKey, pluginKey }
- } = props;
+export default function ProjectAdminPageExtension() {
+ const { extensionKey, pluginKey } = useParams();
+ const { component } = React.useContext(ComponentContext);
const extension =
+ component &&
component.configuration &&
(component.configuration.extensions || []).find(p => p.key === `${pluginKey}/${extensionKey}`);
diff --git a/server/sonar-web/src/main/js/app/components/extensions/ProjectPageExtension.tsx b/server/sonar-web/src/main/js/app/components/extensions/ProjectPageExtension.tsx
index 40f8bd94b39..de3ef4ce9ff 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/ProjectPageExtension.tsx
+++ b/server/sonar-web/src/main/js/app/components/extensions/ProjectPageExtension.tsx
@@ -18,26 +18,32 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { BranchLike } from '../../../types/branch-like';
-import { Component } from '../../../types/types';
+import { useParams } from 'react-router-dom';
+import { ComponentContext } from '../componentContext/ComponentContext';
import NotFound from '../NotFound';
import Extension from './Extension';
export interface ProjectPageExtensionProps {
- branchLike?: BranchLike;
- component: Component;
- params: {
+ params?: {
extensionKey: string;
pluginKey: string;
};
}
-export default function ProjectPageExtension(props: ProjectPageExtensionProps) {
- const { extensionKey, pluginKey } = props.params;
- const { branchLike, component } = props;
- const extension =
- component.extensions &&
- component.extensions.find(p => p.key === `${pluginKey}/${extensionKey}`);
+export default function ProjectPageExtension({ params }: ProjectPageExtensionProps) {
+ const { extensionKey, pluginKey } = useParams();
+ const { branchLike, component } = React.useContext(ComponentContext);
+
+ if (component === undefined) {
+ return null;
+ }
+
+ const fullKey =
+ params !== undefined
+ ? `${params.pluginKey}/${params.extensionKey}`
+ : `${pluginKey}/${extensionKey}`;
+
+ const extension = component.extensions && component.extensions.find(p => p.key === fullKey);
return extension ? (
<Extension extension={extension} options={{ branchLike, component }} />
) : (
diff --git a/server/sonar-web/src/main/js/app/components/extensions/__tests__/GlobalPageExtension-test.tsx b/server/sonar-web/src/main/js/app/components/extensions/__tests__/GlobalPageExtension-test.tsx
new file mode 100644
index 00000000000..62f4375af20
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/extensions/__tests__/GlobalPageExtension-test.tsx
@@ -0,0 +1,69 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { screen } from '@testing-library/react';
+import * as React from 'react';
+import { mockAppState } from '../../../../helpers/testMocks';
+import { renderComponentApp } from '../../../../helpers/testReactTestingUtils';
+import { Extension } from '../../../../types/types';
+import GlobalPageExtension, { GlobalPageExtensionProps } from '../GlobalPageExtension';
+
+jest.mock('../Extension', () => ({
+ __esModule: true,
+ default(props: { extension: { key: string; name: string } }) {
+ return <h1>{props.extension.name}</h1>;
+ }
+}));
+
+const extensions = [{ key: 'plugin123/ext42', name: 'extension 42' }];
+
+it('should find the extension from params', () => {
+ renderGlobalPageExtension('extension/plugin123/ext42', extensions);
+
+ expect(screen.getByText('extension 42')).toBeInTheDocument();
+});
+
+it('should notify if extension is not found', () => {
+ renderGlobalPageExtension('extension/plugin123/wrong-extension', extensions);
+
+ expect(screen.getByText('page_not_found')).toBeInTheDocument();
+});
+
+it('should find the extension from props', () => {
+ const params = { pluginKey: 'plugin123', extensionKey: 'ext42' };
+
+ renderGlobalPageExtension('extension/whatever/overridden', extensions, params);
+
+ expect(screen.getByText('extension 42')).toBeInTheDocument();
+});
+
+function renderGlobalPageExtension(
+ navigateTo: string,
+ globalPages: Extension[] = [],
+ params?: GlobalPageExtensionProps['params']
+) {
+ renderComponentApp(
+ `extension/:pluginKey/:extensionKey`,
+ <GlobalPageExtension params={params} />,
+ {
+ appState: mockAppState({ globalPages }),
+ navigateTo
+ }
+ );
+}
diff --git a/server/sonar-web/src/main/js/app/components/extensions/__tests__/ProjectAdminPageExtension-test.tsx b/server/sonar-web/src/main/js/app/components/extensions/__tests__/ProjectAdminPageExtension-test.tsx
index 7b29077a35f..eea7470d3c2 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/__tests__/ProjectAdminPageExtension-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/extensions/__tests__/ProjectAdminPageExtension-test.tsx
@@ -17,30 +17,59 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { shallow } from 'enzyme';
+import { render, screen } from '@testing-library/react';
import * as React from 'react';
+import { HelmetProvider } from 'react-helmet-async';
+import { IntlProvider } from 'react-intl';
+import { MemoryRouter, Route, Routes } from 'react-router-dom';
+import { getExtensionStart } from '../../../../helpers/extensions';
import { mockComponent } from '../../../../helpers/mocks/component';
-import ProjectAdminPageExtension, {
- ProjectAdminPageExtensionProps
-} from '../ProjectAdminPageExtension';
+import { ComponentContextShape } from '../../../../types/component';
+import { Component } from '../../../../types/types';
+import { ComponentContext } from '../../componentContext/ComponentContext';
+import ProjectAdminPageExtension from '../ProjectAdminPageExtension';
-it('should render correctly', () => {
- expect(
- shallowRender({
- component: mockComponent({
- configuration: { extensions: [{ key: 'foo/bar', name: 'Foo Bar' }] }
- })
- })
- ).toMatchSnapshot('extension exists');
- expect(shallowRender()).toMatchSnapshot('extension not found');
+jest.mock('../../../../helpers/extensions', () => ({
+ getExtensionStart: jest.fn().mockResolvedValue(jest.fn())
+}));
+
+it('should render correctly when the extension is found', () => {
+ renderProjectAdminPageExtension(
+ mockComponent({
+ configuration: { extensions: [{ key: 'pluginId/extensionId', name: 'name' }] }
+ }),
+ { pluginKey: 'pluginId', extensionKey: 'extensionId' }
+ );
+ expect(getExtensionStart).toBeCalledWith('pluginId/extensionId');
+});
+
+it('should render correctly when the extension is not found', () => {
+ renderProjectAdminPageExtension(
+ mockComponent({ extensions: [{ key: 'pluginId/extensionId', name: 'name' }] }),
+ { pluginKey: 'not-found-plugin', extensionKey: 'not-found-extension' }
+ );
+ expect(screen.getByText('page_not_found')).toBeInTheDocument();
});
-function shallowRender(props: Partial<ProjectAdminPageExtensionProps> = {}) {
- return shallow(
- <ProjectAdminPageExtension
- component={mockComponent()}
- params={{ extensionKey: 'bar', pluginKey: 'foo' }}
- {...props}
- />
+function renderProjectAdminPageExtension(
+ component: Component,
+ params: {
+ extensionKey: string;
+ pluginKey: string;
+ }
+) {
+ const { pluginKey, extensionKey } = params;
+ return render(
+ <HelmetProvider context={{}}>
+ <IntlProvider defaultLocale="en" locale="en">
+ <ComponentContext.Provider value={{ component } as ComponentContextShape}>
+ <MemoryRouter initialEntries={[`/${pluginKey}/${extensionKey}`]}>
+ <Routes>
+ <Route path="/:pluginKey/:extensionKey" element={<ProjectAdminPageExtension />} />
+ </Routes>
+ </MemoryRouter>
+ </ComponentContext.Provider>
+ </IntlProvider>
+ </HelmetProvider>
);
}
diff --git a/server/sonar-web/src/main/js/app/components/extensions/__tests__/ProjectPageExtension-test.tsx b/server/sonar-web/src/main/js/app/components/extensions/__tests__/ProjectPageExtension-test.tsx
index 1d0dab75dec..0e8517dab9d 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/__tests__/ProjectPageExtension-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/extensions/__tests__/ProjectPageExtension-test.tsx
@@ -17,33 +17,67 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { shallow } from 'enzyme';
+import { render, screen } from '@testing-library/react';
import * as React from 'react';
-import { mockMainBranch } from '../../../../helpers/mocks/branch-like';
+import { HelmetProvider } from 'react-helmet-async';
+import { IntlProvider } from 'react-intl';
+import { MemoryRouter, Route, Routes } from 'react-router-dom';
+import { getExtensionStart } from '../../../../helpers/extensions';
import { mockComponent } from '../../../../helpers/mocks/component';
+import { ComponentContextShape } from '../../../../types/component';
+import { Component } from '../../../../types/types';
+import { ComponentContext } from '../../componentContext/ComponentContext';
import ProjectPageExtension, { ProjectPageExtensionProps } from '../ProjectPageExtension';
-it('should render correctly', () => {
- const wrapper = shallowRender();
- expect(wrapper).toMatchSnapshot();
+jest.mock('../../../../helpers/extensions', () => ({
+ getExtensionStart: jest.fn().mockResolvedValue(jest.fn())
+}));
+
+it('should not render when no component is passed', () => {
+ renderProjectPageExtension();
+ expect(screen.queryByText('page_not_found')).not.toBeInTheDocument();
+ expect(getExtensionStart).not.toBeCalledWith('pluginId/extensionId');
+});
+
+it('should render correctly when the extension is found', () => {
+ renderProjectPageExtension(
+ mockComponent({ extensions: [{ key: 'pluginId/extensionId', name: 'name' }] }),
+ { params: { pluginKey: 'pluginId', extensionKey: 'extensionId' } }
+ );
+ expect(getExtensionStart).toBeCalledWith('pluginId/extensionId');
});
it('should render correctly when the extension is not found', () => {
- const wrapper = shallowRender({
- params: { pluginKey: 'not-found-plugin', extensionKey: 'not-found-extension' }
- });
- expect(wrapper).toMatchSnapshot();
+ renderProjectPageExtension(
+ mockComponent({ extensions: [{ key: 'pluginId/extensionId', name: 'name' }] }),
+ { params: { pluginKey: 'not-found-plugin', extensionKey: 'not-found-extension' } }
+ );
+ expect(screen.getByText('page_not_found')).toBeInTheDocument();
});
-function shallowRender(props?: Partial<ProjectPageExtensionProps>) {
- return shallow(
- <ProjectPageExtension
- branchLike={mockMainBranch()}
- component={mockComponent({
- extensions: [{ key: 'plugin-key/extension-key', name: 'plugin' }]
- })}
- params={{ extensionKey: 'extension-key', pluginKey: 'plugin-key' }}
- {...props}
- />
+function renderProjectPageExtension(
+ component?: Component,
+ props?: Partial<ProjectPageExtensionProps>
+) {
+ return render(
+ <HelmetProvider context={{}}>
+ <IntlProvider defaultLocale="en" locale="en">
+ <ComponentContext.Provider value={{ component } as ComponentContextShape}>
+ <MemoryRouter>
+ <Routes>
+ <Route
+ path="*"
+ element={
+ <ProjectPageExtension
+ params={{ extensionKey: 'extensionId', pluginKey: 'pluginId' }}
+ {...props}
+ />
+ }
+ />
+ </Routes>
+ </MemoryRouter>
+ </ComponentContext.Provider>
+ </IntlProvider>
+ </HelmetProvider>
);
}
diff --git a/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/Extension-test.tsx.snap b/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/Extension-test.tsx.snap
index 183e24e1dfc..44929f62890 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/Extension-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/Extension-test.tsx.snap
@@ -27,7 +27,6 @@ exports[`should render React extensions correctly 1`] = `
intl={Object {}}
location={
Object {
- "action": "PUSH",
"hash": "",
"key": "key",
"pathname": "/path",
@@ -87,7 +86,6 @@ exports[`should render React extensions correctly 2`] = `
intl={Object {}}
location={
Object {
- "action": "PUSH",
"hash": "",
"key": "key",
"pathname": "/path",
diff --git a/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/PortfolioPage-test.tsx.snap b/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/PortfolioPage-test.tsx.snap
deleted file mode 100644
index dc84ed679d0..00000000000
--- a/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/PortfolioPage-test.tsx.snap
+++ /dev/null
@@ -1,34 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<ProjectPageExtension
- component={
- Object {
- "breadcrumbs": Array [],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": Object {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": Array [
- Object {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": Array [],
- }
- }
- params={
- Object {
- "extensionKey": "portfolio",
- "pluginKey": "governance",
- }
- }
-/>
-`;
diff --git a/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/ProjectAdminPageExtension-test.tsx.snap b/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/ProjectAdminPageExtension-test.tsx.snap
deleted file mode 100644
index 690ff078cd9..00000000000
--- a/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/ProjectAdminPageExtension-test.tsx.snap
+++ /dev/null
@@ -1,50 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly: extension exists 1`] = `
-<injectIntl(withRouter(withAppStateContext(withCurrentUserContext(Extension))))
- extension={
- Object {
- "key": "foo/bar",
- "name": "Foo Bar",
- }
- }
- options={
- Object {
- "component": Object {
- "breadcrumbs": Array [],
- "configuration": Object {
- "extensions": Array [
- Object {
- "key": "foo/bar",
- "name": "Foo Bar",
- },
- ],
- },
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": Object {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": Array [
- Object {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": Array [],
- },
- }
- }
-/>
-`;
-
-exports[`should render correctly: extension not found 1`] = `
-<NotFound
- withContainer={false}
-/>
-`;
diff --git a/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/ProjectPageExtension-test.tsx.snap b/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/ProjectPageExtension-test.tsx.snap
deleted file mode 100644
index c99ec489367..00000000000
--- a/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/ProjectPageExtension-test.tsx.snap
+++ /dev/null
@@ -1,54 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<injectIntl(withRouter(withAppStateContext(withCurrentUserContext(Extension))))
- extension={
- Object {
- "key": "plugin-key/extension-key",
- "name": "plugin",
- }
- }
- options={
- Object {
- "branchLike": Object {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- },
- "component": Object {
- "breadcrumbs": Array [],
- "extensions": Array [
- Object {
- "key": "plugin-key/extension-key",
- "name": "plugin",
- },
- ],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": Object {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": Array [
- Object {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": Array [],
- },
- }
- }
-/>
-`;
-
-exports[`should render correctly when the extension is not found 1`] = `
-<NotFound
- withContainer={false}
-/>
-`;
diff --git a/server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx b/server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx
index 8c192a2490d..16439c7b2f0 100644
--- a/server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx
+++ b/server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx
@@ -22,9 +22,10 @@
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { Alert, AlertProps } from '../../../components/ui/Alert';
import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { queryToSearch } from '../../../helpers/urls';
import { IndexationNotificationType } from '../../../types/indexation';
import { TaskStatuses, TaskTypes } from '../../../types/tasks';
@@ -143,10 +144,10 @@ function renderBackgroundTasksPageLink(hasError: boolean, text: string) {
<Link
to={{
pathname: '/admin/background_tasks',
- query: {
+ search: queryToSearch({
taskType: TaskTypes.IssueSync,
status: hasError ? TaskStatuses.Failed : undefined
- }
+ })
}}>
{text}
</Link>
diff --git a/server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/IndexationNotificationRenderer-test.tsx.snap b/server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/IndexationNotificationRenderer-test.tsx.snap
index aebc9ce3e46..6802c479180 100644
--- a/server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/IndexationNotificationRenderer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/IndexationNotificationRenderer-test.tsx.snap
@@ -95,15 +95,10 @@ exports[`should render correctly for type="CompletedWithFailure" & isSystemAdmin
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/admin/background_tasks",
- "query": Object {
- "status": "FAILED",
- "taskType": "ISSUE_SYNC",
- },
+ "search": "?taskType=ISSUE_SYNC&status=FAILED",
}
}
>
@@ -182,15 +177,10 @@ exports[`should render correctly for type="InProgress" & isSystemAdmin=true 1`]
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/admin/background_tasks",
- "query": Object {
- "status": undefined,
- "taskType": "ISSUE_SYNC",
- },
+ "search": "?taskType=ISSUE_SYNC",
}
}
>
@@ -272,15 +262,10 @@ exports[`should render correctly for type="InProgressWithFailure" & isSystemAdmi
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/admin/background_tasks",
- "query": Object {
- "status": "FAILED",
- "taskType": "ISSUE_SYNC",
- },
+ "search": "?taskType=ISSUE_SYNC&status=FAILED",
}
}
>
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/Breadcrumb.tsx b/server/sonar-web/src/main/js/app/components/nav/component/Breadcrumb.tsx
index c54b4d6161e..f96ee9b78d0 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/Breadcrumb.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/Breadcrumb.tsx
@@ -19,7 +19,7 @@
*/
import { last } from 'lodash';
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import QualifierIcon from '../../../../components/icons/QualifierIcon';
import { isMainBranch } from '../../../../helpers/branch-like';
import { getComponentOverviewUrl } from '../../../../helpers/urls';
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBgTaskNotif.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBgTaskNotif.tsx
index 4b7908296ef..bdd5fce58c6 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBgTaskNotif.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBgTaskNotif.tsx
@@ -19,9 +19,9 @@
*/
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
-import { Link, WithRouterProps } from 'react-router';
+import { Link } from 'react-router-dom';
import { STATUSES } from '../../../../apps/background-tasks/constants';
-import { withRouter } from '../../../../components/hoc/withRouter';
+import { Location, withRouter } from '../../../../components/hoc/withRouter';
import { Alert } from '../../../../components/ui/Alert';
import { hasMessage, translate } from '../../../../helpers/l10n';
import { getComponentBackgroundTaskUrl } from '../../../../helpers/urls';
@@ -29,12 +29,13 @@ import { Task, TaskStatuses } from '../../../../types/tasks';
import { Component } from '../../../../types/types';
import ComponentNavLicenseNotif from './ComponentNavLicenseNotif';
-interface Props extends Pick<WithRouterProps, 'location'> {
+interface Props {
component: Component;
currentTask?: Task;
currentTaskOnSameBranch?: boolean;
isInProgress?: boolean;
isPending?: boolean;
+ location: Location;
}
export class ComponentNavBgTaskNotif extends React.PureComponent<Props> {
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavLicenseNotif.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavLicenseNotif.tsx
index 7ada2c4db56..ba2f645c2d1 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavLicenseNotif.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavLicenseNotif.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { isValidLicense } from '../../../../api/editions';
import { Alert } from '../../../../components/ui/Alert';
import { translate, translateWithParameters } from '../../../../helpers/l10n';
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavProjectBindingErrorNotif.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavProjectBindingErrorNotif.tsx
index 58a0175c583..5cbb5494a0a 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavProjectBindingErrorNotif.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavProjectBindingErrorNotif.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { PULL_REQUEST_DECORATION_BINDING_CATEGORY } from '../../../../apps/settings/constants';
import { Alert } from '../../../../components/ui/Alert';
import { translate } from '../../../../helpers/l10n';
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx b/server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx
index 7b33626b62a..1f8a8888dc8 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx
@@ -18,10 +18,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import classNames from 'classnames';
-import { LocationDescriptorObject } from 'history';
-import { omit } from 'lodash';
import * as React from 'react';
-import { Link, LinkProps } from 'react-router';
+import { NavLink } from 'react-router-dom';
import Dropdown from '../../../../components/controls/Dropdown';
import Tooltip from '../../../../components/controls/Tooltip';
import BulletListIcon from '../../../../components/icons/BulletListIcon';
@@ -33,7 +31,7 @@ import { getPortfolioUrl, getProjectQueryUrl } from '../../../../helpers/urls';
import { AppState } from '../../../../types/appstate';
import { BranchLike, BranchParameters } from '../../../../types/branch-like';
import { ComponentQualifier, isPortfolioLike } from '../../../../types/component';
-import { Component, Extension } from '../../../../types/types';
+import { Component, Dict, Extension } from '../../../../types/types';
import withAppStateContext from '../../app-state/withAppStateContext';
import './Menu.css';
@@ -131,11 +129,12 @@ export class Menu extends React.PureComponent<Props> {
renderMenuLink = ({
label,
- to,
- ...props
- }: Omit<LinkProps, 'to'> & {
+ pathname,
+ additionalQueryParams = {}
+ }: {
label: React.ReactNode;
- to: LocationDescriptorObject;
+ pathname: string;
+ additionalQueryParams?: Dict<string>;
}) => {
const hasAnalysis = this.hasAnalysis();
const isApplicationChildInaccessble = this.isApplicationChildInaccessble();
@@ -146,12 +145,13 @@ export class Menu extends React.PureComponent<Props> {
return (
<li>
{hasAnalysis ? (
- <Link
- activeClassName="active"
- to={{ ...to, query: { ...query, ...to.query } }}
- {...omit(props, ['to'])}>
+ <NavLink
+ to={{
+ pathname,
+ search: new URLSearchParams({ ...query, ...additionalQueryParams }).toString()
+ }}>
{label}
- </Link>
+ </NavLink>
) : (
<Tooltip overlay={translate('layout.must_be_configured')}>
<a aria-disabled="true" className="disabled-link">
@@ -169,9 +169,7 @@ export class Menu extends React.PureComponent<Props> {
if (this.isPortfolio()) {
return this.isGovernanceEnabled() ? (
<li>
- <Link activeClassName="active" to={getPortfolioUrl(id)}>
- {translate('overview.page')}
- </Link>
+ <NavLink to={getPortfolioUrl(id)}>{translate('overview.page')}</NavLink>
</li>
) : null;
}
@@ -182,9 +180,7 @@ export class Menu extends React.PureComponent<Props> {
}
return (
<li>
- <Link activeClassName="active" to={getProjectQueryUrl(id, branchLike)}>
- {translate('overview.page')}
- </Link>
+ <NavLink to={getProjectQueryUrl(id, branchLike)}>{translate('overview.page')}</NavLink>
</li>
);
};
@@ -193,7 +189,7 @@ export class Menu extends React.PureComponent<Props> {
return this.isPortfolio() && this.isGovernanceEnabled()
? this.renderMenuLink({
label: translate('portfolio_breakdown.page'),
- to: { pathname: '/code' }
+ pathname: '/code'
})
: null;
};
@@ -205,7 +201,7 @@ export class Menu extends React.PureComponent<Props> {
const label = this.isApplication() ? translate('view_projects.page') : translate('code.page');
- return this.renderMenuLink({ label, to: { pathname: '/code' } });
+ return this.renderMenuLink({ label, pathname: '/code' });
};
renderActivityLink = () => {
@@ -217,21 +213,22 @@ export class Menu extends React.PureComponent<Props> {
return this.renderMenuLink({
label: translate('project_activity.page'),
- to: { pathname: '/project/activity' }
+ pathname: '/project/activity'
});
};
renderIssuesLink = () => {
return this.renderMenuLink({
label: translate('issues.page'),
- to: { pathname: '/project/issues', query: { resolved: 'false' } }
+ pathname: '/project/issues',
+ additionalQueryParams: { resolved: 'false' }
});
};
renderComponentMeasuresLink = () => {
return this.renderMenuLink({
label: translate('layout.measures'),
- to: { pathname: '/component_measures' }
+ pathname: '/component_measures'
});
};
@@ -241,7 +238,7 @@ export class Menu extends React.PureComponent<Props> {
!isPortfolio &&
this.renderMenuLink({
label: translate('layout.security_hotspots'),
- to: { pathname: '/security_hotspots' }
+ pathname: '/security_hotspots'
})
);
};
@@ -264,7 +261,7 @@ export class Menu extends React.PureComponent<Props> {
return this.renderMenuLink({
label: translate('layout.security_reports'),
- to: { pathname: '/project/extension/securityreport/securityreport' }
+ pathname: '/project/extension/securityreport/securityreport'
});
};
@@ -373,9 +370,10 @@ export class Menu extends React.PureComponent<Props> {
}
return (
<li key="settings">
- <Link activeClassName="active" to={{ pathname: '/project/settings', query }}>
+ <NavLink
+ to={{ pathname: '/project/settings', search: new URLSearchParams(query).toString() }}>
{translate('project_settings.page')}
- </Link>
+ </NavLink>
</li>
);
};
@@ -391,9 +389,10 @@ export class Menu extends React.PureComponent<Props> {
return (
<li key="branches">
- <Link activeClassName="active" to={{ pathname: '/project/branches', query }}>
+ <NavLink
+ to={{ pathname: '/project/branches', search: new URLSearchParams(query).toString() }}>
{translate('project_branch_pull_request.page')}
- </Link>
+ </NavLink>
</li>
);
};
@@ -404,9 +403,10 @@ export class Menu extends React.PureComponent<Props> {
}
return (
<li key="baseline">
- <Link activeClassName="active" to={{ pathname: '/project/baseline', query }}>
+ <NavLink
+ to={{ pathname: '/project/baseline', search: new URLSearchParams(query).toString() }}>
{translate('project_baseline.page')}
- </Link>
+ </NavLink>
</li>
);
};
@@ -417,9 +417,13 @@ export class Menu extends React.PureComponent<Props> {
}
return (
<li key="import-export">
- <Link activeClassName="active" to={{ pathname: '/project/import_export', query }}>
+ <NavLink
+ to={{
+ pathname: '/project/import_export',
+ search: new URLSearchParams(query).toString()
+ }}>
{translate('project_dump.page')}
- </Link>
+ </NavLink>
</li>
);
};
@@ -430,9 +434,13 @@ export class Menu extends React.PureComponent<Props> {
}
return (
<li key="profiles">
- <Link activeClassName="active" to={{ pathname: '/project/quality_profiles', query }}>
+ <NavLink
+ to={{
+ pathname: '/project/quality_profiles',
+ search: new URLSearchParams(query).toString()
+ }}>
{translate('project_quality_profiles.page')}
- </Link>
+ </NavLink>
</li>
);
};
@@ -443,9 +451,10 @@ export class Menu extends React.PureComponent<Props> {
}
return (
<li key="quality_gate">
- <Link activeClassName="active" to={{ pathname: '/project/quality_gate', query }}>
+ <NavLink
+ to={{ pathname: '/project/quality_gate', search: new URLSearchParams(query).toString() }}>
{translate('project_quality_gate.page')}
- </Link>
+ </NavLink>
</li>
);
};
@@ -456,9 +465,9 @@ export class Menu extends React.PureComponent<Props> {
}
return (
<li key="links">
- <Link activeClassName="active" to={{ pathname: '/project/links', query }}>
+ <NavLink to={{ pathname: '/project/links', search: new URLSearchParams(query).toString() }}>
{translate('project_links.page')}
- </Link>
+ </NavLink>
</li>
);
};
@@ -469,9 +478,9 @@ export class Menu extends React.PureComponent<Props> {
}
return (
<li key="permissions">
- <Link activeClassName="active" to={{ pathname: '/project_roles', query }}>
+ <NavLink to={{ pathname: '/project_roles', search: new URLSearchParams(query).toString() }}>
{translate('permissions.page')}
- </Link>
+ </NavLink>
</li>
);
};
@@ -482,9 +491,13 @@ export class Menu extends React.PureComponent<Props> {
}
return (
<li key="background_tasks">
- <Link activeClassName="active" to={{ pathname: '/project/background_tasks', query }}>
+ <NavLink
+ to={{
+ pathname: '/project/background_tasks',
+ search: new URLSearchParams(query).toString()
+ }}>
{translate('background_tasks.page')}
- </Link>
+ </NavLink>
</li>
);
};
@@ -495,9 +508,9 @@ export class Menu extends React.PureComponent<Props> {
}
return (
<li key="update_key">
- <Link activeClassName="active" to={{ pathname: '/project/key', query }}>
+ <NavLink to={{ pathname: '/project/key', search: new URLSearchParams(query).toString() }}>
{translate('update_key.page')}
- </Link>
+ </NavLink>
</li>
);
};
@@ -508,9 +521,10 @@ export class Menu extends React.PureComponent<Props> {
}
return (
<li key="webhooks">
- <Link activeClassName="active" to={{ pathname: '/project/webhooks', query }}>
+ <NavLink
+ to={{ pathname: '/project/webhooks', search: new URLSearchParams(query).toString() }}>
{translate('webhooks.page')}
- </Link>
+ </NavLink>
</li>
);
};
@@ -534,9 +548,10 @@ export class Menu extends React.PureComponent<Props> {
return (
<li key="project_delete">
- <Link activeClassName="active" to={{ pathname: '/project/deletion', query }}>
+ <NavLink
+ to={{ pathname: '/project/deletion', search: new URLSearchParams(query).toString() }}>
{translate('deletion.page')}
- </Link>
+ </NavLink>
</li>
);
};
@@ -546,9 +561,7 @@ export class Menu extends React.PureComponent<Props> {
const query = { ...baseQuery, qualifier: this.props.component.qualifier };
return (
<li key={key}>
- <Link activeClassName="active" to={{ pathname, query }}>
- {name}
- </Link>
+ <NavLink to={{ pathname, search: new URLSearchParams(query).toString() }}>{name}</NavLink>
</li>
);
};
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/Breadcrumb-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/Breadcrumb-test.tsx.snap
index 161aff0449d..e78e2c5452a 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/Breadcrumb-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/Breadcrumb-test.tsx.snap
@@ -14,15 +14,11 @@ exports[`should render correctly 1`] = `
/>
<Link
className="link-no-underline text-ellipsis"
- onlyActiveOnIndex={false}
- style={Object {}}
title="parent-portfolio"
to={
Object {
"pathname": "/portfolio",
- "query": Object {
- "id": "parent-portfolio",
- },
+ "search": "?id=parent-portfolio",
}
}
>
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavLicenseNotif-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavLicenseNotif-test.tsx.snap
index 2dd678f30a4..30fcabe4912 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavLicenseNotif-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavLicenseNotif-test.tsx.snap
@@ -20,8 +20,6 @@ exports[`renders background task license info correctly 1`] = `
Foo
</span>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to="/admin/extension/license/app"
>
license.component_navigation.button.LICENSING
@@ -55,8 +53,6 @@ exports[`renders correctly for LICENSING_LOC error 1`] = `
Foo
</span>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to="/admin/extension/license/app"
>
license.component_navigation.button.LICENSING_LOC
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavProjectBindingErrorNotif-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavProjectBindingErrorNotif-test.tsx.snap
index 4ef746752eb..86ee8a11613 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavProjectBindingErrorNotif-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavProjectBindingErrorNotif-test.tsx.snap
@@ -28,15 +28,10 @@ exports[`should render correctly: project admin 1`] = `
values={
Object {
"action": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/project/settings",
- "query": Object {
- "category": "pull_request_decoration_binding",
- "id": "my-project",
- },
+ "search": "?id=my-project&category=pull_request_decoration_binding",
}
}
>
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/Menu-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/Menu-test.tsx.snap
index 87467c85c81..0ff47f8d61a 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/Menu-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/Menu-test.tsx.snap
@@ -86,21 +86,16 @@ exports[`should disable links if application has inaccessible projects 1`] = `
className="menu"
>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/deletion",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
deletion.page
- </Link>
+ </NavLink>
</li>
</ul>
}
@@ -130,21 +125,16 @@ exports[`should disable links if no analysis has been done 1`] = `
>
<NavBarTabs>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "id": "foo",
- },
+ "search": "?id=foo",
}
}
>
overview.page
- </Link>
+ </NavLink>
</li>
<li>
<Tooltip
@@ -233,22 +223,16 @@ exports[`should render correctly for security extensions 1`] = `
className="menu"
>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/extension/component-bar",
- "query": Object {
- "id": "foo",
- "qualifier": "TRK",
- },
+ "search": "id=foo&qualifier=TRK",
}
}
>
ComponentBar
- </Link>
+ </NavLink>
</li>
</ul>
}
@@ -266,113 +250,76 @@ exports[`should work for a branch 1`] = `
>
<NavBarTabs>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": "release",
- "id": "foo",
- },
+ "search": "?id=foo&branch=release",
}
}
>
overview.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/issues",
- "query": Object {
- "branch": "release",
- "id": "foo",
- "resolved": "false",
- },
+ "search": "id=foo&branch=release&resolved=false",
}
}
>
issues.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/security_hotspots",
- "query": Object {
- "branch": "release",
- "id": "foo",
- },
+ "search": "id=foo&branch=release",
}
}
>
layout.security_hotspots
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/component_measures",
- "query": Object {
- "branch": "release",
- "id": "foo",
- },
+ "search": "id=foo&branch=release",
}
}
>
layout.measures
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/code",
- "query": Object {
- "branch": "release",
- "id": "foo",
- },
+ "search": "id=foo&branch=release",
}
}
>
code.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/activity",
- "query": Object {
- "branch": "release",
- "id": "foo",
- },
+ "search": "id=foo&branch=release",
}
}
>
project_activity.page
- </Link>
+ </NavLink>
</li>
<Dropdown
data-test="extensions"
@@ -381,23 +328,16 @@ exports[`should work for a branch 1`] = `
className="menu"
>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/extension/component-foo",
- "query": Object {
- "branch": "release",
- "id": "foo",
- "qualifier": "TRK",
- },
+ "search": "id=foo&branch=release&qualifier=TRK",
}
}
>
ComponentFoo
- </Link>
+ </NavLink>
</li>
</ul>
}
@@ -414,112 +354,76 @@ exports[`should work for a branch 1`] = `
className="menu"
>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/settings",
- "query": Object {
- "branch": "release",
- "id": "foo",
- },
+ "search": "id=foo&branch=release",
}
}
>
project_settings.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/branches",
- "query": Object {
- "branch": "release",
- "id": "foo",
- },
+ "search": "id=foo&branch=release",
}
}
>
project_branch_pull_request.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/baseline",
- "query": Object {
- "branch": "release",
- "id": "foo",
- },
+ "search": "id=foo&branch=release",
}
}
>
project_baseline.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/import_export",
- "query": Object {
- "branch": "release",
- "id": "foo",
- },
+ "search": "id=foo&branch=release",
}
}
>
project_dump.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/webhooks",
- "query": Object {
- "branch": "release",
- "id": "foo",
- },
+ "search": "id=foo&branch=release",
}
}
>
webhooks.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/deletion",
- "query": Object {
- "branch": "release",
- "id": "foo",
- },
+ "search": "id=foo&branch=release",
}
}
>
deletion.page
- </Link>
+ </NavLink>
</li>
</ul>
}
@@ -550,113 +454,76 @@ exports[`should work for a branch 2`] = `
>
<NavBarTabs>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": "release",
- "id": "foo",
- },
+ "search": "?id=foo&branch=release",
}
}
>
overview.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/issues",
- "query": Object {
- "branch": "release",
- "id": "foo",
- "resolved": "false",
- },
+ "search": "id=foo&branch=release&resolved=false",
}
}
>
issues.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/security_hotspots",
- "query": Object {
- "branch": "release",
- "id": "foo",
- },
+ "search": "id=foo&branch=release",
}
}
>
layout.security_hotspots
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/component_measures",
- "query": Object {
- "branch": "release",
- "id": "foo",
- },
+ "search": "id=foo&branch=release",
}
}
>
layout.measures
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/code",
- "query": Object {
- "branch": "release",
- "id": "foo",
- },
+ "search": "id=foo&branch=release",
}
}
>
code.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/activity",
- "query": Object {
- "branch": "release",
- "id": "foo",
- },
+ "search": "id=foo&branch=release",
}
}
>
project_activity.page
- </Link>
+ </NavLink>
</li>
<Dropdown
data-test="extensions"
@@ -665,23 +532,16 @@ exports[`should work for a branch 2`] = `
className="menu"
>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/extension/component-foo",
- "query": Object {
- "branch": "release",
- "id": "foo",
- "qualifier": "TRK",
- },
+ "search": "id=foo&branch=release&qualifier=TRK",
}
}
>
ComponentFoo
- </Link>
+ </NavLink>
</li>
</ul>
}
@@ -714,95 +574,64 @@ exports[`should work for pull requests 1`] = `
>
<NavBarTabs>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "id": "foo",
- "pullRequest": "1001",
- },
+ "search": "?id=foo&pullRequest=1001",
}
}
>
overview.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/issues",
- "query": Object {
- "id": "foo",
- "pullRequest": "1001",
- "resolved": "false",
- },
+ "search": "id=foo&pullRequest=1001&resolved=false",
}
}
>
issues.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/security_hotspots",
- "query": Object {
- "id": "foo",
- "pullRequest": "1001",
- },
+ "search": "id=foo&pullRequest=1001",
}
}
>
layout.security_hotspots
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/component_measures",
- "query": Object {
- "id": "foo",
- "pullRequest": "1001",
- },
+ "search": "id=foo&pullRequest=1001",
}
}
>
layout.measures
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/code",
- "query": Object {
- "id": "foo",
- "pullRequest": "1001",
- },
+ "search": "id=foo&pullRequest=1001",
}
}
>
code.page
- </Link>
+ </NavLink>
</li>
<Dropdown
data-test="extensions"
@@ -811,23 +640,16 @@ exports[`should work for pull requests 1`] = `
className="menu"
>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/extension/component-foo",
- "query": Object {
- "id": "foo",
- "pullRequest": "1001",
- "qualifier": "TRK",
- },
+ "search": "id=foo&pullRequest=1001&qualifier=TRK",
}
}
>
ComponentFoo
- </Link>
+ </NavLink>
</li>
</ul>
}
@@ -846,95 +668,64 @@ exports[`should work for pull requests 2`] = `
>
<NavBarTabs>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "id": "foo",
- "pullRequest": "1001",
- },
+ "search": "?id=foo&pullRequest=1001",
}
}
>
overview.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/issues",
- "query": Object {
- "id": "foo",
- "pullRequest": "1001",
- "resolved": "false",
- },
+ "search": "id=foo&pullRequest=1001&resolved=false",
}
}
>
issues.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/security_hotspots",
- "query": Object {
- "id": "foo",
- "pullRequest": "1001",
- },
+ "search": "id=foo&pullRequest=1001",
}
}
>
layout.security_hotspots
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/component_measures",
- "query": Object {
- "id": "foo",
- "pullRequest": "1001",
- },
+ "search": "id=foo&pullRequest=1001",
}
}
>
layout.measures
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/code",
- "query": Object {
- "id": "foo",
- "pullRequest": "1001",
- },
+ "search": "id=foo&pullRequest=1001",
}
}
>
code.page
- </Link>
+ </NavLink>
</li>
<Dropdown
data-test="extensions"
@@ -943,23 +734,16 @@ exports[`should work for pull requests 2`] = `
className="menu"
>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/extension/component-foo",
- "query": Object {
- "id": "foo",
- "pullRequest": "1001",
- "qualifier": "TRK",
- },
+ "search": "id=foo&pullRequest=1001&qualifier=TRK",
}
}
>
ComponentFoo
- </Link>
+ </NavLink>
</li>
</ul>
}
@@ -978,107 +762,76 @@ exports[`should work for qualifier: APP, false 1`] = `
>
<NavBarTabs>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "id": "foo",
- },
+ "search": "?id=foo",
}
}
>
overview.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/issues",
- "query": Object {
- "id": "foo",
- "resolved": "false",
- },
+ "search": "id=foo&resolved=false",
}
}
>
issues.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/security_hotspots",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
layout.security_hotspots
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/component_measures",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
layout.measures
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/code",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
view_projects.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/activity",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
project_activity.page
- </Link>
+ </NavLink>
</li>
</NavBarTabs>
<NavBarTabs>
@@ -1089,21 +842,16 @@ exports[`should work for qualifier: APP, false 1`] = `
className="menu"
>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/deletion",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
deletion.page
- </Link>
+ </NavLink>
</li>
</ul>
}
@@ -1134,56 +882,40 @@ exports[`should work for qualifier: SVW, false 1`] = `
>
<NavBarTabs>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/issues",
- "query": Object {
- "id": "foo",
- "resolved": "false",
- },
+ "search": "id=foo&resolved=false",
}
}
>
issues.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/component_measures",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
layout.measures
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/activity",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
project_activity.page
- </Link>
+ </NavLink>
</li>
</NavBarTabs>
<NavBarTabs />
@@ -1196,90 +928,64 @@ exports[`should work for qualifier: SVW, true 1`] = `
>
<NavBarTabs>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/portfolio",
- "query": Object {
- "id": "foo",
- },
+ "search": "?id=foo",
}
}
>
overview.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/code",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
portfolio_breakdown.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/issues",
- "query": Object {
- "id": "foo",
- "resolved": "false",
- },
+ "search": "id=foo&resolved=false",
}
}
>
issues.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/component_measures",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
layout.measures
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/activity",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
project_activity.page
- </Link>
+ </NavLink>
</li>
</NavBarTabs>
<NavBarTabs />
@@ -1292,107 +998,76 @@ exports[`should work for qualifier: TRK, false 1`] = `
>
<NavBarTabs>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "id": "foo",
- },
+ "search": "?id=foo",
}
}
>
overview.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/issues",
- "query": Object {
- "id": "foo",
- "resolved": "false",
- },
+ "search": "id=foo&resolved=false",
}
}
>
issues.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/security_hotspots",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
layout.security_hotspots
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/component_measures",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
layout.measures
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/code",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
code.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/activity",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
project_activity.page
- </Link>
+ </NavLink>
</li>
</NavBarTabs>
<NavBarTabs>
@@ -1403,106 +1078,76 @@ exports[`should work for qualifier: TRK, false 1`] = `
className="menu"
>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/settings",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
project_settings.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/branches",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
project_branch_pull_request.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/baseline",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
project_baseline.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/import_export",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
project_dump.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/webhooks",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
webhooks.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/deletion",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
deletion.page
- </Link>
+ </NavLink>
</li>
</ul>
}
@@ -1533,56 +1178,40 @@ exports[`should work for qualifier: VW, false 1`] = `
>
<NavBarTabs>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/issues",
- "query": Object {
- "id": "foo",
- "resolved": "false",
- },
+ "search": "id=foo&resolved=false",
}
}
>
issues.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/component_measures",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
layout.measures
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/activity",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
project_activity.page
- </Link>
+ </NavLink>
</li>
</NavBarTabs>
<NavBarTabs>
@@ -1593,21 +1222,16 @@ exports[`should work for qualifier: VW, false 1`] = `
className="menu"
>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/deletion",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
deletion.page
- </Link>
+ </NavLink>
</li>
</ul>
}
@@ -1625,90 +1249,64 @@ exports[`should work for qualifier: VW, true 1`] = `
>
<NavBarTabs>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/portfolio",
- "query": Object {
- "id": "foo",
- },
+ "search": "?id=foo",
}
}
>
overview.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/code",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
portfolio_breakdown.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/issues",
- "query": Object {
- "id": "foo",
- "resolved": "false",
- },
+ "search": "id=foo&resolved=false",
}
}
>
issues.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/component_measures",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
layout.measures
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/activity",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
project_activity.page
- </Link>
+ </NavLink>
</li>
</NavBarTabs>
<NavBarTabs>
@@ -1719,21 +1317,16 @@ exports[`should work for qualifier: VW, true 1`] = `
className="menu"
>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/deletion",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
deletion.page
- </Link>
+ </NavLink>
</li>
</ul>
}
@@ -1753,22 +1346,16 @@ exports[`should work with extensions 1`] = `
className="menu"
>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/extension/component-foo",
- "query": Object {
- "id": "foo",
- "qualifier": "TRK",
- },
+ "search": "id=foo&qualifier=TRK",
}
}
>
ComponentFoo
- </Link>
+ </NavLink>
</li>
</ul>
}
@@ -1786,124 +1373,88 @@ exports[`should work with extensions 2`] = `
className="menu"
>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/settings",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
project_settings.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/branches",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
project_branch_pull_request.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/baseline",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
project_baseline.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/admin/extension/foo",
- "query": Object {
- "id": "foo",
- "qualifier": "TRK",
- },
+ "search": "id=foo&qualifier=TRK",
}
}
>
Foo
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/import_export",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
project_dump.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/webhooks",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
webhooks.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/deletion",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
deletion.page
- </Link>
+ </NavLink>
</li>
</ul>
}
@@ -1921,40 +1472,28 @@ exports[`should work with multiple extensions 1`] = `
className="menu"
>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/extension/component-foo",
- "query": Object {
- "id": "foo",
- "qualifier": "TRK",
- },
+ "search": "id=foo&qualifier=TRK",
}
}
>
ComponentFoo
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/extension/component-bar",
- "query": Object {
- "id": "foo",
- "qualifier": "TRK",
- },
+ "search": "id=foo&qualifier=TRK",
}
}
>
ComponentBar
- </Link>
+ </NavLink>
</li>
</ul>
}
@@ -1972,142 +1511,100 @@ exports[`should work with multiple extensions 2`] = `
className="menu"
>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/settings",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
project_settings.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/branches",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
project_branch_pull_request.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/baseline",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
project_baseline.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/admin/extension/foo",
- "query": Object {
- "id": "foo",
- "qualifier": "TRK",
- },
+ "search": "id=foo&qualifier=TRK",
}
}
>
Foo
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/admin/extension/bar",
- "query": Object {
- "id": "foo",
- "qualifier": "TRK",
- },
+ "search": "id=foo&qualifier=TRK",
}
}
>
Bar
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/import_export",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
project_dump.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/webhooks",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
webhooks.page
- </Link>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to={
Object {
"pathname": "/project/deletion",
- "query": Object {
- "id": "foo",
- },
+ "search": "id=foo",
}
}
>
deletion.page
- </Link>
+ </NavLink>
</li>
</ul>
}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/CurrentBranchLike.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/CurrentBranchLike.tsx
index 9512d929c2a..32ec2231bf3 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/CurrentBranchLike.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/CurrentBranchLike.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import DocumentationTooltip from '../../../../../components/common/DocumentationTooltip';
import HelpTooltip from '../../../../../components/controls/HelpTooltip';
import BranchLikeIcon from '../../../../../components/icons/BranchLikeIcon';
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/Menu.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/Menu.tsx
index bbfb7738e85..8e5f1127d15 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/Menu.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/Menu.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { DropdownOverlay } from '../../../../../components/controls/Dropdown';
import SearchBox from '../../../../../components/controls/SearchBox';
import { Router, withRouter } from '../../../../../components/hoc/withRouter';
@@ -30,7 +30,7 @@ import {
} from '../../../../../helpers/branch-like';
import { KeyboardKeys } from '../../../../../helpers/keycodes';
import { translate } from '../../../../../helpers/l10n';
-import { getBranchLikeUrl } from '../../../../../helpers/urls';
+import { getBranchLikeUrl, queryToSearch } from '../../../../../helpers/urls';
import { BranchLike, BranchLikeTree } from '../../../../../types/branch-like';
import { ComponentQualifier } from '../../../../../types/component';
import { Component } from '../../../../../types/types';
@@ -42,7 +42,7 @@ interface Props {
component: Component;
currentBranchLike: BranchLike;
onClose: () => void;
- router: Pick<Router, 'push'>;
+ router: Router;
}
interface State {
@@ -190,7 +190,7 @@ export class Menu extends React.PureComponent<Props, State> {
<div className="hint-container text-right">
<Link
onClick={() => onClose()}
- to={{ pathname: '/project/branches', query: { id: component.key } }}>
+ to={{ pathname: '/project/branches', search: queryToSearch({ id: component.key }) }}>
{translate('branch_like_navigation.manage')}
</Link>
</div>
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/Menu-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/Menu-test.tsx
index 261de2df20e..b11f3a30f7a 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/Menu-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/Menu-test.tsx
@@ -19,7 +19,7 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import SearchBox from '../../../../../../components/controls/SearchBox';
import { KeyboardKeys } from '../../../../../../helpers/keycodes';
import {
@@ -29,6 +29,7 @@ import {
import { mockComponent } from '../../../../../../helpers/mocks/component';
import { mockRouter } from '../../../../../../helpers/testMocks';
import { click, mockEvent } from '../../../../../../helpers/testUtils';
+import { queryToSearch } from '../../../../../../helpers/urls';
import { Menu } from '../Menu';
import { MenuItemList } from '../MenuItemList';
@@ -67,10 +68,10 @@ it('should change url and close menu when an element is selected', () => {
expect(onClose).toHaveBeenCalled();
expect(push).toHaveBeenCalledWith(
expect.objectContaining({
- query: {
+ search: queryToSearch({
id: component.key,
pullRequest: pr.key
- }
+ })
})
);
});
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/CurrentBranchLike-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/CurrentBranchLike-test.tsx.snap
index 1548a5317c3..e45e2c250b4 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/CurrentBranchLike-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/CurrentBranchLike-test.tsx.snap
@@ -77,14 +77,10 @@ exports[`applications should render correctly when there is only one branch and
className="spacer-top spacer-bottom"
/>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/project/admin/extension/developer-server/application-console",
- "query": Object {
- "id": "my-project",
- },
+ "search": "?id=my-project",
}
}
>
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/Menu-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/Menu-test.tsx.snap
index ce2339c7560..df18a2f2f5a 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/Menu-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/Menu-test.tsx.snap
@@ -148,14 +148,10 @@ exports[`should render correctly 1`] = `
>
<Link
onClick={[Function]}
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/project/branches",
- "query": Object {
- "id": "my-project",
- },
+ "search": "?id=my-project",
}
}
>
@@ -313,14 +309,10 @@ exports[`should render correctly with no current branch like 1`] = `
>
<Link
onClick={[Function]}
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/project/branches",
- "query": Object {
- "id": "my-project",
- },
+ "search": "?id=my-project",
}
}
>
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/utils-test.ts b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/utils-test.ts
index 37913495f2c..ecee525da3c 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/utils-test.ts
+++ b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/utils-test.ts
@@ -17,14 +17,13 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { Location } from '../../../../../../../helpers/urls';
+import { Location } from '../../../../../../../components/hoc/withRouter';
import { BadgeOptions, BadgeType, getBadgeSnippet, getBadgeUrl } from '../utils';
jest.mock('../../../../../../../helpers/urls', () => ({
...jest.requireActual('../../../../../../../helpers/urls'),
getHostUrl: () => 'host',
- getPathUrlAsString: (o: Location) =>
- `host${o.pathname}?id=${o.query ? o.query.id : ''}&branch=${o.query ? o.query.branch : ''}`
+ getPathUrlAsString: (o: Location) => `host${o.pathname}${o.search}`
}));
const options: BadgeOptions = {
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/utils.ts b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/utils.ts
index 4e866f51747..8c8dcc73253 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/utils.ts
+++ b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/utils.ts
@@ -43,27 +43,27 @@ export function getBadgeSnippet(type: BadgeType, options: BadgeOptions, token: s
if (format === 'url') {
return url;
- } else {
- let label;
- let projectUrl;
+ }
- switch (type) {
- case BadgeType.measure:
- label = getLocalizedMetricName({ key: metric });
- break;
- case BadgeType.qualityGate:
- default:
- label = 'Quality gate';
- break;
- }
+ let label;
+ let projectUrl;
- if (project) {
- projectUrl = getPathUrlAsString(getProjectUrl(project, branch), false);
- }
+ switch (type) {
+ case BadgeType.measure:
+ label = getLocalizedMetricName({ key: metric });
+ break;
+ case BadgeType.qualityGate:
+ default:
+ label = 'Quality gate';
+ break;
+ }
- const mdImage = `![${label}](${url})`;
- return projectUrl ? `[${mdImage}](${projectUrl})` : mdImage;
+ if (project) {
+ projectUrl = getPathUrlAsString(getProjectUrl(project, branch), false);
}
+
+ const mdImage = `![${label}](${url})`;
+ return projectUrl ? `[${mdImage}](${projectUrl})` : mdImage;
}
export function getBadgeUrl(
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/MetaQualityGate.tsx b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/MetaQualityGate.tsx
index 84333914751..379997fccab 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/MetaQualityGate.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/MetaQualityGate.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { translate } from '../../../../../../helpers/l10n';
import { getQualityGateUrl } from '../../../../../../helpers/urls';
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/MetaQualityProfiles.tsx b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/MetaQualityProfiles.tsx
index 0f0fc343ff5..de1836db9fe 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/MetaQualityProfiles.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/MetaQualityProfiles.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { searchRules } from '../../../../../../api/rules';
import Tooltip from '../../../../../../components/controls/Tooltip';
import { translate, translateWithParameters } from '../../../../../../helpers/l10n';
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/__snapshots__/MetaQualityProfiles-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/__snapshots__/MetaQualityProfiles-test.tsx.snap
index 2e585fd377d..3f7561213ee 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/__snapshots__/MetaQualityProfiles-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/__snapshots__/MetaQualityProfiles-test.tsx.snap
@@ -43,15 +43,10 @@ exports[`should render correctly 1`] = `
)
</span>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/profiles/show",
- "query": Object {
- "language": "css",
- "name": "name",
- },
+ "search": "?name=name&language=css",
}
}
>
@@ -110,15 +105,10 @@ exports[`should render correctly 2`] = `
)
</span>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/profiles/show",
- "query": Object {
- "language": "css",
- "name": "name",
- },
+ "search": "?name=name&language=css",
}
}
>
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavBranding.tsx b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavBranding.tsx
index 6a952f3cc4b..5e0231911bc 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavBranding.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavBranding.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { translate } from '../../../../helpers/l10n';
import { getBaseUrl } from '../../../../helpers/system';
import { AppState } from '../../../../types/appstate';
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.tsx b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.tsx
index e608efc5955..866004c5051 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.tsx
@@ -19,7 +19,7 @@
*/
import classNames from 'classnames';
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link, NavLink } from 'react-router-dom';
import { isMySet } from '../../../../apps/issues/utils';
import Dropdown from '../../../../components/controls/Dropdown';
import DropdownIcon from '../../../../components/icons/DropdownIcon';
@@ -37,6 +37,7 @@ interface Props {
location: { pathname: string };
}
+const ACTIVE_CLASS_NAME = 'active';
export class GlobalNavMenu extends React.PureComponent<Props> {
renderProjects() {
const active =
@@ -55,9 +56,9 @@ export class GlobalNavMenu extends React.PureComponent<Props> {
renderPortfolios() {
return (
<li>
- <Link activeClassName="active" to="/portfolios">
+ <NavLink className={({ isActive }) => (isActive ? ACTIVE_CLASS_NAME : '')} to="/portfolios">
{translate('portfolios.page')}
- </Link>
+ </NavLink>
</li>
);
}
@@ -65,13 +66,13 @@ export class GlobalNavMenu extends React.PureComponent<Props> {
renderIssuesLink() {
const active = this.props.location.pathname.startsWith('/issues');
- const query =
- this.props.currentUser.isLoggedIn && isMySet()
- ? { resolved: 'false', myIssues: 'true' }
- : { resolved: 'false' };
+ const search = (this.props.currentUser.isLoggedIn && isMySet()
+ ? new URLSearchParams({ resolved: 'false', myIssues: 'true' })
+ : new URLSearchParams({ resolved: 'false' })
+ ).toString();
return (
<li>
- <Link className={classNames({ active })} to={{ pathname: '/issues', query }}>
+ <Link className={classNames({ active })} to={{ pathname: '/issues', search }}>
{translate('issues.page')}
</Link>
</li>
@@ -81,9 +82,11 @@ export class GlobalNavMenu extends React.PureComponent<Props> {
renderRulesLink() {
return (
<li>
- <Link activeClassName="active" to="/coding_rules">
+ <NavLink
+ className={({ isActive }) => (isActive ? ACTIVE_CLASS_NAME : '')}
+ to="/coding_rules">
{translate('coding_rules.page')}
- </Link>
+ </NavLink>
</li>
);
}
@@ -91,9 +94,9 @@ export class GlobalNavMenu extends React.PureComponent<Props> {
renderProfilesLink() {
return (
<li>
- <Link activeClassName="active" to="/profiles">
+ <NavLink className={({ isActive }) => (isActive ? ACTIVE_CLASS_NAME : '')} to="/profiles">
{translate('quality_profiles.page')}
- </Link>
+ </NavLink>
</li>
);
}
@@ -101,9 +104,11 @@ export class GlobalNavMenu extends React.PureComponent<Props> {
renderQualityGatesLink() {
return (
<li>
- <Link activeClassName="active" to={getQualityGatesUrl()}>
+ <NavLink
+ className={({ isActive }) => (isActive ? ACTIVE_CLASS_NAME : '')}
+ to={getQualityGatesUrl()}>
{translate('quality_gates.page')}
- </Link>
+ </NavLink>
</li>
);
}
@@ -115,9 +120,9 @@ export class GlobalNavMenu extends React.PureComponent<Props> {
return (
<li>
- <Link activeClassName="active" to="/admin">
+ <NavLink className={({ isActive }) => (isActive ? ACTIVE_CLASS_NAME : '')} to="/admin">
{translate('layout.settings')}
- </Link>
+ </NavLink>
</li>
);
}
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.tsx b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.tsx
index 91079564dbe..3a74eb08d06 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import Dropdown from '../../../../components/controls/Dropdown';
import { Router, withRouter } from '../../../../components/hoc/withRouter';
import Avatar from '../../../../components/ui/Avatar';
@@ -29,7 +29,7 @@ import { rawSizes } from '../../../theme';
interface Props {
currentUser: CurrentUser;
- router: Pick<Router, 'push'>;
+ router: Router;
}
export class GlobalNavUser extends React.PureComponent<Props> {
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavMenu-test.tsx b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavMenu-test.tsx
index 1a56df67d0f..5afe85b9f35 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavMenu-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavMenu-test.tsx
@@ -17,9 +17,10 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { shallow } from 'enzyme';
+import { screen } from '@testing-library/dom';
import * as React from 'react';
-import { mockAppState } from '../../../../../helpers/testMocks';
+import { mockAppState, mockCurrentUser } from '../../../../../helpers/testMocks';
+import { renderComponent } from '../../../../../helpers/testReactTestingUtils';
import { GlobalNavMenu } from '../GlobalNavMenu';
it('should work with extensions', () => {
@@ -27,13 +28,12 @@ it('should work with extensions', () => {
globalPages: [{ key: 'foo', name: 'Foo' }],
qualifiers: ['TRK']
});
+
const currentUser = {
isLoggedIn: false
};
- const wrapper = shallow(
- <GlobalNavMenu appState={appState} currentUser={currentUser} location={{ pathname: '' }} />
- );
- expect(wrapper.find('Dropdown')).toMatchSnapshot();
+ renderGlobalNavMenu({ appState, currentUser });
+ expect(screen.getByText('more')).toBeInTheDocument();
});
it('should show administration menu if the user has the rights', () => {
@@ -45,8 +45,17 @@ it('should show administration menu if the user has the rights', () => {
const currentUser = {
isLoggedIn: false
};
- const wrapper = shallow(
- <GlobalNavMenu appState={appState} currentUser={currentUser} location={{ pathname: '' }} />
- );
- expect(wrapper).toMatchSnapshot();
+
+ renderGlobalNavMenu({ appState, currentUser });
+ expect(screen.getByText('layout.settings')).toBeInTheDocument();
});
+
+function renderGlobalNavMenu({
+ appState = mockAppState(),
+ currentUser = mockCurrentUser(),
+ location = { pathname: '' }
+}: Partial<GlobalNavMenu['props']>) {
+ renderComponent(
+ <GlobalNavMenu appState={appState} currentUser={currentUser} location={location} />
+ );
+}
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavBranding-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavBranding-test.tsx.snap
index 5419fd45cbb..9fe279984b6 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavBranding-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavBranding-test.tsx.snap
@@ -3,8 +3,6 @@
exports[`should render correctly: default 1`] = `
<Link
className="navbar-brand"
- onlyActiveOnIndex={false}
- style={Object {}}
to="/"
>
<img
@@ -20,8 +18,6 @@ exports[`should render correctly: default 1`] = `
exports[`should render correctly: with logo 1`] = `
<Link
className="navbar-brand"
- onlyActiveOnIndex={false}
- style={Object {}}
to="/"
>
<img
@@ -37,8 +33,6 @@ exports[`should render correctly: with logo 1`] = `
exports[`should render correctly: with logo and width 1`] = `
<Link
className="navbar-brand"
- onlyActiveOnIndex={false}
- style={Object {}}
to="/"
>
<img
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavMenu-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavMenu-test.tsx.snap
deleted file mode 100644
index e59e925873f..00000000000
--- a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavMenu-test.tsx.snap
+++ /dev/null
@@ -1,102 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should show administration menu if the user has the rights 1`] = `
-<ul
- className="global-navbar-menu"
->
- <li>
- <Link
- className=""
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/projects"
- >
- projects.page
- </Link>
- </li>
- <li>
- <Link
- className=""
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/issues",
- "query": Object {
- "resolved": "false",
- },
- }
- }
- >
- issues.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/coding_rules"
- >
- coding_rules.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/profiles"
- >
- quality_profiles.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/quality_gates",
- }
- }
- >
- quality_gates.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/admin"
- >
- layout.settings
- </Link>
- </li>
-</ul>
-`;
-
-exports[`should work with extensions 1`] = `
-<Dropdown
- overlay={
- <ul
- className="menu"
- >
- <li>
- <Link
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/extension/foo"
- >
- Foo
- </Link>
- </li>
- </ul>
- }
- tagName="li"
->
- <Component />
-</Dropdown>
-`;
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavUser-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavUser-test.tsx.snap
index f131dfc9555..40956326a7d 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavUser-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavUser-test.tsx.snap
@@ -36,8 +36,6 @@ exports[`should render the right interface for logged in user 1`] = `
/>
<li>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to="/account"
>
my_account.page
diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx b/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx
index 0dd3a155f42..3a1381823d3 100644
--- a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx
@@ -19,8 +19,9 @@
*/
import classNames from 'classnames';
import * as React from 'react';
-import { IndexLink, Link } from 'react-router';
+import { Location, NavLink } from 'react-router-dom';
import Dropdown from '../../../../components/controls/Dropdown';
+import withLocation from '../../../../components/hoc/withLocation';
import DropdownIcon from '../../../../components/icons/DropdownIcon';
import ContextNavBar from '../../../../components/ui/ContextNavBar';
import NavBarTabs from '../../../../components/ui/NavBarTabs';
@@ -37,19 +38,20 @@ interface Props {
extensions: Extension[];
fetchPendingPlugins: () => void;
fetchSystemStatus: () => void;
+ location: Location;
pendingPlugins: PendingPluginResult;
systemStatus: SysStatus;
}
-export default class SettingsNav extends React.PureComponent<Props> {
+export class SettingsNav extends React.PureComponent<Props> {
static defaultProps = {
extensions: []
};
- isSomethingActive(urls: string[]): boolean {
- const path = window.location.pathname;
+ isSomethingActive = (urls: string[]) => {
+ const path = this.props.location.pathname;
return urls.some((url: string) => path.indexOf(getBaseUrl() + url) === 0);
- }
+ };
isSecurityActive() {
const urls = [
@@ -84,9 +86,7 @@ export default class SettingsNav extends React.PureComponent<Props> {
renderExtension = ({ key, name }: Extension) => {
return (
<li key={key}>
- <Link activeClassName="active" to={`/admin/extension/${key}`}>
- {name}
- </Link>
+ <NavLink to={`/admin/extension/${key}`}>{name}</NavLink>
</li>
);
};
@@ -100,19 +100,19 @@ export default class SettingsNav extends React.PureComponent<Props> {
overlay={
<ul className="menu">
<li>
- <IndexLink activeClassName="active" to="/admin/settings">
+ <NavLink end={true} to="/admin/settings">
{translate('settings.page')}
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink activeClassName="active" to="/admin/settings/encryption">
+ <NavLink end={true} to="/admin/settings/encryption">
{translate('property.category.security.encryption')}
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink activeClassName="active" to="/admin/webhooks">
+ <NavLink end={true} to="/admin/webhooks">
{translate('webhooks.page')}
- </IndexLink>
+ </NavLink>
</li>
{extensionsWithoutSupport.map(this.renderExtension)}
</ul>
@@ -150,14 +150,14 @@ export default class SettingsNav extends React.PureComponent<Props> {
overlay={
<ul className="menu">
<li>
- <IndexLink activeClassName="active" to="/admin/projects_management">
+ <NavLink end={true} to="/admin/projects_management">
{translate('management')}
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink activeClassName="active" to="/admin/background_tasks">
+ <NavLink end={true} to="/admin/background_tasks">
{translate('background_tasks.page')}
- </IndexLink>
+ </NavLink>
</li>
</ul>
}
@@ -184,24 +184,24 @@ export default class SettingsNav extends React.PureComponent<Props> {
overlay={
<ul className="menu">
<li>
- <IndexLink activeClassName="active" to="/admin/users">
+ <NavLink end={true} to="/admin/users">
{translate('users.page')}
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink activeClassName="active" to="/admin/groups">
+ <NavLink end={true} to="/admin/groups">
{translate('user_groups.page')}
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink activeClassName="active" to="/admin/permissions">
+ <NavLink end={true} to="/admin/permissions">
{translate('global_permissions.page')}
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink activeClassName="active" to="/admin/permission_templates">
+ <NavLink end={true} to="/admin/permission_templates">
{translate('permission_templates')}
- </IndexLink>
+ </NavLink>
</li>
</ul>
}
@@ -262,30 +262,30 @@ export default class SettingsNav extends React.PureComponent<Props> {
{this.renderProjectsTab()}
<li>
- <IndexLink activeClassName="active" to="/admin/system">
+ <NavLink end={true} to="/admin/system">
{translate('sidebar.system')}
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink activeClassName="active" to="/admin/marketplace">
+ <NavLink end={true} to="/admin/marketplace">
{translate('marketplace.page')}
- </IndexLink>
+ </NavLink>
</li>
{hasGovernanceExtension && (
<li>
- <IndexLink activeClassName="active" to="/admin/audit">
+ <NavLink end={true} to="/admin/audit">
{translate('audit_logs.page')}
- </IndexLink>
+ </NavLink>
</li>
)}
{hasSupportExtension && (
<li>
- <IndexLink activeClassName="active" to="/admin/extension/license/support">
+ <NavLink end={true} to="/admin/extension/license/support">
{translate('support')}
- </IndexLink>
+ </NavLink>
</li>
)}
</NavBarTabs>
@@ -293,3 +293,5 @@ export default class SettingsNav extends React.PureComponent<Props> {
);
}
}
+
+export default withLocation(SettingsNav);
diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/SystemRestartNotif.tsx b/server/sonar-web/src/main/js/app/components/nav/settings/SystemRestartNotif.tsx
index b127a6f2f09..b16dca9e9cb 100644
--- a/server/sonar-web/src/main/js/app/components/nav/settings/SystemRestartNotif.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/settings/SystemRestartNotif.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { Alert } from '../../../../components/ui/Alert';
import { translate } from '../../../../helpers/l10n';
import { getInstance } from '../../../../helpers/system';
@@ -32,11 +32,7 @@ export default function SystemRestartNotif() {
id="system.instance_restarting"
values={{
instance: getInstance(),
- link: (
- <Link to={{ pathname: '/admin/background_tasks' }}>
- {translate('background_tasks.page')}
- </Link>
- )
+ link: <Link to="/admin/background_tasks">{translate('background_tasks.page')}</Link>
}}
/>
</Alert>
diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/SettingsNav-test.tsx b/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/SettingsNav-test.tsx
index 7b96b2cc92a..c2264d9e1d6 100644
--- a/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/SettingsNav-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/SettingsNav-test.tsx
@@ -19,8 +19,9 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { mockLocation } from '../../../../../helpers/testMocks';
import { AdminPageExtension } from '../../../../../types/extension';
-import SettingsNav from '../SettingsNav';
+import { SettingsNav } from '../SettingsNav';
it('should work with extensions', () => {
const wrapper = shallowRender();
@@ -65,6 +66,7 @@ function shallowRender(props: Partial<SettingsNav['props']> = {}) {
extensions={[{ key: 'foo', name: 'Foo' }]}
fetchPendingPlugins={jest.fn()}
fetchSystemStatus={jest.fn()}
+ location={mockLocation()}
pendingPlugins={{ installing: [], removing: [], updating: [] }}
systemStatus="UP"
{...props}
diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsNav-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsNav-test.tsx.snap
index a93612af5d0..029675a85cb 100644
--- a/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsNav-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsNav-test.tsx.snap
@@ -43,38 +43,35 @@ exports[`should render correctly when governance is active 1`] = `
className="menu"
>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/settings"
>
settings.page
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/settings/encryption"
>
property.category.security.encryption
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/webhooks"
>
webhooks.page
- </IndexLink>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to="/admin/extension/governance/views_console"
>
governance
- </Link>
+ </NavLink>
</li>
</ul>
}
@@ -88,36 +85,36 @@ exports[`should render correctly when governance is active 1`] = `
className="menu"
>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/users"
>
users.page
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/groups"
>
user_groups.page
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/permissions"
>
global_permissions.page
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/permission_templates"
>
permission_templates
- </IndexLink>
+ </NavLink>
</li>
</ul>
}
@@ -131,20 +128,20 @@ exports[`should render correctly when governance is active 1`] = `
className="menu"
>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/projects_management"
>
management
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/background_tasks"
>
background_tasks.page
- </IndexLink>
+ </NavLink>
</li>
</ul>
}
@@ -153,28 +150,28 @@ exports[`should render correctly when governance is active 1`] = `
<Component />
</Dropdown>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/system"
>
sidebar.system
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/marketplace"
>
marketplace.page
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/audit"
>
audit_logs.page
- </IndexLink>
+ </NavLink>
</li>
</NavBarTabs>
</ContextNavBar>
@@ -199,38 +196,35 @@ exports[`should work with extensions 1`] = `
className="menu"
>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/settings"
>
settings.page
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/settings/encryption"
>
property.category.security.encryption
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/webhooks"
>
webhooks.page
- </IndexLink>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to="/admin/extension/foo"
>
Foo
- </Link>
+ </NavLink>
</li>
</ul>
}
@@ -244,36 +238,36 @@ exports[`should work with extensions 1`] = `
className="menu"
>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/users"
>
users.page
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/groups"
>
user_groups.page
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/permissions"
>
global_permissions.page
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/permission_templates"
>
permission_templates
- </IndexLink>
+ </NavLink>
</li>
</ul>
}
@@ -287,20 +281,20 @@ exports[`should work with extensions 1`] = `
className="menu"
>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/projects_management"
>
management
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/background_tasks"
>
background_tasks.page
- </IndexLink>
+ </NavLink>
</li>
</ul>
}
@@ -309,20 +303,20 @@ exports[`should work with extensions 1`] = `
<Component />
</Dropdown>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/system"
>
sidebar.system
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/marketplace"
>
marketplace.page
- </IndexLink>
+ </NavLink>
</li>
</NavBarTabs>
</ContextNavBar>
@@ -336,38 +330,35 @@ Array [
className="menu"
>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/settings"
>
settings.page
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/settings/encryption"
>
property.category.security.encryption
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/webhooks"
>
webhooks.page
- </IndexLink>
+ </NavLink>
</li>
<li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
+ <NavLink
to="/admin/extension/foo"
>
Foo
- </Link>
+ </NavLink>
</li>
</ul>
}
@@ -381,36 +372,36 @@ Array [
className="menu"
>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/users"
>
users.page
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/groups"
>
user_groups.page
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/permissions"
>
global_permissions.page
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/permission_templates"
>
permission_templates
- </IndexLink>
+ </NavLink>
</li>
</ul>
}
@@ -424,20 +415,20 @@ Array [
className="menu"
>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/projects_management"
>
management
- </IndexLink>
+ </NavLink>
</li>
<li>
- <IndexLink
- activeClassName="active"
+ <NavLink
+ end={true}
to="/admin/background_tasks"
>
background_tasks.page
- </IndexLink>
+ </NavLink>
</li>
</ul>
}
diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SystemRestartNotif-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SystemRestartNotif-test.tsx.snap
index b6b1713bc1d..1bec0d5aa3a 100644
--- a/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SystemRestartNotif-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SystemRestartNotif-test.tsx.snap
@@ -12,13 +12,7 @@ exports[`should render correctly 1`] = `
Object {
"instance": undefined,
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/admin/background_tasks",
- }
- }
+ to="/admin/background_tasks"
>
background_tasks.page
</Link>,
diff --git a/server/sonar-web/src/main/js/app/components/search/Search.tsx b/server/sonar-web/src/main/js/app/components/search/Search.tsx
index 03b0ea8d70c..cc7d82729ba 100644
--- a/server/sonar-web/src/main/js/app/components/search/Search.tsx
+++ b/server/sonar-web/src/main/js/app/components/search/Search.tsx
@@ -20,11 +20,11 @@
import { debounce, keyBy, uniqBy } from 'lodash';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
-import { withRouter, WithRouterProps } from 'react-router';
import { getSuggestions } from '../../../api/components';
import { DropdownOverlay } from '../../../components/controls/Dropdown';
import OutsideClickHandler from '../../../components/controls/OutsideClickHandler';
import SearchBox from '../../../components/controls/SearchBox';
+import { Router, withRouter } from '../../../components/hoc/withRouter';
import ClockIcon from '../../../components/icons/ClockIcon';
import { lazyLoadComponent } from '../../../components/lazyLoadComponent';
import DeferredSpinner from '../../../components/ui/DeferredSpinner';
@@ -42,6 +42,10 @@ import { ComponentResult, More, Results, sortQualifiers } from './utils';
const SearchResults = lazyLoadComponent(() => import('./SearchResults'));
const SearchResult = lazyLoadComponent(() => import('./SearchResult'));
+interface Props {
+ router: Router;
+}
+
interface State {
loading: boolean;
loadingMore?: string;
@@ -54,13 +58,13 @@ interface State {
shortQuery: boolean;
}
-export class Search extends React.PureComponent<WithRouterProps, State> {
+export class Search extends React.PureComponent<Props, State> {
input?: HTMLInputElement | null;
node?: HTMLElement | null;
nodes: Dict<HTMLElement>;
mounted = false;
- constructor(props: WithRouterProps) {
+ constructor(props: Props) {
super(props);
this.nodes = {};
this.search = debounce(this.search, 250);
@@ -80,7 +84,7 @@ export class Search extends React.PureComponent<WithRouterProps, State> {
document.addEventListener('keydown', this.handleSKeyDown);
}
- componentDidUpdate(_prevProps: WithRouterProps, prevState: State) {
+ componentDidUpdate(_prevProps: Props, prevState: State) {
if (prevState.selected !== this.state.selected) {
this.scrollToSelected();
}
@@ -403,4 +407,4 @@ export class Search extends React.PureComponent<WithRouterProps, State> {
}
}
-export default withRouter<{}>(Search);
+export default withRouter(Search);
diff --git a/server/sonar-web/src/main/js/app/components/search/SearchResult.tsx b/server/sonar-web/src/main/js/app/components/search/SearchResult.tsx
index 183fd66509e..16f946915d7 100644
--- a/server/sonar-web/src/main/js/app/components/search/SearchResult.tsx
+++ b/server/sonar-web/src/main/js/app/components/search/SearchResult.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import Tooltip from '../../../components/controls/Tooltip';
import ClockIcon from '../../../components/icons/ClockIcon';
import FavoriteIcon from '../../../components/icons/FavoriteIcon';
diff --git a/server/sonar-web/src/main/js/app/components/search/__tests__/Search-test.tsx b/server/sonar-web/src/main/js/app/components/search/__tests__/Search-test.tsx
index 13935091084..9a08c0d4980 100644
--- a/server/sonar-web/src/main/js/app/components/search/__tests__/Search-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/search/__tests__/Search-test.tsx
@@ -22,6 +22,7 @@ import * as React from 'react';
import { KeyboardKeys } from '../../../../helpers/keycodes';
import { mockRouter } from '../../../../helpers/testMocks';
import { elementKeydown, keydown } from '../../../../helpers/testUtils';
+import { queryToSearch } from '../../../../helpers/urls';
import { ComponentQualifier } from '../../../../types/component';
import { Search } from '../Search';
@@ -54,7 +55,10 @@ it('opens selected project on enter', () => {
});
elementKeydown(form.find('SearchBox'), KeyboardKeys.Enter);
- expect(router.push).toBeCalledWith({ pathname: '/dashboard', query: { id: selectedKey } });
+ expect(router.push).toBeCalledWith({
+ pathname: '/dashboard',
+ search: queryToSearch({ id: selectedKey })
+ });
});
it('opens selected portfolio on enter', () => {
@@ -70,7 +74,10 @@ it('opens selected portfolio on enter', () => {
});
elementKeydown(form.find('SearchBox'), KeyboardKeys.Enter);
- expect(router.push).toBeCalledWith({ pathname: '/portfolio', query: { id: selectedKey } });
+ expect(router.push).toBeCalledWith({
+ pathname: '/portfolio',
+ search: queryToSearch({ id: selectedKey })
+ });
});
it('opens selected subportfolio on enter', () => {
@@ -86,7 +93,10 @@ it('opens selected subportfolio on enter', () => {
});
elementKeydown(form.find('SearchBox'), KeyboardKeys.Enter);
- expect(router.push).toBeCalledWith({ pathname: '/portfolio', query: { id: selectedKey } });
+ expect(router.push).toBeCalledWith({
+ pathname: '/portfolio',
+ search: queryToSearch({ id: selectedKey })
+ });
});
it('shows warning about short input', () => {
diff --git a/server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.tsx.snap b/server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.tsx.snap
index d8f8d4dcb7e..d4e19eaf97b 100644
--- a/server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.tsx.snap
@@ -13,14 +13,10 @@ exports[`renders favorite 1`] = `
<Link
data-key="foo"
onClick={[MockFunction]}
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "id": "foo",
- },
+ "search": "?id=foo",
}
}
>
@@ -64,14 +60,10 @@ exports[`renders match 1`] = `
<Link
data-key="foo"
onClick={[MockFunction]}
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "id": "foo",
- },
+ "search": "?id=foo",
}
}
>
@@ -114,14 +106,10 @@ exports[`renders projects 1`] = `
<Link
data-key="qwe"
onClick={[MockFunction]}
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "id": "qwe",
- },
+ "search": "?id=qwe",
}
}
>
@@ -169,14 +157,10 @@ exports[`renders recently browsed 1`] = `
<Link
data-key="foo"
onClick={[MockFunction]}
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "id": "foo",
- },
+ "search": "?id=foo",
}
}
>
@@ -219,14 +203,10 @@ exports[`renders selected 1`] = `
<Link
data-key="foo"
onClick={[MockFunction]}
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "id": "foo",
- },
+ "search": "?id=foo",
}
}
>
@@ -267,14 +247,10 @@ exports[`renders selected 2`] = `
<Link
data-key="foo"
onClick={[MockFunction]}
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "id": "foo",
- },
+ "search": "?id=foo",
}
}
>
diff --git a/server/sonar-web/src/main/js/app/index.ts b/server/sonar-web/src/main/js/app/index.ts
index 85532d23a70..a665c02f376 100644
--- a/server/sonar-web/src/main/js/app/index.ts
+++ b/server/sonar-web/src/main/js/app/index.ts
@@ -19,9 +19,11 @@
*/
import { installExtensionsHandler, installWebAnalyticsHandler } from '../helpers/extensionsHandler';
import { loadL10nBundle } from '../helpers/l10nBundle';
-import { parseJSON, request } from '../helpers/request';
+import { HttpStatus, parseJSON, request } from '../helpers/request';
import { getBaseUrl, getSystemStatus } from '../helpers/system';
import { AppState } from '../types/appstate';
+import { L10nBundle } from '../types/l10nBundle';
+import { CurrentUser } from '../types/users';
import './styles/sonar.ts';
installWebAnalyticsHandler();
@@ -29,12 +31,12 @@ installWebAnalyticsHandler();
if (isMainApp()) {
installExtensionsHandler();
- Promise.all([loadL10nBundle(), loadUser(), loadAppState(), loadApp()]).then(
+ loadAll(loadAppState, loadUser).then(
([l10nBundle, user, appState, startReactApp]) => {
startReactApp(l10nBundle.locale, appState, user);
},
error => {
- if (isResponse(error) && error.status === 401) {
+ if (isResponse(error) && error.status === HttpStatus.Unauthorized) {
redirectToLogin();
} else {
logError(error);
@@ -44,24 +46,19 @@ if (isMainApp()) {
} else {
// login, maintenance or setup pages
- const appStatePromise: Promise<AppState> = new Promise(resolve => {
- loadAppState()
- .then(data => {
- resolve(data);
- })
- .catch(() => {
- resolve({
- edition: undefined,
- productionDatabase: true,
- qualifiers: [],
- settings: {},
- version: ''
- });
- });
- });
+ const appStateLoader = () =>
+ loadAppState().catch(() => {
+ return {
+ edition: undefined,
+ productionDatabase: true,
+ qualifiers: [],
+ settings: {},
+ version: ''
+ };
+ });
- Promise.all([loadL10nBundle(), appStatePromise, loadApp()]).then(
- ([l10nBundle, appState, startReactApp]) => {
+ loadAll(appStateLoader).then(
+ ([l10nBundle, _user, appState, startReactApp]) => {
startReactApp(l10nBundle.locale, appState);
},
error => {
@@ -70,6 +67,28 @@ if (isMainApp()) {
);
}
+async function loadAll(
+ appStateLoader: () => Promise<AppState>,
+ userLoader?: () => Promise<CurrentUser | undefined>
+): Promise<
+ [
+ Required<L10nBundle>,
+ CurrentUser | undefined,
+ AppState,
+ (lang: string, appState: AppState, currentUser?: CurrentUser) => void
+ ]
+> {
+ const [l10nBundle, user, appState] = await Promise.all([
+ loadL10nBundle(),
+ userLoader ? userLoader() : undefined,
+ appStateLoader()
+ ]);
+
+ const startReactApp = await loadApp();
+
+ return [l10nBundle, user, appState, startReactApp];
+}
+
function loadUser() {
return request('/api/users/current')
.submit()
diff --git a/server/sonar-web/src/main/js/app/utils/NavigateWithParams.tsx b/server/sonar-web/src/main/js/app/utils/NavigateWithParams.tsx
new file mode 100644
index 00000000000..6550da209a3
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/utils/NavigateWithParams.tsx
@@ -0,0 +1,46 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { Navigate, Params, useLocation, useParams, useSearchParams } from 'react-router-dom';
+import { Dict } from '../../types/types';
+
+export interface NavigateWithParamsProps {
+ pathname: string;
+ transformParams: (params: Params) => Dict<string>;
+}
+
+export default function NavigateWithParams({ pathname, transformParams }: NavigateWithParamsProps) {
+ const urlParams = useParams();
+ const location = useLocation();
+ const [searchParams] = useSearchParams();
+
+ /* Append transformed path params to search params */
+ const transformedParams = transformParams(urlParams);
+ Object.keys(transformedParams).forEach(key => {
+ searchParams.append(key, transformedParams[key]);
+ });
+
+ return (
+ <Navigate
+ to={{ pathname, search: searchParams.toString(), hash: location.hash }}
+ replace={true}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/app/utils/NavigateWithSearchAndHash.tsx b/server/sonar-web/src/main/js/app/utils/NavigateWithSearchAndHash.tsx
new file mode 100644
index 00000000000..048e5451867
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/utils/NavigateWithSearchAndHash.tsx
@@ -0,0 +1,31 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { Navigate, useLocation } from 'react-router-dom';
+
+export interface NavigateWithSearchAndHashProps {
+ pathname: string;
+}
+
+export default function NavigateWithSearchAndHash({ pathname }: NavigateWithSearchAndHashProps) {
+ const location = useLocation();
+
+ return <Navigate to={{ ...location, pathname }} replace={true} />;
+}
diff --git a/server/sonar-web/src/main/js/app/utils/__tests__/NavigateWithParams-test.tsx b/server/sonar-web/src/main/js/app/utils/__tests__/NavigateWithParams-test.tsx
new file mode 100644
index 00000000000..b4f47021d50
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/utils/__tests__/NavigateWithParams-test.tsx
@@ -0,0 +1,51 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+import { render, screen } from '@testing-library/react';
+import React from 'react';
+import { MemoryRouter, Params, Route, Routes } from 'react-router-dom';
+import { CatchAll } from '../../../helpers/testReactTestingUtils';
+import { Dict } from '../../../types/types';
+import NavigateWithParams from '../NavigateWithParams';
+
+it('should transform path parameters to search params', () => {
+ const transformParams = jest.fn((params: Params) => {
+ return { also: 'this', ...params };
+ });
+
+ renderNavigateWithParams(transformParams);
+
+ expect(transformParams).toBeCalled();
+ expect(screen.getByText('/target?also=this&key=hello&subkey=test')).toBeInTheDocument();
+});
+
+function renderNavigateWithParams(transformParams: (params: Params) => Dict<string>) {
+ render(
+ <MemoryRouter initialEntries={['/source/hello/test']}>
+ <Routes>
+ <Route
+ path="/source/:key/:subkey"
+ element={<NavigateWithParams pathname="/target" transformParams={transformParams} />}
+ />
+ <Route path="*" element={<CatchAll />} />
+ </Routes>
+ </MemoryRouter>
+ );
+}
diff --git a/server/sonar-web/src/main/js/app/utils/__tests__/handleRequiredAuthorization-test.ts b/server/sonar-web/src/main/js/app/utils/__tests__/handleRequiredAuthorization-test.ts
index cb100fec0c3..5fc4a3c8b3e 100644
--- a/server/sonar-web/src/main/js/app/utils/__tests__/handleRequiredAuthorization-test.ts
+++ b/server/sonar-web/src/main/js/app/utils/__tests__/handleRequiredAuthorization-test.ts
@@ -18,14 +18,36 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import getHistory from '../../../helpers/getHistory';
import handleRequiredAuthorization from '../handleRequiredAuthorization';
-jest.mock('../../../helpers/getHistory', () => jest.fn());
+const originalLocation = window.location;
+
+const replace = jest.fn();
+
+beforeAll(() => {
+ const location = {
+ ...window.location,
+ pathname: '/path',
+ search: '?id=12',
+ hash: '#tag',
+ replace
+ };
+ Object.defineProperty(window, 'location', {
+ writable: true,
+ value: location
+ });
+});
+
+afterAll(() => {
+ Object.defineProperty(window, 'location', {
+ writable: true,
+ value: originalLocation
+ });
+});
it('should not render for anonymous user', () => {
- const replace = jest.fn();
- (getHistory as jest.Mock<any>).mockReturnValue({ replace });
handleRequiredAuthorization();
- expect(replace).toBeCalledWith(expect.objectContaining({ pathname: '/sessions/new' }));
+ expect(replace).toBeCalledWith(
+ '/sessions/new?return_to=%2Fpath%3Fid%3D12%23tag&authorizationError=true'
+ );
});
diff --git a/server/sonar-web/src/main/js/app/utils/exportModulesAsGlobals.ts b/server/sonar-web/src/main/js/app/utils/exportModulesAsGlobals.ts
index f9351d9ef26..df3a3308f43 100644
--- a/server/sonar-web/src/main/js/app/utils/exportModulesAsGlobals.ts
+++ b/server/sonar-web/src/main/js/app/utils/exportModulesAsGlobals.ts
@@ -26,7 +26,7 @@ import React from 'react';
import * as ReactDom from 'react-dom';
import * as ReactIntl from 'react-intl';
import ReactModal from 'react-modal';
-import * as ReactRouter from 'react-router';
+import * as ReactRouterDom from 'react-router-dom';
/*
* Expose dependencies to extensions
@@ -41,5 +41,5 @@ export default function exportModulesAsGlobals() {
w.ReactDOM = ReactDom;
w.ReactIntl = ReactIntl;
w.ReactModal = ReactModal;
- w.ReactRouter = ReactRouter;
+ w.ReactRouterDom = ReactRouterDom;
}
diff --git a/server/sonar-web/src/main/js/app/utils/handleRequiredAuthorization.ts b/server/sonar-web/src/main/js/app/utils/handleRequiredAuthorization.ts
index 44d052feb73..fd1ec8cca5c 100644
--- a/server/sonar-web/src/main/js/app/utils/handleRequiredAuthorization.ts
+++ b/server/sonar-web/src/main/js/app/utils/handleRequiredAuthorization.ts
@@ -17,13 +17,8 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import getHistory from '../../helpers/getHistory';
-
export default function handleRequiredAuthorization() {
- const history = getHistory();
const returnTo = window.location.pathname + window.location.search + window.location.hash;
- history.replace({
- pathname: '/sessions/new',
- query: { return_to: returnTo, authorizationError: true }
- });
+ const searchParams = new URLSearchParams({ return_to: returnTo, authorizationError: 'true' });
+ window.location.replace(`/sessions/new?${searchParams.toString()}`);
}
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 622c56db585..ace3ec81bd5 100644
--- a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx
+++ b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx
@@ -17,24 +17,22 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-
-import { Location } from 'history';
-import { pick } from 'lodash';
import * as React from 'react';
import { render } from 'react-dom';
import { HelmetProvider } from 'react-helmet-async';
import { IntlProvider } from 'react-intl';
-import { IndexRoute, Redirect, Route, RouteConfig, RouteProps, Router } from 'react-router';
+import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
import accountRoutes from '../../apps/account/routes';
import auditLogsRoutes from '../../apps/audit-logs/routes';
import backgroundTasksRoutes from '../../apps/background-tasks/routes';
+import ChangeAdminPasswordApp from '../../apps/change-admin-password/ChangeAdminPasswordApp';
import codeRoutes from '../../apps/code/routes';
import codingRulesRoutes from '../../apps/coding-rules/routes';
import componentMeasuresRoutes from '../../apps/component-measures/routes';
import documentationRoutes from '../../apps/documentation/routes';
import groupsRoutes from '../../apps/groups/routes';
-import Issues from '../../apps/issues/components/AppContainer';
-import { maintenanceRoutes, setupRoutes } from '../../apps/maintenance/routes';
+import { globalIssuesRoutes, projectIssuesRoutes } from '../../apps/issues/routes';
+import maintenanceRoutes from '../../apps/maintenance/routes';
import marketplaceRoutes from '../../apps/marketplace/routes';
import overviewRoutes from '../../apps/overview/routes';
import permissionTemplatesRoutes from '../../apps/permission-templates/routes';
@@ -42,13 +40,17 @@ import { globalPermissionsRoutes, projectPermissionsRoutes } from '../../apps/pe
import projectActivityRoutes from '../../apps/projectActivity/routes';
import projectBaselineRoutes from '../../apps/projectBaseline/routes';
import projectBranchesRoutes from '../../apps/projectBranches/routes';
+import ProjectDeletionApp from '../../apps/projectDeletion/App';
import projectDumpRoutes from '../../apps/projectDump/routes';
+import ProjectKeyApp from '../../apps/projectKey/Key';
+import ProjectLinksApp from '../../apps/projectLinks/App';
import projectQualityGateRoutes from '../../apps/projectQualityGate/routes';
import projectQualityProfilesRoutes from '../../apps/projectQualityProfiles/routes';
import projectsRoutes from '../../apps/projects/routes';
import projectsManagementRoutes from '../../apps/projectsManagement/routes';
import qualityGatesRoutes from '../../apps/quality-gates/routes';
import qualityProfilesRoutes from '../../apps/quality-profiles/routes';
+import SecurityHotspotsApp from '../../apps/security-hotspots/SecurityHotspotsApp';
import sessionsRoutes from '../../apps/sessions/routes';
import settingsRoutes from '../../apps/settings/routes';
import systemRoutes from '../../apps/system/routes';
@@ -56,197 +58,146 @@ import tutorialsRoutes from '../../apps/tutorials/routes';
import usersRoutes from '../../apps/users/routes';
import webAPIRoutes from '../../apps/web-api/routes';
import webhooksRoutes from '../../apps/webhooks/routes';
-import withIndexationGuard from '../../components/hoc/withIndexationGuard';
-import { lazyLoadComponent } from '../../components/lazyLoadComponent';
-import getHistory from '../../helpers/getHistory';
+import { omitNil } from '../../helpers/request';
import { AppState } from '../../types/appstate';
import { CurrentUser } from '../../types/users';
+import AdminContainer from '../components/AdminContainer';
import App from '../components/App';
import AppStateContextProvider from '../components/app-state/AppStateContextProvider';
+import ComponentContainer from '../components/ComponentContainer';
import CurrentUserContextProvider from '../components/current-user/CurrentUserContextProvider';
+import GlobalAdminPageExtension from '../components/extensions/GlobalAdminPageExtension';
+import GlobalPageExtension from '../components/extensions/GlobalPageExtension';
+import PortfolioPage from '../components/extensions/PortfolioPage';
+import PortfoliosPage from '../components/extensions/PortfoliosPage';
+import ProjectAdminPageExtension from '../components/extensions/ProjectAdminPageExtension';
+import ProjectPageExtension from '../components/extensions/ProjectPageExtension';
+import FormattingHelp from '../components/FormattingHelp';
import GlobalContainer from '../components/GlobalContainer';
import GlobalMessagesContainer from '../components/GlobalMessagesContainer';
-import { PageContext } from '../components/indexation/PageUnavailableDueToIndexation';
+import Landing from '../components/Landing';
import MigrationContainer from '../components/MigrationContainer';
import NonAdminPagesContainer from '../components/NonAdminPagesContainer';
+import NotFound from '../components/NotFound';
+import PluginRiskConsent from '../components/PluginRiskConsent';
+import ProjectAdminContainer from '../components/ProjectAdminContainer';
+import ResetPassword from '../components/ResetPassword';
+import SimpleContainer from '../components/SimpleContainer';
import exportModulesAsGlobals from './exportModulesAsGlobals';
+import NavigateWithParams from './NavigateWithParams';
+import NavigateWithSearchAndHash from './NavigateWithSearchAndHash';
-function handleUpdate(this: { state: { location: Location } }) {
- const { action } = this.state.location;
-
- if (action === 'PUSH') {
- window.scrollTo(0, 0);
- }
+function renderRedirect({ from, to }: { from: string; to: string }) {
+ return <Route path={from} element={<Navigate to={{ pathname: to }} replace={true} />} />;
}
-// this is not an official api
-export const RouteWithChildRoutes = Route as React.ComponentClass<
- RouteProps & { childRoutes: RouteConfig }
->;
-
function renderRedirects() {
return (
<>
<Route
path="/account/issues"
- onEnter={(_, replace) => {
- replace({ pathname: '/issues', query: { myIssues: 'true', resolved: 'false' } });
- }}
+ element={
+ <NavigateWithParams
+ pathname="/issues"
+ transformParams={() => ({ myIssues: 'true', resolved: 'false' })}
+ />
+ }
/>
- <Route
- path="/codingrules"
- onEnter={(_, replace) => {
- replace(`/coding_rules${window.location.hash}`);
- }}
- />
+ <Route path="/codingrules" element={<NavigateWithSearchAndHash pathname="/coding_rules" />} />
<Route
path="/dashboard/index/:key"
- onEnter={(nextState, replace) => {
- replace({ pathname: '/dashboard', query: { id: nextState.params.key } });
- }}
+ element={
+ <NavigateWithParams
+ pathname="/dashboard"
+ transformParams={params => omitNil({ id: params['key'] })}
+ />
+ }
/>
<Route
path="/application/console"
- onEnter={(nextState, replace) => {
- replace({
- pathname: '/project/admin/extension/developer-server/application-console',
- query: { id: nextState.location.query.id }
- });
- }}
+ element={
+ <NavigateWithSearchAndHash pathname="/project/admin/extension/developer-server/application-console" />
+ }
/>
<Route
path="/application/settings"
- onEnter={(nextState, replace) => {
- replace({
- pathname: '/project/admin/extension/governance/application_report',
- query: { id: nextState.location.query.id }
- });
- }}
+ element={
+ <NavigateWithSearchAndHash pathname="/project/admin/extension/governance/application_report" />
+ }
/>
- <Route
- path="/issues/search"
- onEnter={(_, replace) => {
- replace(`/issues${window.location.hash}`);
- }}
- />
+ <Route path="/issues/search" element={<NavigateWithSearchAndHash pathname="/issues" />} />
- <Redirect from="/admin" to="/admin/settings" />
- <Redirect from="/background_tasks" to="/admin/background_tasks" />
- <Redirect from="/component/index" to="/component" />
- <Redirect from="/component_issues" to="/project/issues" />
- <Redirect from="/dashboard/index" to="/dashboard" />
- <Redirect
- from="/documentation/analysis/languages/vb"
- to="/documentation/analysis/languages/vbnet/"
- />
- <Redirect from="/governance" to="/portfolio" />
- <Redirect from="/groups" to="/admin/groups" />
- <Redirect from="/extension/governance/portfolios" to="/portfolios" />
- <Redirect from="/permission_templates" to="/admin/permission_templates" />
- <Redirect from="/profiles/index" to="/profiles" />
- <Redirect from="/projects_admin" to="/admin/projects_management" />
- <Redirect from="/quality_gates/index" to="/quality_gates" />
- <Redirect from="/roles/global" to="/admin/permissions" />
- <Redirect from="/admin/roles/global" to="/admin/permissions" />
- <Redirect from="/settings" to="/admin/settings" />
- <Redirect from="/settings/encryption" to="/admin/settings/encryption" />
- <Redirect from="/settings/index" to="/admin/settings" />
- <Redirect from="/sessions/login" to="/sessions/new" />
- <Redirect from="/system" to="/admin/system" />
- <Redirect from="/system/index" to="/admin/system" />
- <Redirect from="/view" to="/portfolio" />
- <Redirect from="/users" to="/admin/users" />
- <Redirect from="/onboarding" to="/projects/create" />
- <Redirect from="markdown/help" to="formatting/help" />
+ {renderRedirect({ from: '/admin', to: '/admin/settings' })}
+ {renderRedirect({ from: '/background_tasks', to: '/admin/background_tasks' })}
+ {renderRedirect({
+ from: '/documentation/analysis/languages/vb',
+ to: '/documentation/analysis/languages/vbnet/'
+ })}
+ {renderRedirect({ from: '/groups', to: '/admin/groups' })}
+ {renderRedirect({ from: '/extension/governance/portfolios', to: '/portfolios' })}
+ {renderRedirect({ from: '/permission_templates', to: '/admin/permission_templates' })}
+ {renderRedirect({ from: '/profiles/index', to: '/profiles' })}
+ {renderRedirect({ from: '/projects_admin', to: '/admin/projects_management' })}
+ {renderRedirect({ from: '/quality_gates/index', to: '/quality_gates' })}
+ {renderRedirect({ from: '/roles/global', to: '/admin/permissions' })}
+ {renderRedirect({ from: '/admin/roles/global', to: '/admin/permissions' })}
+ {renderRedirect({ from: '/settings', to: '/admin/settings' })}
+ {renderRedirect({ from: '/settings/encryption', to: '/admin/settings/encryption' })}
+ {renderRedirect({ from: '/settings/index', to: '/admin/settings' })}
+ {renderRedirect({ from: '/sessions/login', to: '/sessions/new' })}
+ {renderRedirect({ from: '/system', to: '/admin/system' })}
+ {renderRedirect({ from: '/system/index', to: '/admin/system' })}
+ {renderRedirect({ from: '/users', to: '/admin/users' })}
+ {renderRedirect({ from: '/onboarding', to: '/projects/create' })}
+ {renderRedirect({ from: '/markdown/help', to: '/formatting/help' })}
</>
);
}
function renderComponentRoutes() {
return (
- <Route component={lazyLoadComponent(() => import('../components/ComponentContainer'))}>
+ <Route element={<ComponentContainer />}>
{/* This container is a catch-all for all non-admin pages */}
- <Route component={NonAdminPagesContainer}>
- <RouteWithChildRoutes path="code" childRoutes={codeRoutes} />
- <RouteWithChildRoutes path="component_measures" childRoutes={componentMeasuresRoutes} />
- <RouteWithChildRoutes path="dashboard" childRoutes={overviewRoutes} />
- <Route
- path="portfolio"
- component={lazyLoadComponent(() => import('../components/extensions/PortfolioPage'))}
- />
- <RouteWithChildRoutes path="project/activity" childRoutes={projectActivityRoutes} />
+ <Route element={<NonAdminPagesContainer />}>
+ {codeRoutes()}
+ {componentMeasuresRoutes()}
+ {overviewRoutes()}
+ <Route path="portfolio" element={<PortfolioPage />} />
+ {projectActivityRoutes()}
<Route
path="project/extension/:pluginKey/:extensionKey"
- component={lazyLoadComponent(() =>
- import('../components/extensions/ProjectPageExtension')
- )}
+ element={<ProjectPageExtension />}
/>
- <Route
- path="project/issues"
- component={Issues}
- onEnter={({ location: { query } }, replace) => {
- if (query.types) {
- if (query.types === 'SECURITY_HOTSPOT') {
- replace({
- pathname: '/security_hotspots',
- query: {
- ...pick(query, ['id', 'branch', 'pullRequest']),
- assignedToMe: false
- }
- });
- } else {
- query.types = query.types
- .split(',')
- .filter((type: string) => type !== 'SECURITY_HOTSPOT')
- .join(',');
- }
- }
- }}
- />
- <Route
- path="security_hotspots"
- component={lazyLoadComponent(() =>
- import('../../apps/security-hotspots/SecurityHotspotsApp')
- )}
- />
- <RouteWithChildRoutes path="project/quality_gate" childRoutes={projectQualityGateRoutes} />
- <RouteWithChildRoutes
- path="project/quality_profiles"
- childRoutes={projectQualityProfilesRoutes}
- />
- <RouteWithChildRoutes path="tutorials" childRoutes={tutorialsRoutes} />
+ {projectIssuesRoutes()}
+ <Route path="security_hotspots" element={<SecurityHotspotsApp />} />
+ {projectQualityGateRoutes()}
+ {projectQualityProfilesRoutes()}
+
+ {tutorialsRoutes()}
</Route>
- <Route component={lazyLoadComponent(() => import('../components/ProjectAdminContainer'))}>
- <Route
- path="project/admin/extension/:pluginKey/:extensionKey"
- component={lazyLoadComponent(() =>
- import('../components/extensions/ProjectAdminPageExtension')
- )}
- />
- <RouteWithChildRoutes path="project/background_tasks" childRoutes={backgroundTasksRoutes} />
- <RouteWithChildRoutes path="project/baseline" childRoutes={projectBaselineRoutes} />
- <RouteWithChildRoutes path="project/branches" childRoutes={projectBranchesRoutes} />
- <RouteWithChildRoutes path="project/import_export" childRoutes={projectDumpRoutes} />
- <RouteWithChildRoutes path="project/settings" childRoutes={settingsRoutes} />
- <RouteWithChildRoutes path="project_roles" childRoutes={projectPermissionsRoutes} />
- <RouteWithChildRoutes path="project/webhooks" childRoutes={webhooksRoutes} />
- <Route
- path="project/deletion"
- component={lazyLoadComponent(() => import('../../apps/projectDeletion/App'))}
- />
- <Route
- path="project/links"
- component={lazyLoadComponent(() => import('../../apps/projectLinks/App'))}
- />
- <Route
- path="project/key"
- component={lazyLoadComponent(() => import('../../apps/projectKey/Key'))}
- />
+ <Route element={<ProjectAdminContainer />}>
+ <Route path="project">
+ <Route
+ path="admin/extension/:pluginKey/:extensionKey"
+ element={<ProjectAdminPageExtension />}
+ />
+ {backgroundTasksRoutes()}
+ {projectBaselineRoutes()}
+ {projectBranchesRoutes()}
+ {projectDumpRoutes()}
+ {settingsRoutes()}
+ {webhooksRoutes()}
+
+ <Route path="deletion" element={<ProjectDeletionApp />} />
+ <Route path="links" element={<ProjectLinksApp />} />
+ <Route path="key" element={<ProjectKeyApp />} />
+ </Route>
+ {projectPermissionsRoutes()}
</Route>
</Route>
);
@@ -254,24 +205,19 @@ function renderComponentRoutes() {
function renderAdminRoutes() {
return (
- <Route component={lazyLoadComponent(() => import('../components/AdminContainer'))} path="admin">
- <Route
- path="extension/:pluginKey/:extensionKey"
- component={lazyLoadComponent(() =>
- import('../components/extensions/GlobalAdminPageExtension')
- )}
- />
- <RouteWithChildRoutes path="audit" childRoutes={auditLogsRoutes} />
- <RouteWithChildRoutes path="background_tasks" childRoutes={backgroundTasksRoutes} />
- <RouteWithChildRoutes path="groups" childRoutes={groupsRoutes} />
- <RouteWithChildRoutes path="permission_templates" childRoutes={permissionTemplatesRoutes} />
- <RouteWithChildRoutes path="permissions" childRoutes={globalPermissionsRoutes} />
- <RouteWithChildRoutes path="projects_management" childRoutes={projectsManagementRoutes} />
- <RouteWithChildRoutes path="settings" childRoutes={settingsRoutes} />
- <RouteWithChildRoutes path="system" childRoutes={systemRoutes} />
- <RouteWithChildRoutes path="marketplace" childRoutes={marketplaceRoutes} />
- <RouteWithChildRoutes path="users" childRoutes={usersRoutes} />
- <RouteWithChildRoutes path="webhooks" childRoutes={webhooksRoutes} />
+ <Route path="admin" element={<AdminContainer />}>
+ <Route path="extension/:pluginKey/:extensionKey" element={<GlobalAdminPageExtension />} />
+ {settingsRoutes()}
+ {auditLogsRoutes()}
+ {backgroundTasksRoutes()}
+ {groupsRoutes()}
+ {permissionTemplatesRoutes()}
+ {globalPermissionsRoutes()}
+ {projectsManagementRoutes()}
+ {systemRoutes()}
+ {marketplaceRoutes()}
+ {usersRoutes()}
+ {webhooksRoutes()}
</Route>
);
}
@@ -281,100 +227,78 @@ export default function startReactApp(lang: string, appState: AppState, currentU
const el = document.getElementById('content');
- const history = getHistory();
-
render(
<HelmetProvider>
<AppStateContextProvider appState={appState}>
<CurrentUserContextProvider currentUser={currentUser}>
<IntlProvider defaultLocale={lang} locale={lang}>
<GlobalMessagesContainer />
- <Router history={history} onUpdate={handleUpdate}>
- {renderRedirects()}
+ <BrowserRouter>
+ <Routes>
+ {renderRedirects()}
- <Route
- path="formatting/help"
- component={lazyLoadComponent(() => import('../components/FormattingHelp'))}
- />
+ <Route path="formatting/help" element={<FormattingHelp />} />
- <Route component={lazyLoadComponent(() => import('../components/SimpleContainer'))}>
- <Route path="maintenance">{maintenanceRoutes}</Route>
- <Route path="setup">{setupRoutes}</Route>
- </Route>
+ <Route element={<SimpleContainer />}>{maintenanceRoutes()}</Route>
- <Route component={MigrationContainer}>
- <Route
- component={lazyLoadComponent(() =>
- import('../components/SimpleSessionsContainer')
- )}>
- <RouteWithChildRoutes path="/sessions" childRoutes={sessionsRoutes} />
- </Route>
+ <Route element={<MigrationContainer />}>
+ {sessionsRoutes()}
+
+ <Route path="/" element={<App />}>
+ <Route index={true} element={<Landing />} />
+
+ <Route element={<GlobalContainer />}>
+ {accountRoutes()}
+
+ {codingRulesRoutes()}
- <Route path="/" component={App}>
- <IndexRoute
- component={lazyLoadComponent(() => import('../components/Landing'))}
- />
+ {documentationRoutes()}
- <Route component={GlobalContainer}>
- <RouteWithChildRoutes path="account" childRoutes={accountRoutes} />
- <RouteWithChildRoutes path="coding_rules" childRoutes={codingRulesRoutes} />
- <RouteWithChildRoutes path="documentation" childRoutes={documentationRoutes} />
+ <Route
+ path="extension/:pluginKey/:extensionKey"
+ element={<GlobalPageExtension />}
+ />
+
+ {globalIssuesRoutes()}
+
+ {projectsRoutes()}
+
+ {qualityGatesRoutes()}
+ {qualityProfilesRoutes()}
+
+ <Route path="portfolios" element={<PortfoliosPage />} />
+ {webAPIRoutes()}
+
+ {renderComponentRoutes()}
+
+ {renderAdminRoutes()}
+ </Route>
<Route
- path="extension/:pluginKey/:extensionKey"
- component={lazyLoadComponent(() =>
- import('../components/extensions/GlobalPageExtension')
- )}
+ // 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
- path="issues"
- component={withIndexationGuard(Issues, PageContext.Issues)}
+ // 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 />}
/>
- <RouteWithChildRoutes path="projects" childRoutes={projectsRoutes} />
- <RouteWithChildRoutes path="quality_gates" childRoutes={qualityGatesRoutes} />
+
<Route
- path="portfolios"
- component={lazyLoadComponent(() =>
- import('../components/extensions/PortfoliosPage')
- )}
+ // 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 />}
/>
- <RouteWithChildRoutes path="profiles" childRoutes={qualityProfilesRoutes} />
- <RouteWithChildRoutes path="web_api" childRoutes={webAPIRoutes} />
-
- {renderComponentRoutes()}
-
- {renderAdminRoutes()}
+ <Route path="not_found" element={<NotFound />} />
+ <Route path="*" element={<NotFound />} />
</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"
- component={lazyLoadComponent(() => import('../components/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"
- component={lazyLoadComponent(() =>
- import('../../apps/change-admin-password/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"
- component={lazyLoadComponent(() => import('../components/PluginRiskConsent'))}
- />
- <Route
- path="not_found"
- component={lazyLoadComponent(() => import('../components/NotFound'))}
- />
- <Route
- path="*"
- component={lazyLoadComponent(() => import('../components/NotFound'))}
- />
</Route>
- </Route>
- </Router>
+ </Routes>
+ </BrowserRouter>
</IntlProvider>
</CurrentUserContextProvider>
</AppStateContextProvider>