aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web
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
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')
-rw-r--r--server/sonar-web/package.json4
-rw-r--r--server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts2
-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/helpers/getHistory.ts)24
-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
-rw-r--r--server/sonar-web/src/main/js/apps/account/Account.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx17
-rw-r--r--server/sonar-web/src/main/js/apps/account/components/Nav.tsx18
-rw-r--r--server/sonar-web/src/main/js/apps/account/projects/ProjectCard.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/account/routes.ts45
-rw-r--r--server/sonar-web/src/main/js/apps/account/routes.tsx37
-rw-r--r--server/sonar-web/src/main/js/apps/audit-logs/components/AuditApp.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/audit-logs/components/AuditAppRenderer.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/__snapshots__/AuditAppRenderer-test.tsx.snap24
-rw-r--r--server/sonar-web/src/main/js/apps/audit-logs/routes.tsx (renamed from server/sonar-web/src/main/js/apps/groups/routes.ts)10
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.tsx15
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/Header.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/BackgroundTasksApp-test.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/BackgroundTasksApp-test.tsx.snap72
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/TaskComponent-test.tsx.snap34
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/routes.tsx (renamed from server/sonar-web/src/main/js/apps/code/routes.ts)10
-rw-r--r--server/sonar-web/src/main/js/apps/change-admin-password/__tests__/__snapshots__/ChangeAdminPasswordApp-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx12
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/SourceViewerWrapper.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/__tests__/CodeApp-test.tsx13
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/ComponentName-test.tsx.snap31
-rw-r--r--server/sonar-web/src/main/js/apps/code/routes.tsx (renamed from server/sonar-web/src/main/js/apps/overview/routes.ts)10
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts10
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/App.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsCustomRules.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsProfiles.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/RuleListItem.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/App-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleListItem-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetailsIssues-test.tsx.snap23
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetailsMeta-test.tsx.snap7
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleListItem-test.tsx.snap14
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/routes.tsx (renamed from server/sonar-web/src/main/js/apps/coding-rules/routes.ts)48
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/__tests__/MeasuresApp-it.tsx (renamed from server/sonar-web/src/main/js/helpers/__tests__/getHistory-test.ts)21
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/App.tsx22
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/App-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.tsx.snap16
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/ComponentCell-test.tsx62
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/ComponentCell-test.tsx.snap73
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/routes.ts65
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/routes.tsx79
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/AzureProjectAccordion.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/AzureProjectCreate.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/AzureProjectCreateRenderer.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/AzureProjectsList.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/BitbucketCloudProjectCreate.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/BitbucketCloudSearchForm.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/BitbucketImportRepositoryForm.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/BitbucketProjectAccordion.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/BitbucketProjectCreate.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreate.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreateRenderer.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreate.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/GitlabProjectSelectionForm.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/WrongBindingCountAlert.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectAccordion-test.tsx.snap14
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectCreateRenderer-test.tsx.snap6
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectsList-test.tsx.snap7
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketCloudSearchForm-test.tsx.snap21
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketImportRepositoryForm-test.tsx.snap7
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketProjectAccordion-test.tsx.snap49
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap5
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitHubProjectCreateRenderer-test.tsx.snap9
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectSelectionForm-test.tsx.snap28
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/WrongBindingCountAlert-test.tsx.snap6
-rw-r--r--server/sonar-web/src/main/js/apps/documentation/components/App.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/documentation/components/MenuItem.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/documentation/components/SearchResultEntry.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/documentation/components/__tests__/App-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/App-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/MenuItem-test.tsx.snap6
-rw-r--r--server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/SearchResultEntry-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/documentation/routes.tsx31
-rw-r--r--server/sonar-web/src/main/js/apps/groups/routes.tsx (renamed from server/sonar-web/src/main/js/apps/users/routes.ts)10
-rw-r--r--server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx29
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/AppContainer.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/IssueTabViewer.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesApp-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/ComponentSourceSnippetGroupViewer-test.tsx17
-rw-r--r--server/sonar-web/src/main/js/apps/issues/routes.tsx71
-rw-r--r--server/sonar-web/src/main/js/apps/maintenance/components/App.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/maintenance/components/MaintenanceAppContainer.tsx15
-rw-r--r--server/sonar-web/src/main/js/apps/maintenance/components/SetupAppContainer.tsx13
-rw-r--r--server/sonar-web/src/main/js/apps/maintenance/routes.tsx16
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/App.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/MarketplaceAppContainer.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/App-test.tsx.snap4
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/MarketplaceAppContainer-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/LicensePromptModal.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/routes.ts28
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/routes.tsx (renamed from server/sonar-web/src/main/js/apps/quality-gates/routes.ts)8
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/FirstAnalysisNextStepsNotif.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanel.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/__tests__/MeasuresPanel-test.tsx30
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/__tests__/MeasuresPanelNoNewCode-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/FirstAnalysisNextStepsNotif-test.tsx.snap32
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanel-test.tsx.snap6931
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanelNoNewCode-test.tsx.snap6
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/App.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/IssueLabel.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/QualityGateCondition.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/IssueLabel-test.tsx.snap92
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/QualityGateCondition-test.tsx.snap98
-rw-r--r--server/sonar-web/src/main/js/apps/overview/pullRequests/LargeQualityGateBadge.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/__snapshots__/LargeQualityGateBadge-test.tsx.snap4
-rw-r--r--server/sonar-web/src/main/js/apps/overview/routes.tsx (renamed from server/sonar-web/src/main/js/apps/projectBaseline/routes.ts)10
-rw-r--r--server/sonar-web/src/main/js/apps/overview/utils.ts13
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/App.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/NameCell.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/TemplateHeader.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/ActionsCell-test.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/NameCell-test.tsx.snap6
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/routes.tsx (renamed from server/sonar-web/src/main/js/apps/permission-templates/routes.ts)10
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/project/components/App.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/App-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/routes.ts32
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/routes.tsx31
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/DefinitionChangeEventInner.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/EventInner.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.tsx73
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/RichQualityGateEventInner.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/EventInner-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityAppContainer-it.tsx120
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityAppContainer-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/DefinitionChangeEventInner-test.tsx.snap28
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/RichQualityGateEventInner-test.tsx.snap14
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/routes.ts30
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/routes.tsx26
-rw-r--r--server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/projectBaseline/components/AppHeader.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/__snapshots__/AppHeader-test.tsx.snap6
-rw-r--r--server/sonar-web/src/main/js/apps/projectBaseline/routes.tsx26
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/components/LifetimeInformationRenderer.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/components/ProjectBranchesApp.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/LifetimeInformationRenderer-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/routes.ts28
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/routes.tsx (renamed from server/sonar-web/src/main/js/apps/background-tasks/routes.ts)10
-rw-r--r--server/sonar-web/src/main/js/apps/projectDeletion/App.tsx16
-rw-r--r--server/sonar-web/src/main/js/apps/projectDeletion/Form.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectDeletion/__tests__/App-test.tsx30
-rw-r--r--server/sonar-web/src/main/js/apps/projectDeletion/__tests__/__snapshots__/App-test.tsx.snap32
-rw-r--r--server/sonar-web/src/main/js/apps/projectDump/ProjectDumpApp.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/projectDump/components/Import.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectDump/components/__tests__/__snapshots__/Import-test.tsx.snap9
-rw-r--r--server/sonar-web/src/main/js/apps/projectDump/routes.tsx (renamed from server/sonar-web/src/main/js/apps/projectDump/routes.ts)8
-rw-r--r--server/sonar-web/src/main/js/apps/projectKey/Key.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/projectKey/__tests__/Key-test.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/projectKey/__tests__/__snapshots__/Key-test.tsx.snap16
-rw-r--r--server/sonar-web/src/main/js/apps/projectLinks/App.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/projectLinks/__tests__/App-test.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/projectQualityGate/ProjectQualityGateApp.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/projectQualityGate/ProjectQualityGateAppRenderer.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/ProjectQualityGateApp-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/__snapshots__/ProjectQualityGateAppRenderer-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/projectQualityGate/routes.ts28
-rw-r--r--server/sonar-web/src/main/js/apps/projectQualityGate/routes.tsx26
-rw-r--r--server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesApp.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesAppRenderer.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/ProjectQualityProfilesApp-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/__snapshots__/ProjectQualityProfilesAppRenderer-test.tsx.snap63
-rw-r--r--server/sonar-web/src/main/js/apps/projectQualityProfiles/components/LanguageProfileSelectOption.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectQualityProfiles/components/__tests__/__snapshots__/LanguageProfileSelectOption-test.tsx.snap7
-rw-r--r--server/sonar-web/src/main/js/apps/projectQualityProfiles/routes.ts30
-rw-r--r--server/sonar-web/src/main/js/apps/projectQualityProfiles/routes.tsx28
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx73
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx120
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/EmptyFavoriteSearch.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/EmptyInstance.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.tsx27
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenu.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenuItem.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx24
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/ApplicationCreation-test.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/DefaultPageSelector-test.tsx110
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/FavoriteFilter-test.tsx36
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/DefaultPageSelector-test.tsx.snap7
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/FavoriteFilter-test.tsx.snap46
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/NoFavoriteProjects-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCreationMenu-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCreationMenuItem-test.tsx.snap12
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCard-test.tsx.snap42
-rw-r--r--server/sonar-web/src/main/js/apps/projects/routes.tsx (renamed from server/sonar-web/src/main/js/apps/projects/routes.ts)41
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectManagementApp-it.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap7
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRow-test.tsx.snap18
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/routes.ts28
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/routes.tsx27
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/App.tsx26
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/CopyQualityGateForm.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/CreateQualityGateForm.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/DeleteQualityGateForm.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/List.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/App-it.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/routes.tsx31
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/Changelog-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResults.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonResults-test.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.tsx90
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileLink.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileNotFound.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/QualityProfilesApp.tsx19
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileActions-test.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileContainer-test.tsx98
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileLink-test.tsx (renamed from server/sonar-web/src/main/js/app/components/extensions/__tests__/PortfolioPage-test.tsx)32
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/QualityProfilesApp-test.tsx12
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/ProfileActions-test.tsx.snap50
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/QualityProfilesApp-test.tsx.snap16
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesDeprecatedWarning.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowOfType.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowTotal.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesSonarWayComparison.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileDetails-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileHeader-test.tsx.snap36
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileProjects-test.tsx.snap21
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRules-test.tsx.snap7
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesDeprecatedWarning-test.tsx.snap8
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowOfType-test.tsx.snap32
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowTotal-test.tsx.snap28
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesSonarWayComparison-test.tsx.snap9
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.tsx24
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListHeader.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/ProfilesList-test.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/EvolutionDeprecated-test.tsx.snap32
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/PageHeader-test.tsx.snap11
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/ProfilesListRow-test.tsx.snap52
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/qualityProfilesContext.tsx54
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/routes.ts48
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/routes.tsx40
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/utils.ts9
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/EmptyHotspotsPage.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotHeader.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/EmptyHotspotsPage-test.tsx.snap4
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotHeader-test.tsx.snap7
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/components/LoginContainer.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/routes.ts37
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/routes.tsx35
-rw-r--r--server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.ts10
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx15
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/AnalysisScope.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/NewCodePeriod.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/SettingsApp.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/SettingsSearch.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/SettingsSearchRenderer.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/__tests__/AllCategoriesList-test.tsx37
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsApp-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsSearch-test.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AllCategoriesList-test.tsx.snap230
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AnalysisScope-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/NewCodePeriod-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SettingsSearchRenderer-test.tsx.snap12
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionBox.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionFormField.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegration.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/AzureForm.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketCloudForm.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketServerForm.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/GithubForm.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/GitlabForm.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionBox-test.tsx.snap4
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionFormField-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AzureForm-test.tsx.snap4
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketCloudForm-test.tsx.snap4
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketServerForm-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GithubForm-test.tsx.snap4
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GitlabForm-test.tsx.snap4
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/AlmSpecificForm-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBindingRenderer-test.tsx.snap13
-rw-r--r--server/sonar-web/src/main/js/apps/settings/encryption/EncryptionForm.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/encryption/GenerateSecretKeyForm.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/encryption/__tests__/__snapshots__/EncryptionForm-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/encryption/__tests__/__snapshots__/GenerateSecretKeyForm-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/routes.ts32
-rw-r--r--server/sonar-web/src/main/js/apps/settings/routes.tsx32
-rw-r--r--server/sonar-web/src/main/js/apps/settings/utils.ts4
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/App.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/__tests__/App-test.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/system/routes.ts28
-rw-r--r--server/sonar-web/src/main/js/apps/system/routes.tsx26
-rw-r--r--server/sonar-web/src/main/js/apps/tutorials/components/TutorialsApp.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/tutorials/routes.ts28
-rw-r--r--server/sonar-web/src/main/js/apps/tutorials/routes.tsx (renamed from server/sonar-web/src/main/js/apps/audit-logs/routes.ts)10
-rw-r--r--server/sonar-web/src/main/js/apps/users/UsersApp.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-test.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/users/routes.tsx26
-rw-r--r--server/sonar-web/src/main/js/apps/web-api/components/Action.tsx13
-rw-r--r--server/sonar-web/src/main/js/apps/web-api/components/Menu.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx17
-rw-r--r--server/sonar-web/src/main/js/apps/web-api/components/__tests__/WebApiApp-test.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/web-api/components/__tests__/__snapshots__/Action-test.tsx.snap4
-rw-r--r--server/sonar-web/src/main/js/apps/web-api/components/__tests__/__snapshots__/Menu-test.tsx.snap50
-rw-r--r--server/sonar-web/src/main/js/apps/web-api/components/__tests__/__snapshots__/WebApiApp-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/web-api/routes.ts32
-rw-r--r--server/sonar-web/src/main/js/apps/web-api/routes.tsx (renamed from server/sonar-web/src/main/js/apps/documentation/routes.ts)13
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/App.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/PageHeader.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/__tests__/App-test.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/PageHeader-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/routes.ts28
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/routes.tsx26
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeaderSlim.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerHeader-test.tsx.snap64
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerHeaderSlim-test.tsx.snap27
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlay-test.tsx.snap21
-rw-r--r--server/sonar-web/src/main/js/components/charts/BubbleChart.tsx8
-rw-r--r--server/sonar-web/src/main/js/components/charts/TreeMapRect.tsx7
-rw-r--r--server/sonar-web/src/main/js/components/charts/__tests__/BubbleChart-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/BubbleChart-test.tsx.snap4
-rw-r--r--server/sonar-web/src/main/js/components/common/ActivityLink.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/common/DocumentationTooltip.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/common/MeasuresLink.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/ActivityLink-test.tsx.snap30
-rw-r--r--server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/DocumentationTooltip-test.tsx.snap6
-rw-r--r--server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MeasuresLink-test.tsx.snap21
-rw-r--r--server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx5
-rw-r--r--server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ActionsDropdown-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/components/docs/DocLink.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/docs/DocTooltipLink.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocLink-test.tsx.snap6
-rw-r--r--server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocTooltipLink-test.tsx.snap4
-rw-r--r--server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/__snapshots__/EmbedDocsPopup-test.tsx.snap4
-rw-r--r--server/sonar-web/src/main/js/components/hoc/withLocation.tsx31
-rw-r--r--server/sonar-web/src/main/js/components/hoc/withRouter.tsx80
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap52
-rw-r--r--server/sonar-web/src/main/js/components/shared/DrilldownLink.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/shared/__tests__/__snapshots__/DrilldownLink-test.tsx.snap17
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/TutorialSelection.tsx7
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/AlertClassicEditor.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/PublishSteps.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/__tests__/__snapshots__/AlertClassicEditor-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/__tests__/__snapshots__/PublishSteps-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/components/EditTokenModal.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/jenkins/PreRequisitesStep.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/__snapshots__/PreRequisitesStep-test.tsx.snap6
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/manual/DoneNextSteps.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/manual/TokenStep.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/manual/__tests__/__snapshots__/DoneNextSteps-test.tsx.snap8
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/manual/__tests__/__snapshots__/TokenStep-test.tsx.snap12
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/manual/commands/DotNetExecute.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/manual/commands/ExecBuildWrapper.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/manual/commands/ExecScanner.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/manual/commands/JavaGradle.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/manual/commands/JavaMaven.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/manual/commands/__tests__/__snapshots__/DotNetExecute-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/manual/commands/__tests__/__snapshots__/ExecBuildWrapper-test.tsx.snap6
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/manual/commands/__tests__/__snapshots__/ExecScanner-test.tsx.snap8
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/manual/commands/__tests__/__snapshots__/JavaGradle-test.tsx.snap4
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/manual/commands/__tests__/__snapshots__/JavaMaven-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/helpers/__tests__/handleRequiredAuthentication-test.ts30
-rw-r--r--server/sonar-web/src/main/js/helpers/__tests__/urls-test.ts368
-rw-r--r--server/sonar-web/src/main/js/helpers/branch-like.ts3
-rw-r--r--server/sonar-web/src/main/js/helpers/handleRequiredAuthentication.ts7
-rw-r--r--server/sonar-web/src/main/js/helpers/testMocks.ts11
-rw-r--r--server/sonar-web/src/main/js/helpers/testReactTestingUtils.tsx85
-rw-r--r--server/sonar-web/src/main/js/helpers/urls.ts176
-rw-r--r--server/sonar-web/src/main/js/types/admin.ts24
-rw-r--r--server/sonar-web/src/main/js/types/component.ts15
-rw-r--r--server/sonar-web/yarn.lock111
486 files changed, 10129 insertions, 6706 deletions
diff --git a/server/sonar-web/package.json b/server/sonar-web/package.json
index 16eacdb717d..4c8c0a85765 100644
--- a/server/sonar-web/package.json
+++ b/server/sonar-web/package.json
@@ -19,7 +19,6 @@
"date-fns": "1.30.1",
"dompurify": "2.3.6",
"formik": "1.2.0",
- "history": "3.3.0",
"lodash": "4.17.21",
"lunr": "2.3.9",
"mdast-util-toc": "5.0.2",
@@ -31,7 +30,7 @@
"react-helmet-async": "1.2.3",
"react-intl": "3.12.1",
"react-modal": "3.14.4",
- "react-router": "3.2.6",
+ "react-router-dom": "6.3.0",
"react-select": "4.3.1",
"react-virtualized": "9.22.3",
"regenerator-runtime": "0.13.9",
@@ -69,7 +68,6 @@
"@types/react-dom": "16.8.4",
"@types/react-helmet": "5.0.15",
"@types/react-modal": "3.13.1",
- "@types/react-router": "3.0.20",
"@types/react-select": "4.0.16",
"@types/react-virtualized": "9.21.20",
"@types/valid-url": "1.0.3",
diff --git a/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts
index 65128a0e82e..8b641a56d62 100644
--- a/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts
+++ b/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts
@@ -269,7 +269,7 @@ export default class IssuesServiceMock {
};
handleSearchIssues = (query: RequestData): Promise<RawIssuesResponse> => {
- const facets = query.facets.split(',').map((name: string) => {
+ const facets = (query.facets ?? '').split(',').map((name: string) => {
if (name === 'owaspTop10-2021') {
return this.owasp2021FacetList();
}
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/helpers/getHistory.ts b/server/sonar-web/src/main/js/app/components/admin/withAdminPagesOutletContext.tsx
index 849bc0ed070..961f4f93626 100644
--- a/server/sonar-web/src/main/js/helpers/getHistory.ts
+++ b/server/sonar-web/src/main/js/app/components/admin/withAdminPagesOutletContext.tsx
@@ -17,20 +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 { createHistory, History } from 'history';
-import { useRouterHistory } from 'react-router';
-import { getBaseUrl } from './system';
+import * as React from 'react';
+import { useOutletContext } from 'react-router-dom';
+import { AdminPagesContext } from '../../../types/admin';
-let history: History;
+export default function withAdminPagesOutletContext(
+ WrappedComponent: React.ComponentType<AdminPagesContext>
+) {
+ return function WithAdminPagesOutletContext() {
+ const { adminPages } = useOutletContext<AdminPagesContext>();
-function ensureHistory() {
- // eslint-disable-next-line react-hooks/rules-of-hooks
- history = useRouterHistory(createHistory)({
- basename: getBaseUrl()
- });
- return history;
-}
-
-export default function getHistory() {
- return history ? history : ensureHistory();
+ 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>
diff --git a/server/sonar-web/src/main/js/apps/account/Account.tsx b/server/sonar-web/src/main/js/apps/account/Account.tsx
index 1a7b5fcd4eb..6cfc316a9eb 100644
--- a/server/sonar-web/src/main/js/apps/account/Account.tsx
+++ b/server/sonar-web/src/main/js/apps/account/Account.tsx
@@ -19,6 +19,7 @@
*/
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
+import { Outlet } from 'react-router-dom';
import withCurrentUserContext from '../../app/components/current-user/withCurrentUserContext';
import A11ySkipTarget from '../../components/a11y/A11ySkipTarget';
import Suggestions from '../../components/embed-docs-modal/Suggestions';
@@ -41,7 +42,7 @@ export class Account extends React.PureComponent<Props> {
}
render() {
- const { currentUser, children } = this.props;
+ const { currentUser } = this.props;
if (!currentUser.isLoggedIn) {
return null;
@@ -60,7 +61,7 @@ export class Account extends React.PureComponent<Props> {
</div>
</header>
- {children}
+ <Outlet />
</div>
);
}
diff --git a/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx b/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx
index 5fc50ba13bc..b2f4304d0ce 100644
--- a/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx
+++ b/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx
@@ -24,7 +24,6 @@ import selectEvent from 'react-select-event';
import { getMyProjects, getScannableProjects } from '../../../api/components';
import NotificationsMock from '../../../api/mocks/NotificationsMock';
import UserTokensMock from '../../../api/mocks/UserTokensMock';
-import getHistory from '../../../helpers/getHistory';
import { mockCurrentUser, mockLoggedInUser } from '../../../helpers/testMocks';
import { renderApp } from '../../../helpers/testReactTestingUtils';
import { Permissions } from '../../../types/permissions';
@@ -128,11 +127,21 @@ jest.mock('../../../api/users', () => ({
changePassword: jest.fn().mockResolvedValue(true)
}));
-it('should handle a currentUser not logged in', async () => {
+it('should handle a currentUser not logged in', () => {
+ const replace = jest.fn();
+ const locationMock = jest.spyOn(window, 'location', 'get').mockReturnValue(({
+ pathname: '/account',
+ search: '',
+ hash: '',
+ replace
+ } as unknown) as Location);
+
renderAccountApp(mockCurrentUser());
// Make sure we're redirected to the login screen
- expect(await screen.findByText('/sessions/new?return_to=%2Faccount')).toBeInTheDocument();
+ expect(replace).toBeCalledWith('/sessions/new?return_to=%2Faccount');
+
+ locationMock.mockRestore();
});
it('should render the top menu', () => {
@@ -544,5 +553,5 @@ function getCheckboxByRowName(name: string) {
}
function renderAccountApp(currentUser: CurrentUser, navigateTo?: string) {
- renderApp('account', routes, { currentUser, history: getHistory(), navigateTo });
+ renderApp('account', routes, { currentUser, navigateTo });
}
diff --git a/server/sonar-web/src/main/js/apps/account/components/Nav.tsx b/server/sonar-web/src/main/js/apps/account/components/Nav.tsx
index 54545c89085..76159e9c080 100644
--- a/server/sonar-web/src/main/js/apps/account/components/Nav.tsx
+++ b/server/sonar-web/src/main/js/apps/account/components/Nav.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { IndexLink, Link } from 'react-router';
+import { NavLink } from 'react-router-dom';
import NavBarTabs from '../../../components/ui/NavBarTabs';
import { translate } from '../../../helpers/l10n';
@@ -27,24 +27,18 @@ export default function Nav() {
<nav className="account-nav">
<NavBarTabs>
<li>
- <IndexLink activeClassName="active" to="/account/">
+ <NavLink end={true} to="/account">
{translate('my_account.profile')}
- </IndexLink>
+ </NavLink>
</li>
<li>
- <Link activeClassName="active" to="/account/security/">
- {translate('my_account.security')}
- </Link>
+ <NavLink to="/account/security">{translate('my_account.security')}</NavLink>
</li>
<li>
- <Link activeClassName="active" to="/account/notifications">
- {translate('my_account.notifications')}
- </Link>
+ <NavLink to="/account/notifications">{translate('my_account.notifications')}</NavLink>
</li>
<li>
- <Link activeClassName="active" to="/account/projects/">
- {translate('my_account.projects')}
- </Link>
+ <NavLink to="/account/projects">{translate('my_account.projects')}</NavLink>
</li>
</NavBarTabs>
</nav>
diff --git a/server/sonar-web/src/main/js/apps/account/projects/ProjectCard.tsx b/server/sonar-web/src/main/js/apps/account/projects/ProjectCard.tsx
index 620f401b13a..f8d3e916aad 100644
--- a/server/sonar-web/src/main/js/apps/account/projects/ProjectCard.tsx
+++ b/server/sonar-web/src/main/js/apps/account/projects/ProjectCard.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 MetaLink from '../../../app/components/nav/component/projectInformation/meta/MetaLink';
import HelpTooltip from '../../../components/controls/HelpTooltip';
import DateFromNow from '../../../components/intl/DateFromNow';
diff --git a/server/sonar-web/src/main/js/apps/account/routes.ts b/server/sonar-web/src/main/js/apps/account/routes.ts
deleted file mode 100644
index 618015e7955..00000000000
--- a/server/sonar-web/src/main/js/apps/account/routes.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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 { lazyLoadComponent } from '../../components/lazyLoadComponent';
-
-const routes = [
- {
- component: lazyLoadComponent(() => import('./Account')),
- childRoutes: [
- {
- indexRoute: { component: lazyLoadComponent(() => import('./profile/Profile')) }
- },
- {
- path: 'security',
- component: lazyLoadComponent(() => import('./security/Security'))
- },
- {
- path: 'projects',
- component: lazyLoadComponent(() => import('./projects/ProjectsContainer'))
- },
- {
- path: 'notifications',
- component: lazyLoadComponent(() => import('./notifications/Notifications'))
- }
- ]
- }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/account/routes.tsx b/server/sonar-web/src/main/js/apps/account/routes.tsx
new file mode 100644
index 00000000000..a39d51dd419
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/account/routes.tsx
@@ -0,0 +1,37 @@
+/*
+ * 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 React from 'react';
+import { Route } from 'react-router-dom';
+import Account from './Account';
+import Notifications from './notifications/Notifications';
+import Profile from './profile/Profile';
+import ProjectsContainer from './projects/ProjectsContainer';
+import Security from './security/Security';
+
+const routes = () => (
+ <Route path="account" element={<Account />}>
+ <Route index={true} element={<Profile />} />
+ <Route path="security" element={<Security />} />
+ <Route path="projects" element={<ProjectsContainer />} />
+ <Route path="notifications" element={<Notifications />} />
+ </Route>
+);
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/audit-logs/components/AuditApp.tsx b/server/sonar-web/src/main/js/apps/audit-logs/components/AuditApp.tsx
index da2e3196a93..30790038dc8 100644
--- a/server/sonar-web/src/main/js/apps/audit-logs/components/AuditApp.tsx
+++ b/server/sonar-web/src/main/js/apps/audit-logs/components/AuditApp.tsx
@@ -19,6 +19,7 @@
*/
import * as React from 'react';
import { getValues } from '../../../api/settings';
+import withAdminPagesOutletContext from '../../../app/components/admin/withAdminPagesOutletContext';
import { AdminPageExtension } from '../../../types/extension';
import { SettingsKey } from '../../../types/settings';
import { Extension } from '../../../types/types';
@@ -37,7 +38,7 @@ interface State {
selection: RangeOption;
}
-export default class AuditApp extends React.PureComponent<Props, State> {
+export class AuditApp extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
@@ -102,3 +103,5 @@ export default class AuditApp extends React.PureComponent<Props, State> {
);
}
}
+
+export default withAdminPagesOutletContext(AuditApp);
diff --git a/server/sonar-web/src/main/js/apps/audit-logs/components/AuditAppRenderer.tsx b/server/sonar-web/src/main/js/apps/audit-logs/components/AuditAppRenderer.tsx
index c8f087b80c3..c3f1952a905 100644
--- a/server/sonar-web/src/main/js/apps/audit-logs/components/AuditAppRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/audit-logs/components/AuditAppRenderer.tsx
@@ -21,11 +21,12 @@ import { subDays } from 'date-fns';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import { FormattedMessage } from 'react-intl';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import DateRangeInput from '../../../components/controls/DateRangeInput';
import Radio from '../../../components/controls/Radio';
import Suggestions from '../../../components/embed-docs-modal/Suggestions';
import { translate } from '../../../helpers/l10n';
+import { queryToSearch } from '../../../helpers/urls';
import '../style.css';
import { HousekeepingPolicy, now, RangeOption } from '../utils';
import DownloadButton from './DownloadButton';
@@ -90,7 +91,7 @@ export default function AuditAppRenderer(props: AuditAppRendererProps) {
<Link
to={{
pathname: '/admin/settings',
- query: { category: 'housekeeping' },
+ search: queryToSearch({ category: 'housekeeping' }),
hash: '#auditLogs'
}}>
{translate('audit_logs.page.description.link')}
diff --git a/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-test.tsx b/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-test.tsx
index baac6e88cda..5d519095633 100644
--- a/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-test.tsx
+++ b/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-test.tsx
@@ -24,7 +24,7 @@ import { getValues } from '../../../../api/settings';
import { waitAndUpdate } from '../../../../helpers/testUtils';
import { AdminPageExtension } from '../../../../types/extension';
import { HousekeepingPolicy, RangeOption } from '../../utils';
-import AuditApp from '../AuditApp';
+import { AuditApp } from '../AuditApp';
import AuditAppRenderer from '../AuditAppRenderer';
jest.mock('../../../../api/settings', () => ({
diff --git a/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/__snapshots__/AuditAppRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/__snapshots__/AuditAppRenderer-test.tsx.snap
index f5c6b757702..fa377d15b9c 100644
--- a/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/__snapshots__/AuditAppRenderer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/__snapshots__/AuditAppRenderer-test.tsx.snap
@@ -35,15 +35,11 @@ exports[`should render correctly for Monthly housekeeping policy 1`] = `
Object {
"housekeeping": "audit_logs.housekeeping_policy.Monthly",
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"hash": "#auditLogs",
"pathname": "/admin/settings",
- "query": Object {
- "category": "housekeeping",
- },
+ "search": "?category=housekeeping",
}
}
>
@@ -161,15 +157,11 @@ exports[`should render correctly for Trimestrial housekeeping policy 1`] = `
Object {
"housekeeping": "audit_logs.housekeeping_policy.Trimestrial",
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"hash": "#auditLogs",
"pathname": "/admin/settings",
- "query": Object {
- "category": "housekeeping",
- },
+ "search": "?category=housekeeping",
}
}
>
@@ -299,15 +291,11 @@ exports[`should render correctly for Weekly housekeeping policy 1`] = `
Object {
"housekeeping": "audit_logs.housekeeping_policy.Weekly",
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"hash": "#auditLogs",
"pathname": "/admin/settings",
- "query": Object {
- "category": "housekeeping",
- },
+ "search": "?category=housekeeping",
}
}
>
@@ -413,15 +401,11 @@ exports[`should render correctly for Yearly housekeeping policy 1`] = `
Object {
"housekeeping": "audit_logs.housekeeping_policy.Yearly",
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"hash": "#auditLogs",
"pathname": "/admin/settings",
- "query": Object {
- "category": "housekeeping",
- },
+ "search": "?category=housekeeping",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/groups/routes.ts b/server/sonar-web/src/main/js/apps/audit-logs/routes.tsx
index 9d8f2bd42e4..247f80add12 100644
--- a/server/sonar-web/src/main/js/apps/groups/routes.ts
+++ b/server/sonar-web/src/main/js/apps/audit-logs/routes.tsx
@@ -17,12 +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 { lazyLoadComponent } from '../../components/lazyLoadComponent';
+import React from 'react';
+import { Route } from 'react-router-dom';
+import AuditApp from './components/AuditApp';
-const routes = [
- {
- indexRoute: { component: lazyLoadComponent(() => import('./components/App')) }
- }
-];
+const routes = () => <Route path="audit" element={<AuditApp />} />;
export default routes;
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.tsx
index 82bc95ab111..3e10c43987f 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.tsx
@@ -27,13 +27,14 @@ import {
getStatus,
getTypes
} from '../../../api/ce';
+import withComponentContext from '../../../app/components/componentContext/withComponentContext';
import Suggestions from '../../../components/embed-docs-modal/Suggestions';
-import { Location, Router } from '../../../components/hoc/withRouter';
+import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
import { toShortNotSoISOString } from '../../../helpers/dates';
import { translate } from '../../../helpers/l10n';
import { parseAsDate } from '../../../helpers/query';
import { Task, TaskStatuses } from '../../../types/tasks';
-import { Component } from '../../../types/types';
+import { Component, RawQuery } from '../../../types/types';
import '../background-tasks.css';
import { CURRENTS, DEBOUNCE_DELAY, DEFAULT_FILTERS } from '../constants';
import { mapFiltersToParameters, Query, updateTask } from '../utils';
@@ -44,9 +45,9 @@ import Stats from './Stats';
import Tasks from './Tasks';
interface Props {
- component?: Pick<Component, 'key'> & { id: string }; // id should be removed when api/ce/activity accept a component key instead of an id
+ component?: Component;
location: Location;
- router: Pick<Router, 'push'>;
+ router: Router;
}
interface State {
@@ -58,7 +59,7 @@ interface State {
types?: string[];
}
-export default class BackgroundTasksApp extends React.PureComponent<Props, State> {
+export class BackgroundTasksApp extends React.PureComponent<Props, State> {
loadTasksDebounced: () => void;
mounted = false;
@@ -134,7 +135,7 @@ export default class BackgroundTasksApp extends React.PureComponent<Props, State
};
handleFilterUpdate = (nextState: Partial<Query>) => {
- const nextQuery = { ...this.props.location.query, ...nextState };
+ const nextQuery: RawQuery = { ...this.props.location.query, ...nextState };
// remove defaults
Object.keys(DEFAULT_FILTERS).forEach((key: keyof typeof DEFAULT_FILTERS) => {
@@ -253,3 +254,5 @@ export default class BackgroundTasksApp extends React.PureComponent<Props, State
);
}
}
+
+export default withComponentContext(withRouter(BackgroundTasksApp));
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/Header.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/Header.tsx
index 660d47c34f4..fa7c2e92c3a 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/Header.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/Header.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 Workers from './Workers';
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.tsx
index ebbc373e4d3..f642e4521d5 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.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 BranchIcon from '../../../components/icons/BranchIcon';
import PullRequestIcon from '../../../components/icons/PullRequestIcon';
import QualifierIcon from '../../../components/icons/QualifierIcon';
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/BackgroundTasksApp-test.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/BackgroundTasksApp-test.tsx
index 886c779709f..dccd2c33373 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/BackgroundTasksApp-test.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/BackgroundTasksApp-test.tsx
@@ -19,9 +19,10 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { mockComponent } from '../../../../helpers/mocks/component';
import { mockLocation, mockRouter } from '../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../helpers/testUtils';
-import BackgroundTasksApp from '../BackgroundTasksApp';
+import { BackgroundTasksApp } from '../BackgroundTasksApp';
jest.mock('../../../../api/ce', () => ({
getTypes: jest.fn().mockResolvedValue({
@@ -88,7 +89,7 @@ it('should render correctly', async () => {
function shallowRender(props: Partial<BackgroundTasksApp['props']> = {}) {
return shallow(
<BackgroundTasksApp
- component={{ key: 'foo', id: '564' }}
+ component={mockComponent({ key: 'foo' })}
location={mockLocation()}
router={mockRouter()}
{...props}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/BackgroundTasksApp-test.tsx.snap b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/BackgroundTasksApp-test.tsx.snap
index 75c79b093c4..5e4d8554894 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/BackgroundTasksApp-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/BackgroundTasksApp-test.tsx.snap
@@ -26,16 +26,48 @@ exports[`should render correctly: loaded 1`] = `
<Header
component={
Object {
- "id": "564",
+ "breadcrumbs": Array [],
"key": "foo",
+ "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 [],
}
}
/>
<Stats
component={
Object {
- "id": "564",
+ "breadcrumbs": Array [],
"key": "foo",
+ "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 [],
}
}
failingCount={15}
@@ -46,8 +78,24 @@ exports[`should render correctly: loaded 1`] = `
<Search
component={
Object {
- "id": "564",
+ "breadcrumbs": Array [],
"key": "foo",
+ "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 [],
}
}
currents="__ALL__"
@@ -71,8 +119,24 @@ exports[`should render correctly: loaded 1`] = `
<Tasks
component={
Object {
- "id": "564",
+ "breadcrumbs": Array [],
"key": "foo",
+ "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 [],
}
}
loading={false}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/TaskComponent-test.tsx.snap b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/TaskComponent-test.tsx.snap
index 3dadcaf9fbf..44c3899b20b 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/TaskComponent-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/TaskComponent-test.tsx.snap
@@ -11,15 +11,10 @@ exports[`renders correctly 1`] = `
</span>
<Link
className="spacer-right"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "id": "foo",
- },
+ "search": "?id=foo",
}
}
>
@@ -38,15 +33,10 @@ exports[`renders correctly: branch 1`] = `
/>
<Link
className="spacer-right"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": "feature",
- "id": "foo",
- },
+ "search": "?branch=feature&id=foo",
}
}
>
@@ -81,15 +71,10 @@ exports[`renders correctly: branch 2`] = `
/>
<Link
className="spacer-right"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": "branch-6.7",
- "id": "foo",
- },
+ "search": "?branch=branch-6.7&id=foo",
}
}
>
@@ -128,14 +113,10 @@ exports[`renders correctly: portfolio 1`] = `
</span>
<Link
className="spacer-right"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/portfolio",
- "query": Object {
- "id": "foo",
- },
+ "search": "?id=foo",
}
}
>
@@ -154,15 +135,10 @@ exports[`renders correctly: pull request 1`] = `
/>
<Link
className="spacer-right"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "id": "foo",
- "pullRequest": "pr-89",
- },
+ "search": "?id=foo&pullRequest=pr-89",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/code/routes.ts b/server/sonar-web/src/main/js/apps/background-tasks/routes.tsx
index 947e2e92bc0..71f07c4e582 100644
--- a/server/sonar-web/src/main/js/apps/code/routes.ts
+++ b/server/sonar-web/src/main/js/apps/background-tasks/routes.tsx
@@ -17,12 +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 { lazyLoadComponent } from '../../components/lazyLoadComponent';
+import React from 'react';
+import { Route } from 'react-router-dom';
+import BackgroundTasksApp from './components/BackgroundTasksApp';
-const routes = [
- {
- indexRoute: { component: lazyLoadComponent(() => import('./components/CodeApp')) }
- }
-];
+const routes = () => <Route path="background_tasks" element={<BackgroundTasksApp />} />;
export default routes;
diff --git a/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/__snapshots__/ChangeAdminPasswordApp-test.tsx.snap b/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/__snapshots__/ChangeAdminPasswordApp-test.tsx.snap
index e7e4585e6ae..1403f55f727 100644
--- a/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/__snapshots__/ChangeAdminPasswordApp-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/__snapshots__/ChangeAdminPasswordApp-test.tsx.snap
@@ -5,7 +5,6 @@ exports[`should render correctly: admin is not using the default password 1`] =
confirmPasswordValue=""
location={
Object {
- "action": "PUSH",
"hash": "",
"key": "key",
"pathname": "/path",
@@ -29,7 +28,6 @@ exports[`should render correctly: default 1`] = `
confirmPasswordValue=""
location={
Object {
- "action": "PUSH",
"hash": "",
"key": "key",
"pathname": "/path",
diff --git a/server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx b/server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx
index b49a0559215..6383c73a0a0 100644
--- a/server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx
+++ b/server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx
@@ -20,17 +20,17 @@
*/
import styled from '@emotion/styled';
import classNames from 'classnames';
-import { Location } from 'history';
import { debounce, intersection } from 'lodash';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
-import { InjectedRouter } from 'react-router';
import withBranchStatusActions from '../../../app/components/branch-status/withBranchStatusActions';
+import withComponentContext from '../../../app/components/componentContext/withComponentContext';
import withMetricsContext from '../../../app/components/metrics/withMetricsContext';
import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
import HelpTooltip from '../../../components/controls/HelpTooltip';
import ListFooter from '../../../components/controls/ListFooter';
import Suggestions from '../../../components/embed-docs-modal/Suggestions';
+import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
import { Alert } from '../../../components/ui/Alert';
import { isPullRequest, isSameBranchLike } from '../../../helpers/branch-like';
import { translate } from '../../../helpers/l10n';
@@ -55,8 +55,8 @@ interface Props {
branchLike?: BranchLike;
component: Component;
fetchBranchStatus: (branchLike: BranchLike, projectKey: string) => Promise<void>;
- location: Pick<Location, 'query'>;
- router: Pick<InjectedRouter, 'push'>;
+ location: Location;
+ router: Router;
metrics: Dict<Metric>;
}
@@ -402,4 +402,6 @@ const AlertContent = styled.div`
align-items: center;
`;
-export default withBranchStatusActions(withMetricsContext(CodeApp));
+export default withRouter(
+ withComponentContext(withBranchStatusActions(withMetricsContext(CodeApp)))
+);
diff --git a/server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx b/server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx
index fae113cae52..6c96b8a34cf 100644
--- a/server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx
+++ b/server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx
@@ -18,13 +18,13 @@
* 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 { colors } from '../../../app/theme';
import BranchIcon from '../../../components/icons/BranchIcon';
import QualifierIcon from '../../../components/icons/QualifierIcon';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { translate } from '../../../helpers/l10n';
-import { CodeScope, getComponentOverviewUrl } from '../../../helpers/urls';
+import { CodeScope, getComponentOverviewUrl, queryToSearch } from '../../../helpers/urls';
import { BranchLike } from '../../../types/branch-like';
import {
ComponentQualifier,
@@ -150,7 +150,7 @@ function renderNameWithIcon(
Object.assign(query, { selected: component.key });
}
return (
- <Link className="link-with-icon" to={{ pathname: '/code', query }}>
+ <Link className="link-with-icon" to={{ pathname: '/code', search: queryToSearch(query) }}>
<QualifierIcon qualifier={component.qualifier} /> <span>{name}</span>
</Link>
);
diff --git a/server/sonar-web/src/main/js/apps/code/components/SourceViewerWrapper.tsx b/server/sonar-web/src/main/js/apps/code/components/SourceViewerWrapper.tsx
index 7e65b8e497e..58a88ad11f2 100644
--- a/server/sonar-web/src/main/js/apps/code/components/SourceViewerWrapper.tsx
+++ b/server/sonar-web/src/main/js/apps/code/components/SourceViewerWrapper.tsx
@@ -17,9 +17,9 @@
* 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 * as React from 'react';
import withKeyboardNavigation from '../../../components/hoc/withKeyboardNavigation';
+import { Location } from '../../../components/hoc/withRouter';
import SourceViewer from '../../../components/SourceViewer/SourceViewer';
import { scrollToElement } from '../../../helpers/scrolling';
import { BranchLike } from '../../../types/branch-like';
@@ -29,7 +29,7 @@ interface Props {
branchLike?: BranchLike;
component: string;
componentMeasures: Measure[] | undefined;
- location: Pick<Location, 'query'>;
+ location: Location;
onIssueChange?: (issue: Issue) => void;
}
diff --git a/server/sonar-web/src/main/js/apps/code/components/__tests__/CodeApp-test.tsx b/server/sonar-web/src/main/js/apps/code/components/__tests__/CodeApp-test.tsx
index 08af19dcb29..e0964d7c0c3 100644
--- a/server/sonar-web/src/main/js/apps/code/components/__tests__/CodeApp-test.tsx
+++ b/server/sonar-web/src/main/js/apps/code/components/__tests__/CodeApp-test.tsx
@@ -21,8 +21,9 @@ import { shallow } from 'enzyme';
import * as React from 'react';
import { mockPullRequest } from '../../../../helpers/mocks/branch-like';
import { mockComponent, mockComponentMeasure } from '../../../../helpers/mocks/component';
-import { mockIssue, mockRouter } from '../../../../helpers/testMocks';
+import { mockIssue, mockLocation, mockRouter } from '../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../helpers/testUtils';
+import { queryToSearch } from '../../../../helpers/urls';
import { ComponentQualifier } from '../../../../types/component';
import { loadMoreChildren, retrieveComponent } from '../../utils';
import { CodeApp } from '../CodeApp';
@@ -161,7 +162,7 @@ it('should handle go to parent correctly', async () => {
expect(wrapper.state().highlighted).toBe(breadcrumb);
expect(router.push).toHaveBeenCalledWith({
pathname: '/code',
- query: { id: 'foo', line: undefined, selected: 'key1' }
+ search: queryToSearch({ id: 'foo', line: undefined, selected: 'key1' })
});
});
@@ -214,7 +215,7 @@ it('should handle select correctly', () => {
wrapper.instance().handleSelect(mockComponentMeasure(true, { refKey: 'test' }));
expect(router.push).toHaveBeenCalledWith({
pathname: '/dashboard',
- query: { branch: undefined, id: 'test', code_scope: 'new' }
+ search: queryToSearch({ branch: undefined, id: 'test', code_scope: 'new' })
});
expect(wrapper.state().highlighted).toBeUndefined();
@@ -223,13 +224,13 @@ it('should handle select correctly', () => {
wrapper.instance().handleSelect(mockComponentMeasure(true, { refKey: 'test' }));
expect(router.push).toHaveBeenCalledWith({
pathname: '/dashboard',
- query: { branch: undefined, id: 'test', code_scope: 'overall' }
+ search: queryToSearch({ branch: undefined, id: 'test', code_scope: 'overall' })
});
wrapper.instance().handleSelect(mockComponentMeasure());
expect(router.push).toHaveBeenCalledWith({
pathname: '/code',
- query: { id: 'foo', line: undefined, selected: 'foo' }
+ search: queryToSearch({ id: 'foo', line: undefined, selected: 'foo' })
});
});
@@ -276,7 +277,7 @@ function shallowRender(props: Partial<CodeApp['props']> = {}) {
qualifier: 'FOO'
}}
fetchBranchStatus={jest.fn()}
- location={{ query: { branch: 'b', id: 'foo', line: '7' } }}
+ location={mockLocation({ search: queryToSearch({ branch: 'b', id: 'foo', line: '7' }) })}
metrics={METRICS}
router={mockRouter()}
{...props}
diff --git a/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/ComponentName-test.tsx.snap b/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/ComponentName-test.tsx.snap
index 8f38318ed18..522ceab3d25 100644
--- a/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/ComponentName-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/ComponentName-test.tsx.snap
@@ -163,15 +163,10 @@ foo:src/index.tsx"
>
<Link
className="link-with-icon"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/code",
- "query": Object {
- "id": "foo",
- "selected": "foo:src/index.tsx",
- },
+ "search": "?id=foo&selected=foo%3Asrc%2Findex.tsx",
}
}
>
@@ -282,16 +277,10 @@ foo"
>
<Link
className="link-with-icon"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "code_scope": "new",
- "id": "src/main/ts/app",
- },
+ "search": "?id=src%2Fmain%2Fts%2Fapp&code_scope=new",
}
}
>
@@ -320,16 +309,10 @@ foo"
>
<Link
className="link-with-icon"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": "foo",
- "code_scope": "new",
- "id": "src/main/ts/app",
- },
+ "search": "?id=src%2Fmain%2Fts%2Fapp&branch=foo&code_scope=new",
}
}
>
@@ -369,16 +352,10 @@ foo"
>
<Link
className="link-with-icon"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "code_scope": "new",
- "id": "src/main/ts/app",
- },
+ "search": "?id=src%2Fmain%2Fts%2Fapp&code_scope=new",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/overview/routes.ts b/server/sonar-web/src/main/js/apps/code/routes.tsx
index 9d8f2bd42e4..2674cb21130 100644
--- a/server/sonar-web/src/main/js/apps/overview/routes.ts
+++ b/server/sonar-web/src/main/js/apps/code/routes.tsx
@@ -17,12 +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 { lazyLoadComponent } from '../../components/lazyLoadComponent';
+import React from 'react';
+import { Route } from 'react-router-dom';
+import CodeApp from './components/CodeApp';
-const routes = [
- {
- indexRoute: { component: lazyLoadComponent(() => import('./components/App')) }
- }
-];
+const routes = () => <Route path="code" element={<CodeApp />} />;
export default routes;
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts
index 3665406e76d..ce037b05e6f 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts
+++ b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts
@@ -317,6 +317,16 @@ it('should be able to bulk deactivate quality profile', async () => {
).toBeInTheDocument();
});
+it('should handle hash parameters', async () => {
+ renderCodingRulesApp(mockLoggedInUser(), 'coding_rules#languages=c,js|types=BUG');
+
+ // 2 languages
+ expect(await screen.findByText('x_selected.2')).toBeInTheDocument();
+ expect(screen.getAllByTitle('issue.type.BUG')).toHaveLength(2);
+ // Only 3 rules shown
+ expect(screen.getByText('x_of_y_shown.3.3')).toBeInTheDocument();
+});
+
function renderCodingRulesApp(currentUser?: CurrentUser, navigateTo?: string) {
renderApp('coding_rules', routes, {
navigateTo,
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/App.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/App.tsx
index 94a9b44e2ea..fa8ce209274 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/App.tsx
@@ -20,7 +20,6 @@
import { keyBy } from 'lodash';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
-import { withRouter, WithRouterProps } from 'react-router';
import { Profile, searchQualityProfiles } from '../../../api/quality-profiles';
import { getRulesApp, searchRules } from '../../../api/rules';
import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext';
@@ -30,6 +29,7 @@ import ScreenPositionHelper from '../../../components/common/ScreenPositionHelpe
import ListFooter from '../../../components/controls/ListFooter';
import SearchBox from '../../../components/controls/SearchBox';
import Suggestions from '../../../components/embed-docs-modal/Suggestions';
+import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
import BackIcon from '../../../components/icons/BackIcon';
import '../../../components/search-navigator.css';
import { isInput, isShortcut } from '../../../helpers/keyboardEventHelpers';
@@ -79,8 +79,10 @@ const PAGE_SIZE = 100;
const MAX_SEARCH_LENGTH = 200;
const LIMIT_BEFORE_LOAD_MORE = 5;
-interface Props extends WithRouterProps {
+interface Props {
currentUser: CurrentUser;
+ location: Location;
+ router: Router;
}
interface State {
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsCustomRules.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsCustomRules.tsx
index 4d0fbf62131..569154d2109 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsCustomRules.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsCustomRules.tsx
@@ -19,7 +19,7 @@
*/
import { sortBy } from 'lodash';
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { deleteRule, searchRules } from '../../../api/rules';
import { Button } from '../../../components/controls/buttons';
import ConfirmButton from '../../../components/controls/ConfirmButton';
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx
index 1bdf3484a73..b2337639259 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.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 { getFacet } from '../../../api/issues';
import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import Tooltip from '../../../components/controls/Tooltip';
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx
index 2a3a8cd9e9b..b0582542437 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.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 { ButtonLink } from '../../../components/controls/buttons';
import Dropdown from '../../../components/controls/Dropdown';
import HelpTooltip from '../../../components/controls/HelpTooltip';
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsProfiles.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsProfiles.tsx
index 4bf55ae6f30..e0ed6fe2146 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsProfiles.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsProfiles.tsx
@@ -19,7 +19,7 @@
*/
import { filter } from 'lodash';
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { activateRule, deactivateRule, Profile } from '../../../api/quality-profiles';
import InstanceMessage from '../../../components/common/InstanceMessage';
import { Button } from '../../../components/controls/buttons';
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleListItem.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleListItem.tsx
index 04d47de7054..28a19498e66 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleListItem.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleListItem.tsx
@@ -19,7 +19,7 @@
*/
import classNames from 'classnames';
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { deactivateRule, Profile } from '../../../api/quality-profiles';
import { Button } from '../../../components/controls/buttons';
import ConfirmButton from '../../../components/controls/ConfirmButton';
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/App-test.tsx
index 86ebb0910a7..5b9fab3829d 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/App-test.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/App-test.tsx
@@ -112,9 +112,7 @@ function shallowRender(props: Partial<App['props']> = {}) {
isLoggedIn: true
})}
location={mockLocation()}
- params={{}}
router={mockRouter()}
- routes={[]}
{...props}
/>
);
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleListItem-test.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleListItem-test.tsx
index 763fc8a2761..acfc1083c4a 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleListItem-test.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleListItem-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 { deactivateRule } from '../../../../api/quality-profiles';
import { mockQualityProfile, mockRule } from '../../../../helpers/testMocks';
import { mockEvent, waitAndUpdate } from '../../../../helpers/testUtils';
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetailsIssues-test.tsx.snap b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetailsIssues-test.tsx.snap
index 120eb95f601..bdfbd11492e 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetailsIssues-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetailsIssues-test.tsx.snap
@@ -16,15 +16,10 @@ exports[`should fetch issues and render 1`] = `
>
(
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/issues",
- "query": Object {
- "resolved": "false",
- "rules": "foo",
- },
+ "search": "?resolved=false&rules=foo",
}
}
>
@@ -57,16 +52,10 @@ exports[`should fetch issues and render 1`] = `
className="coding-rules-detail-list-parameters"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/issues",
- "query": Object {
- "projects": "sample-key",
- "resolved": "false",
- "rules": "foo",
- },
+ "search": "?resolved=false&rules=foo&projects=sample-key",
}
}
>
@@ -86,16 +75,10 @@ exports[`should fetch issues and render 1`] = `
className="coding-rules-detail-list-parameters"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/issues",
- "query": Object {
- "projects": "example-key",
- "resolved": "false",
- "rules": "foo",
- },
+ "search": "?resolved=false&rules=foo&projects=example-key",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetailsMeta-test.tsx.snap b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetailsMeta-test.tsx.snap
index aec10d999a0..cbfba172d8a 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetailsMeta-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetailsMeta-test.tsx.snap
@@ -17,16 +17,11 @@ exports[`should display right meta info 1`] = `
</span>
<Link
className="coding-rules-detail-permalink link-no-underline spacer-left text-middle"
- onlyActiveOnIndex={false}
- style={Object {}}
title="permalink"
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "open": "squid:S1133",
- "rule_key": "squid:S1133",
- },
+ "search": "?open=squid%3AS1133&rule_key=squid%3AS1133",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleListItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleListItem-test.tsx.snap
index 9c8e861744c..773efa6f1a9 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleListItem-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleListItem-test.tsx.snap
@@ -119,15 +119,10 @@ exports[`should render correctly: default 1`] = `
<Link
className="link-no-underline"
onClick={[Function]}
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "open": "javascript:S1067",
- "rule_key": "javascript:S1067",
- },
+ "search": "?open=javascript%3AS1067&rule_key=javascript%3AS1067",
}
}
>
@@ -221,15 +216,10 @@ exports[`should render correctly: with activation 1`] = `
<Link
className="link-no-underline"
onClick={[Function]}
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "open": "javascript:S1067",
- "rule_key": "javascript:S1067",
- },
+ "search": "?open=javascript%3AS1067&rule_key=javascript%3AS1067",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/routes.ts b/server/sonar-web/src/main/js/apps/coding-rules/routes.tsx
index 5f03341e08b..f3b9c92c99c 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/routes.ts
+++ b/server/sonar-web/src/main/js/apps/coding-rules/routes.tsx
@@ -17,40 +17,48 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { RedirectFunction, RouterState } from 'react-router';
-import { lazyLoadComponent } from '../../components/lazyLoadComponent';
+import React, { useEffect } from 'react';
+import { Route, useLocation, useNavigate } from 'react-router-dom';
import { RawQuery } from '../../types/types';
+import App from './components/App';
import { parseQuery, serializeQuery } from './query';
+const EXPECTED_SPLIT_PARTS = 2;
+
function parseHash(hash: string): RawQuery {
const query: RawQuery = {};
const parts = hash.split('|');
parts.forEach(part => {
const tokens = part.split('=');
- if (tokens.length === 2) {
+ if (tokens.length === EXPECTED_SPLIT_PARTS) {
query[decodeURIComponent(tokens[0])] = decodeURIComponent(tokens[1]);
}
});
return query;
}
-const routes = [
- {
- indexRoute: {
- onEnter: (nextState: RouterState, replace: RedirectFunction) => {
- const { hash } = window.location;
- if (hash.length > 1) {
- const query = parseHash(hash.substr(1));
- const normalizedQuery = {
- ...serializeQuery(parseQuery(query)),
- open: query.open
- };
- replace({ pathname: nextState.location.pathname, query: normalizedQuery });
- }
- },
- component: lazyLoadComponent(() => import('./components/App'))
+function HashEditWrapper() {
+ const location = useLocation();
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ const { hash } = location;
+ if (hash.length > 1) {
+ const query = parseHash(hash.substr(1));
+ const normalizedQuery = {
+ ...serializeQuery(parseQuery(query)),
+ open: query.open
+ };
+ navigate(
+ { pathname: location.pathname, search: new URLSearchParams(normalizedQuery).toString() },
+ { replace: true }
+ );
}
- }
-];
+ }, [location, navigate]);
+
+ return <App />;
+}
+
+const routes = () => <Route path="coding_rules" element={<HashEditWrapper />} />;
export default routes;
diff --git a/server/sonar-web/src/main/js/helpers/__tests__/getHistory-test.ts b/server/sonar-web/src/main/js/apps/component-measures/__tests__/MeasuresApp-it.tsx
index 3c934028296..0d65561407f 100644
--- a/server/sonar-web/src/main/js/helpers/__tests__/getHistory-test.ts
+++ b/server/sonar-web/src/main/js/apps/component-measures/__tests__/MeasuresApp-it.tsx
@@ -17,13 +17,18 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { browserHistory } from 'react-router';
-import getHistory from '../getHistory';
+import { screen } from '@testing-library/react';
+import { renderApp } from '../../../helpers/testReactTestingUtils';
+import routes from '../routes';
-it('should get browser history properly', () => {
- expect(getHistory()).not.toBeUndefined();
- expect(getHistory().getCurrentLocation().pathname).toBe('/');
- const pathname = '/foo/bar';
- browserHistory.push(pathname);
- expect(getHistory().getCurrentLocation().pathname).toBe(pathname);
+it('should redirect old history route', () => {
+ renderMeasuresApp('component_measures/metric/bugs/history');
+
+ expect(
+ screen.getByText('/project/activity?graph=custom&custom_metrics=bugs')
+ ).toBeInTheDocument();
});
+
+function renderMeasuresApp(navigateTo?: string) {
+ renderApp('component_measures', routes, { navigateTo });
+}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/App.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/App.tsx
index 280b6076506..b730f43dbfa 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/App.tsx
@@ -21,13 +21,14 @@ import styled from '@emotion/styled';
import { debounce, keyBy } from 'lodash';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
-import { withRouter, WithRouterProps } from 'react-router';
import { getMeasuresWithPeriod } from '../../../api/measures';
import { getAllMetrics } from '../../../api/metrics';
import withBranchStatusActions from '../../../app/components/branch-status/withBranchStatusActions';
+import { ComponentContext } from '../../../app/components/componentContext/ComponentContext';
import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
import HelpTooltip from '../../../components/controls/HelpTooltip';
import Suggestions from '../../../components/embed-docs-modal/Suggestions';
+import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
import { enhanceMeasure } from '../../../components/measure/utils';
import '../../../components/search-navigator.css';
import { Alert } from '../../../components/ui/Alert';
@@ -73,10 +74,12 @@ import MeasureContent from './MeasureContent';
import MeasureOverviewContainer from './MeasureOverviewContainer';
import MeasuresEmpty from './MeasuresEmpty';
-interface Props extends WithRouterProps {
+interface Props {
branchLike?: BranchLike;
component: ComponentMeasure;
fetchBranchStatus: (branchLike: BranchLike, projectKey: string) => Promise<void>;
+ location: Location;
+ router: Router;
}
interface State {
@@ -359,4 +362,17 @@ const AlertContent = styled.div`
align-items: center;
`;
-export default withRouter(withBranchStatusActions(App));
+/*
+ * This needs to be refactored: the issue
+ * is that we can't use the usual withComponentContext HOC, because the type
+ * of `component` isn't the same. It probably used to work because of the lazy loading
+ */
+const WrappedApp = withRouter(withBranchStatusActions(App));
+
+function AppWithComponentContext() {
+ const { branchLike, component } = React.useContext(ComponentContext);
+
+ return <WrappedApp branchLike={branchLike} component={component as ComponentMeasure} />;
+}
+
+export default AppWithComponentContext;
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx
index 37abf52ec71..37ab7387f28 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx
@@ -18,10 +18,10 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { InjectedRouter } from 'react-router';
import { getComponentTree } from '../../../api/components';
import { getMeasures } from '../../../api/measures';
import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
+import { Router } from '../../../components/hoc/withRouter';
import SourceViewer from '../../../components/SourceViewer/SourceViewer';
import PageActions from '../../../components/ui/PageActions';
import { getBranchLikeQuery, isSameBranchLike } from '../../../helpers/branch-like';
@@ -62,7 +62,7 @@ interface Props {
metrics: Dict<Metric>;
onIssueChange?: (issue: Issue) => void;
rootComponent: ComponentMeasure;
- router: InjectedRouter;
+ router: Router;
selected?: string;
asc?: boolean;
updateQuery: (query: Partial<Query>) => void;
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx
index 5a65ec912ca..3911ea503b2 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.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 LanguageDistribution from '../../../components/charts/LanguageDistribution';
import Tooltip from '../../../components/controls/Tooltip';
import HistoryIcon from '../../../components/icons/HistoryIcon';
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.tsx
index 3cca0254dcc..3fc1dc8d30e 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.tsx
@@ -18,8 +18,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { InjectedRouter } from 'react-router';
import { getComponentShow } from '../../../api/components';
+import { Router } from '../../../components/hoc/withRouter';
import { getBranchLikeQuery, isSameBranchLike } from '../../../helpers/branch-like';
import { getProjectUrl } from '../../../helpers/urls';
import { BranchLike } from '../../../types/branch-like';
@@ -43,7 +43,7 @@ interface Props {
metrics: Dict<Metric>;
onIssueChange?: (issue: Issue) => void;
rootComponent: ComponentMeasure;
- router: InjectedRouter;
+ router: Router;
selected?: string;
updateQuery: (query: Partial<Query>) => void;
}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/App-test.tsx
index fd9cfdfd2fd..61d1a6ab94e 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/App-test.tsx
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/App-test.tsx
@@ -164,9 +164,7 @@ function shallowRender(props: Partial<App['props']> = {}) {
component={mockComponent({ key: 'foo', name: 'Foo' })}
fetchBranchStatus={jest.fn()}
location={mockLocation({ pathname: '/component_measures', query: { metric: 'coverage' } })}
- params={{}}
router={mockRouter()}
- routes={[]}
{...props}
/>
);
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.tsx.snap
index 643a2bb9dd8..8235bbe62da 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.tsx.snap
@@ -31,16 +31,10 @@ exports[`should render correctly 1`] = `
>
<Link
className="js-show-history spacer-left button button-small"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/project/activity",
- "query": Object {
- "custom_metrics": "reliability_rating",
- "graph": "custom",
- "id": "foo",
- },
+ "search": "?id=foo&graph=custom&custom_metrics=reliability_rating",
}
}
>
@@ -212,16 +206,10 @@ exports[`should work with measure without value 1`] = `
>
<Link
className="js-show-history spacer-left button button-small"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/project/activity",
- "query": Object {
- "custom_metrics": "reliability_rating",
- "graph": "custom",
- "id": "foo",
- },
+ "search": "?id=foo&graph=custom&custom_metrics=reliability_rating",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.tsx
index 1d33534d3b9..585601da5a6 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.tsx
+++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.tsx
@@ -17,9 +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 { LocationDescriptor } from 'history';
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link, To } from 'react-router-dom';
import BranchIcon from '../../../components/icons/BranchIcon';
import LinkIcon from '../../../components/icons/LinkIcon';
import QualifierIcon from '../../../components/icons/QualifierIcon';
@@ -64,7 +63,7 @@ export default function ComponentCell(props: ComponentCellProps) {
({ head, tail } = splitPath(component.path));
}
- let path: LocationDescriptor;
+ let path: To;
const targetKey = component.refKey || rootComponent.key;
const selectionKey = component.refKey ? '' : component.key;
diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/ComponentCell-test.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/ComponentCell-test.tsx
index d93d13f3046..1465e2a1351 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/ComponentCell-test.tsx
+++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/ComponentCell-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 {
mockComponentMeasure,
mockComponentMeasureEnhanced
@@ -63,7 +63,12 @@ it('should properly deal with key and refKey', () => {
})
.find(Link)
.props().to
- ).toEqual(expect.objectContaining({ query: expect.objectContaining({ id: 'port-key' }) }));
+ ).toEqual(
+ expect.objectContaining({
+ pathname: '/component_measures',
+ search: '?id=port-key&metric=bugs&view=list'
+ })
+ );
expect(
shallowRender()
@@ -71,7 +76,8 @@ it('should properly deal with key and refKey', () => {
.props().to
).toEqual(
expect.objectContaining({
- query: expect.objectContaining({ id: 'foo', selected: 'foo:src/index.tsx' })
+ pathname: '/component_measures',
+ search: '?id=foo&metric=bugs&view=list&selected=foo%3Asrc%2Findex.tsx'
})
);
});
@@ -80,62 +86,66 @@ it.each([
[
ComponentQualifier.File,
MetricKey.bugs,
- expect.objectContaining({
+ {
pathname: '/component_measures',
- query: expect.objectContaining({ branch: 'develop' })
- })
+ search: `?id=foo&metric=${MetricKey.bugs}&branch=develop&view=list&selected=foo`
+ }
],
[
ComponentQualifier.Directory,
MetricKey.bugs,
- expect.objectContaining({
+ {
pathname: '/component_measures',
- query: expect.objectContaining({ branch: 'develop' })
- })
+ search: `?id=foo&metric=${MetricKey.bugs}&branch=develop&view=list&selected=foo`
+ }
],
[
ComponentQualifier.Project,
MetricKey.projects,
- expect.objectContaining({
+ {
pathname: '/dashboard',
- query: expect.objectContaining({ branch: 'develop' })
- })
+ search: '?id=foo&branch=develop'
+ }
],
[
ComponentQualifier.Application,
MetricKey.releasability_rating,
- expect.objectContaining({
+ {
pathname: '/dashboard',
- query: expect.objectContaining({ branch: 'develop' })
- })
+ search: '?id=foo&branch=develop'
+ }
],
[
ComponentQualifier.Project,
MetricKey.releasability_rating,
- expect.objectContaining({
+ {
pathname: '/dashboard',
- query: expect.objectContaining({ branch: 'develop' })
- })
+ search: '?id=foo&branch=develop'
+ }
],
[
ComponentQualifier.Application,
MetricKey.alert_status,
- expect.objectContaining({
+ {
pathname: '/dashboard',
- query: expect.objectContaining({ branch: 'develop' })
- })
+ search: '?id=foo&branch=develop'
+ }
],
[
ComponentQualifier.Project,
MetricKey.alert_status,
- expect.objectContaining({
+ {
pathname: '/dashboard',
- query: expect.objectContaining({ branch: 'develop' })
- })
+ search: '?id=foo&branch=develop'
+ }
]
])(
'should display the proper link path for %s component qualifier and %s metric key',
- (componentQualifier: ComponentQualifier, metricKey: MetricKey, expectedTo: any) => {
+ (
+ componentQualifier: ComponentQualifier,
+ metricKey: MetricKey,
+ expectedTo: { pathname: string; search: string }
+ ) => {
const wrapper = shallowRender(
{
component: mockComponentMeasureEnhanced({
@@ -146,7 +156,7 @@ it.each([
metricKey
);
- expect(wrapper.find(Link).props().to).toEqual(expectedTo);
+ expect(wrapper.find(Link).props().to).toEqual(expect.objectContaining(expectedTo));
}
);
diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/ComponentCell-test.tsx.snap b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/ComponentCell-test.tsx.snap
index 730e6e4fb06..96fcb66989e 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/ComponentCell-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/ComponentCell-test.tsx.snap
@@ -10,19 +10,10 @@ exports[`should render correctly for a "APP" root component and a component with
<Link
className="link-no-underline"
id="component-measures-component-link-foo"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/component_measures",
- "query": Object {
- "asc": undefined,
- "branch": "develop",
- "id": "foo",
- "metric": "bugs",
- "selected": "foo",
- "view": "list",
- },
+ "search": "?id=foo&metric=bugs&branch=develop&view=list&selected=foo",
}
}
>
@@ -60,18 +51,10 @@ exports[`should render correctly for a "APP" root component and a component with
<Link
className="link-no-underline"
id="component-measures-component-link-foo"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/component_measures",
- "query": Object {
- "asc": undefined,
- "id": "foo",
- "metric": "bugs",
- "selected": "foo",
- "view": "list",
- },
+ "search": "?id=foo&metric=bugs&view=list&selected=foo",
}
}
>
@@ -106,19 +89,10 @@ exports[`should render correctly for a "TRK" root component and a component with
<Link
className="link-no-underline"
id="component-measures-component-link-foo"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/component_measures",
- "query": Object {
- "asc": undefined,
- "branch": "develop",
- "id": "foo",
- "metric": "bugs",
- "selected": "foo",
- "view": "list",
- },
+ "search": "?id=foo&metric=bugs&branch=develop&view=list&selected=foo",
}
}
>
@@ -148,18 +122,10 @@ exports[`should render correctly for a "TRK" root component and a component with
<Link
className="link-no-underline"
id="component-measures-component-link-foo"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/component_measures",
- "query": Object {
- "asc": undefined,
- "id": "foo",
- "metric": "bugs",
- "selected": "foo",
- "view": "list",
- },
+ "search": "?id=foo&metric=bugs&view=list&selected=foo",
}
}
>
@@ -189,19 +155,10 @@ exports[`should render correctly for a "VW" root component and a component with
<Link
className="link-no-underline"
id="component-measures-component-link-foo"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/component_measures",
- "query": Object {
- "asc": undefined,
- "branch": "develop",
- "id": "foo",
- "metric": "bugs",
- "selected": "foo",
- "view": "list",
- },
+ "search": "?id=foo&metric=bugs&branch=develop&view=list&selected=foo",
}
}
>
@@ -239,18 +196,10 @@ exports[`should render correctly for a "VW" root component and a component with
<Link
className="link-no-underline"
id="component-measures-component-link-foo"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/component_measures",
- "query": Object {
- "asc": undefined,
- "id": "foo",
- "metric": "bugs",
- "selected": "foo",
- "view": "list",
- },
+ "search": "?id=foo&metric=bugs&view=list&selected=foo",
}
}
>
@@ -285,18 +234,10 @@ exports[`should render correctly: default 1`] = `
<Link
className="link-no-underline"
id="component-measures-component-link-foo:src/index.tsx"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/component_measures",
- "query": Object {
- "asc": undefined,
- "id": "foo",
- "metric": "bugs",
- "selected": "foo:src/index.tsx",
- "view": "list",
- },
+ "search": "?id=foo&metric=bugs&view=list&selected=foo%3Asrc%2Findex.tsx",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/component-measures/routes.ts b/server/sonar-web/src/main/js/apps/component-measures/routes.ts
deleted file mode 100644
index 2430817f5f4..00000000000
--- a/server/sonar-web/src/main/js/apps/component-measures/routes.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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 { RedirectFunction, RouterState } from 'react-router';
-import { lazyLoadComponent } from '../../components/lazyLoadComponent';
-
-const routes = [
- {
- indexRoute: { component: lazyLoadComponent(() => import('./components/App')) }
- },
- {
- path: 'domain/:domainName',
- onEnter(nextState: RouterState, replace: RedirectFunction) {
- replace({
- pathname: '/component_measures',
- query: {
- ...nextState.location.query,
- metric: nextState.params.domainName
- }
- });
- }
- },
- {
- path: 'metric/:metricKey(/:view)',
- onEnter(nextState: RouterState, replace: RedirectFunction) {
- if (nextState.params.view === 'history') {
- replace({
- pathname: '/project/activity',
- query: {
- id: nextState.location.query.id,
- graph: 'custom',
- custom_metrics: nextState.params.metricKey
- }
- });
- } else {
- replace({
- pathname: '/component_measures',
- query: {
- ...nextState.location.query,
- metric: nextState.params.metricKey,
- view: nextState.params.view
- }
- });
- }
- }
- }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/component-measures/routes.tsx b/server/sonar-web/src/main/js/apps/component-measures/routes.tsx
new file mode 100644
index 00000000000..04e9d573fb4
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/component-measures/routes.tsx
@@ -0,0 +1,79 @@
+/*
+ * 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 React from 'react';
+import { Navigate, Route, useParams, useSearchParams } from 'react-router-dom';
+import NavigateWithParams from '../../app/utils/NavigateWithParams';
+import { omitNil } from '../../helpers/request';
+import { searchParamsToQuery } from '../../helpers/urls';
+import App from './components/App';
+
+const routes = () => (
+ <Route path="component_measures">
+ <Route index={true} element={<App />} />
+ <Route
+ path="domain/:domainName"
+ element={
+ <NavigateWithParams
+ pathname="/component_measures"
+ transformParams={params =>
+ omitNil({
+ metric: params['domainName']
+ })
+ }
+ />
+ }
+ />
+
+ <Route path="metric/:metricKey" element={<MetricRedirect />} />
+ <Route path="metric/:metricKey/:view" element={<MetricRedirect />} />
+ </Route>
+);
+
+function MetricRedirect() {
+ const params = useParams();
+ const [searchParams] = useSearchParams();
+
+ if (params.view === 'history') {
+ const to = {
+ pathname: '/project/activity',
+ search: new URLSearchParams(
+ omitNil({
+ id: searchParams.get('id') ?? undefined,
+ graph: 'custom',
+ custom_metrics: params.metricKey
+ })
+ ).toString()
+ };
+ return <Navigate to={to} replace={true} />;
+ }
+ const to = {
+ pathname: '/component_measures',
+ search: new URLSearchParams(
+ omitNil({
+ ...searchParamsToQuery(searchParams),
+ metric: params.metricKey,
+ view: params.view
+ })
+ ).toString()
+ };
+ return <Navigate to={to} replace={true} />;
+}
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/create/project/AzureProjectAccordion.tsx b/server/sonar-web/src/main/js/apps/create/project/AzureProjectAccordion.tsx
index 7fd11744ddf..ec51beea670 100644
--- a/server/sonar-web/src/main/js/apps/create/project/AzureProjectAccordion.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/AzureProjectAccordion.tsx
@@ -20,7 +20,7 @@
import classNames from 'classnames';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { colors } from '../../../app/theme';
import BoxedGroupAccordion from '../../../components/controls/BoxedGroupAccordion';
import ListFooter from '../../../components/controls/ListFooter';
@@ -29,7 +29,7 @@ import CheckIcon from '../../../components/icons/CheckIcon';
import { Alert } from '../../../components/ui/Alert';
import DeferredSpinner from '../../../components/ui/DeferredSpinner';
import { translate } from '../../../helpers/l10n';
-import { getProjectUrl } from '../../../helpers/urls';
+import { getProjectUrl, queryToSearch } from '../../../helpers/urls';
import { AzureProject, AzureRepository } from '../../../types/alm-integration';
import { CreateProjectModes } from './types';
@@ -110,7 +110,7 @@ export default function AzureProjectAccordion(props: AzureProjectAccordionProps)
<Link
to={{
pathname: '/projects/create',
- query: { mode: CreateProjectModes.AzureDevOps, resetPat: 1 }
+ search: queryToSearch({ mode: CreateProjectModes.AzureDevOps, resetPat: 1 })
}}>
{translate('onboarding.create_project.update_your_token')}
</Link>
diff --git a/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreate.tsx
index 9084102b075..963e3dd32c8 100644
--- a/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreate.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreate.tsx
@@ -19,7 +19,6 @@
*/
import { groupBy } from 'lodash';
import * as React from 'react';
-import { WithRouterProps } from 'react-router';
import {
checkPersonalAccessTokenIsValid,
getAzureProjects,
@@ -28,16 +27,19 @@ import {
searchAzureRepositories,
setAlmPersonalAccessToken
} from '../../../api/alm-integrations';
+import { Location, Router } from '../../../components/hoc/withRouter';
import { AzureProject, AzureRepository } from '../../../types/alm-integration';
import { AlmSettingsInstance } from '../../../types/alm-settings';
import { Dict } from '../../../types/types';
import AzureCreateProjectRenderer from './AzureProjectCreateRenderer';
-interface Props extends Pick<WithRouterProps, 'location' | 'router'> {
+interface Props {
canAdmin: boolean;
loadingBindings: boolean;
onProjectCreate: (projectKey: string) => void;
settings: AlmSettingsInstance[];
+ location: Location;
+ router: Router;
}
interface State {
diff --git a/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreateRenderer.tsx b/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreateRenderer.tsx
index 51e3911aa03..c4965a8dca7 100644
--- a/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreateRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreateRenderer.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 { Button } from '../../../components/controls/buttons';
import SearchBox from '../../../components/controls/SearchBox';
import { Alert } from '../../../components/ui/Alert';
diff --git a/server/sonar-web/src/main/js/apps/create/project/AzureProjectsList.tsx b/server/sonar-web/src/main/js/apps/create/project/AzureProjectsList.tsx
index 0bbea571367..19f38414288 100644
--- a/server/sonar-web/src/main/js/apps/create/project/AzureProjectsList.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/AzureProjectsList.tsx
@@ -19,10 +19,11 @@
*/
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import ListFooter from '../../../components/controls/ListFooter';
import { Alert } from '../../../components/ui/Alert';
import { translate } from '../../../helpers/l10n';
+import { queryToSearch } from '../../../helpers/urls';
import { AzureProject, AzureRepository } from '../../../types/alm-integration';
import { Dict } from '../../../types/types';
import AzureProjectAccordion from './AzureProjectAccordion';
@@ -73,7 +74,7 @@ export default function AzureProjectsList(props: AzureProjectsListProps) {
<Link
to={{
pathname: '/projects/create',
- query: { mode: CreateProjectModes.AzureDevOps, resetPat: 1 }
+ search: queryToSearch({ mode: CreateProjectModes.AzureDevOps, resetPat: 1 })
}}>
{translate('onboarding.create_project.update_your_token')}
</Link>
diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloudProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloudProjectCreate.tsx
index cd1274896c7..74fe4a49838 100644
--- a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloudProjectCreate.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloudProjectCreate.tsx
@@ -18,21 +18,23 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { WithRouterProps } from 'react-router';
import {
importBitbucketCloudRepository,
searchForBitbucketCloudRepositories
} from '../../../api/alm-integrations';
+import { Location, Router } from '../../../components/hoc/withRouter';
import { BitbucketCloudRepository } from '../../../types/alm-integration';
import { AlmSettingsInstance } from '../../../types/alm-settings';
import { Paging } from '../../../types/types';
import BitbucketCloudProjectCreateRenderer from './BitbucketCloudProjectCreateRender';
-interface Props extends Pick<WithRouterProps, 'location' | 'router'> {
+interface Props {
canAdmin: boolean;
settings: AlmSettingsInstance[];
loadingBindings: boolean;
onProjectCreate: (projectKey: string) => void;
+ location: Location;
+ router: Router;
}
interface State {
diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloudSearchForm.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloudSearchForm.tsx
index 442ac14aee0..03cb73fc033 100644
--- a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloudSearchForm.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloudSearchForm.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 { Button } from '../../../components/controls/buttons';
import SearchBox from '../../../components/controls/SearchBox';
import Tooltip from '../../../components/controls/Tooltip';
@@ -30,7 +30,7 @@ import { Alert } from '../../../components/ui/Alert';
import DeferredSpinner from '../../../components/ui/DeferredSpinner';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { formatMeasure } from '../../../helpers/measures';
-import { getProjectUrl } from '../../../helpers/urls';
+import { getProjectUrl, queryToSearch } from '../../../helpers/urls';
import { BitbucketCloudRepository } from '../../../types/alm-integration';
import { ComponentQualifier } from '../../../types/component';
import { CreateProjectModes } from './types';
@@ -72,7 +72,7 @@ export default function BitbucketCloudSearchForm(props: BitbucketCloudSearchForm
<Link
to={{
pathname: '/projects/create',
- query: { mode: CreateProjectModes.BitbucketCloud, resetPat: 1 }
+ search: queryToSearch({ mode: CreateProjectModes.BitbucketCloud, resetPat: 1 })
}}>
{translate('onboarding.create_project.update_your_token')}
</Link>
diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketImportRepositoryForm.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketImportRepositoryForm.tsx
index cdbafd18724..d1a741729a7 100644
--- a/server/sonar-web/src/main/js/apps/create/project/BitbucketImportRepositoryForm.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketImportRepositoryForm.tsx
@@ -19,10 +19,11 @@
*/
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import SearchBox from '../../../components/controls/SearchBox';
import { Alert } from '../../../components/ui/Alert';
import { translate } from '../../../helpers/l10n';
+import { queryToSearch } from '../../../helpers/urls';
import {
BitbucketProject,
BitbucketProjectRepositories,
@@ -64,7 +65,7 @@ export default function BitbucketImportRepositoryForm(props: BitbucketImportRepo
<Link
to={{
pathname: '/projects/create',
- query: { mode: CreateProjectModes.BitbucketServer, resetPat: 1 }
+ search: queryToSearch({ mode: CreateProjectModes.BitbucketServer, resetPat: 1 })
}}>
{translate('onboarding.create_project.update_your_token')}
</Link>
diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketProjectAccordion.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketProjectAccordion.tsx
index 9f220c1b39f..b1c05705ec8 100644
--- a/server/sonar-web/src/main/js/apps/create/project/BitbucketProjectAccordion.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketProjectAccordion.tsx
@@ -20,14 +20,14 @@
import classNames from 'classnames';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { colors } from '../../../app/theme';
import BoxedGroupAccordion from '../../../components/controls/BoxedGroupAccordion';
import Radio from '../../../components/controls/Radio';
import CheckIcon from '../../../components/icons/CheckIcon';
import { Alert } from '../../../components/ui/Alert';
import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { getProjectUrl } from '../../../helpers/urls';
+import { getProjectUrl, queryToSearch } from '../../../helpers/urls';
import { BitbucketProject, BitbucketRepository } from '../../../types/alm-integration';
import { CreateProjectModes } from './types';
@@ -85,7 +85,10 @@ export default function BitbucketProjectAccordion(props: BitbucketProjectAccordi
<Link
to={{
pathname: '/projects/create',
- query: { mode: CreateProjectModes.BitbucketServer, resetPat: 1 }
+ search: queryToSearch({
+ mode: CreateProjectModes.BitbucketServer,
+ resetPat: 1
+ })
}}>
{translate('onboarding.create_project.update_your_token')}
</Link>
diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketProjectCreate.tsx
index ba764483aa7..5c56e0683c5 100644
--- a/server/sonar-web/src/main/js/apps/create/project/BitbucketProjectCreate.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketProjectCreate.tsx
@@ -18,13 +18,13 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { WithRouterProps } from 'react-router';
import {
getBitbucketServerProjects,
getBitbucketServerRepositories,
importBitbucketServerProject,
searchForBitbucketServerRepositories
} from '../../../api/alm-integrations';
+import { Location, Router } from '../../../components/hoc/withRouter';
import {
BitbucketProject,
BitbucketProjectRepositories,
@@ -34,11 +34,13 @@ import { AlmSettingsInstance } from '../../../types/alm-settings';
import BitbucketCreateProjectRenderer from './BitbucketProjectCreateRenderer';
import { DEFAULT_BBS_PAGE_SIZE } from './constants';
-interface Props extends Pick<WithRouterProps, 'location' | 'router'> {
+interface Props {
canAdmin: boolean;
bitbucketSettings: AlmSettingsInstance[];
loadingBindings: boolean;
onProjectCreate: (projectKey: string) => void;
+ location: Location;
+ router: Router;
}
interface State {
diff --git a/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx b/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx
index 1c793becabc..384f7906a66 100644
--- a/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx
@@ -19,10 +19,10 @@
*/
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
-import { WithRouterProps } from 'react-router';
import { getAlmSettings } from '../../../api/alm-settings';
import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
+import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
import { translate } from '../../../helpers/l10n';
import { getProjectUrl } from '../../../helpers/urls';
import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings';
@@ -38,8 +38,10 @@ import ManualProjectCreate from './ManualProjectCreate';
import './style.css';
import { CreateProjectModes } from './types';
-interface Props extends Pick<WithRouterProps, 'router' | 'location'> {
+interface Props {
appState: AppState;
+ location: Location;
+ router: Router;
}
interface State {
@@ -270,4 +272,4 @@ export class CreateProjectPage extends React.PureComponent<Props, State> {
}
}
-export default withAppStateContext(CreateProjectPage);
+export default withRouter(withAppStateContext(CreateProjectPage));
diff --git a/server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreate.tsx
index 12e69d5816d..2871ac8eda8 100644
--- a/server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreate.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreate.tsx
@@ -19,24 +19,26 @@
*/
import { debounce } from 'lodash';
import * as React from 'react';
-import { WithRouterProps } from 'react-router';
import {
getGithubClientId,
getGithubOrganizations,
getGithubRepositories,
importGithubRepository
} from '../../../api/alm-integrations';
+import { Location, Router } from '../../../components/hoc/withRouter';
import { getHostUrl } from '../../../helpers/urls';
import { GithubOrganization, GithubRepository } from '../../../types/alm-integration';
import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings';
import { Paging } from '../../../types/types';
import GitHubProjectCreateRenderer from './GitHubProjectCreateRenderer';
-interface Props extends Pick<WithRouterProps, 'location' | 'router'> {
+interface Props {
canAdmin: boolean;
loadingBindings: boolean;
onProjectCreate: (projectKey: string) => void;
settings: AlmSettingsInstance[];
+ location: Location;
+ router: Router;
}
interface State {
diff --git a/server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreateRenderer.tsx b/server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreateRenderer.tsx
index e801b649039..7fbfedb3606 100644
--- a/server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreateRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreateRenderer.tsx
@@ -22,7 +22,7 @@
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { colors } from '../../../app/theme';
import { Button } from '../../../components/controls/buttons';
import ListFooter from '../../../components/controls/ListFooter';
diff --git a/server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreate.tsx
index 6dfa1f8d974..f1dab9ad7be 100644
--- a/server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreate.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreate.tsx
@@ -18,18 +18,20 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { WithRouterProps } from 'react-router';
import { getGitlabProjects, importGitlabProject } from '../../../api/alm-integrations';
+import { Location, Router } from '../../../components/hoc/withRouter';
import { GitlabProject } from '../../../types/alm-integration';
import { AlmSettingsInstance } from '../../../types/alm-settings';
import { Paging } from '../../../types/types';
import GitlabProjectCreateRenderer from './GitlabProjectCreateRenderer';
-interface Props extends Pick<WithRouterProps, 'location' | 'router'> {
+interface Props {
canAdmin: boolean;
loadingBindings: boolean;
onProjectCreate: (projectKey: string) => void;
settings: AlmSettingsInstance[];
+ location: Location;
+ router: Router;
}
interface State {
diff --git a/server/sonar-web/src/main/js/apps/create/project/GitlabProjectSelectionForm.tsx b/server/sonar-web/src/main/js/apps/create/project/GitlabProjectSelectionForm.tsx
index 5726f4b4c23..bdbcc5f93be 100644
--- a/server/sonar-web/src/main/js/apps/create/project/GitlabProjectSelectionForm.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/GitlabProjectSelectionForm.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 { Button } from '../../../components/controls/buttons';
import ListFooter from '../../../components/controls/ListFooter';
import SearchBox from '../../../components/controls/SearchBox';
@@ -30,7 +30,7 @@ import QualifierIcon from '../../../components/icons/QualifierIcon';
import { Alert } from '../../../components/ui/Alert';
import DeferredSpinner from '../../../components/ui/DeferredSpinner';
import { translate } from '../../../helpers/l10n';
-import { getProjectUrl } from '../../../helpers/urls';
+import { getProjectUrl, queryToSearch } from '../../../helpers/urls';
import { GitlabProject } from '../../../types/alm-integration';
import { ComponentQualifier } from '../../../types/component';
import { Paging } from '../../../types/types';
@@ -69,7 +69,7 @@ export default function GitlabProjectSelectionForm(props: GitlabProjectSelection
<Link
to={{
pathname: '/projects/create',
- query: { mode: CreateProjectModes.GitLab, resetPat: 1 }
+ search: queryToSearch({ mode: CreateProjectModes.GitLab, resetPat: 1 })
}}>
{translate('onboarding.create_project.update_your_token')}
</Link>
diff --git a/server/sonar-web/src/main/js/apps/create/project/WrongBindingCountAlert.tsx b/server/sonar-web/src/main/js/apps/create/project/WrongBindingCountAlert.tsx
index bed32e27e6f..821d2a1ed7f 100644
--- a/server/sonar-web/src/main/js/apps/create/project/WrongBindingCountAlert.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/WrongBindingCountAlert.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 { getGlobalSettingsUrl } from '../../../helpers/urls';
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectAccordion-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectAccordion-test.tsx.snap
index 75ce5ede02c..85fc0eecdd7 100644
--- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectAccordion-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectAccordion-test.tsx.snap
@@ -151,16 +151,11 @@ exports[`should render correctly: search results 1`] = `
className="little-spacer-bottom text-ellipsis"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
title="SQ Name"
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "id": "sq-key",
- },
+ "search": "?id=sq-key",
}
}
>
@@ -236,16 +231,11 @@ exports[`should render correctly: with repositories 1`] = `
className="little-spacer-bottom text-ellipsis"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
title="SQ Name"
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "id": "sq-key",
- },
+ "search": "?id=sq-key",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectCreateRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectCreateRenderer-test.tsx.snap
index dfcbf392e44..d39ae29d2d9 100644
--- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectCreateRenderer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectCreateRenderer-test.tsx.snap
@@ -165,14 +165,10 @@ exports[`should render correctly: setting missing url, admin 1`] = `
Object {
"alm": "onboarding.alm.azure",
"url": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/admin/settings",
- "query": Object {
- "category": "almintegration",
- },
+ "search": "?category=almintegration",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectsList-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectsList-test.tsx.snap
index f26d19c132b..271f606cb68 100644
--- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectsList-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectsList-test.tsx.snap
@@ -36,15 +36,10 @@ exports[`should render correctly: empty 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/projects/create",
- "query": Object {
- "mode": "azure",
- "resetPat": 1,
- },
+ "search": "?mode=azure&resetPat=1",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketCloudSearchForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketCloudSearchForm-test.tsx.snap
index 2f9463a21fb..bbaa3079000 100644
--- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketCloudSearchForm-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketCloudSearchForm-test.tsx.snap
@@ -11,15 +11,10 @@ exports[`Should render correctly 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/projects/create",
- "query": Object {
- "mode": "bitbucketcloud",
- "resetPat": 1,
- },
+ "search": "?mode=bitbucketcloud&resetPat=1",
}
}
>
@@ -109,15 +104,10 @@ exports[`Should render correctly: Importing 1`] = `
className="project-name display-inline-block text-ellipsis"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "id": "sq-key",
- },
+ "search": "?id=sq-key",
}
}
>
@@ -373,15 +363,10 @@ exports[`Should render correctly: Show more 1`] = `
className="project-name display-inline-block text-ellipsis"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "id": "sq-key",
- },
+ "search": "?id=sq-key",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketImportRepositoryForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketImportRepositoryForm-test.tsx.snap
index fa7fa8fc11d..171179efbaf 100644
--- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketImportRepositoryForm-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketImportRepositoryForm-test.tsx.snap
@@ -62,15 +62,10 @@ exports[`should render correctly: no projects 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/projects/create",
- "query": Object {
- "mode": "bitbucket",
- "resetPat": 1,
- },
+ "search": "?mode=bitbucket&resetPat=1",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketProjectAccordion-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketProjectAccordion-test.tsx.snap
index e995e7e8379..4afde710ce4 100644
--- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketProjectAccordion-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketProjectAccordion-test.tsx.snap
@@ -60,15 +60,10 @@ exports[`should render correctly: default 1`] = `
title="Bar"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "id": "bar",
- },
+ "search": "?id=bar",
}
}
>
@@ -132,15 +127,10 @@ exports[`should render correctly: disable options 1`] = `
title="Bar"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "id": "bar",
- },
+ "search": "?id=bar",
}
}
>
@@ -204,15 +194,10 @@ exports[`should render correctly: no click handler 1`] = `
title="Bar"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "id": "bar",
- },
+ "search": "?id=bar",
}
}
>
@@ -276,15 +261,10 @@ exports[`should render correctly: no project info 1`] = `
title="Bar"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "id": "bar",
- },
+ "search": "?id=bar",
}
}
>
@@ -324,15 +304,10 @@ exports[`should render correctly: no repos 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/projects/create",
- "query": Object {
- "mode": "bitbucket",
- "resetPat": 1,
- },
+ "search": "?mode=bitbucket&resetPat=1",
}
}
>
@@ -393,15 +368,10 @@ exports[`should render correctly: not showing all repos 1`] = `
title="Bar"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "id": "bar",
- },
+ "search": "?id=bar",
}
}
>
@@ -470,15 +440,10 @@ exports[`should render correctly: selected repo 1`] = `
title="Bar"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "id": "bar",
- },
+ "search": "?id=bar",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap
index 70474361ec7..1d7755f6e7a 100644
--- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap
@@ -96,7 +96,6 @@ exports[`should render correctly for azure mode 1`] = `
loadingBindings={true}
location={
Object {
- "action": "PUSH",
"hash": "",
"key": "key",
"pathname": "/path",
@@ -149,7 +148,6 @@ exports[`should render correctly for bitbucket mode 1`] = `
loadingBindings={true}
location={
Object {
- "action": "PUSH",
"hash": "",
"key": "key",
"pathname": "/path",
@@ -200,7 +198,6 @@ exports[`should render correctly for bitbucketcloud mode 1`] = `
loadingBindings={true}
location={
Object {
- "action": "PUSH",
"hash": "",
"key": "key",
"pathname": "/path",
@@ -252,7 +249,6 @@ exports[`should render correctly for github mode 1`] = `
loadingBindings={true}
location={
Object {
- "action": "PUSH",
"hash": "",
"key": "key",
"pathname": "/path",
@@ -304,7 +300,6 @@ exports[`should render correctly for gitlab mode 1`] = `
loadingBindings={true}
location={
Object {
- "action": "PUSH",
"hash": "",
"key": "key",
"pathname": "/path",
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitHubProjectCreateRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitHubProjectCreateRenderer-test.tsx.snap
index 56300a11343..8179e528969 100644
--- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitHubProjectCreateRenderer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitHubProjectCreateRenderer-test.tsx.snap
@@ -112,8 +112,6 @@ exports[`should render correctly: error for admin 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to="/admin/settings?category=almintegration"
>
onboarding.create_project.github.warning.message_admin.link
@@ -375,15 +373,10 @@ exports[`should render correctly: repositories 1`] = `
>
<Link
className="display-flex-center max-width-60"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "id": "repo2",
- },
+ "search": "?id=repo2",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectSelectionForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectSelectionForm-test.tsx.snap
index f179ea42dac..daa8b66ab26 100644
--- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectSelectionForm-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectSelectionForm-test.tsx.snap
@@ -75,15 +75,10 @@ exports[`should render correctly: importing 1`] = `
className="project-name display-inline-block text-ellipsis"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "id": "already-imported",
- },
+ "search": "?id=already-imported",
}
}
>
@@ -154,15 +149,10 @@ exports[`should render correctly: no projects 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/projects/create",
- "query": Object {
- "mode": "gitlab",
- "resetPat": 1,
- },
+ "search": "?mode=gitlab&resetPat=1",
}
}
>
@@ -276,15 +266,10 @@ exports[`should render correctly: projects 1`] = `
className="project-name display-inline-block text-ellipsis"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "id": "already-imported",
- },
+ "search": "?id=already-imported",
}
}
>
@@ -355,15 +340,10 @@ exports[`should render correctly: undefined projects 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/projects/create",
- "query": Object {
- "mode": "gitlab",
- "resetPat": 1,
- },
+ "search": "?mode=gitlab&resetPat=1",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/WrongBindingCountAlert-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/WrongBindingCountAlert-test.tsx.snap
index b30756a2619..7dff8982253 100644
--- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/WrongBindingCountAlert-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/WrongBindingCountAlert-test.tsx.snap
@@ -27,14 +27,10 @@ exports[`should render correctly: for admin 1`] = `
Object {
"alm": "onboarding.alm.bitbucket",
"url": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/admin/settings",
- "query": Object {
- "category": "almintegration",
- },
+ "search": "?category=almintegration",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/documentation/components/App.tsx b/server/sonar-web/src/main/js/apps/documentation/components/App.tsx
index a594ce38dec..05d14b82c1c 100644
--- a/server/sonar-web/src/main/js/apps/documentation/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/documentation/components/App.tsx
@@ -21,7 +21,7 @@ import * as navigationTreeSonarQube from 'Docs/../static/SonarQubeNavigationTree
import { DocNavigationItem } from 'Docs/@types/types';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
-import { Link } from 'react-router';
+import { Link, useLocation, useParams } from 'react-router-dom';
import { getInstalledPlugins } from '../../../api/plugins';
import { getPluginStaticFileContent } from '../../../api/static';
import NotFound from '../../../app/components/NotFound';
@@ -54,7 +54,7 @@ interface State {
const LANGUAGES_BASE_URL = 'analysis/languages';
-export default class App extends React.PureComponent<Props, State> {
+export class App extends React.PureComponent<Props, State> {
mounted = false;
state: State = {
loading: false,
@@ -227,3 +227,10 @@ export default class App extends React.PureComponent<Props, State> {
);
}
}
+
+export default function AppWrapper() {
+ const params = useParams();
+ const location = useLocation();
+
+ return <App params={{ splat: params['*'] }} location={location} />;
+}
diff --git a/server/sonar-web/src/main/js/apps/documentation/components/MenuItem.tsx b/server/sonar-web/src/main/js/apps/documentation/components/MenuItem.tsx
index 2b51c32ef8f..4e57fee57aa 100644
--- a/server/sonar-web/src/main/js/apps/documentation/components/MenuItem.tsx
+++ b/server/sonar-web/src/main/js/apps/documentation/components/MenuItem.tsx
@@ -19,7 +19,7 @@
*/
import classNames from 'classnames';
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { testPathAgainstUrl } from '../navTreeUtils';
import { DocumentationEntry } from '../utils';
diff --git a/server/sonar-web/src/main/js/apps/documentation/components/SearchResultEntry.tsx b/server/sonar-web/src/main/js/apps/documentation/components/SearchResultEntry.tsx
index b4da77ead35..6490f365a16 100644
--- a/server/sonar-web/src/main/js/apps/documentation/components/SearchResultEntry.tsx
+++ b/server/sonar-web/src/main/js/apps/documentation/components/SearchResultEntry.tsx
@@ -19,7 +19,7 @@
*/
import classNames from 'classnames';
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { Dict } from '../../../types/types';
import { cutWords, DocumentationEntry, highlightMarks } from '../utils';
diff --git a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/App-test.tsx
index 85d7439ecf1..bc3b3c3ef48 100644
--- a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/App-test.tsx
+++ b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/App-test.tsx
@@ -24,7 +24,7 @@ import { request } from '../../../../helpers/request';
import { waitAndUpdate } from '../../../../helpers/testUtils';
import { InstalledPlugin } from '../../../../types/plugins';
import getPages from '../../pages';
-import App from '../App';
+import { App } from '../App';
jest.mock('../../../../components/common/ScreenPositionHelper');
diff --git a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/App-test.tsx.snap
index 94824017b6d..a27c118ffa2 100644
--- a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/App-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/App-test.tsx.snap
@@ -69,8 +69,6 @@ exports[`should render correctly for SonarQube 2`] = `
weight={10}
/>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to="/documentation/"
>
<h1>
diff --git a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/MenuItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/MenuItem-test.tsx.snap
index 795f0dbdd79..73eca5dd152 100644
--- a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/MenuItem-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/MenuItem-test.tsx.snap
@@ -4,8 +4,6 @@ exports[`should not render a high depth differently than a depth of 3 1`] = `
<Link
className="list-group-item depth-3"
key="/bar"
- onlyActiveOnIndex={false}
- style={Object {}}
to="/documentation/bar"
>
<h3
@@ -18,8 +16,6 @@ exports[`should render correctly 1`] = `
<Link
className="list-group-item"
key="/bar"
- onlyActiveOnIndex={false}
- style={Object {}}
to="/documentation/bar"
>
<h3
@@ -32,8 +28,6 @@ exports[`should render correctly if the current node matches the splat 1`] = `
<Link
className="list-group-item active"
key="/bar"
- onlyActiveOnIndex={false}
- style={Object {}}
to="/documentation/bar"
>
<h3
diff --git a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/SearchResultEntry-test.tsx.snap b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/SearchResultEntry-test.tsx.snap
index c45f65fc1a5..2a50912a76b 100644
--- a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/SearchResultEntry-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/SearchResultEntry-test.tsx.snap
@@ -3,8 +3,6 @@
exports[`SearchResultEntry should render 1`] = `
<Link
className="list-group-item active"
- onlyActiveOnIndex={false}
- style={Object {}}
to="/documentation/foo/bar"
>
<SearchResultTitle
diff --git a/server/sonar-web/src/main/js/apps/documentation/routes.tsx b/server/sonar-web/src/main/js/apps/documentation/routes.tsx
new file mode 100644
index 00000000000..c7be80023d0
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/documentation/routes.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 React from 'react';
+import { Route } from 'react-router-dom';
+import App from './components/App';
+
+const routes = () => (
+ <Route path="documentation">
+ <Route index={true} element={<App />} />
+ <Route path="*" element={<App />} />
+ </Route>
+);
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/users/routes.ts b/server/sonar-web/src/main/js/apps/groups/routes.tsx
index 90ec2e15515..945f3bba3d8 100644
--- a/server/sonar-web/src/main/js/apps/users/routes.ts
+++ b/server/sonar-web/src/main/js/apps/groups/routes.tsx
@@ -17,12 +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 { lazyLoadComponent } from '../../components/lazyLoadComponent';
+import React from 'react';
+import { Route } from 'react-router-dom';
+import App from './components/App';
-const routes = [
- {
- indexRoute: { component: lazyLoadComponent(() => import('./UsersApp')) }
- }
-];
+const routes = () => <Route path="groups" element={<App />} />;
export default routes;
diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx
index 65066ba0845..c6c177b5b85 100644
--- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx
@@ -19,10 +19,13 @@
*/
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
+import React from 'react';
import IssuesServiceMock from '../../../api/mocks/IssuesServiceMock';
import { renderOwaspTop102021Category } from '../../../helpers/security-standard';
-import { renderComponentApp } from '../../../helpers/testReactTestingUtils';
+import { renderApp, renderComponentApp } from '../../../helpers/testReactTestingUtils';
+import { IssueType } from '../../../types/issues';
import AppContainer from '../components/AppContainer';
+import { projectIssuesRoutes } from '../routes';
jest.mock('../../../api/issues');
jest.mock('../../../api/rules');
@@ -106,6 +109,28 @@ it('should support OWASP Top 10 version 2021', async () => {
);
});
+describe('redirects', () => {
+ it('should work for hotspots', () => {
+ renderProjectIssuesApp(`project/issues?types=${IssueType.SecurityHotspot}`);
+
+ expect(screen.getByText('/security_hotspots?assignedToMe=false')).toBeInTheDocument();
+ });
+
+ it('should filter out hotspots', async () => {
+ renderProjectIssuesApp(
+ `project/issues?types=${IssueType.SecurityHotspot},${IssueType.CodeSmell}`
+ );
+
+ expect(
+ await screen.findByRole('link', { name: `issue.type.${IssueType.CodeSmell}` })
+ ).toBeInTheDocument();
+ });
+});
+
function renderIssueApp() {
- renderComponentApp('project/issues', AppContainer);
+ renderComponentApp('project/issues', <AppContainer />);
+}
+
+function renderProjectIssuesApp(navigateTo?: string) {
+ renderApp('project/issues', projectIssuesRoutes, { navigateTo });
}
diff --git a/server/sonar-web/src/main/js/apps/issues/components/AppContainer.tsx b/server/sonar-web/src/main/js/apps/issues/components/AppContainer.tsx
index e3060ab0d0f..04630efd53c 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/AppContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/AppContainer.tsx
@@ -19,9 +19,14 @@
*/
import withBranchStatusActions from '../../../app/components/branch-status/withBranchStatusActions';
import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext';
+import { PageContext } from '../../../app/components/indexation/PageUnavailableDueToIndexation';
+import withIndexationGuard from '../../../components/hoc/withIndexationGuard';
import { withRouter } from '../../../components/hoc/withRouter';
import { lazyLoadComponent } from '../../../components/lazyLoadComponent';
const IssuesAppContainer = lazyLoadComponent(() => import('./IssuesApp'), 'IssuesAppContainer');
-export default withRouter(withCurrentUserContext(withBranchStatusActions(IssuesAppContainer)));
+export default withIndexationGuard(
+ withRouter(withCurrentUserContext(withBranchStatusActions(IssuesAppContainer))),
+ PageContext.Issues
+);
diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssueTabViewer.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssueTabViewer.tsx
index cdb493c95f4..d234224c8d4 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/IssueTabViewer.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/IssueTabViewer.tsx
@@ -19,7 +19,7 @@
*/
import classNames from 'classnames';
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import BoxedTabs from '../../../components/controls/BoxedTabs';
import { translate } from '../../../helpers/l10n';
import { sanitizeString } from '../../../helpers/sanitize';
diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx
index 34f4dd92f3c..66997756890 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx
@@ -25,6 +25,7 @@ import { Helmet } from 'react-helmet-async';
import { FormattedMessage } from 'react-intl';
import { searchIssues } from '../../../api/issues';
import { getRuleDetails } from '../../../api/rules';
+import withComponentContext from '../../../app/components/componentContext/withComponentContext';
import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
import EmptySearch from '../../../components/common/EmptySearch';
import FiltersHeader from '../../../components/common/FiltersHeader';
@@ -104,7 +105,7 @@ interface Props {
currentUser: CurrentUser;
fetchBranchStatus: (branchLike: BranchLike, projectKey: string) => void;
location: Location;
- router: Pick<Router, 'push' | 'replace'>;
+ router: Router;
}
export interface State {
@@ -141,7 +142,7 @@ const DEFAULT_QUERY = { resolved: 'false' };
const MAX_INITAL_FETCH = 1000;
const BRANCH_STATUS_REFRESH_INTERVAL = 1000;
-export default class App extends React.PureComponent<Props, State> {
+export class App extends React.PureComponent<Props, State> {
mounted = false;
constructor(props: Props) {
@@ -1154,3 +1155,5 @@ const AlertContent = styled.div`
display: flex;
align-items: center;
`;
+
+export default withComponentContext(App);
diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesApp-test.tsx b/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesApp-test.tsx
index 015fe3499e3..3b035bb3258 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesApp-test.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesApp-test.tsx
@@ -52,7 +52,7 @@ import {
selectPreviousLocation
} from '../../actions';
import BulkChangeModal from '../BulkChangeModal';
-import App from '../IssuesApp';
+import { App } from '../IssuesApp';
import IssuesSourceViewer from '../IssuesSourceViewer';
import IssueViewerTabs from '../IssueTabViewer';
diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/ComponentSourceSnippetGroupViewer-test.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/ComponentSourceSnippetGroupViewer-test.tsx
index e39ceb01d82..12d34757886 100644
--- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/ComponentSourceSnippetGroupViewer-test.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/ComponentSourceSnippetGroupViewer-test.tsx
@@ -38,6 +38,23 @@ jest.mock('../../../../api/components', () => ({
getSources: jest.fn().mockResolvedValue([])
}));
+/*
+ * Quick & dirty fix to make the tests pass
+ * this whole thing should be replaced by RTL tests!
+ */
+jest.mock('react-router-dom', () => {
+ const routerDom = jest.requireActual('react-router-dom');
+
+ function Link() {
+ return <div>Link</div>;
+ }
+
+ return {
+ ...routerDom,
+ Link
+ };
+});
+
beforeEach(() => {
jest.clearAllMocks();
});
diff --git a/server/sonar-web/src/main/js/apps/issues/routes.tsx b/server/sonar-web/src/main/js/apps/issues/routes.tsx
new file mode 100644
index 00000000000..ded5c5d5bd3
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/issues/routes.tsx
@@ -0,0 +1,71 @@
+/*
+ * 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 React, { useEffect } from 'react';
+import { Route, useNavigate, useSearchParams } from 'react-router-dom';
+import { omitNil } from '../../helpers/request';
+import { IssueType } from '../../types/issues';
+import AppContainer from './components/AppContainer';
+
+export const globalIssuesRoutes = () => <Route path="issues" element={<AppContainer />} />;
+
+export const projectIssuesRoutes = () => (
+ <Route path="project/issues" element={<IssuesNavigate />} />
+);
+
+function IssuesNavigate() {
+ const [searchParams, setSearchParams] = useSearchParams();
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ if (searchParams.has('types')) {
+ const types = searchParams.get('types') ?? '';
+
+ if (types === IssueType.SecurityHotspot) {
+ navigate(
+ {
+ pathname: '/security_hotspots',
+ search: new URLSearchParams(
+ omitNil({
+ id: searchParams.get('id'),
+ branch: searchParams.get('branch'),
+ pullRequest: searchParams.get('pullRequest'),
+ assignedToMe: 'false'
+ })
+ ).toString()
+ },
+ { replace: true }
+ );
+ } else {
+ const filteredTypes = types
+ .split(',')
+ .filter((type: string) => type !== IssueType.SecurityHotspot)
+ .join(',');
+
+ if (types !== filteredTypes) {
+ searchParams.set('types', filteredTypes);
+
+ setSearchParams(searchParams, { replace: true });
+ }
+ }
+ }
+ }, [navigate, searchParams, setSearchParams]);
+
+ return <AppContainer />;
+}
diff --git a/server/sonar-web/src/main/js/apps/maintenance/components/App.tsx b/server/sonar-web/src/main/js/apps/maintenance/components/App.tsx
index 7cf60e0d92e..447c8510fec 100644
--- a/server/sonar-web/src/main/js/apps/maintenance/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/maintenance/components/App.tsx
@@ -32,7 +32,7 @@ import { getReturnUrl } from '../../../helpers/urls';
import '../styles.css';
interface Props {
- location: { query: { return_to?: string } };
+ location: { query?: { return_to?: string } };
setup: boolean;
}
diff --git a/server/sonar-web/src/main/js/apps/maintenance/components/MaintenanceAppContainer.tsx b/server/sonar-web/src/main/js/apps/maintenance/components/MaintenanceAppContainer.tsx
index 1f76587ae92..16b194ab532 100644
--- a/server/sonar-web/src/main/js/apps/maintenance/components/MaintenanceAppContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/maintenance/components/MaintenanceAppContainer.tsx
@@ -18,12 +18,17 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { useSearchParams } from 'react-router-dom';
+import { omitNil } from '../../../helpers/request';
import App from './App';
-interface Props {
- location: { query: { return_to: string } };
-}
+export default function MaintenanceAppContainer() {
+ const [searchParams] = useSearchParams();
-export default function MaintenanceAppContainer(props: Props) {
- return <App setup={false} {...props} />;
+ return (
+ <App
+ setup={false}
+ location={{ query: omitNil({ return_to: searchParams.get('return_to') }) }}
+ />
+ );
}
diff --git a/server/sonar-web/src/main/js/apps/maintenance/components/SetupAppContainer.tsx b/server/sonar-web/src/main/js/apps/maintenance/components/SetupAppContainer.tsx
index 58be849409f..2619cade0eb 100644
--- a/server/sonar-web/src/main/js/apps/maintenance/components/SetupAppContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/maintenance/components/SetupAppContainer.tsx
@@ -18,12 +18,15 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { useSearchParams } from 'react-router-dom';
+
+import { omitNil } from '../../../helpers/request';
import App from './App';
-interface Props {
- location: { query: { return_to: string } };
-}
+export default function MaintenanceAppContainer() {
+ const [searchParams] = useSearchParams();
-export default function MaintenanceAppContainer(props: Props) {
- return <App setup={true} {...props} />;
+ return (
+ <App setup={true} location={{ query: omitNil({ return_to: searchParams.get('return_to') }) }} />
+ );
}
diff --git a/server/sonar-web/src/main/js/apps/maintenance/routes.tsx b/server/sonar-web/src/main/js/apps/maintenance/routes.tsx
index 75efeb40688..c5997b49845 100644
--- a/server/sonar-web/src/main/js/apps/maintenance/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/maintenance/routes.tsx
@@ -18,13 +18,15 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { IndexRoute } from 'react-router';
-import { lazyLoadComponent } from '../../components/lazyLoadComponent';
+import { Route } from 'react-router-dom';
+import MaintenanceAppContainer from './components/MaintenanceAppContainer';
+import SetupAppContainer from './components/SetupAppContainer';
-export const maintenanceRoutes = (
- <IndexRoute component={lazyLoadComponent(() => import('./components/MaintenanceAppContainer'))} />
+const routes = () => (
+ <>
+ <Route path="maintenance" element={<MaintenanceAppContainer />} />
+ <Route path="setup" element={<SetupAppContainer />} />
+ </>
);
-export const setupRoutes = (
- <IndexRoute component={lazyLoadComponent(() => import('./components/SetupAppContainer'))} />
-);
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/marketplace/App.tsx b/server/sonar-web/src/main/js/apps/marketplace/App.tsx
index 65076bc8a40..debe1da43a7 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/App.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/App.tsx
@@ -21,7 +21,7 @@ import { sortBy, uniqBy } from 'lodash';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import { FormattedMessage } from 'react-intl';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import {
getAvailablePlugins,
getInstalledPlugins,
@@ -51,7 +51,7 @@ interface Props {
fetchPendingPlugins: () => void;
pendingPlugins: PendingPluginResult;
location: Location;
- router: Pick<Router, 'push'>;
+ router: Router;
standaloneMode?: boolean;
updateCenterActive: boolean;
}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/MarketplaceAppContainer.tsx b/server/sonar-web/src/main/js/apps/marketplace/MarketplaceAppContainer.tsx
index 77050b3ac91..3c39c05c05c 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/MarketplaceAppContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/MarketplaceAppContainer.tsx
@@ -20,14 +20,14 @@
import * as React from 'react';
import AdminContext from '../../app/components/AdminContext';
import withAppStateContext from '../../app/components/app-state/withAppStateContext';
+import { Location, withRouter } from '../../components/hoc/withRouter';
import { AppState } from '../../types/appstate';
import { EditionKey } from '../../types/editions';
import { GlobalSettingKeys } from '../../types/settings';
-import { RawQuery } from '../../types/types';
import App from './App';
export interface MarketplaceAppContainerProps {
- location: { pathname: string; query: RawQuery };
+ location: Location;
appState: AppState;
}
@@ -54,4 +54,4 @@ export function MarketplaceAppContainer(props: MarketplaceAppContainerProps) {
);
}
-export default withAppStateContext(MarketplaceAppContainer);
+export default withRouter(withAppStateContext(MarketplaceAppContainer));
diff --git a/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/App-test.tsx.snap
index e90ab06818d..f839316faff 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/App-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/App-test.tsx.snap
@@ -44,8 +44,6 @@ exports[`should render correctly: loaded 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/instance-administration/marketplace/"
>
@@ -144,8 +142,6 @@ exports[`should render correctly: loading 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/instance-administration/marketplace/"
>
diff --git a/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/MarketplaceAppContainer-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/MarketplaceAppContainer-test.tsx.snap
index 58d697aede5..4f08e115a49 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/MarketplaceAppContainer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/MarketplaceAppContainer-test.tsx.snap
@@ -6,7 +6,6 @@ exports[`should render correctly: default 1`] = `
fetchPendingPlugins={[Function]}
location={
Object {
- "action": "PUSH",
"hash": "",
"key": "key",
"pathname": "/path",
@@ -33,7 +32,6 @@ exports[`should render correctly: update center active 1`] = `
fetchPendingPlugins={[Function]}
location={
Object {
- "action": "PUSH",
"hash": "",
"key": "key",
"pathname": "/path",
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/LicensePromptModal.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/LicensePromptModal.tsx
index 14eca641da9..56882029a15 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/LicensePromptModal.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/LicensePromptModal.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 { ResetButtonLink } from '../../../components/controls/buttons';
import Modal from '../../../components/controls/Modal';
import { translate } from '../../../helpers/l10n';
diff --git a/server/sonar-web/src/main/js/apps/marketplace/routes.ts b/server/sonar-web/src/main/js/apps/marketplace/routes.ts
deleted file mode 100644
index 211f08c5a06..00000000000
--- a/server/sonar-web/src/main/js/apps/marketplace/routes.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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 { lazyLoadComponent } from '../../components/lazyLoadComponent';
-
-const routes = [
- {
- indexRoute: { component: lazyLoadComponent(() => import('./MarketplaceAppContainer')) }
- }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/routes.ts b/server/sonar-web/src/main/js/apps/marketplace/routes.tsx
index 89ddf2f098f..95af0d4350c 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/routes.ts
+++ b/server/sonar-web/src/main/js/apps/marketplace/routes.tsx
@@ -17,10 +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 { lazyLoadComponent } from '../../components/lazyLoadComponent';
+import React from 'react';
+import { Route } from 'react-router-dom';
+import MarketplaceAppContainer from './MarketplaceAppContainer';
-const App = lazyLoadComponent(() => import('./components/App'));
-
-const routes = [{ indexRoute: { component: App } }, { path: 'show/:id', component: App }];
+export const routes = () => <Route path="marketplace" element={<MarketplaceAppContainer />} />;
export default routes;
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/FirstAnalysisNextStepsNotif.tsx b/server/sonar-web/src/main/js/apps/overview/branches/FirstAnalysisNextStepsNotif.tsx
index a70c5ea4b0b..cbb88c81447 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/FirstAnalysisNextStepsNotif.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/FirstAnalysisNextStepsNotif.tsx
@@ -19,10 +19,11 @@
*/
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext';
import DismissableAlert from '../../../components/ui/DismissableAlert';
import { translate } from '../../../helpers/l10n';
+import { queryToSearch } from '../../../helpers/urls';
import { ProjectAlmBindingResponse } from '../../../types/alm-settings';
import { ComponentQualifier } from '../../../types/component';
import { Component } from '../../../types/types';
@@ -66,7 +67,7 @@ export function FirstAnalysisNextStepsNotif(props: FirstAnalysisNextStepsNotifPr
<Link
to={{
pathname: '/tutorials',
- query: { id: component.key }
+ search: queryToSearch({ id: component.key })
}}>
{translate('overview.project.next_steps.links.set_up_ci')}
</Link>
@@ -75,10 +76,10 @@ export function FirstAnalysisNextStepsNotif(props: FirstAnalysisNextStepsNotifPr
<Link
to={{
pathname: '/project/settings',
- query: {
+ search: queryToSearch({
id: component.key,
category: PULL_REQUEST_DECORATION_BINDING_CATEGORY
- }
+ })
}}>
{translate('overview.project.next_steps.links.project_settings')}
</Link>
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanel.tsx
index c490803dc3d..0f1e50315e5 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanel.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanel.tsx
@@ -18,10 +18,10 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { withRouter, Location } from '../../../components/hoc/withRouter';
import { rawSizes } from '../../../app/theme';
import BoxedTabs from '../../../components/controls/BoxedTabs';
import ComponentReportActions from '../../../components/controls/ComponentReportActions';
+import { Location, withRouter } from '../../../components/hoc/withRouter';
import DeferredSpinner from '../../../components/ui/DeferredSpinner';
import { translate } from '../../../helpers/l10n';
import { findMeasure, isDiffMetric } from '../../../helpers/measures';
@@ -54,7 +54,7 @@ export enum MeasuresPanelTabs {
Overall
}
-function MeasuresPanel(props: MeasuresPanelProps) {
+export function MeasuresPanel(props: MeasuresPanelProps) {
const { appLeak, branch, component, loading, measures = [], period, location } = props;
const hasDiffMeasures = measures.some(m => isDiffMetric(m.metric.key));
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx
index 65305b2424d..7e58f648703 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx
@@ -19,10 +19,11 @@
*/
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { translate } from '../../../helpers/l10n';
import { getBaseUrl } from '../../../helpers/system';
+import { queryToSearch } from '../../../helpers/urls';
import { Branch } from '../../../types/branch-like';
import { ComponentQualifier } from '../../../types/component';
import { Component, Period } from '../../../types/types';
@@ -77,7 +78,7 @@ export default function MeasuresPanelNoNewCode(props: MeasuresPanelNoNewCodeProp
<Link
to={{
pathname: '/project/baseline',
- query: { id: component.key, ...getBranchLikeQuery(branch) }
+ search: queryToSearch({ id: component.key, ...getBranchLikeQuery(branch) })
}}>
{translate('settings.new_code_period.category')}
</Link>
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/MeasuresPanel-test.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/MeasuresPanel-test.tsx
index 0c85d349392..2564e650feb 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/MeasuresPanel-test.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/MeasuresPanel-test.tsx
@@ -30,7 +30,7 @@ import {
} from '../../../../helpers/testMocks';
import { ComponentQualifier } from '../../../../types/component';
import { MetricKey } from '../../../../types/metrics';
-import MeasuresPanel, { MeasuresPanelProps, MeasuresPanelTabs } from '../MeasuresPanel';
+import { MeasuresPanel, MeasuresPanelProps, MeasuresPanelTabs } from '../MeasuresPanel';
jest.mock('react', () => {
return {
@@ -41,24 +41,18 @@ jest.mock('react', () => {
it('should render correctly for projects', () => {
const wrapper = shallowRender();
- expect(wrapper).toMatchSnapshot();
- wrapper
- .dive()
- .find(BoxedTabs)
- .prop<Function>('onSelect')(MeasuresPanelTabs.Overall);
- expect(wrapper).toMatchSnapshot();
+ expect(wrapper).toMatchSnapshot('default');
+ wrapper.find(BoxedTabs).prop<Function>('onSelect')(MeasuresPanelTabs.Overall);
+ expect(wrapper).toMatchSnapshot('overall');
});
it('should render correctly for applications', () => {
const wrapper = shallowRender({
component: mockComponent({ qualifier: ComponentQualifier.Application })
});
- expect(wrapper).toMatchSnapshot();
- wrapper
- .dive()
- .find(BoxedTabs)
- .prop<Function>('onSelect')(MeasuresPanelTabs.Overall);
- expect(wrapper).toMatchSnapshot();
+ expect(wrapper).toMatchSnapshot('default');
+ wrapper.find(BoxedTabs).prop<Function>('onSelect')(MeasuresPanelTabs.Overall);
+ expect(wrapper).toMatchSnapshot('overall');
});
it('should render correctly if there is no new code measures', () => {
@@ -68,10 +62,7 @@ it('should render correctly if there is no new code measures', () => {
mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.bugs }) })
]
});
- wrapper
- .dive()
- .find(BoxedTabs)
- .prop<Function>('onSelect')(MeasuresPanelTabs.New);
+ wrapper.find(BoxedTabs).prop<Function>('onSelect')(MeasuresPanelTabs.New);
expect(wrapper).toMatchSnapshot();
});
@@ -84,10 +75,7 @@ it('should render correctly if branch is misconfigured', () => {
],
period: mockPeriod({ date: undefined, mode: 'REFERENCE_BRANCH', parameter: 'own-reference' })
});
- wrapper
- .dive()
- .find(BoxedTabs)
- .prop<Function>('onSelect')(MeasuresPanelTabs.New);
+ wrapper.find(BoxedTabs).prop<Function>('onSelect')(MeasuresPanelTabs.New);
expect(wrapper).toMatchSnapshot('hide settings');
wrapper.setProps({ component: mockComponent({ configuration: { showSettings: true } }) });
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/MeasuresPanelNoNewCode-test.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/MeasuresPanelNoNewCode-test.tsx
index 74d1e4d4649..959649c2057 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/MeasuresPanelNoNewCode-test.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/MeasuresPanelNoNewCode-test.tsx
@@ -61,8 +61,6 @@ it('should render the default message', () => {
values={
Object {
"learn_more_link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to="/documentation/user-guide/clean-as-you-code/"
>
learn_more
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/FirstAnalysisNextStepsNotif-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/FirstAnalysisNextStepsNotif-test.tsx.snap
index cb8c39ea0b6..c16bf79e93d 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/FirstAnalysisNextStepsNotif-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/FirstAnalysisNextStepsNotif-test.tsx.snap
@@ -11,14 +11,10 @@ exports[`should render correctly: show prompt to configure CI 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/tutorials",
- "query": Object {
- "id": "my-project",
- },
+ "search": "?id=my-project",
}
}
>
@@ -41,29 +37,20 @@ exports[`should render correctly: show prompt to configure PR decoration + CI, p
values={
Object {
"link_ci": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/tutorials",
- "query": Object {
- "id": "my-project",
- },
+ "search": "?id=my-project",
}
}
>
overview.project.next_steps.links.set_up_ci
</Link>,
"link_project_settings": <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",
}
}
>
@@ -86,14 +73,10 @@ exports[`should render correctly: show prompt to configure PR decoration + CI, r
values={
Object {
"link_ci": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/tutorials",
- "query": Object {
- "id": "my-project",
- },
+ "search": "?id=my-project",
}
}
>
@@ -116,15 +99,10 @@ exports[`should render correctly: show prompt to configure PR decoration, projec
values={
Object {
"link_project_settings": <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/apps/overview/branches/__tests__/__snapshots__/MeasuresPanel-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanel-test.tsx.snap
index 338eab97b8c..97d53169603 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanel-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanel-test.tsx.snap
@@ -1,1151 +1,5884 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should render correctly for applications 1`] = `
-<Memo(MeasuresPanel)
- branch={
- Object {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- }
- }
- component={
- Object {
- "breadcrumbs": Array [],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "APP",
- "qualityGate": Object {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": Array [
- Object {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": Array [],
- }
- }
- location={
- Object {
- "action": "PUSH",
- "hash": "",
- "key": "key",
- "pathname": "/path",
- "query": Object {},
- "search": "",
- "state": Object {},
- }
- }
- measures={
- Array [
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "coverage",
- "key": "coverage",
- "name": "Coverage",
- "type": "PERCENT",
- },
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
- },
- "value": "1.0",
- },
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "new_coverage",
- "key": "new_coverage",
- "name": "New_coverage",
- "type": "PERCENT",
- },
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
- },
- "value": "1.0",
- },
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "bugs",
- "key": "bugs",
- "name": "Bugs",
- "type": "PERCENT",
- },
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
- },
- "value": "1.0",
- },
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "new_bugs",
- "key": "new_bugs",
- "name": "New_bugs",
- "type": "PERCENT",
+exports[`should render correctly for applications: default 1`] = `
+<div
+ className="overview-panel"
+ data-test="overview__measures-panel"
+>
+ <div
+ className="display-flex-space-between display-flex-start"
+ >
+ <h2
+ className="overview-panel-title"
+ >
+ overview.measures
+ </h2>
+ <withCurrentUserContext(withAppStateContext(ComponentReportActions))
+ branch={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "qualifier": "APP",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ />
+ </div>
+ <BoxedTabs
+ onSelect={[Function]}
+ selected={0}
+ tabs={
+ Array [
+ Object {
+ "key": 0,
+ "label": <div
+ className="text-left overview-measures-tab"
+ >
+ <span
+ className="text-bold"
+ >
+ overview.new_code
+ </span>
+ </div>,
},
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
+ Object {
+ "key": 1,
+ "label": <div
+ className="text-left overview-measures-tab"
+ >
+ <span
+ className="text-bold"
+ style={
+ Object {
+ "position": "absolute",
+ "top": 16,
+ }
+ }
+ >
+ overview.overall_code
+ </span>
+ </div>,
},
- "value": "1.0",
- },
- ]
- }
-/>
+ ]
+ }
+ />
+ <div
+ className="overview-panel-content flex-1 bordered"
+ >
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "qualifier": "APP",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ isNewCodeTab={true}
+ key="BUG"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="BUG"
+ />
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "qualifier": "APP",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ isNewCodeTab={true}
+ key="VULNERABILITY"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="VULNERABILITY"
+ />
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "qualifier": "APP",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ isNewCodeTab={true}
+ key="SECURITY_HOTSPOT"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="SECURITY_HOTSPOT"
+ />
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "qualifier": "APP",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ isNewCodeTab={true}
+ key="CODE_SMELL"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="CODE_SMELL"
+ />
+ <div
+ className="display-flex-row overview-measures-row"
+ >
+ <div
+ className="overview-panel-huge-padded flex-1 bordered-right display-flex-center"
+ data-test="overview__measures-coverage"
+ >
+ <MeasurementLabel
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ centered={true}
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "qualifier": "APP",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="COVERAGE"
+ useDiffMetric={true}
+ />
+ </div>
+ <div
+ className="overview-panel-huge-padded flex-1 display-flex-center"
+ >
+ <MeasurementLabel
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ centered={true}
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "qualifier": "APP",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="DUPLICATION"
+ useDiffMetric={true}
+ />
+ </div>
+ </div>
+ </div>
+</div>
`;
-exports[`should render correctly for applications 2`] = `
-<Memo(MeasuresPanel)
- branch={
- Object {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- }
- }
- component={
- Object {
- "breadcrumbs": Array [],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "APP",
- "qualityGate": Object {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": Array [
- Object {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": Array [],
- }
- }
- location={
- Object {
- "action": "PUSH",
- "hash": "",
- "key": "key",
- "pathname": "/path",
- "query": Object {},
- "search": "",
- "state": Object {},
- }
- }
- measures={
- Array [
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "coverage",
- "key": "coverage",
- "name": "Coverage",
- "type": "PERCENT",
- },
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
- },
- "value": "1.0",
- },
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "new_coverage",
- "key": "new_coverage",
- "name": "New_coverage",
- "type": "PERCENT",
- },
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
- },
- "value": "1.0",
- },
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "bugs",
- "key": "bugs",
- "name": "Bugs",
- "type": "PERCENT",
- },
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
- },
- "value": "1.0",
- },
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "new_bugs",
- "key": "new_bugs",
- "name": "New_bugs",
- "type": "PERCENT",
+exports[`should render correctly for applications: overall 1`] = `
+<div
+ className="overview-panel"
+ data-test="overview__measures-panel"
+>
+ <div
+ className="display-flex-space-between display-flex-start"
+ >
+ <h2
+ className="overview-panel-title"
+ >
+ overview.measures
+ </h2>
+ <withCurrentUserContext(withAppStateContext(ComponentReportActions))
+ branch={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "qualifier": "APP",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ />
+ </div>
+ <BoxedTabs
+ onSelect={[Function]}
+ selected={1}
+ tabs={
+ Array [
+ Object {
+ "key": 0,
+ "label": <div
+ className="text-left overview-measures-tab"
+ >
+ <span
+ className="text-bold"
+ >
+ overview.new_code
+ </span>
+ </div>,
},
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
+ Object {
+ "key": 1,
+ "label": <div
+ className="text-left overview-measures-tab"
+ >
+ <span
+ className="text-bold"
+ style={
+ Object {
+ "position": "absolute",
+ "top": 16,
+ }
+ }
+ >
+ overview.overall_code
+ </span>
+ </div>,
},
- "value": "1.0",
- },
- ]
- }
-/>
+ ]
+ }
+ />
+ <div
+ className="overview-panel-content flex-1 bordered"
+ >
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "qualifier": "APP",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ isNewCodeTab={false}
+ key="BUG"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="BUG"
+ />
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "qualifier": "APP",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ isNewCodeTab={false}
+ key="VULNERABILITY"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="VULNERABILITY"
+ />
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "qualifier": "APP",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ isNewCodeTab={false}
+ key="SECURITY_HOTSPOT"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="SECURITY_HOTSPOT"
+ />
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "qualifier": "APP",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ isNewCodeTab={false}
+ key="CODE_SMELL"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="CODE_SMELL"
+ />
+ <div
+ className="display-flex-row overview-measures-row"
+ >
+ <div
+ className="overview-panel-huge-padded flex-1 bordered-right display-flex-center"
+ data-test="overview__measures-coverage"
+ >
+ <MeasurementLabel
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ centered={false}
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "qualifier": "APP",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="COVERAGE"
+ useDiffMetric={false}
+ />
+ <div
+ className="huge-spacer-left"
+ >
+ <DrilldownMeasureValue
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "qualifier": "APP",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ metric="tests"
+ />
+ </div>
+ </div>
+ <div
+ className="overview-panel-huge-padded flex-1 display-flex-center"
+ >
+ <MeasurementLabel
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ centered={false}
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "qualifier": "APP",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="DUPLICATION"
+ useDiffMetric={false}
+ />
+ <div
+ className="huge-spacer-left"
+ >
+ <DrilldownMeasureValue
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "qualifier": "APP",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ metric="duplicated_blocks"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
`;
-exports[`should render correctly for projects 1`] = `
-<Memo(MeasuresPanel)
- branch={
- Object {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- }
- }
- 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 [],
- }
- }
- location={
- Object {
- "action": "PUSH",
- "hash": "",
- "key": "key",
- "pathname": "/path",
- "query": Object {},
- "search": "",
- "state": Object {},
- }
- }
- measures={
- Array [
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "coverage",
- "key": "coverage",
- "name": "Coverage",
- "type": "PERCENT",
- },
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
- },
- "value": "1.0",
- },
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "new_coverage",
- "key": "new_coverage",
- "name": "New_coverage",
- "type": "PERCENT",
- },
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
- },
- "value": "1.0",
- },
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "bugs",
- "key": "bugs",
- "name": "Bugs",
- "type": "PERCENT",
- },
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
- },
- "value": "1.0",
- },
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "new_bugs",
- "key": "new_bugs",
- "name": "New_bugs",
- "type": "PERCENT",
+exports[`should render correctly for projects: default 1`] = `
+<div
+ className="overview-panel"
+ data-test="overview__measures-panel"
+>
+ <div
+ className="display-flex-space-between display-flex-start"
+ >
+ <h2
+ className="overview-panel-title"
+ >
+ overview.measures
+ </h2>
+ <withCurrentUserContext(withAppStateContext(ComponentReportActions))
+ branch={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ />
+ </div>
+ <BoxedTabs
+ onSelect={[Function]}
+ selected={0}
+ tabs={
+ Array [
+ Object {
+ "key": 0,
+ "label": <div
+ className="text-left overview-measures-tab"
+ >
+ <span
+ className="text-bold"
+ >
+ overview.new_code
+ </span>
+ </div>,
},
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
+ Object {
+ "key": 1,
+ "label": <div
+ className="text-left overview-measures-tab"
+ >
+ <span
+ className="text-bold"
+ style={
+ Object {
+ "position": "absolute",
+ "top": 16,
+ }
+ }
+ >
+ overview.overall_code
+ </span>
+ </div>,
},
- "value": "1.0",
- },
- ]
- }
-/>
+ ]
+ }
+ />
+ <div
+ className="overview-panel-content flex-1 bordered"
+ >
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ isNewCodeTab={true}
+ key="BUG"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="BUG"
+ />
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ isNewCodeTab={true}
+ key="VULNERABILITY"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="VULNERABILITY"
+ />
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ isNewCodeTab={true}
+ key="SECURITY_HOTSPOT"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="SECURITY_HOTSPOT"
+ />
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ isNewCodeTab={true}
+ key="CODE_SMELL"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="CODE_SMELL"
+ />
+ <div
+ className="display-flex-row overview-measures-row"
+ >
+ <div
+ className="overview-panel-huge-padded flex-1 bordered-right display-flex-center"
+ data-test="overview__measures-coverage"
+ >
+ <MeasurementLabel
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ centered={true}
+ 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 [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="COVERAGE"
+ useDiffMetric={true}
+ />
+ </div>
+ <div
+ className="overview-panel-huge-padded flex-1 display-flex-center"
+ >
+ <MeasurementLabel
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ centered={true}
+ 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 [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="DUPLICATION"
+ useDiffMetric={true}
+ />
+ </div>
+ </div>
+ </div>
+</div>
`;
-exports[`should render correctly for projects 2`] = `
-<Memo(MeasuresPanel)
- branch={
- Object {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- }
- }
- 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 [],
- }
- }
- location={
- Object {
- "action": "PUSH",
- "hash": "",
- "key": "key",
- "pathname": "/path",
- "query": Object {},
- "search": "",
- "state": Object {},
- }
- }
- measures={
- Array [
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "coverage",
- "key": "coverage",
- "name": "Coverage",
- "type": "PERCENT",
- },
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
- },
- "value": "1.0",
- },
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "new_coverage",
- "key": "new_coverage",
- "name": "New_coverage",
- "type": "PERCENT",
- },
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
- },
- "value": "1.0",
- },
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "bugs",
- "key": "bugs",
- "name": "Bugs",
- "type": "PERCENT",
- },
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
- },
- "value": "1.0",
- },
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "new_bugs",
- "key": "new_bugs",
- "name": "New_bugs",
- "type": "PERCENT",
+exports[`should render correctly for projects: overall 1`] = `
+<div
+ className="overview-panel"
+ data-test="overview__measures-panel"
+>
+ <div
+ className="display-flex-space-between display-flex-start"
+ >
+ <h2
+ className="overview-panel-title"
+ >
+ overview.measures
+ </h2>
+ <withCurrentUserContext(withAppStateContext(ComponentReportActions))
+ branch={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ />
+ </div>
+ <BoxedTabs
+ onSelect={[Function]}
+ selected={1}
+ tabs={
+ Array [
+ Object {
+ "key": 0,
+ "label": <div
+ className="text-left overview-measures-tab"
+ >
+ <span
+ className="text-bold"
+ >
+ overview.new_code
+ </span>
+ </div>,
},
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
+ Object {
+ "key": 1,
+ "label": <div
+ className="text-left overview-measures-tab"
+ >
+ <span
+ className="text-bold"
+ style={
+ Object {
+ "position": "absolute",
+ "top": 16,
+ }
+ }
+ >
+ overview.overall_code
+ </span>
+ </div>,
},
- "value": "1.0",
- },
- ]
- }
-/>
+ ]
+ }
+ />
+ <div
+ className="overview-panel-content flex-1 bordered"
+ >
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ isNewCodeTab={false}
+ key="BUG"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="BUG"
+ />
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ isNewCodeTab={false}
+ key="VULNERABILITY"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="VULNERABILITY"
+ />
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ isNewCodeTab={false}
+ key="SECURITY_HOTSPOT"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="SECURITY_HOTSPOT"
+ />
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ isNewCodeTab={false}
+ key="CODE_SMELL"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="CODE_SMELL"
+ />
+ <div
+ className="display-flex-row overview-measures-row"
+ >
+ <div
+ className="overview-panel-huge-padded flex-1 bordered-right display-flex-center"
+ data-test="overview__measures-coverage"
+ >
+ <MeasurementLabel
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ centered={false}
+ 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 [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="COVERAGE"
+ useDiffMetric={false}
+ />
+ <div
+ className="huge-spacer-left"
+ >
+ <DrilldownMeasureValue
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ metric="tests"
+ />
+ </div>
+ </div>
+ <div
+ className="overview-panel-huge-padded flex-1 display-flex-center"
+ >
+ <MeasurementLabel
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ centered={false}
+ 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 [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="DUPLICATION"
+ useDiffMetric={false}
+ />
+ <div
+ className="huge-spacer-left"
+ >
+ <DrilldownMeasureValue
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ metric="duplicated_blocks"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
`;
exports[`should render correctly if branch is misconfigured: hide settings 1`] = `
-<Memo(MeasuresPanel)
- branch={
- Object {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "own-reference",
- }
- }
- 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 [],
- }
- }
- location={
- Object {
- "action": "PUSH",
- "hash": "",
- "key": "key",
- "pathname": "/path",
- "query": Object {},
- "search": "",
- "state": Object {},
- }
- }
- measures={
- Array [
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "coverage",
- "key": "coverage",
- "name": "Coverage",
- "type": "PERCENT",
- },
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
- },
- "value": "1.0",
- },
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "bugs",
- "key": "bugs",
- "name": "Bugs",
- "type": "PERCENT",
+<div
+ className="overview-panel"
+ data-test="overview__measures-panel"
+>
+ <div
+ className="display-flex-space-between display-flex-start"
+ >
+ <h2
+ className="overview-panel-title"
+ >
+ overview.measures
+ </h2>
+ <withCurrentUserContext(withAppStateContext(ComponentReportActions))
+ branch={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": false,
+ "name": "own-reference",
+ }
+ }
+ 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 [],
+ }
+ }
+ />
+ </div>
+ <BoxedTabs
+ onSelect={[Function]}
+ selected={0}
+ tabs={
+ Array [
+ Object {
+ "key": 0,
+ "label": <div
+ className="text-left overview-measures-tab"
+ >
+ <span
+ className="text-bold"
+ >
+ overview.new_code
+ </span>
+ <LeakPeriodInfo
+ leakPeriod={
+ Object {
+ "date": undefined,
+ "index": 0,
+ "mode": "REFERENCE_BRANCH",
+ "parameter": "own-reference",
+ }
+ }
+ />
+ </div>,
},
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
+ Object {
+ "key": 1,
+ "label": <div
+ className="text-left overview-measures-tab"
+ >
+ <span
+ className="text-bold"
+ style={
+ Object {
+ "position": "absolute",
+ "top": 16,
+ }
+ }
+ >
+ overview.overall_code
+ </span>
+ </div>,
},
- "value": "1.0",
- },
- ]
- }
- period={
- Object {
- "date": undefined,
- "index": 0,
- "mode": "REFERENCE_BRANCH",
- "parameter": "own-reference",
+ ]
}
- }
-/>
+ />
+ <div
+ className="overview-panel-content flex-1 bordered"
+ >
+ <MeasuresPanelNoNewCode
+ branch={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": false,
+ "name": "own-reference",
+ }
+ }
+ 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 [],
+ }
+ }
+ period={
+ Object {
+ "date": undefined,
+ "index": 0,
+ "mode": "REFERENCE_BRANCH",
+ "parameter": "own-reference",
+ }
+ }
+ />
+ </div>
+</div>
`;
exports[`should render correctly if branch is misconfigured: show settings 1`] = `
-<Memo(MeasuresPanel)
- branch={
- Object {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "own-reference",
- }
- }
- 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 [],
- }
- }
- location={
- Object {
- "action": "PUSH",
- "hash": "",
- "key": "key",
- "pathname": "/path",
- "query": Object {},
- "search": "",
- "state": Object {},
- }
- }
- measures={
- Array [
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "coverage",
- "key": "coverage",
- "name": "Coverage",
- "type": "PERCENT",
- },
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
- },
- "value": "1.0",
- },
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "bugs",
- "key": "bugs",
- "name": "Bugs",
- "type": "PERCENT",
+<div
+ className="overview-panel"
+ data-test="overview__measures-panel"
+>
+ <div
+ className="display-flex-space-between display-flex-start"
+ >
+ <h2
+ className="overview-panel-title"
+ >
+ overview.measures
+ </h2>
+ <withCurrentUserContext(withAppStateContext(ComponentReportActions))
+ branch={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": false,
+ "name": "own-reference",
+ }
+ }
+ 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 [],
+ }
+ }
+ />
+ </div>
+ <BoxedTabs
+ onSelect={[Function]}
+ selected={0}
+ tabs={
+ Array [
+ Object {
+ "key": 0,
+ "label": <div
+ className="text-left overview-measures-tab"
+ >
+ <span
+ className="text-bold"
+ >
+ overview.new_code
+ </span>
+ <LeakPeriodInfo
+ leakPeriod={
+ Object {
+ "date": undefined,
+ "index": 0,
+ "mode": "REFERENCE_BRANCH",
+ "parameter": "own-reference",
+ }
+ }
+ />
+ </div>,
},
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
+ Object {
+ "key": 1,
+ "label": <div
+ className="text-left overview-measures-tab"
+ >
+ <span
+ className="text-bold"
+ style={
+ Object {
+ "position": "absolute",
+ "top": 16,
+ }
+ }
+ >
+ overview.overall_code
+ </span>
+ </div>,
},
- "value": "1.0",
- },
- ]
- }
- period={
- Object {
- "date": undefined,
- "index": 0,
- "mode": "REFERENCE_BRANCH",
- "parameter": "own-reference",
+ ]
}
- }
-/>
+ />
+ <div
+ className="overview-panel-content flex-1 bordered"
+ >
+ <MeasuresPanelNoNewCode
+ branch={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": false,
+ "name": "own-reference",
+ }
+ }
+ 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 [],
+ }
+ }
+ period={
+ Object {
+ "date": undefined,
+ "index": 0,
+ "mode": "REFERENCE_BRANCH",
+ "parameter": "own-reference",
+ }
+ }
+ />
+ </div>
+</div>
`;
exports[`should render correctly if the data is still loading 1`] = `
-<Memo(MeasuresPanel)
- branch={
- Object {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- }
- }
- 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 [],
- }
- }
- loading={true}
- location={
- Object {
- "action": "PUSH",
- "hash": "",
- "key": "key",
- "pathname": "/path",
- "query": Object {},
- "search": "",
- "state": Object {},
- }
- }
- measures={
- Array [
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "coverage",
- "key": "coverage",
- "name": "Coverage",
- "type": "PERCENT",
- },
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
- },
- "value": "1.0",
- },
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "new_coverage",
- "key": "new_coverage",
- "name": "New_coverage",
- "type": "PERCENT",
- },
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
- },
- "value": "1.0",
- },
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "bugs",
- "key": "bugs",
- "name": "Bugs",
- "type": "PERCENT",
- },
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
- },
- "value": "1.0",
- },
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "new_bugs",
- "key": "new_bugs",
- "name": "New_bugs",
- "type": "PERCENT",
- },
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
- },
- "value": "1.0",
- },
- ]
- }
-/>
+<div
+ className="overview-panel"
+ data-test="overview__measures-panel"
+>
+ <div
+ className="display-flex-space-between display-flex-start"
+ >
+ <h2
+ className="overview-panel-title"
+ >
+ overview.measures
+ </h2>
+ <withCurrentUserContext(withAppStateContext(ComponentReportActions))
+ branch={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ />
+ </div>
+ <div
+ className="overview-panel-content overview-panel-big-padded"
+ >
+ <DeferredSpinner
+ loading={true}
+ />
+ </div>
+</div>
`;
exports[`should render correctly if there is no coverage 1`] = `
-<Memo(MeasuresPanel)
- branch={
- Object {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- }
- }
- 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 [],
- }
- }
- location={
- Object {
- "action": "PUSH",
- "hash": "",
- "key": "key",
- "pathname": "/path",
- "query": Object {},
- "search": "",
- "state": Object {},
- }
- }
- measures={
- Array [
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "bugs",
- "key": "bugs",
- "name": "Bugs",
- "type": "PERCENT",
- },
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
- },
- "value": "1.0",
- },
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "new_bugs",
- "key": "new_bugs",
- "name": "New_bugs",
- "type": "PERCENT",
+<div
+ className="overview-panel"
+ data-test="overview__measures-panel"
+>
+ <div
+ className="display-flex-space-between display-flex-start"
+ >
+ <h2
+ className="overview-panel-title"
+ >
+ overview.measures
+ </h2>
+ <withCurrentUserContext(withAppStateContext(ComponentReportActions))
+ branch={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ />
+ </div>
+ <BoxedTabs
+ onSelect={[Function]}
+ selected={0}
+ tabs={
+ Array [
+ Object {
+ "key": 0,
+ "label": <div
+ className="text-left overview-measures-tab"
+ >
+ <span
+ className="text-bold"
+ >
+ overview.new_code
+ </span>
+ </div>,
},
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
+ Object {
+ "key": 1,
+ "label": <div
+ className="text-left overview-measures-tab"
+ >
+ <span
+ className="text-bold"
+ style={
+ Object {
+ "position": "absolute",
+ "top": 16,
+ }
+ }
+ >
+ overview.overall_code
+ </span>
+ </div>,
},
- "value": "1.0",
- },
- ]
- }
-/>
+ ]
+ }
+ />
+ <div
+ className="overview-panel-content flex-1 bordered"
+ >
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ isNewCodeTab={true}
+ key="BUG"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="BUG"
+ />
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ isNewCodeTab={true}
+ key="VULNERABILITY"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="VULNERABILITY"
+ />
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ isNewCodeTab={true}
+ key="SECURITY_HOTSPOT"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="SECURITY_HOTSPOT"
+ />
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ isNewCodeTab={true}
+ key="CODE_SMELL"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="CODE_SMELL"
+ />
+ <div
+ className="display-flex-row overview-measures-row"
+ >
+ <div
+ className="overview-panel-huge-padded flex-1 display-flex-center"
+ >
+ <MeasurementLabel
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ centered={true}
+ 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 [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="DUPLICATION"
+ useDiffMetric={true}
+ />
+ </div>
+ </div>
+ </div>
+</div>
`;
exports[`should render correctly if there is no new code measures 1`] = `
-<Memo(MeasuresPanel)
- branch={
- Object {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- }
- }
- 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 [],
- }
- }
- location={
- Object {
- "action": "PUSH",
- "hash": "",
- "key": "key",
- "pathname": "/path",
- "query": Object {},
- "search": "",
- "state": Object {},
- }
- }
- measures={
- Array [
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "coverage",
- "key": "coverage",
- "name": "Coverage",
- "type": "PERCENT",
- },
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
- },
- "value": "1.0",
- },
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "bugs",
- "key": "bugs",
- "name": "Bugs",
- "type": "PERCENT",
+<div
+ className="overview-panel"
+ data-test="overview__measures-panel"
+>
+ <div
+ className="display-flex-space-between display-flex-start"
+ >
+ <h2
+ className="overview-panel-title"
+ >
+ overview.measures
+ </h2>
+ <withCurrentUserContext(withAppStateContext(ComponentReportActions))
+ branch={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ />
+ </div>
+ <BoxedTabs
+ onSelect={[Function]}
+ selected={0}
+ tabs={
+ Array [
+ Object {
+ "key": 0,
+ "label": <div
+ className="text-left overview-measures-tab"
+ >
+ <span
+ className="text-bold"
+ >
+ overview.new_code
+ </span>
+ </div>,
},
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
+ Object {
+ "key": 1,
+ "label": <div
+ className="text-left overview-measures-tab"
+ >
+ <span
+ className="text-bold"
+ style={
+ Object {
+ "position": "absolute",
+ "top": 16,
+ }
+ }
+ >
+ overview.overall_code
+ </span>
+ </div>,
},
- "value": "1.0",
- },
- ]
- }
-/>
+ ]
+ }
+ />
+ <div
+ className="overview-panel-content flex-1 bordered"
+ >
+ <MeasuresPanelNoNewCode
+ branch={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ />
+ </div>
+</div>
`;
exports[`should render correctly when code scope is new code 1`] = `
-<Memo(MeasuresPanel)
- branch={
- Object {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- }
- }
- 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 [],
- }
- }
- location={
- Object {
- "action": "PUSH",
- "hash": "",
- "key": "key",
- "pathname": "/dashboard",
- "query": Object {
- "code_scope": "new",
- },
- "search": "",
- "state": Object {},
- }
- }
- measures={
- Array [
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "coverage",
- "key": "coverage",
- "name": "Coverage",
- "type": "PERCENT",
- },
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
- },
- "value": "1.0",
- },
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "new_coverage",
- "key": "new_coverage",
- "name": "New_coverage",
- "type": "PERCENT",
- },
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
- },
- "value": "1.0",
- },
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "bugs",
- "key": "bugs",
- "name": "Bugs",
- "type": "PERCENT",
- },
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
- },
- "value": "1.0",
- },
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "new_bugs",
- "key": "new_bugs",
- "name": "New_bugs",
- "type": "PERCENT",
+<div
+ className="overview-panel"
+ data-test="overview__measures-panel"
+>
+ <div
+ className="display-flex-space-between display-flex-start"
+ >
+ <h2
+ className="overview-panel-title"
+ >
+ overview.measures
+ </h2>
+ <withCurrentUserContext(withAppStateContext(ComponentReportActions))
+ branch={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ />
+ </div>
+ <BoxedTabs
+ onSelect={[Function]}
+ selected={0}
+ tabs={
+ Array [
+ Object {
+ "key": 0,
+ "label": <div
+ className="text-left overview-measures-tab"
+ >
+ <span
+ className="text-bold"
+ >
+ overview.new_code
+ </span>
+ </div>,
},
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
+ Object {
+ "key": 1,
+ "label": <div
+ className="text-left overview-measures-tab"
+ >
+ <span
+ className="text-bold"
+ style={
+ Object {
+ "position": "absolute",
+ "top": 16,
+ }
+ }
+ >
+ overview.overall_code
+ </span>
+ </div>,
},
- "value": "1.0",
- },
- ]
- }
-/>
+ ]
+ }
+ />
+ <div
+ className="overview-panel-content flex-1 bordered"
+ >
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ isNewCodeTab={true}
+ key="BUG"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="BUG"
+ />
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ isNewCodeTab={true}
+ key="VULNERABILITY"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="VULNERABILITY"
+ />
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ isNewCodeTab={true}
+ key="SECURITY_HOTSPOT"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="SECURITY_HOTSPOT"
+ />
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ isNewCodeTab={true}
+ key="CODE_SMELL"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="CODE_SMELL"
+ />
+ <div
+ className="display-flex-row overview-measures-row"
+ >
+ <div
+ className="overview-panel-huge-padded flex-1 bordered-right display-flex-center"
+ data-test="overview__measures-coverage"
+ >
+ <MeasurementLabel
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ centered={true}
+ 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 [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="COVERAGE"
+ useDiffMetric={true}
+ />
+ </div>
+ <div
+ className="overview-panel-huge-padded flex-1 display-flex-center"
+ >
+ <MeasurementLabel
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ centered={true}
+ 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 [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="DUPLICATION"
+ useDiffMetric={true}
+ />
+ </div>
+ </div>
+ </div>
+</div>
`;
exports[`should render correctly when code scope is overall code 1`] = `
-<Memo(MeasuresPanel)
- branch={
- Object {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- }
- }
- 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 [],
- }
- }
- location={
- Object {
- "action": "PUSH",
- "hash": "",
- "key": "key",
- "pathname": "/dashboard",
- "query": Object {
- "code_scope": "overall",
- },
- "search": "",
- "state": Object {},
- }
- }
- measures={
- Array [
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "coverage",
- "key": "coverage",
- "name": "Coverage",
- "type": "PERCENT",
- },
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
- },
- "value": "1.0",
- },
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "new_coverage",
- "key": "new_coverage",
- "name": "New_coverage",
- "type": "PERCENT",
- },
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
- },
- "value": "1.0",
- },
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "bugs",
- "key": "bugs",
- "name": "Bugs",
- "type": "PERCENT",
- },
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
- },
- "value": "1.0",
- },
- Object {
- "bestValue": true,
- "leak": "1",
- "metric": Object {
- "id": "new_bugs",
- "key": "new_bugs",
- "name": "New_bugs",
- "type": "PERCENT",
+<div
+ className="overview-panel"
+ data-test="overview__measures-panel"
+>
+ <div
+ className="display-flex-space-between display-flex-start"
+ >
+ <h2
+ className="overview-panel-title"
+ >
+ overview.measures
+ </h2>
+ <withCurrentUserContext(withAppStateContext(ComponentReportActions))
+ branch={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ />
+ </div>
+ <BoxedTabs
+ onSelect={[Function]}
+ selected={1}
+ tabs={
+ Array [
+ Object {
+ "key": 0,
+ "label": <div
+ className="text-left overview-measures-tab"
+ >
+ <span
+ className="text-bold"
+ >
+ overview.new_code
+ </span>
+ </div>,
},
- "period": Object {
- "bestValue": true,
- "index": 1,
- "value": "1.0",
+ Object {
+ "key": 1,
+ "label": <div
+ className="text-left overview-measures-tab"
+ >
+ <span
+ className="text-bold"
+ style={
+ Object {
+ "position": "absolute",
+ "top": 16,
+ }
+ }
+ >
+ overview.overall_code
+ </span>
+ </div>,
},
- "value": "1.0",
- },
- ]
- }
-/>
+ ]
+ }
+ />
+ <div
+ className="overview-panel-content flex-1 bordered"
+ >
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ isNewCodeTab={false}
+ key="BUG"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="BUG"
+ />
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ isNewCodeTab={false}
+ key="VULNERABILITY"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="VULNERABILITY"
+ />
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ isNewCodeTab={false}
+ key="SECURITY_HOTSPOT"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="SECURITY_HOTSPOT"
+ />
+ <MeasuresPanelIssueMeasureRow
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ isNewCodeTab={false}
+ key="CODE_SMELL"
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="CODE_SMELL"
+ />
+ <div
+ className="display-flex-row overview-measures-row"
+ >
+ <div
+ className="overview-panel-huge-padded flex-1 bordered-right display-flex-center"
+ data-test="overview__measures-coverage"
+ >
+ <MeasurementLabel
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ centered={false}
+ 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 [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="COVERAGE"
+ useDiffMetric={false}
+ />
+ <div
+ className="huge-spacer-left"
+ >
+ <DrilldownMeasureValue
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ metric="tests"
+ />
+ </div>
+ </div>
+ <div
+ className="overview-panel-huge-padded flex-1 display-flex-center"
+ >
+ <MeasurementLabel
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ centered={false}
+ 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 [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ type="DUPLICATION"
+ useDiffMetric={false}
+ />
+ <div
+ className="huge-spacer-left"
+ >
+ <DrilldownMeasureValue
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ }
+ }
+ 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 [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "New_coverage",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1",
+ "metric": Object {
+ "id": "new_bugs",
+ "key": "new_bugs",
+ "name": "New_bugs",
+ "type": "PERCENT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "1.0",
+ },
+ ]
+ }
+ metric="duplicated_blocks"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
`;
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanelNoNewCode-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanelNoNewCode-test.tsx.snap
index 098db30ab90..17a5cf7abcc 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanelNoNewCode-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanelNoNewCode-test.tsx.snap
@@ -35,8 +35,6 @@ exports[`should render "bad code setting" explanation: no link 1`] = `
values={
Object {
"learn_more_link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to="/documentation/user-guide/clean-as-you-code/"
>
learn_more
@@ -84,8 +82,6 @@ exports[`should render "bad code setting" explanation: with link 1`] = `
values={
Object {
"learn_more_link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to="/documentation/user-guide/clean-as-you-code/"
>
learn_more
@@ -165,8 +161,6 @@ exports[`should render the default message 6`] = `
values={
Object {
"learn_more_link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to="/documentation/user-guide/clean-as-you-code/"
>
learn_more
diff --git a/server/sonar-web/src/main/js/apps/overview/components/App.tsx b/server/sonar-web/src/main/js/apps/overview/components/App.tsx
index 91c19c67eb3..e31cee5531d 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/components/App.tsx
@@ -19,6 +19,7 @@
*/
import * as React from 'react';
import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
+import withComponentContext from '../../../app/components/componentContext/withComponentContext';
import Suggestions from '../../../components/embed-docs-modal/Suggestions';
import { lazyLoadComponent } from '../../../components/lazyLoadComponent';
import { isPullRequest } from '../../../helpers/branch-like';
@@ -92,4 +93,4 @@ export class App extends React.PureComponent<Props> {
}
}
-export default withAppStateContext(App);
+export default withComponentContext(withAppStateContext(App));
diff --git a/server/sonar-web/src/main/js/apps/overview/components/IssueLabel.tsx b/server/sonar-web/src/main/js/apps/overview/components/IssueLabel.tsx
index 4c16e59fa66..f1cad7fab13 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/IssueLabel.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/components/IssueLabel.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 HelpTooltip from '../../../components/controls/HelpTooltip';
import { getLeakValue } from '../../../components/measure/utils';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
diff --git a/server/sonar-web/src/main/js/apps/overview/components/QualityGateCondition.tsx b/server/sonar-web/src/main/js/apps/overview/components/QualityGateCondition.tsx
index 7a956c0249e..084d159d323 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/QualityGateCondition.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/components/QualityGateCondition.tsx
@@ -19,18 +19,14 @@
*/
import classNames from 'classnames';
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link, Path } from 'react-router-dom';
import IssueTypeIcon from '../../../components/icons/IssueTypeIcon';
import Measure from '../../../components/measure/Measure';
import DrilldownLink from '../../../components/shared/DrilldownLink';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { translate } from '../../../helpers/l10n';
import { formatMeasure, isDiffMetric, localizeMetric } from '../../../helpers/measures';
-import {
- getComponentIssuesUrl,
- getComponentSecurityHotspotsUrl,
- Location
-} from '../../../helpers/urls';
+import { getComponentIssuesUrl, getComponentSecurityHotspotsUrl } from '../../../helpers/urls';
import { BranchLike } from '../../../types/branch-like';
import { IssueType } from '../../../types/issues';
import { MetricKey } from '../../../types/metrics';
@@ -97,7 +93,7 @@ export default class QualityGateCondition extends React.PureComponent<Props> {
const metricKey = condition.measure.metric.key;
- const METRICS_TO_URL_MAPPING: Dict<() => Location> = {
+ const METRICS_TO_URL_MAPPING: Dict<() => Path> = {
[MetricKey.reliability_rating]: () =>
this.getUrlForBugsOrVulnerabilities(IssueType.Bug, false),
[MetricKey.new_reliability_rating]: () =>
diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/IssueLabel-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/IssueLabel-test.tsx.snap
index a172ab650a8..ffd998312f2 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/IssueLabel-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/IssueLabel-test.tsx.snap
@@ -4,18 +4,11 @@ exports[`should render correctly for bugs 1`] = `
<Fragment>
<Link
className="overview-measures-value text-light"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
+ "hash": "",
"pathname": "/project/issues",
- "query": Object {
- "id": "my-project",
- "pullRequest": "1001",
- "resolved": "false",
- "sinceLeakPeriod": "false",
- "types": "BUG",
- },
+ "search": "?pullRequest=1001&resolved=false&types=BUG&sinceLeakPeriod=false&id=my-project",
}
}
>
@@ -32,18 +25,11 @@ exports[`should render correctly for bugs 2`] = `
<Fragment>
<Link
className="overview-measures-value text-light"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
+ "hash": "",
"pathname": "/project/issues",
- "query": Object {
- "id": "my-project",
- "pullRequest": "1001",
- "resolved": "false",
- "sinceLeakPeriod": "true",
- "types": "BUG",
- },
+ "search": "?pullRequest=1001&resolved=false&types=BUG&sinceLeakPeriod=true&id=my-project",
}
}
>
@@ -60,18 +46,11 @@ exports[`should render correctly for code smells 1`] = `
<Fragment>
<Link
className="overview-measures-value text-light"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
+ "hash": "",
"pathname": "/project/issues",
- "query": Object {
- "id": "my-project",
- "pullRequest": "1001",
- "resolved": "false",
- "sinceLeakPeriod": "false",
- "types": "CODE_SMELL",
- },
+ "search": "?pullRequest=1001&resolved=false&types=CODE_SMELL&sinceLeakPeriod=false&id=my-project",
}
}
>
@@ -88,18 +67,11 @@ exports[`should render correctly for code smells 2`] = `
<Fragment>
<Link
className="overview-measures-value text-light"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
+ "hash": "",
"pathname": "/project/issues",
- "query": Object {
- "id": "my-project",
- "pullRequest": "1001",
- "resolved": "false",
- "sinceLeakPeriod": "true",
- "types": "CODE_SMELL",
- },
+ "search": "?pullRequest=1001&resolved=false&types=CODE_SMELL&sinceLeakPeriod=true&id=my-project",
}
}
>
@@ -116,20 +88,11 @@ exports[`should render correctly for hotspots 1`] = `
<Fragment>
<Link
className="overview-measures-value text-light"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
+ "hash": "",
"pathname": "/security_hotspots",
- "query": Object {
- "assignedToMe": undefined,
- "branch": undefined,
- "file": undefined,
- "hotspots": undefined,
- "id": "my-project",
- "pullRequest": "1001",
- "sinceLeakPeriod": "false",
- },
+ "search": "?id=my-project&pullRequest=1001&sinceLeakPeriod=false",
}
}
>
@@ -150,20 +113,11 @@ exports[`should render correctly for hotspots 2`] = `
<Fragment>
<Link
className="overview-measures-value text-light"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
+ "hash": "",
"pathname": "/security_hotspots",
- "query": Object {
- "assignedToMe": undefined,
- "branch": undefined,
- "file": undefined,
- "hotspots": undefined,
- "id": "my-project",
- "pullRequest": "1001",
- "sinceLeakPeriod": "true",
- },
+ "search": "?id=my-project&pullRequest=1001&sinceLeakPeriod=true",
}
}
>
@@ -184,18 +138,11 @@ exports[`should render correctly for vulnerabilities 1`] = `
<Fragment>
<Link
className="overview-measures-value text-light"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
+ "hash": "",
"pathname": "/project/issues",
- "query": Object {
- "id": "my-project",
- "pullRequest": "1001",
- "resolved": "false",
- "sinceLeakPeriod": "false",
- "types": "VULNERABILITY",
- },
+ "search": "?pullRequest=1001&resolved=false&types=VULNERABILITY&sinceLeakPeriod=false&id=my-project",
}
}
>
@@ -212,18 +159,11 @@ exports[`should render correctly for vulnerabilities 2`] = `
<Fragment>
<Link
className="overview-measures-value text-light"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
+ "hash": "",
"pathname": "/project/issues",
- "query": Object {
- "id": "my-project",
- "pullRequest": "1001",
- "resolved": "false",
- "sinceLeakPeriod": "true",
- "types": "VULNERABILITY",
- },
+ "search": "?pullRequest=1001&resolved=false&types=VULNERABILITY&sinceLeakPeriod=true&id=my-project",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/QualityGateCondition-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/QualityGateCondition-test.tsx.snap
index eef919feded..b4eb8987e86 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/QualityGateCondition-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/QualityGateCondition-test.tsx.snap
@@ -45,17 +45,11 @@ exports[`should render correclty 1`] = `
exports[`should render correclty 2`] = `
<Link
className="overview-quality-gate-condition overview-quality-gate-condition-error"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
+ "hash": "",
"pathname": "/project/issues",
- "query": Object {
- "id": "abcd-key",
- "resolved": "false",
- "severities": "BLOCKER,CRITICAL,MAJOR,MINOR",
- "types": "BUG",
- },
+ "search": "?resolved=false&types=BUG&severities=BLOCKER%2CCRITICAL%2CMAJOR%2CMINOR&id=abcd-key",
}
}
>
@@ -97,17 +91,11 @@ exports[`should render correclty 2`] = `
exports[`should render correclty 3`] = `
<Link
className="overview-quality-gate-condition overview-quality-gate-condition-error"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
+ "hash": "",
"pathname": "/project/issues",
- "query": Object {
- "id": "abcd-key",
- "resolved": "false",
- "severities": "BLOCKER,CRITICAL,MAJOR,MINOR",
- "types": "VULNERABILITY",
- },
+ "search": "?resolved=false&types=VULNERABILITY&severities=BLOCKER%2CCRITICAL%2CMAJOR%2CMINOR&id=abcd-key",
}
}
>
@@ -149,16 +137,11 @@ exports[`should render correclty 3`] = `
exports[`should render correclty 4`] = `
<Link
className="overview-quality-gate-condition overview-quality-gate-condition-error"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
+ "hash": "",
"pathname": "/project/issues",
- "query": Object {
- "id": "abcd-key",
- "resolved": "false",
- "types": "CODE_SMELL",
- },
+ "search": "?resolved=false&types=CODE_SMELL&id=abcd-key",
}
}
>
@@ -200,18 +183,11 @@ exports[`should render correclty 4`] = `
exports[`should render correclty 5`] = `
<Link
className="overview-quality-gate-condition overview-quality-gate-condition-error"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
+ "hash": "",
"pathname": "/project/issues",
- "query": Object {
- "id": "abcd-key",
- "resolved": "false",
- "severities": "BLOCKER,CRITICAL,MAJOR,MINOR",
- "sinceLeakPeriod": "true",
- "types": "BUG",
- },
+ "search": "?resolved=false&types=BUG&severities=BLOCKER%2CCRITICAL%2CMAJOR%2CMINOR&sinceLeakPeriod=true&id=abcd-key",
}
}
>
@@ -253,18 +229,11 @@ exports[`should render correclty 5`] = `
exports[`should render correclty 6`] = `
<Link
className="overview-quality-gate-condition overview-quality-gate-condition-error"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
+ "hash": "",
"pathname": "/project/issues",
- "query": Object {
- "id": "abcd-key",
- "resolved": "false",
- "severities": "BLOCKER,CRITICAL,MAJOR,MINOR",
- "sinceLeakPeriod": "true",
- "types": "VULNERABILITY",
- },
+ "search": "?resolved=false&types=VULNERABILITY&severities=BLOCKER%2CCRITICAL%2CMAJOR%2CMINOR&sinceLeakPeriod=true&id=abcd-key",
}
}
>
@@ -306,17 +275,11 @@ exports[`should render correclty 6`] = `
exports[`should render correclty 7`] = `
<Link
className="overview-quality-gate-condition overview-quality-gate-condition-error"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
+ "hash": "",
"pathname": "/project/issues",
- "query": Object {
- "id": "abcd-key",
- "resolved": "false",
- "sinceLeakPeriod": "true",
- "types": "CODE_SMELL",
- },
+ "search": "?resolved=false&types=CODE_SMELL&sinceLeakPeriod=true&id=abcd-key",
}
}
>
@@ -358,20 +321,11 @@ exports[`should render correclty 7`] = `
exports[`should render correclty 8`] = `
<Link
className="overview-quality-gate-condition overview-quality-gate-condition-error"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
+ "hash": "",
"pathname": "/security_hotspots",
- "query": Object {
- "assignedToMe": undefined,
- "branch": undefined,
- "file": undefined,
- "hotspots": undefined,
- "id": "abcd-key",
- "pullRequest": undefined,
- "sinceLeakPeriod": undefined,
- },
+ "search": "?id=abcd-key",
}
}
>
@@ -413,20 +367,11 @@ exports[`should render correclty 8`] = `
exports[`should render correclty 9`] = `
<Link
className="overview-quality-gate-condition overview-quality-gate-condition-error"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
+ "hash": "",
"pathname": "/security_hotspots",
- "query": Object {
- "assignedToMe": undefined,
- "branch": undefined,
- "file": undefined,
- "hotspots": undefined,
- "id": "abcd-key",
- "pullRequest": undefined,
- "sinceLeakPeriod": "true",
- },
+ "search": "?id=abcd-key&sinceLeakPeriod=true",
}
}
>
@@ -468,18 +413,11 @@ exports[`should render correclty 9`] = `
exports[`should work with branch 1`] = `
<Link
className="overview-quality-gate-condition overview-quality-gate-condition-error"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
+ "hash": "",
"pathname": "/project/issues",
- "query": Object {
- "branch": "branch-6.7",
- "id": "abcd-key",
- "resolved": "false",
- "sinceLeakPeriod": "true",
- "types": "CODE_SMELL",
- },
+ "search": "?resolved=false&branch=branch-6.7&types=CODE_SMELL&sinceLeakPeriod=true&id=abcd-key",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/LargeQualityGateBadge.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/LargeQualityGateBadge.tsx
index f82942cc59f..53460033875 100644
--- a/server/sonar-web/src/main/js/apps/overview/pullRequests/LargeQualityGateBadge.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/LargeQualityGateBadge.tsx
@@ -20,7 +20,7 @@
import classNames from 'classnames';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { colors } from '../../../app/theme';
import HelpTooltip from '../../../components/controls/HelpTooltip';
import HelpIcon from '../../../components/icons/HelpIcon';
diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/__snapshots__/LargeQualityGateBadge-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/__snapshots__/LargeQualityGateBadge-test.tsx.snap
index 517bbf540be..527e390ba6b 100644
--- a/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/__snapshots__/LargeQualityGateBadge-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/__snapshots__/LargeQualityGateBadge-test.tsx.snap
@@ -19,8 +19,6 @@ exports[`should render correctly for SQ 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/quality_gates/show/30",
@@ -67,8 +65,6 @@ exports[`should render correctly for SQ 2`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/quality_gates/show/30",
diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/routes.ts b/server/sonar-web/src/main/js/apps/overview/routes.tsx
index 9d8f2bd42e4..0b6e26d0770 100644
--- a/server/sonar-web/src/main/js/apps/projectBaseline/routes.ts
+++ b/server/sonar-web/src/main/js/apps/overview/routes.tsx
@@ -17,12 +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 { lazyLoadComponent } from '../../components/lazyLoadComponent';
+import React from 'react';
+import { Route } from 'react-router-dom';
+import App from './components/App';
-const routes = [
- {
- indexRoute: { component: lazyLoadComponent(() => import('./components/App')) }
- }
-];
+const routes = () => <Route path="dashboard" element={<App />} />;
export default routes;
diff --git a/server/sonar-web/src/main/js/apps/overview/utils.ts b/server/sonar-web/src/main/js/apps/overview/utils.ts
index d3eff6be5fe..d49a26488bc 100644
--- a/server/sonar-web/src/main/js/apps/overview/utils.ts
+++ b/server/sonar-web/src/main/js/apps/overview/utils.ts
@@ -17,7 +17,6 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { Query } from 'history';
import { memoize } from 'lodash';
import CoverageRating from '../../components/ui/CoverageRating';
import DuplicationsRating from '../../components/ui/DuplicationsRating';
@@ -183,10 +182,8 @@ export function getMeasurementLabelKeys(type: MeasurementType, useDiffMetric: bo
};
}
-export const parseQuery = memoize(
- (urlQuery: RawQuery): Query => {
- return {
- codeScope: parseAsString(urlQuery['code_scope'])
- };
- }
-);
+export const parseQuery = memoize((urlQuery: RawQuery): { codeScope: string } => {
+ return {
+ codeScope: parseAsString(urlQuery['code_scope'])
+ };
+});
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.tsx
index 40806703a81..da13a3a8d61 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.tsx
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.tsx
@@ -28,6 +28,7 @@ import ActionsDropdown, { ActionsDropdownItem } from '../../../components/contro
import { Router, withRouter } from '../../../components/hoc/withRouter';
import QualifierIcon from '../../../components/icons/QualifierIcon';
import { translate } from '../../../helpers/l10n';
+import { queryToSearch } from '../../../helpers/urls';
import { PermissionTemplate } from '../../../types/types';
import { PERMISSION_TEMPLATES_PATH } from '../utils';
import DeleteForm from './DeleteForm';
@@ -37,7 +38,7 @@ interface Props {
fromDetails?: boolean;
permissionTemplate: PermissionTemplate;
refresh: () => void;
- router: Pick<Router, 'replace'>;
+ router: Router;
topQualifiers: string[];
}
@@ -160,7 +161,8 @@ export class ActionsCell extends React.PureComponent<Props, State> {
{this.renderSetDefaultsControl()}
{!this.props.fromDetails && (
- <ActionsDropdownItem to={{ pathname: PERMISSION_TEMPLATES_PATH, query: { id: t.id } }}>
+ <ActionsDropdownItem
+ to={{ pathname: PERMISSION_TEMPLATES_PATH, search: queryToSearch({ id: t.id }) }}>
{translate('edit_permissions')}
</ActionsDropdownItem>
)}
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/App.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/App.tsx
index 7e16f3c2522..649ee420207 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/App.tsx
@@ -17,12 +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 { Location } from 'history';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import { getPermissionTemplates } from '../../../api/permissions';
import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import Suggestions from '../../../components/embed-docs-modal/Suggestions';
+import { Location, withRouter } from '../../../components/hoc/withRouter';
import { translate } from '../../../helpers/l10n';
import { AppState } from '../../../types/appstate';
import { Permission, PermissionTemplate } from '../../../types/types';
@@ -121,4 +121,4 @@ export class App extends React.PureComponent<Props, State> {
}
}
-export default withAppStateContext(App);
+export default withRouter(withAppStateContext(App));
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx
index 7f58cc9ad0c..7494e4260da 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx
@@ -28,7 +28,7 @@ import Form from './Form';
interface Props {
ready?: boolean;
refresh: () => Promise<void>;
- router: Pick<Router, 'push'>;
+ router: Router;
}
interface State {
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/NameCell.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/NameCell.tsx
index 2e471157090..02ead1cf2a2 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/components/NameCell.tsx
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/NameCell.tsx
@@ -18,7 +18,8 @@
* 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 { queryToSearch } from '../../../helpers/urls';
import { PermissionTemplate } from '../../../types/types';
import { PERMISSION_TEMPLATES_PATH } from '../utils';
import Defaults from './Defaults';
@@ -32,7 +33,7 @@ export default function NameCell({ template }: Props) {
return (
<td className="little-padded-left little-padded-right">
- <Link to={{ pathname, query: { id: template.id } }}>
+ <Link to={{ pathname, search: queryToSearch({ id: template.id }) }}>
<strong className="js-name">{template.name}</strong>
</Link>
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/TemplateHeader.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/TemplateHeader.tsx
index 55e25524583..4a4addbb9ae 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/components/TemplateHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/TemplateHeader.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 { PermissionTemplate } from '../../../types/types';
import { PERMISSION_TEMPLATES_PATH } from '../utils';
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/ActionsCell-test.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/ActionsCell-test.tsx
index 030d3e594a6..f079f5b0477 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/ActionsCell-test.tsx
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/ActionsCell-test.tsx
@@ -19,6 +19,7 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { mockRouter } from '../../../../helpers/testMocks';
import { ActionsCell } from '../ActionsCell';
const SAMPLE = {
@@ -34,7 +35,7 @@ function renderActionsCell(props?: Partial<ActionsCell['props']>) {
<ActionsCell
permissionTemplate={SAMPLE}
refresh={() => true}
- router={{ replace: jest.fn() }}
+ router={mockRouter()}
topQualifiers={['TRK', 'VW']}
{...props}
/>
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/NameCell-test.tsx.snap b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/NameCell-test.tsx.snap
index 66c34bcc15b..b1b66feeff5 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/NameCell-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/NameCell-test.tsx.snap
@@ -5,14 +5,10 @@ exports[`render correctly 1`] = `
className="little-padded-left little-padded-right"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/permission_templates",
- "query": Object {
- "id": "1",
- },
+ "search": "?id=1",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/routes.ts b/server/sonar-web/src/main/js/apps/permission-templates/routes.tsx
index 9d8f2bd42e4..5b57573de98 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/routes.ts
+++ b/server/sonar-web/src/main/js/apps/permission-templates/routes.tsx
@@ -17,12 +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 { lazyLoadComponent } from '../../components/lazyLoadComponent';
+import React from 'react';
+import { Route } from 'react-router-dom';
+import App from './components/App';
-const routes = [
- {
- indexRoute: { component: lazyLoadComponent(() => import('./components/App')) }
- }
-];
+const routes = () => <Route path="permission_templates" element={<App />} />;
export default routes;
diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/App.tsx b/server/sonar-web/src/main/js/apps/permissions/project/components/App.tsx
index aab9f301032..d3b830873e3 100644
--- a/server/sonar-web/src/main/js/apps/permissions/project/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/permissions/project/components/App.tsx
@@ -21,6 +21,7 @@ import { without } from 'lodash';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import * as api from '../../../../api/permissions';
+import withComponentContext from '../../../../app/components/componentContext/withComponentContext';
import VisibilitySelector from '../../../../components/common/VisibilitySelector';
import { translate } from '../../../../helpers/l10n';
import { Component, Paging, PermissionGroup, PermissionUser } from '../../../../types/types';
@@ -46,7 +47,7 @@ interface State {
usersPaging?: Paging;
}
-export default class App extends React.PureComponent<Props, State> {
+export class App extends React.PureComponent<Props, State> {
mounted = false;
constructor(props: Props) {
@@ -389,3 +390,5 @@ export default class App extends React.PureComponent<Props, State> {
);
}
}
+
+export default withComponentContext(App);
diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/App-test.tsx
index d1ab71cc4f9..de0d08d4d63 100644
--- a/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/App-test.tsx
+++ b/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/App-test.tsx
@@ -27,7 +27,7 @@ import {
} from '../../../../../api/permissions';
import { mockComponent } from '../../../../../helpers/mocks/component';
import { waitAndUpdate } from '../../../../../helpers/testUtils';
-import App from '../App';
+import { App } from '../App';
jest.mock('../../../../../api/permissions', () => ({
getPermissionsGroupsForComponent: jest.fn().mockResolvedValue({
diff --git a/server/sonar-web/src/main/js/apps/permissions/routes.ts b/server/sonar-web/src/main/js/apps/permissions/routes.ts
deleted file mode 100644
index 98674c85e9f..00000000000
--- a/server/sonar-web/src/main/js/apps/permissions/routes.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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 { lazyLoadComponent } from '../../components/lazyLoadComponent';
-
-export const globalPermissionsRoutes = [
- {
- indexRoute: { component: lazyLoadComponent(() => import('./global/components/App')) }
- }
-];
-
-export const projectPermissionsRoutes = [
- {
- indexRoute: { component: lazyLoadComponent(() => import('./project/components/App')) }
- }
-];
diff --git a/server/sonar-web/src/main/js/apps/permissions/routes.tsx b/server/sonar-web/src/main/js/apps/permissions/routes.tsx
new file mode 100644
index 00000000000..c075a9dc57a
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/permissions/routes.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 React from 'react';
+import { Route } from 'react-router-dom';
+import GlobalPermissionsApp from './global/components/App';
+import ProjectPermissionsApp from './project/components/App';
+
+export const globalPermissionsRoutes = () => (
+ <Route path="permissions" element={<GlobalPermissionsApp />} />
+);
+
+export const projectPermissionsRoutes = () => (
+ <Route path="project_roles" element={<ProjectPermissionsApp />} />
+);
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/DefinitionChangeEventInner.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/DefinitionChangeEventInner.tsx
index c238dcb58d7..7d3b7dc09db 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/DefinitionChangeEventInner.tsx
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/DefinitionChangeEventInner.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 { ButtonLink } from '../../../components/controls/buttons';
import BranchIcon from '../../../components/icons/BranchIcon';
import DropdownIcon from '../../../components/icons/DropdownIcon';
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/EventInner.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/EventInner.tsx
index 208f378d575..8b636c67b47 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/EventInner.tsx
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/EventInner.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { ComponentContext } from '../../../app/components/ComponentContext';
+import { ComponentContext } from '../../../app/components/componentContext/ComponentContext';
import Tooltip from '../../../components/controls/Tooltip';
import { translate } from '../../../helpers/l10n';
import { AnalysisEvent } from '../../../types/types';
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.tsx
index a41482e9796..399286545bc 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.tsx
@@ -17,9 +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 { Location } from 'history';
import * as React from 'react';
-import { InjectedRouter } from 'react-router';
+import { useSearchParams } from 'react-router-dom';
import { getAllMetrics } from '../../../api/metrics';
import {
changeEvent,
@@ -30,12 +29,14 @@ import {
ProjectActivityStatuses
} from '../../../api/projectActivity';
import { getAllTimeMachineData } from '../../../api/time-machine';
+import withComponentContext from '../../../app/components/componentContext/withComponentContext';
import {
DEFAULT_GRAPH,
getActivityGraph,
getHistoryMetrics,
isCustomGraph
} from '../../../components/activity-graph/utils';
+import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { parseDate } from '../../../helpers/dates';
import { serializeStringArray } from '../../../helpers/query';
@@ -57,7 +58,7 @@ interface Props {
branchLike?: BranchLike;
component: Component;
location: Location;
- router: Pick<InjectedRouter, 'push' | 'replace'>;
+ router: Router;
}
export interface State {
@@ -75,7 +76,7 @@ export const PROJECT_ACTIVITY_GRAPH = 'sonar_project_activity.graph';
const ACTIVITY_PAGE_SIZE_FIRST_BATCH = 100;
const ACTIVITY_PAGE_SIZE = 500;
-export default class ProjectActivityAppContainer extends React.PureComponent<Props, State> {
+export class ProjectActivityAppContainer extends React.PureComponent<Props, State> {
mounted = false;
constructor(props: Props) {
@@ -93,25 +94,8 @@ export default class ProjectActivityAppContainer extends React.PureComponent<Pro
componentDidMount() {
this.mounted = true;
- if (this.shouldRedirect()) {
- const { graph, customGraphs } = getActivityGraph(
- PROJECT_ACTIVITY_GRAPH,
- this.props.component.key
- );
- const newQuery = { ...this.state.query, graph };
- if (isCustomGraph(newQuery.graph)) {
- newQuery.customMetrics = customGraphs;
- }
- this.props.router.replace({
- pathname: this.props.location.pathname,
- query: {
- ...serializeUrlQuery(newQuery),
- ...getBranchLikeQuery(this.props.branchLike)
- }
- });
- } else {
- this.firstLoadData(this.state.query, this.props.component);
- }
+
+ this.firstLoadData(this.state.query, this.props.component);
}
componentDidUpdate(prevProps: Props) {
@@ -371,10 +355,6 @@ export default class ProjectActivityAppContainer extends React.PureComponent<Pro
};
render() {
- if (this.shouldRedirect()) {
- return null;
- }
-
return (
<ProjectActivityApp
addCustomEvent={this.addCustomEvent}
@@ -395,3 +375,42 @@ export default class ProjectActivityAppContainer extends React.PureComponent<Pro
);
}
}
+
+const isFiltered = (searchParams: URLSearchParams) => {
+ let filtered = false;
+ searchParams.forEach((value, key) => {
+ if (key !== 'id' && value !== '') {
+ filtered = true;
+ }
+ });
+ return filtered;
+};
+
+function RedirectWrapper(props: Props) {
+ const [searchParams, setSearchParams] = useSearchParams();
+
+ const filtered = isFiltered(searchParams);
+
+ const { graph, customGraphs } = getActivityGraph(PROJECT_ACTIVITY_GRAPH, props.component.key);
+ const emptyCustomGraph = isCustomGraph(graph) && customGraphs.length <= 0;
+
+ // if there is no filter, but there are saved preferences in the localStorage
+ // also don't redirect to custom if there is no metrics selected for it
+ const shouldRedirect = !filtered && graph != null && graph !== DEFAULT_GRAPH && !emptyCustomGraph;
+
+ React.useEffect(() => {
+ if (shouldRedirect) {
+ const query = parseQuery(searchParams);
+ const newQuery = { ...query, graph };
+ if (isCustomGraph(newQuery.graph)) {
+ searchParams.set('custom_metrics', customGraphs.join(','));
+ }
+ searchParams.set('graph', graph);
+ setSearchParams(searchParams, { replace: true });
+ }
+ }, [customGraphs, graph, searchParams, setSearchParams, shouldRedirect]);
+
+ return shouldRedirect ? null : <ProjectActivityAppContainer {...props} />;
+}
+
+export default withComponentContext(withRouter(RedirectWrapper));
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/RichQualityGateEventInner.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/RichQualityGateEventInner.tsx
index b489191f394..542540066fb 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/RichQualityGateEventInner.tsx
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/RichQualityGateEventInner.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 { ResetButtonLink } from '../../../components/controls/buttons';
import DropdownIcon from '../../../components/icons/DropdownIcon';
import Level from '../../../components/ui/Level';
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/EventInner-test.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/EventInner-test.tsx
index 3c733053808..e83d18c993f 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/EventInner-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/EventInner-test.tsx
@@ -23,7 +23,7 @@ import { mockAnalysisEvent } from '../../../../helpers/testMocks';
import { BranchLike } from '../../../../types/branch-like';
import EventInner, { EventInnerProps } from '../EventInner';
-jest.mock('../../../../app/components/ComponentContext', () => {
+jest.mock('../../../../app/components/componentContext/ComponentContext', () => {
const { mockBranch } = jest.requireActual('../../../../helpers/mocks/branch-like');
return {
ComponentContext: {
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityAppContainer-it.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityAppContainer-it.tsx
new file mode 100644
index 00000000000..c981572da89
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityAppContainer-it.tsx
@@ -0,0 +1,120 @@
+/*
+ * 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 React from 'react';
+import { ComponentContext } from '../../../../app/components/componentContext/ComponentContext';
+import { getActivityGraph } from '../../../../components/activity-graph/utils';
+import { mockComponent } from '../../../../helpers/mocks/component';
+import { renderComponentApp } from '../../../../helpers/testReactTestingUtils';
+import { ComponentQualifier } from '../../../../types/component';
+import { Component } from '../../../../types/types';
+import ProjectActivityAppContainer from '../ProjectActivityAppContainer';
+
+jest.mock('../../../../api/time-machine', () => {
+ const { mockPaging } = jest.requireActual('../../../../helpers/testMocks');
+ return {
+ getAllTimeMachineData: jest.fn().mockResolvedValue({
+ measures: [
+ {
+ metric: 'bugs',
+ history: [{ date: '2022-01-01', value: '10' }]
+ }
+ ],
+ paging: mockPaging({ total: 1 })
+ })
+ };
+});
+
+jest.mock('../../../../api/metrics', () => {
+ const { mockMetric } = jest.requireActual('../../../../helpers/testMocks');
+ return {
+ getAllMetrics: jest.fn().mockResolvedValue([mockMetric()])
+ };
+});
+
+jest.mock('../../../../api/projectActivity', () => {
+ const { mockAnalysis, mockPaging } = jest.requireActual('../../../../helpers/testMocks');
+ return {
+ ...jest.requireActual('../../../../api/projectActivity'),
+ createEvent: jest.fn(),
+ changeEvent: jest.fn(),
+ getProjectActivity: jest.fn().mockResolvedValue({
+ analyses: [mockAnalysis({ key: 'foo' })],
+ paging: mockPaging({ total: 1 })
+ })
+ };
+});
+
+jest.mock('../../../../components/activity-graph/utils', () => {
+ const actual = jest.requireActual('../../../../components/activity-graph/utils');
+ return {
+ ...actual,
+ getActivityGraph: jest.fn()
+ };
+});
+
+it('should render default graph', async () => {
+ (getActivityGraph as jest.Mock).mockImplementation(() => {
+ return {
+ graph: 'issues'
+ };
+ });
+
+ renderProjectActivityAppContainer();
+
+ expect(await screen.findByText('project_activity.graphs.issues')).toBeInTheDocument();
+});
+
+it('should reload custom graph from local storage', async () => {
+ (getActivityGraph as jest.Mock).mockImplementation(() => {
+ return {
+ graph: 'custom',
+ customGraphs: ['bugs', 'code_smells']
+ };
+ });
+
+ renderProjectActivityAppContainer();
+
+ expect(await screen.findByText('project_activity.graphs.custom')).toBeInTheDocument();
+});
+
+function renderProjectActivityAppContainer(
+ { component, navigateTo }: { component: Component; navigateTo?: string } = {
+ component: mockComponent({
+ breadcrumbs: [
+ { key: 'breadcrumb', name: 'breadcrumb', qualifier: ComponentQualifier.Project }
+ ]
+ })
+ }
+) {
+ return renderComponentApp(
+ 'project/activity',
+ <ComponentContext.Provider
+ value={{
+ branchLikes: [],
+ onBranchesChange: jest.fn(),
+ onComponentChange: jest.fn(),
+ component
+ }}>
+ <ProjectActivityAppContainer />
+ </ComponentContext.Provider>,
+ { navigateTo }
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityAppContainer-test.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityAppContainer-test.tsx
index 7ebc710a5cb..bb74b018308 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityAppContainer-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityAppContainer-test.tsx
@@ -30,7 +30,7 @@ import {
import { waitAndUpdate } from '../../../../helpers/testUtils';
import { ComponentQualifier } from '../../../../types/component';
import { MetricKey } from '../../../../types/metrics';
-import ProjectActivityAppContainer from '../ProjectActivityAppContainer';
+import { ProjectActivityAppContainer } from '../ProjectActivityAppContainer';
jest.mock('../../../../helpers/dates', () => ({
parseDate: jest.fn(date => `PARSED:${date}`)
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/DefinitionChangeEventInner-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/DefinitionChangeEventInner-test.tsx.snap
index d76718a77e4..d2384be643a 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/DefinitionChangeEventInner-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/DefinitionChangeEventInner-test.tsx.snap
@@ -71,16 +71,11 @@ exports[`should render 2`] = `
</span>,
"project": <Link
onClick={[Function]}
- onlyActiveOnIndex={false}
- style={Object {}}
title="Foo"
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": "master",
- "id": "foo",
- },
+ "search": "?id=foo&branch=master",
}
}
>
@@ -114,16 +109,11 @@ exports[`should render 2`] = `
</span>,
"project": <Link
onClick={[Function]}
- onlyActiveOnIndex={false}
- style={Object {}}
title="Bar"
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": "master",
- "id": "bar",
- },
+ "search": "?id=bar&branch=master",
}
}
>
@@ -185,16 +175,11 @@ exports[`should render for a branch 1`] = `
</span>,
"project": <Link
onClick={[Function]}
- onlyActiveOnIndex={false}
- style={Object {}}
title="Foo"
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": "feature-x",
- "id": "foo",
- },
+ "search": "?id=foo&branch=feature-x",
}
}
>
@@ -234,16 +219,11 @@ exports[`should render for a branch 1`] = `
</span>,
"project": <Link
onClick={[Function]}
- onlyActiveOnIndex={false}
- style={Object {}}
title="Bar"
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": "feature-y",
- "id": "bar",
- },
+ "search": "?id=bar&branch=feature-y",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/RichQualityGateEventInner-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/RichQualityGateEventInner-test.tsx.snap
index 6e77a28f596..c263d815f9c 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/RichQualityGateEventInner-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/RichQualityGateEventInner-test.tsx.snap
@@ -87,16 +87,11 @@ exports[`should render 2`] = `
>
<Link
onClick={[Function]}
- onlyActiveOnIndex={false}
- style={Object {}}
title="Foo"
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": "master",
- "id": "foo",
- },
+ "search": "?id=foo&branch=master",
}
}
>
@@ -123,16 +118,11 @@ exports[`should render 2`] = `
>
<Link
onClick={[Function]}
- onlyActiveOnIndex={false}
- style={Object {}}
title="Bar"
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": "master",
- "id": "bar",
- },
+ "search": "?id=bar&branch=master",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/routes.ts b/server/sonar-web/src/main/js/apps/projectActivity/routes.ts
deleted file mode 100644
index 9ab389a7471..00000000000
--- a/server/sonar-web/src/main/js/apps/projectActivity/routes.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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 { lazyLoadComponent } from '../../components/lazyLoadComponent';
-
-const routes = [
- {
- indexRoute: {
- component: lazyLoadComponent(() => import('./components/ProjectActivityAppContainer'))
- }
- }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/routes.tsx b/server/sonar-web/src/main/js/apps/projectActivity/routes.tsx
new file mode 100644
index 00000000000..f94de3ff633
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projectActivity/routes.tsx
@@ -0,0 +1,26 @@
+/*
+ * 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 React from 'react';
+import { Route } from 'react-router-dom';
+import ProjectActivityAppContainer from './components/ProjectActivityAppContainer';
+
+const routes = () => <Route path="project/activity" element={<ProjectActivityAppContainer />} />;
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx
index 5e28f80a41d..a9021768e71 100644
--- a/server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx
@@ -22,6 +22,7 @@ import { debounce } from 'lodash';
import * as React from 'react';
import { getNewCodePeriod, resetNewCodePeriod, setNewCodePeriod } from '../../../api/newCodePeriod';
import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
+import withComponentContext from '../../../app/components/componentContext/withComponentContext';
import Suggestions from '../../../components/embed-docs-modal/Suggestions';
import AlertSuccessIcon from '../../../components/icons/AlertSuccessIcon';
import DeferredSpinner from '../../../components/ui/DeferredSpinner';
@@ -320,4 +321,4 @@ export class App extends React.PureComponent<Props, State> {
}
}
-export default withAppStateContext(App);
+export default withComponentContext(withAppStateContext(App));
diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/AppHeader.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/AppHeader.tsx
index ff232c6c7f0..6adb1055806 100644
--- a/server/sonar-web/src/main/js/apps/projectBaseline/components/AppHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/AppHeader.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 { translate } from '../../../helpers/l10n';
export interface AppHeaderProps {
diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/__snapshots__/AppHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/__snapshots__/AppHeader-test.tsx.snap
index e698388bcc1..f411099adfa 100644
--- a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/__snapshots__/AppHeader-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/__snapshots__/AppHeader-test.tsx.snap
@@ -18,8 +18,6 @@ exports[`should render correctly: can admin 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to="/documentation/project-administration/new-code-period/"
>
project_baseline.page.description.link
@@ -34,8 +32,6 @@ exports[`should render correctly: can admin 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to="/admin/settings?category=new_code_period"
>
project_baseline.page.description2.link
@@ -65,8 +61,6 @@ exports[`should render correctly: cannot admin 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to="/documentation/project-administration/new-code-period/"
>
project_baseline.page.description.link
diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/routes.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/routes.tsx
new file mode 100644
index 00000000000..789fbbdee32
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projectBaseline/routes.tsx
@@ -0,0 +1,26 @@
+/*
+ * 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 React from 'react';
+import { Route } from 'react-router-dom';
+import App from './components/App';
+
+const routes = () => <Route path="baseline" element={<App />} />;
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/LifetimeInformationRenderer.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/LifetimeInformationRenderer.tsx
index 189bc387adb..42c6826000e 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/components/LifetimeInformationRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/projectBranches/components/LifetimeInformationRenderer.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 DeferredSpinner from '../../../components/ui/DeferredSpinner';
import { translate } from '../../../helpers/l10n';
import { formatMeasure } from '../../../helpers/measures';
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/ProjectBranchesApp.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/ProjectBranchesApp.tsx
index fbb5c222e65..6fdab4764ea 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/components/ProjectBranchesApp.tsx
+++ b/server/sonar-web/src/main/js/apps/projectBranches/components/ProjectBranchesApp.tsx
@@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import withComponentContext from '../../../app/components/componentContext/withComponentContext';
import { translate } from '../../../helpers/l10n';
import { BranchLike } from '../../../types/branch-like';
import { Component } from '../../../types/types';
@@ -49,4 +50,4 @@ export function ProjectBranchesApp(props: ProjectBranchesAppProps) {
);
}
-export default React.memo(ProjectBranchesApp);
+export default withComponentContext(React.memo(ProjectBranchesApp));
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/LifetimeInformationRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/LifetimeInformationRenderer-test.tsx.snap
index d28884d996f..810c4081524 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/LifetimeInformationRenderer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/LifetimeInformationRenderer-test.tsx.snap
@@ -48,8 +48,6 @@ exports[`should render correctly when user is admin 1`] = `
values={
Object {
"settings": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to="/admin/settings"
>
settings.page
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/routes.ts b/server/sonar-web/src/main/js/apps/projectBranches/routes.ts
deleted file mode 100644
index 58d607edaa5..00000000000
--- a/server/sonar-web/src/main/js/apps/projectBranches/routes.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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 { lazyLoadComponent } from '../../components/lazyLoadComponent';
-
-const routes = [
- {
- indexRoute: { component: lazyLoadComponent(() => import('./components/ProjectBranchesApp')) }
- }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/routes.ts b/server/sonar-web/src/main/js/apps/projectBranches/routes.tsx
index 3fa150dbc24..00e3f2825cc 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/routes.ts
+++ b/server/sonar-web/src/main/js/apps/projectBranches/routes.tsx
@@ -17,12 +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 { lazyLoadComponent } from '../../components/lazyLoadComponent';
+import React from 'react';
+import { Route } from 'react-router-dom';
+import ProjectBranchesApp from './components/ProjectBranchesApp';
-const routes = [
- {
- indexRoute: { component: lazyLoadComponent(() => import('./components/BackgroundTasksApp')) }
- }
-];
+const routes = () => <Route path="branches" element={<ProjectBranchesApp />} />;
export default routes;
diff --git a/server/sonar-web/src/main/js/apps/projectDeletion/App.tsx b/server/sonar-web/src/main/js/apps/projectDeletion/App.tsx
index aa523500aa3..65f1954f956 100644
--- a/server/sonar-web/src/main/js/apps/projectDeletion/App.tsx
+++ b/server/sonar-web/src/main/js/apps/projectDeletion/App.tsx
@@ -19,21 +19,23 @@
*/
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
+import { ComponentContext } from '../../app/components/componentContext/ComponentContext';
import { translate } from '../../helpers/l10n';
-import { Component } from '../../types/types';
import Form from './Form';
import Header from './Header';
-interface Props {
- component: Pick<Component, 'key' | 'name' | 'qualifier'>;
-}
+export default function App() {
+ const { component } = React.useContext(ComponentContext);
+
+ if (component === undefined) {
+ return null;
+ }
-export default function App(props: Props) {
return (
<div className="page page-limited">
<Helmet defer={false} title={translate('deletion.page')} />
- <Header component={props.component} />
- <Form component={props.component} />
+ <Header component={component} />
+ <Form component={component} />
</div>
);
}
diff --git a/server/sonar-web/src/main/js/apps/projectDeletion/Form.tsx b/server/sonar-web/src/main/js/apps/projectDeletion/Form.tsx
index 6f2de530e76..c087dddab94 100644
--- a/server/sonar-web/src/main/js/apps/projectDeletion/Form.tsx
+++ b/server/sonar-web/src/main/js/apps/projectDeletion/Form.tsx
@@ -30,7 +30,7 @@ import { Component } from '../../types/types';
interface Props {
component: Pick<Component, 'key' | 'name' | 'qualifier'>;
- router: Pick<Router, 'replace'>;
+ router: Router;
}
export class Form extends React.PureComponent<Props> {
diff --git a/server/sonar-web/src/main/js/apps/projectDeletion/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/projectDeletion/__tests__/App-test.tsx
index 351b3a06ff8..aad3a0b30d7 100644
--- a/server/sonar-web/src/main/js/apps/projectDeletion/__tests__/App-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectDeletion/__tests__/App-test.tsx
@@ -17,12 +17,32 @@
* 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/react';
import * as React from 'react';
+import { ComponentContext } from '../../../app/components/componentContext/ComponentContext';
+import { mockComponent } from '../../../helpers/mocks/component';
+import { renderComponentApp } from '../../../helpers/testReactTestingUtils';
+import { ComponentContextShape } from '../../../types/component';
+import { Component } from '../../../types/types';
import App from '../App';
-it('should render', () => {
- expect(
- shallow(<App component={{ key: 'foo', name: 'Foo', qualifier: 'TRK' }} />)
- ).toMatchSnapshot();
+it('should render with component', () => {
+ renderProjectDeletionApp(mockComponent({ key: 'foo', name: 'Foo', qualifier: 'TRK' }));
+
+ expect(screen.getByText('deletion.page')).toBeInTheDocument();
});
+
+it('should render with no component', () => {
+ renderProjectDeletionApp();
+
+ expect(screen.queryByText('deletion.page')).not.toBeInTheDocument();
+});
+
+function renderProjectDeletionApp(component?: Component) {
+ renderComponentApp(
+ 'project-delete',
+ <ComponentContext.Provider value={{ component } as ComponentContextShape}>
+ <App />
+ </ComponentContext.Provider>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/projectDeletion/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectDeletion/__tests__/__snapshots__/App-test.tsx.snap
deleted file mode 100644
index b42883c32a7..00000000000
--- a/server/sonar-web/src/main/js/apps/projectDeletion/__tests__/__snapshots__/App-test.tsx.snap
+++ /dev/null
@@ -1,32 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render 1`] = `
-<div
- className="page page-limited"
->
- <Helmet
- defer={false}
- encodeSpecialCharacters={true}
- prioritizeSeoTags={false}
- title="deletion.page"
- />
- <Header
- component={
- Object {
- "key": "foo",
- "name": "Foo",
- "qualifier": "TRK",
- }
- }
- />
- <withRouter(Form)
- component={
- Object {
- "key": "foo",
- "name": "Foo",
- "qualifier": "TRK",
- }
- }
- />
-</div>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projectDump/ProjectDumpApp.tsx b/server/sonar-web/src/main/js/apps/projectDump/ProjectDumpApp.tsx
index 43c17531ec2..6342f16940a 100644
--- a/server/sonar-web/src/main/js/apps/projectDump/ProjectDumpApp.tsx
+++ b/server/sonar-web/src/main/js/apps/projectDump/ProjectDumpApp.tsx
@@ -21,6 +21,7 @@ import * as React from 'react';
import { getActivity } from '../../api/ce';
import { getStatus } from '../../api/project-dump';
import withAppStateContext from '../../app/components/app-state/withAppStateContext';
+import withComponentContext from '../../app/components/componentContext/withComponentContext';
import { throwGlobalError } from '../../helpers/error';
import { translate } from '../../helpers/l10n';
import { AppState } from '../../types/appstate';
@@ -199,4 +200,4 @@ export class ProjectDumpApp extends React.Component<Props, State> {
}
}
-export default withAppStateContext(ProjectDumpApp);
+export default withComponentContext(withAppStateContext(ProjectDumpApp));
diff --git a/server/sonar-web/src/main/js/apps/projectDump/components/Import.tsx b/server/sonar-web/src/main/js/apps/projectDump/components/Import.tsx
index 000906deebe..39a28c846ef 100644
--- a/server/sonar-web/src/main/js/apps/projectDump/components/Import.tsx
+++ b/server/sonar-web/src/main/js/apps/projectDump/components/Import.tsx
@@ -19,7 +19,7 @@
*/
import classNames from 'classnames';
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { doImport } from '../../../api/project-dump';
import { Button } from '../../../components/controls/buttons';
import DateFromNow from '../../../components/intl/DateFromNow';
diff --git a/server/sonar-web/src/main/js/apps/projectDump/components/__tests__/__snapshots__/Import-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectDump/components/__tests__/__snapshots__/Import-test.tsx.snap
index 6073edbe109..0c62310f98d 100644
--- a/server/sonar-web/src/main/js/apps/projectDump/components/__tests__/__snapshots__/Import-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectDump/components/__tests__/__snapshots__/Import-test.tsx.snap
@@ -43,16 +43,11 @@ exports[`should render correctly: failed 1`] = `
project_dump.failed_import
<Link
className="spacer-left"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
+ "hash": "",
"pathname": "/project/background_tasks",
- "query": Object {
- "id": "key",
- "status": "FAILED",
- "taskType": "PROJECT_IMPORT",
- },
+ "search": "?id=key&status=FAILED&taskType=PROJECT_IMPORT",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/projectDump/routes.ts b/server/sonar-web/src/main/js/apps/projectDump/routes.tsx
index db889495b12..331c7789909 100644
--- a/server/sonar-web/src/main/js/apps/projectDump/routes.ts
+++ b/server/sonar-web/src/main/js/apps/projectDump/routes.tsx
@@ -17,12 +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 React from 'react';
+import { Route } from 'react-router-dom';
import ProjectDumpApp from './ProjectDumpApp';
-const routes = [
- {
- indexRoute: { component: ProjectDumpApp }
- }
-];
+const routes = () => <Route path="import_export" element={<ProjectDumpApp />} />;
export default routes;
diff --git a/server/sonar-web/src/main/js/apps/projectKey/Key.tsx b/server/sonar-web/src/main/js/apps/projectKey/Key.tsx
index 39f2a8438b4..93a833b6e30 100644
--- a/server/sonar-web/src/main/js/apps/projectKey/Key.tsx
+++ b/server/sonar-web/src/main/js/apps/projectKey/Key.tsx
@@ -19,18 +19,20 @@
*/
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
-import { withRouter, WithRouterProps } from 'react-router';
import { changeKey } from '../../api/components';
+import withComponentContext from '../../app/components/componentContext/withComponentContext';
import RecentHistory from '../../app/components/RecentHistory';
+import { Router, withRouter } from '../../components/hoc/withRouter';
import { translate } from '../../helpers/l10n';
import { Component } from '../../types/types';
import UpdateForm from './UpdateForm';
interface Props {
- component: Pick<Component, 'key' | 'name'>;
+ component: Component;
+ router: Router;
}
-export class Key extends React.PureComponent<Props & WithRouterProps> {
+export class Key extends React.PureComponent<Props> {
handleChangeKey = (newKey: string) => {
return changeKey({ from: this.props.component.key, to: newKey }).then(() => {
RecentHistory.remove(this.props.component.key);
@@ -53,4 +55,4 @@ export class Key extends React.PureComponent<Props & WithRouterProps> {
}
}
-export default withRouter(Key);
+export default withComponentContext(withRouter(Key));
diff --git a/server/sonar-web/src/main/js/apps/projectKey/__tests__/Key-test.tsx b/server/sonar-web/src/main/js/apps/projectKey/__tests__/Key-test.tsx
index 75247121c5c..bc9ba0fa64f 100644
--- a/server/sonar-web/src/main/js/apps/projectKey/__tests__/Key-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectKey/__tests__/Key-test.tsx
@@ -19,8 +19,8 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { WithRouterProps } from 'react-router';
import { changeKey } from '../../../api/components';
+import { mockComponent } from '../../../helpers/mocks/component';
import { Key } from '../Key';
jest.mock('../../../api/components', () => ({
@@ -28,8 +28,10 @@ jest.mock('../../../api/components', () => ({
}));
it('should render and change key', async () => {
- const withRouterProps = { router: { replace: jest.fn() } as any } as WithRouterProps;
- const wrapper = shallow(<Key component={{ key: 'foo', name: 'Foo' }} {...withRouterProps} />);
+ const withRouterProps = { router: { replace: jest.fn() } as any };
+ const wrapper = shallow(
+ <Key component={mockComponent({ key: 'foo', name: 'Foo' })} {...withRouterProps} />
+ );
expect(wrapper).toMatchSnapshot();
wrapper.find('UpdateForm').prop<Function>('onKeyChange')('bar');
diff --git a/server/sonar-web/src/main/js/apps/projectKey/__tests__/__snapshots__/Key-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectKey/__tests__/__snapshots__/Key-test.tsx.snap
index 24be29a2347..4d009e92500 100644
--- a/server/sonar-web/src/main/js/apps/projectKey/__tests__/__snapshots__/Key-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectKey/__tests__/__snapshots__/Key-test.tsx.snap
@@ -28,8 +28,24 @@ exports[`should render and change key 1`] = `
<UpdateForm
component={
Object {
+ "breadcrumbs": Array [],
"key": "foo",
"name": "Foo",
+ "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 [],
}
}
onKeyChange={[Function]}
diff --git a/server/sonar-web/src/main/js/apps/projectLinks/App.tsx b/server/sonar-web/src/main/js/apps/projectLinks/App.tsx
index b28a709ab84..c70f6a1648e 100644
--- a/server/sonar-web/src/main/js/apps/projectLinks/App.tsx
+++ b/server/sonar-web/src/main/js/apps/projectLinks/App.tsx
@@ -20,6 +20,7 @@
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import { createLink, deleteLink, getProjectLinks } from '../../api/projectLinks';
+import withComponentContext from '../../app/components/componentContext/withComponentContext';
import DeferredSpinner from '../../components/ui/DeferredSpinner';
import { translate } from '../../helpers/l10n';
import { Component, ProjectLink } from '../../types/types';
@@ -27,7 +28,7 @@ import Header from './Header';
import Table from './Table';
interface Props {
- component: Pick<Component, 'key'>;
+ component: Component;
}
interface State {
@@ -35,7 +36,7 @@ interface State {
loading: boolean;
}
-export default class App extends React.PureComponent<Props, State> {
+export class App extends React.PureComponent<Props, State> {
mounted = false;
state: State = { loading: true };
@@ -102,3 +103,5 @@ export default class App extends React.PureComponent<Props, State> {
);
}
}
+
+export default withComponentContext(App);
diff --git a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/App-test.tsx
index 04c7b55911f..6eabdd1a432 100644
--- a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/App-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/App-test.tsx
@@ -20,8 +20,9 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { createLink, deleteLink, getProjectLinks } from '../../../api/projectLinks';
+import { mockComponent } from '../../../helpers/mocks/component';
import { waitAndUpdate } from '../../../helpers/testUtils';
-import App from '../App';
+import { App } from '../App';
// import { getProjectLinks, createLink, deleteLink } from '../../api/projectLinks';
jest.mock('../../../api/projectLinks', () => ({
@@ -36,14 +37,14 @@ jest.mock('../../../api/projectLinks', () => ({
}));
it('should fetch links and render', async () => {
- const wrapper = shallow(<App component={{ key: 'comp' }} />);
+ const wrapper = shallow(<App component={mockComponent({ key: 'comp' })} />);
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
expect(getProjectLinks).toBeCalledWith('comp');
});
it('should fetch links when component changes', async () => {
- const wrapper = shallow(<App component={{ key: 'comp' }} />);
+ const wrapper = shallow(<App component={mockComponent({ key: 'comp' })} />);
await waitAndUpdate(wrapper);
expect(getProjectLinks).lastCalledWith('comp');
@@ -52,7 +53,7 @@ it('should fetch links when component changes', async () => {
});
it('should create link', async () => {
- const wrapper = shallow(<App component={{ key: 'comp' }} />);
+ const wrapper = shallow(<App component={mockComponent({ key: 'comp' })} />);
await waitAndUpdate(wrapper);
wrapper.find('Header').prop<Function>('onCreate')('bar', 'http://example.com/bar');
@@ -66,7 +67,7 @@ it('should create link', async () => {
});
it('should delete link', async () => {
- const wrapper = shallow(<App component={{ key: 'comp' }} />);
+ const wrapper = shallow(<App component={mockComponent({ key: 'comp' })} />);
await waitAndUpdate(wrapper);
wrapper.find('Table').prop<Function>('onDelete')('foo');
diff --git a/server/sonar-web/src/main/js/apps/projectQualityGate/ProjectQualityGateApp.tsx b/server/sonar-web/src/main/js/apps/projectQualityGate/ProjectQualityGateApp.tsx
index 8f5df7ab9a1..64ba93947c4 100644
--- a/server/sonar-web/src/main/js/apps/projectQualityGate/ProjectQualityGateApp.tsx
+++ b/server/sonar-web/src/main/js/apps/projectQualityGate/ProjectQualityGateApp.tsx
@@ -26,6 +26,7 @@ import {
getGateForProject,
searchProjects
} from '../../api/quality-gates';
+import withComponentContext from '../../app/components/componentContext/withComponentContext';
import handleRequiredAuthorization from '../../app/utils/handleRequiredAuthorization';
import { addGlobalSuccessMessage } from '../../helpers/globalMessages';
import { translate } from '../../helpers/l10n';
@@ -46,7 +47,7 @@ interface State {
submitting: boolean;
}
-export default class ProjectQualityGateApp extends React.PureComponent<Props, State> {
+export class ProjectQualityGateApp extends React.PureComponent<Props, State> {
mounted = false;
state: State = {
loading: true,
@@ -201,3 +202,5 @@ export default class ProjectQualityGateApp extends React.PureComponent<Props, St
);
}
}
+
+export default withComponentContext(ProjectQualityGateApp);
diff --git a/server/sonar-web/src/main/js/apps/projectQualityGate/ProjectQualityGateAppRenderer.tsx b/server/sonar-web/src/main/js/apps/projectQualityGate/ProjectQualityGateAppRenderer.tsx
index 9c211b81b73..3cae6090497 100644
--- a/server/sonar-web/src/main/js/apps/projectQualityGate/ProjectQualityGateAppRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/projectQualityGate/ProjectQualityGateAppRenderer.tsx
@@ -20,7 +20,7 @@
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import { FormattedMessage } from 'react-intl';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { components, OptionProps } from 'react-select';
import A11ySkipTarget from '../../components/a11y/A11ySkipTarget';
import DisableableSelectOption from '../../components/common/DisableableSelectOption';
diff --git a/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/ProjectQualityGateApp-test.tsx b/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/ProjectQualityGateApp-test.tsx
index a45b3468083..525826ad426 100644
--- a/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/ProjectQualityGateApp-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/ProjectQualityGateApp-test.tsx
@@ -31,7 +31,7 @@ import { mockComponent } from '../../../helpers/mocks/component';
import { mockQualityGate } from '../../../helpers/mocks/quality-gates';
import { waitAndUpdate } from '../../../helpers/testUtils';
import { USE_SYSTEM_DEFAULT } from '../constants';
-import ProjectQualityGateApp from '../ProjectQualityGateApp';
+import { ProjectQualityGateApp } from '../ProjectQualityGateApp';
jest.mock('../../../api/quality-gates', () => {
const { mockQualityGate } = jest.requireActual('../../../helpers/mocks/quality-gates');
diff --git a/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/__snapshots__/ProjectQualityGateAppRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/__snapshots__/ProjectQualityGateAppRenderer-test.tsx.snap
index 2d639fb2fd9..d46f48ec678 100644
--- a/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/__snapshots__/ProjectQualityGateAppRenderer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/__snapshots__/ProjectQualityGateAppRenderer-test.tsx.snap
@@ -479,8 +479,6 @@ exports[`should render correctly: show new code warning 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/quality_gates/show/3",
diff --git a/server/sonar-web/src/main/js/apps/projectQualityGate/routes.ts b/server/sonar-web/src/main/js/apps/projectQualityGate/routes.ts
deleted file mode 100644
index 9d677aa62e1..00000000000
--- a/server/sonar-web/src/main/js/apps/projectQualityGate/routes.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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 { lazyLoadComponent } from '../../components/lazyLoadComponent';
-
-const routes = [
- {
- indexRoute: { component: lazyLoadComponent(() => import('./ProjectQualityGateApp')) }
- }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/projectQualityGate/routes.tsx b/server/sonar-web/src/main/js/apps/projectQualityGate/routes.tsx
new file mode 100644
index 00000000000..22a9040d379
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projectQualityGate/routes.tsx
@@ -0,0 +1,26 @@
+/*
+ * 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 React from 'react';
+import { Route } from 'react-router-dom';
+import ProjectQualityGateApp from './ProjectQualityGateApp';
+
+const routes = () => <Route path="project/quality_gate" element={<ProjectQualityGateApp />} />;
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesApp.tsx b/server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesApp.tsx
index 25455df1466..03aa8b8a692 100644
--- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesApp.tsx
+++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesApp.tsx
@@ -26,6 +26,7 @@ import {
Profile,
searchQualityProfiles
} from '../../api/quality-profiles';
+import withComponentContext from '../../app/components/componentContext/withComponentContext';
import handleRequiredAuthorization from '../../app/utils/handleRequiredAuthorization';
import { addGlobalSuccessMessage } from '../../helpers/globalMessages';
import { translateWithParameters } from '../../helpers/l10n';
@@ -46,7 +47,7 @@ interface State {
showProjectProfileInModal?: ProjectProfile;
}
-export default class ProjectQualityProfilesApp extends React.PureComponent<Props, State> {
+export class ProjectQualityProfilesApp extends React.PureComponent<Props, State> {
mounted = false;
state: State = { loading: true };
@@ -291,3 +292,5 @@ export default class ProjectQualityProfilesApp extends React.PureComponent<Props
);
}
}
+
+export default withComponentContext(ProjectQualityProfilesApp);
diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesAppRenderer.tsx b/server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesAppRenderer.tsx
index 28445dfe0e3..8fd0186beae 100644
--- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesAppRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesAppRenderer.tsx
@@ -20,7 +20,7 @@
import { groupBy, orderBy } from 'lodash';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { Profile } from '../../api/quality-profiles';
import A11ySkipTarget from '../../components/a11y/A11ySkipTarget';
import { Button } from '../../components/controls/buttons';
diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/ProjectQualityProfilesApp-test.tsx b/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/ProjectQualityProfilesApp-test.tsx
index 90777d416e7..ffc9eb0f6a0 100644
--- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/ProjectQualityProfilesApp-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/ProjectQualityProfilesApp-test.tsx
@@ -29,7 +29,7 @@ import {
import handleRequiredAuthorization from '../../../app/utils/handleRequiredAuthorization';
import { mockComponent } from '../../../helpers/mocks/component';
import { waitAndUpdate } from '../../../helpers/testUtils';
-import ProjectQualityProfilesApp from '../ProjectQualityProfilesApp';
+import { ProjectQualityProfilesApp } from '../ProjectQualityProfilesApp';
jest.mock('../../../api/quality-profiles', () => {
const { mockQualityProfile } = jest.requireActual('../../../helpers/testMocks');
diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/__snapshots__/ProjectQualityProfilesAppRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/__snapshots__/ProjectQualityProfilesAppRenderer-test.tsx.snap
index 838cb7835cf..08e1b441407 100644
--- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/__snapshots__/ProjectQualityProfilesAppRenderer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/__snapshots__/ProjectQualityProfilesAppRenderer-test.tsx.snap
@@ -100,15 +100,10 @@ exports[`should render correctly: add language 1`] = `
className="nowrap text-right"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "bar",
- },
+ "search": "?activation=true&qprofile=bar",
}
}
>
@@ -147,15 +142,10 @@ exports[`should render correctly: add language 1`] = `
className="nowrap text-right"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "baz",
- },
+ "search": "?activation=true&qprofile=baz",
}
}
>
@@ -196,15 +186,10 @@ exports[`should render correctly: add language 1`] = `
className="nowrap text-right"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "foo",
- },
+ "search": "?activation=true&qprofile=foo",
}
}
>
@@ -415,15 +400,10 @@ exports[`should render correctly: default 1`] = `
className="nowrap text-right"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "bar",
- },
+ "search": "?activation=true&qprofile=bar",
}
}
>
@@ -462,15 +442,10 @@ exports[`should render correctly: default 1`] = `
className="nowrap text-right"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "baz",
- },
+ "search": "?activation=true&qprofile=baz",
}
}
>
@@ -511,15 +486,10 @@ exports[`should render correctly: default 1`] = `
className="nowrap text-right"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "foo",
- },
+ "search": "?activation=true&qprofile=foo",
}
}
>
@@ -751,15 +721,10 @@ exports[`should render correctly: open profile 1`] = `
className="nowrap text-right"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "bar",
- },
+ "search": "?activation=true&qprofile=bar",
}
}
>
@@ -798,15 +763,10 @@ exports[`should render correctly: open profile 1`] = `
className="nowrap text-right"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "baz",
- },
+ "search": "?activation=true&qprofile=baz",
}
}
>
@@ -847,15 +807,10 @@ exports[`should render correctly: open profile 1`] = `
className="nowrap text-right"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "foo",
- },
+ "search": "?activation=true&qprofile=foo",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/LanguageProfileSelectOption.tsx b/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/LanguageProfileSelectOption.tsx
index 376f4b96316..b506a93878e 100644
--- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/LanguageProfileSelectOption.tsx
+++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/LanguageProfileSelectOption.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 { components, OptionProps } from 'react-select';
import DisableableSelectOption from '../../../components/common/DisableableSelectOption';
import { BasicSelectOption } from '../../../components/controls/Select';
diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/__tests__/__snapshots__/LanguageProfileSelectOption-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/__tests__/__snapshots__/LanguageProfileSelectOption-test.tsx.snap
index 3c3a2ec0c26..534f179f976 100644
--- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/__tests__/__snapshots__/LanguageProfileSelectOption-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/__tests__/__snapshots__/LanguageProfileSelectOption-test.tsx.snap
@@ -34,15 +34,10 @@ exports[`tooltip should render correctly: default 1`] = `
project_quality_profile.add_language_modal.profile_unavailable_no_active_rules
</p>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/profiles/show",
- "query": Object {
- "language": "Java",
- "name": "Profile 1",
- },
+ "search": "?name=Profile+1&language=Java",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/routes.ts b/server/sonar-web/src/main/js/apps/projectQualityProfiles/routes.ts
deleted file mode 100644
index 7e0a67bb37d..00000000000
--- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/routes.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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 { lazyLoadComponent } from '../../components/lazyLoadComponent';
-
-const routes = [
- {
- indexRoute: {
- component: lazyLoadComponent(() => import('./ProjectQualityProfilesApp'))
- }
- }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/routes.tsx b/server/sonar-web/src/main/js/apps/projectQualityProfiles/routes.tsx
new file mode 100644
index 00000000000..35add6e93ff
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/routes.tsx
@@ -0,0 +1,28 @@
+/*
+ * 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 React from 'react';
+import { Route } from 'react-router-dom';
+import ProjectQualityProfilesApp from './ProjectQualityProfilesApp';
+
+const routes = () => (
+ <Route path="project/quality_profiles" element={<ProjectQualityProfilesApp />} />
+);
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx
index a60da0bc7b4..d7de7ef7727 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx
@@ -20,6 +20,7 @@
import { omitBy } from 'lodash';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
+import { useSearchParams } from 'react-router-dom';
import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext';
import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
@@ -37,7 +38,7 @@ import { AppState } from '../../../types/appstate';
import { ComponentQualifier } from '../../../types/component';
import { RawQuery } from '../../../types/types';
import { CurrentUser, isLoggedIn } from '../../../types/users';
-import { hasFilterParams, hasViewParams, parseUrlQuery, Query } from '../query';
+import { hasFilterParams, parseUrlQuery, Query } from '../query';
import '../styles.css';
import { Facets, Project } from '../types';
import { fetchProjects, parseSorting, SORTING_SWITCH } from '../utils';
@@ -48,9 +49,9 @@ import ProjectsList from './ProjectsList';
interface Props {
currentUser: CurrentUser;
isFavorite: boolean;
- location: Pick<Location, 'pathname' | 'query'>;
+ location: Location;
appState: AppState;
- router: Pick<Router, 'push' | 'replace'>;
+ router: Router;
}
interface State {
@@ -80,13 +81,13 @@ export class AllProjects extends React.PureComponent<Props, State> {
handleRequiredAuthentication();
return;
}
- this.handleQueryChange(true);
+ this.handleQueryChange();
addSideBarClass();
}
componentDidUpdate(prevProps: Props) {
if (prevProps.location.query !== this.props.location.query) {
- this.handleQueryChange(false);
+ this.handleQueryChange();
}
}
@@ -128,20 +129,6 @@ export class AllProjects extends React.PureComponent<Props, State> {
getSort = () => this.state.query.sort || 'name';
- getStorageOptions = () => {
- const options: {
- sort?: string;
- view?: string;
- } = {};
- if (get(LS_PROJECTS_SORT)) {
- options.sort = get(LS_PROJECTS_SORT) || undefined;
- }
- if (get(LS_PROJECTS_VIEW)) {
- options.view = get(LS_PROJECTS_VIEW) || undefined;
- }
- return options;
- };
-
getView = () => this.state.query.view || 'overall';
handleClearAll = () => {
@@ -184,16 +171,10 @@ export class AllProjects extends React.PureComponent<Props, State> {
save(LS_PROJECTS_VIEW, query.view);
};
- handleQueryChange(initialMount: boolean) {
+ handleQueryChange() {
const query = parseUrlQuery(this.props.location.query);
- const savedOptions = this.getStorageOptions();
- const savedOptionsSet = savedOptions.sort || savedOptions.view;
- if (initialMount && !hasViewParams(query) && savedOptionsSet) {
- this.props.router.replace({ pathname: this.props.location.pathname, query: savedOptions });
- } else {
- this.fetchProjects(query);
- }
+ this.fetchProjects(query);
}
handleSortChange = (sort: string, desc: boolean) => {
@@ -318,4 +299,40 @@ export class AllProjects extends React.PureComponent<Props, State> {
}
}
-export default withRouter(withCurrentUserContext(withAppStateContext(AllProjects)));
+function getStorageOptions() {
+ const options: {
+ sort?: string;
+ view?: string;
+ } = {};
+ if (get(LS_PROJECTS_SORT)) {
+ options.sort = get(LS_PROJECTS_SORT) || undefined;
+ }
+ if (get(LS_PROJECTS_VIEW)) {
+ options.view = get(LS_PROJECTS_VIEW) || undefined;
+ }
+ return options;
+}
+
+function SetSearchParamsWrapper(props: Props) {
+ const [searchParams, setSearchParams] = useSearchParams();
+ const savedOptions = getStorageOptions();
+
+ React.useEffect(
+ () => {
+ const hasViewParams = searchParams.get('sort') || searchParams.get('view');
+ const hasSavedOptions = savedOptions.sort || savedOptions.view;
+
+ if (!hasViewParams && hasSavedOptions) {
+ setSearchParams(savedOptions);
+ }
+ },
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [
+ /* Run once on mount only */
+ ]
+ );
+
+ return <AllProjects {...props} />;
+}
+
+export default withRouter(withCurrentUserContext(withAppStateContext(SetSearchParamsWrapper)));
diff --git a/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx b/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx
index d8886bd7be4..fa938e1a828 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx
@@ -18,84 +18,84 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { useNavigate } from 'react-router-dom';
import { searchProjects } from '../../../api/components';
import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext';
-import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
+import { useLocation } from '../../../components/hoc/withRouter';
import { get } from '../../../helpers/storage';
import { hasGlobalPermission } from '../../../helpers/users';
import { CurrentUser, isLoggedIn } from '../../../types/users';
import { PROJECTS_ALL, PROJECTS_DEFAULT_FILTER, PROJECTS_FAVORITE } from '../utils';
import AllProjectsContainer from './AllProjectsContainer';
-interface Props {
+export interface DefaultPageSelectorProps {
currentUser: CurrentUser;
- location: Pick<Location, 'pathname' | 'query'>;
- router: Pick<Router, 'replace'>;
}
-interface State {
- checking: boolean;
-}
-
-export class DefaultPageSelector extends React.PureComponent<Props, State> {
- state: State = { checking: true };
-
- componentDidMount() {
- this.checkIfNeedsRedirecting();
- }
-
- checkIfNeedsRedirecting = async () => {
- const { currentUser, router, location } = this.props;
- const setting = get(PROJECTS_DEFAULT_FILTER);
+export function DefaultPageSelector(props: DefaultPageSelectorProps) {
+ const [checking, setChecking] = React.useState(true);
+ const navigate = useNavigate();
+ const location = useLocation();
- // 1. Don't have to redirect if:
- // 1.1 User is anonymous
- // 1.2 There's a query, which means the user is interacting with the current page
- // 1.3 The last interaction with the filter was to set it to "all"
- if (
- !isLoggedIn(currentUser) ||
- Object.keys(location.query).length > 0 ||
- setting === PROJECTS_ALL
- ) {
- this.setState({ checking: false });
- return;
- }
+ React.useEffect(
+ () => {
+ async function checkRedirect() {
+ const { currentUser } = props;
+ const setting = get(PROJECTS_DEFAULT_FILTER);
- // 2. Redirect to the favorites page if:
- // 2.1 The last interaction with the filter was to set it to "favorites"
- // 2.2 The user has starred some projects
- if (
- setting === PROJECTS_FAVORITE ||
- (await searchProjects({ filter: 'isFavorite', ps: 1 })).paging.total > 0
- ) {
- router.replace('/projects/favorite');
- return;
- }
+ // 1. Don't have to redirect if:
+ // 1.1 User is anonymous
+ // 1.2 There's a query, which means the user is interacting with the current page
+ // 1.3 The last interaction with the filter was to set it to "all"
+ if (
+ !isLoggedIn(currentUser) ||
+ Object.keys(location.query).length > 0 ||
+ setting === PROJECTS_ALL
+ ) {
+ setChecking(false);
+ return;
+ }
- // 3. Redirect to the create project page if:
- // 3.1 The user has permission to provision projects, AND there are 0 projects on the instance
- if (
- hasGlobalPermission(currentUser, 'provisioning') &&
- (await searchProjects({ ps: 1 })).paging.total === 0
- ) {
- this.props.router.replace('/projects/create');
- }
+ // 2. Redirect to the favorites page if:
+ // 2.1 The last interaction with the filter was to set it to "favorites"
+ // 2.2 The user has starred some projects
+ if (
+ setting === PROJECTS_FAVORITE ||
+ (await searchProjects({ filter: 'isFavorite', ps: 1 })).paging.total > 0
+ ) {
+ navigate('/projects/favorite', { replace: true });
+ return;
+ }
- // None of the above apply. Do not redirect, and stay on this page.
- this.setState({ checking: false });
- };
+ // 3. Redirect to the create project page if:
+ // 3.1 The user has permission to provision projects, AND there are 0 projects on the instance
+ if (
+ hasGlobalPermission(currentUser, 'provisioning') &&
+ (await searchProjects({ ps: 1 })).paging.total === 0
+ ) {
+ navigate('/projects/create', { replace: true });
+ return;
+ }
- render() {
- const { checking } = this.state;
+ // None of the above apply. Do not redirect, and stay on this page.
+ setChecking(false);
+ }
- if (checking) {
- // We don't return a loader here, on purpose. We don't want to show anything
- // just yet.
- return null;
- }
+ checkRedirect();
+ },
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [
+ /* run only once on mount*/
+ ]
+ );
- return <AllProjectsContainer isFavorite={false} />;
+ if (checking) {
+ // We don't return a loader here, on purpose. We don't want to show anything
+ // just yet.
+ return null;
}
+
+ return <AllProjectsContainer isFavorite={false} />;
}
-export default withCurrentUserContext(withRouter(DefaultPageSelector));
+export default withCurrentUserContext(DefaultPageSelector);
diff --git a/server/sonar-web/src/main/js/apps/projects/components/EmptyFavoriteSearch.tsx b/server/sonar-web/src/main/js/apps/projects/components/EmptyFavoriteSearch.tsx
index 8b3197d67e9..b5cde9bdd1d 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/EmptyFavoriteSearch.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/EmptyFavoriteSearch.tsx
@@ -19,9 +19,11 @@
*/
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import '../../../components/common/EmptySearch.css';
import { translate } from '../../../helpers/l10n';
+import { queryToSearch } from '../../../helpers/urls';
+import { Dict } from '../../../types/types';
import { Query } from '../query';
export default function EmptyFavoriteSearch({ query }: { query: Query }) {
@@ -33,7 +35,15 @@ export default function EmptyFavoriteSearch({ query }: { query: Query }) {
defaultMessage={translate('no_results_search.favorites.2')}
id="no_results_search.favorites.2"
values={{
- url: <Link to={{ pathname: '/projects', query }}>{translate('all')}</Link>
+ url: (
+ <Link
+ to={{
+ pathname: '/projects',
+ search: queryToSearch(query as Dict<string | undefined | number>)
+ }}>
+ {translate('all')}
+ </Link>
+ )
}}
/>
</p>
diff --git a/server/sonar-web/src/main/js/apps/projects/components/EmptyInstance.tsx b/server/sonar-web/src/main/js/apps/projects/components/EmptyInstance.tsx
index 6a201c2eb1b..fe09b641a15 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/EmptyInstance.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/EmptyInstance.tsx
@@ -18,9 +18,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { WithRouterProps } from 'react-router';
import { Button } from '../../../components/controls/buttons';
-import { withRouter } from '../../../components/hoc/withRouter';
+import { Router, withRouter } from '../../../components/hoc/withRouter';
import { translate } from '../../../helpers/l10n';
import { hasGlobalPermission } from '../../../helpers/users';
import { Permissions } from '../../../types/permissions';
@@ -28,7 +27,7 @@ import { CurrentUser, isLoggedIn } from '../../../types/users';
export interface EmptyInstanceProps {
currentUser: CurrentUser;
- router: WithRouterProps['router'];
+ router: Router;
}
export function EmptyInstance(props: EmptyInstanceProps) {
diff --git a/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.tsx b/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.tsx
index 2b5374afe61..d6847d5756b 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.tsx
@@ -18,10 +18,11 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { IndexLink, Link } from 'react-router';
+import { NavLink } from 'react-router-dom';
import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext';
import { translate } from '../../../helpers/l10n';
import { save } from '../../../helpers/storage';
+import { queryToSearch } from '../../../helpers/urls';
import { RawQuery } from '../../../types/types';
import { CurrentUser, isLoggedIn } from '../../../types/users';
import { PROJECTS_ALL, PROJECTS_DEFAULT_FILTER, PROJECTS_FAVORITE } from '../utils';
@@ -31,6 +32,9 @@ interface Props {
query?: RawQuery;
}
+const linkClass = ({ isActive }: { isActive: boolean }) =>
+ isActive ? 'button button-active' : 'button';
+
export class FavoriteFilter extends React.PureComponent<Props> {
handleSaveFavorite = () => {
save(PROJECTS_DEFAULT_FILTER, PROJECTS_FAVORITE);
@@ -48,25 +52,26 @@ export class FavoriteFilter extends React.PureComponent<Props> {
const pathnameForFavorite = '/projects/favorite';
const pathnameForAll = '/projects';
+ const search = queryToSearch(this.props.query);
+
return (
<div className="page-header text-center">
<div className="button-group little-spacer-top">
- <Link
- activeClassName="button-active"
- className="button"
+ <NavLink
+ className={linkClass}
id="favorite-projects"
onClick={this.handleSaveFavorite}
- to={{ pathname: pathnameForFavorite, query: this.props.query }}>
+ to={{ pathname: pathnameForFavorite, search }}>
{translate('my_favorites')}
- </Link>
- <IndexLink
- activeClassName="button-active"
- className="button"
+ </NavLink>
+ <NavLink
+ end={true}
+ className={linkClass}
id="all-projects"
onClick={this.handleSaveAll}
- to={{ pathname: pathnameForAll, query: this.props.query }}>
+ to={{ pathname: pathnameForAll, search }}>
{translate('all')}
- </IndexLink>
+ </NavLink>
</div>
</div>
);
diff --git a/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx b/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx
index 77f23173f20..1cf78b4a997 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.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';
export default function NoFavoriteProjects() {
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenu.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenu.tsx
index b1cd167c7f5..3fa8a85e11d 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenu.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenu.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 { getAlmSettings } from '../../../api/alm-settings';
import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext';
import { Button } from '../../../components/controls/buttons';
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenuItem.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenuItem.tsx
index 32bb5c92a32..7d933de7d9d 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenuItem.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenuItem.tsx
@@ -18,10 +18,11 @@
* 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 ChevronsIcon from '../../../components/icons/ChevronsIcon';
import { translate } from '../../../helpers/l10n';
import { getBaseUrl } from '../../../helpers/system';
+import { queryToSearch } from '../../../helpers/urls';
import { AlmKeys } from '../../../types/alm-settings';
export interface ProjectCreationMenuItemProps {
@@ -37,7 +38,7 @@ export default function ProjectCreationMenuItem(props: ProjectCreationMenuItemPr
return (
<Link
className="display-flex-center"
- to={{ pathname: '/projects/create', query: { mode: alm } }}>
+ to={{ pathname: '/projects/create', search: queryToSearch({ mode: alm }) }}>
{alm === 'manual' ? (
<ChevronsIcon className="spacer-right" />
) : (
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx
index 63b97a12434..77d28e87274 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx
@@ -20,9 +20,8 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { get, save } from '../../../../helpers/storage';
-import { mockAppState } from '../../../../helpers/testMocks';
+import { mockAppState, mockLocation } from '../../../../helpers/testMocks';
import { ComponentQualifier } from '../../../../types/component';
-import { Dict } from '../../../../types/types';
import { AllProjects, LS_PROJECTS_SORT, LS_PROJECTS_VIEW } from '../AllProjects';
jest.mock(
@@ -103,25 +102,6 @@ it('fetches projects', () => {
);
});
-it('redirects to the saved search', () => {
- const localeStorageMock: Dict<string> = {
- [LS_PROJECTS_VIEW]: 'leak',
- [LS_PROJECTS_SORT]: 'coverage'
- };
-
- (get as jest.Mock).mockImplementation((key: string) => localeStorageMock[key]);
- const replace = jest.fn();
- shallowRender({}, jest.fn(), replace);
-
- expect(replace).lastCalledWith({
- pathname: '/projects',
- query: {
- view: localeStorageMock[LS_PROJECTS_VIEW],
- sort: localeStorageMock[LS_PROJECTS_SORT]
- }
- });
-});
-
it('changes sort', () => {
const push = jest.fn();
const wrapper = shallowRender({}, push);
@@ -174,7 +154,7 @@ function shallowRender(
<AllProjects
currentUser={{ isLoggedIn: true }}
isFavorite={false}
- location={{ pathname: '/projects', query: {} }}
+ location={mockLocation({ pathname: '/projects', query: {} })}
appState={mockAppState({
qualifiers: [ComponentQualifier.Project, ComponentQualifier.Application]
})}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ApplicationCreation-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ApplicationCreation-test.tsx
index 4528e3afe64..ecdfa1b29fe 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ApplicationCreation-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ApplicationCreation-test.tsx
@@ -23,6 +23,7 @@ import { getComponentNavigation } from '../../../../api/nav';
import CreateApplicationForm from '../../../../app/components/extensions/CreateApplicationForm';
import { Button } from '../../../../components/controls/buttons';
import { mockAppState, mockLoggedInUser, mockRouter } from '../../../../helpers/testMocks';
+import { queryToSearch } from '../../../../helpers/urls';
import { ComponentQualifier } from '../../../../types/component';
import { ApplicationCreation, ApplicationCreationProps } from '../ApplicationCreation';
@@ -51,9 +52,9 @@ it('should show form and callback when submitted - admin', async () => {
expect(routerPush).toBeCalledWith({
pathname: '/project/admin/extension/developer-server/application-console',
- query: {
+ search: queryToSearch({
id: 'new app'
- }
+ })
});
});
@@ -68,9 +69,9 @@ it('should show form and callback when submitted - user', async () => {
expect(routerPush).toBeCalledWith({
pathname: '/dashboard',
- query: {
+ search: queryToSearch({
id: 'new app'
- }
+ })
});
});
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/DefaultPageSelector-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/DefaultPageSelector-test.tsx
index 2dacccf17b2..2c35ecc0571 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/DefaultPageSelector-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/DefaultPageSelector-test.tsx
@@ -17,17 +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 { shallow } from 'enzyme';
+import { render, screen } from '@testing-library/react';
import * as React from 'react';
+import { MemoryRouter, Route, Routes } from 'react-router-dom';
import { searchProjects } from '../../../../api/components';
+import { useLocation } from '../../../../components/hoc/withRouter';
import { get } from '../../../../helpers/storage';
-import {
- mockCurrentUser,
- mockLocation,
- mockLoggedInUser,
- mockRouter
-} from '../../../../helpers/testMocks';
-import { waitAndUpdate } from '../../../../helpers/testUtils';
+import { mockCurrentUser, mockLoggedInUser } from '../../../../helpers/testMocks';
import { hasGlobalPermission } from '../../../../helpers/users';
import { CurrentUser } from '../../../../types/users';
import { DefaultPageSelector } from '../DefaultPageSelector';
@@ -37,7 +33,7 @@ jest.mock(
() =>
// eslint-disable-next-line
function AllProjectsContainer() {
- return null;
+ return <div>All Projects</div>;
}
);
@@ -56,102 +52,82 @@ jest.mock('../../../../api/components', () => ({
beforeEach(jest.clearAllMocks);
-it('renders correctly', () => {
- expect(shallowRender({ currentUser: mockLoggedInUser() }).type()).toBeNull(); // checking
- expect(shallowRender({ currentUser: mockCurrentUser() })).toMatchSnapshot('default');
-});
-
it("1.1 doesn't redirect for anonymous users", async () => {
- const replace = jest.fn();
- const wrapper = shallowRender({
- currentUser: mockCurrentUser(),
- router: mockRouter({ replace })
- });
- await waitAndUpdate(wrapper);
- expect(replace).not.toBeCalled();
+ renderDefaultPageSelector({ currentUser: mockCurrentUser() });
+
+ expect(await screen.findByText('All Projects')).toBeInTheDocument();
});
it("1.2 doesn't redirect if there's an existing filter in location", async () => {
- const replace = jest.fn();
- const wrapper = shallowRender({
- location: mockLocation({ query: { size: '1' } }),
- router: mockRouter({ replace })
- });
+ renderDefaultPageSelector({ path: '/projects?size=1' });
- await waitAndUpdate(wrapper);
-
- expect(replace).not.toBeCalled();
+ expect(await screen.findByText('All Projects')).toBeInTheDocument();
});
it("1.3 doesn't redirect if the user previously used the 'all' filter", async () => {
(get as jest.Mock).mockReturnValueOnce('all');
- const replace = jest.fn();
- const wrapper = shallowRender({ router: mockRouter({ replace }) });
-
- await waitAndUpdate(wrapper);
+ renderDefaultPageSelector();
- expect(replace).not.toBeCalled();
+ expect(await screen.findByText('All Projects')).toBeInTheDocument();
});
it('2.1 redirects to favorites if the user previously used the "favorites" filter', async () => {
(get as jest.Mock).mockReturnValueOnce('favorite');
- const replace = jest.fn();
- const wrapper = shallowRender({ router: mockRouter({ replace }) });
-
- await waitAndUpdate(wrapper);
+ renderDefaultPageSelector();
- expect(replace).toBeCalledWith('/projects/favorite');
+ expect(await screen.findByText('/projects/favorite')).toBeInTheDocument();
});
it('2.2 redirects to favorites if the user has starred projects', async () => {
(searchProjects as jest.Mock).mockResolvedValueOnce({ paging: { total: 3 } });
- const replace = jest.fn();
- const wrapper = shallowRender({ router: mockRouter({ replace }) });
-
- await waitAndUpdate(wrapper);
+ renderDefaultPageSelector();
expect(searchProjects).toHaveBeenLastCalledWith({ filter: 'isFavorite', ps: 1 });
- expect(replace).toBeCalledWith('/projects/favorite');
+ expect(await screen.findByText('/projects/favorite')).toBeInTheDocument();
});
it('3.1 redirects to create project page, if user has correct permissions AND there are 0 projects', async () => {
(hasGlobalPermission as jest.Mock).mockReturnValueOnce(true);
- const replace = jest.fn();
- const wrapper = shallowRender({ router: mockRouter({ replace }) });
-
- await waitAndUpdate(wrapper);
+ renderDefaultPageSelector();
- expect(replace).toBeCalledWith('/projects/create');
+ expect(await screen.findByText('/projects/create')).toBeInTheDocument();
});
it("3.1 doesn't redirect to create project page, if user has no permissions", async () => {
- const replace = jest.fn();
- const wrapper = shallowRender({ router: mockRouter({ replace }) });
-
- await waitAndUpdate(wrapper);
+ renderDefaultPageSelector();
- expect(replace).not.toBeCalled();
+ expect(await screen.findByText('All Projects')).toBeInTheDocument();
});
it("3.1 doesn't redirect to create project page, if there's existing projects", async () => {
(searchProjects as jest.Mock)
.mockResolvedValueOnce({ paging: { total: 0 } }) // no favorites
.mockResolvedValueOnce({ paging: { total: 3 } }); // existing projects
- const replace = jest.fn();
- const wrapper = shallowRender({ router: mockRouter({ replace }) });
+ renderDefaultPageSelector();
- await waitAndUpdate(wrapper);
-
- expect(replace).not.toBeCalled();
+ expect(await screen.findByText('All Projects')).toBeInTheDocument();
});
-function shallowRender(props: Partial<DefaultPageSelector['props']> = {}) {
- return shallow<DefaultPageSelector>(
- <DefaultPageSelector
- currentUser={mockLoggedInUser()}
- location={mockLocation({ pathname: '/projects' })}
- router={mockRouter()}
- {...props}
- />
+function RouteDisplayer() {
+ const location = useLocation();
+ return <div>{location.pathname}</div>;
+}
+
+function renderDefaultPageSelector({
+ path = '/projects',
+ currentUser = mockLoggedInUser()
+}: {
+ path?: string;
+ currentUser?: CurrentUser;
+} = {}) {
+ return render(
+ <MemoryRouter initialEntries={[path]}>
+ <Routes>
+ <Route path="projects">
+ <Route index={true} element={<DefaultPageSelector currentUser={currentUser} />} />
+ <Route path="*" element={<RouteDisplayer />} />
+ </Route>
+ </Routes>
+ </MemoryRouter>
);
}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/FavoriteFilter-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/FavoriteFilter-test.tsx
index 58da88c51a5..07ff5c33866 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/FavoriteFilter-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/FavoriteFilter-test.tsx
@@ -17,37 +17,47 @@
* 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/react';
+import userEvent from '@testing-library/user-event';
import * as React from 'react';
import { save } from '../../../../helpers/storage';
-import { click } from '../../../../helpers/testUtils';
+import { mockCurrentUser, mockLoggedInUser } from '../../../../helpers/testMocks';
+import { renderComponent } from '../../../../helpers/testReactTestingUtils';
import { FavoriteFilter } from '../FavoriteFilter';
jest.mock('../../../../helpers/storage', () => ({
save: jest.fn()
}));
-const currentUser = { isLoggedIn: true };
-const query = { size: 1 };
-
beforeEach(() => {
(save as jest.Mock<any>).mockClear();
});
it('renders for logged in user', () => {
- expect(shallow(<FavoriteFilter currentUser={currentUser} query={query} />)).toMatchSnapshot();
+ renderFavoriteFilter();
+ expect(screen.queryByText('my_favorites')).toBeInTheDocument();
+ expect(screen.queryByText('all')).toBeInTheDocument();
});
-it('saves last selection', () => {
- const wrapper = shallow(<FavoriteFilter currentUser={currentUser} query={query} />);
- click(wrapper.find('#favorite-projects'));
+it('saves last selection', async () => {
+ const user = userEvent.setup();
+
+ renderFavoriteFilter();
+
+ await user.click(screen.getByText('my_favorites'));
expect(save).toBeCalledWith('sonarqube.projects.default', 'favorite');
- click(wrapper.find('#all-projects'));
+ await user.click(screen.getByText('all'));
expect(save).toBeCalledWith('sonarqube.projects.default', 'all');
});
it('does not render for anonymous', () => {
- expect(
- shallow(<FavoriteFilter currentUser={{ isLoggedIn: false }} query={query} />).type()
- ).toBeNull();
+ renderFavoriteFilter({ currentUser: mockCurrentUser() });
+ expect(screen.queryByText('my_favorites')).not.toBeInTheDocument();
});
+
+function renderFavoriteFilter({
+ currentUser = mockLoggedInUser(),
+ query = { size: 1 }
+}: Partial<FavoriteFilter['props']> = {}) {
+ renderComponent(<FavoriteFilter currentUser={currentUser} query={query} />);
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/DefaultPageSelector-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/DefaultPageSelector-test.tsx.snap
deleted file mode 100644
index 92729bd4dbd..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/DefaultPageSelector-test.tsx.snap
+++ /dev/null
@@ -1,7 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders correctly: default 1`] = `
-<AllProjectsContainer
- isFavorite={false}
-/>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/FavoriteFilter-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/FavoriteFilter-test.tsx.snap
deleted file mode 100644
index 55d42cf6c82..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/FavoriteFilter-test.tsx.snap
+++ /dev/null
@@ -1,46 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders for logged in user 1`] = `
-<div
- className="page-header text-center"
->
- <div
- className="button-group little-spacer-top"
- >
- <Link
- activeClassName="button-active"
- className="button"
- id="favorite-projects"
- onClick={[Function]}
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/projects/favorite",
- "query": Object {
- "size": 1,
- },
- }
- }
- >
- my_favorites
- </Link>
- <IndexLink
- activeClassName="button-active"
- className="button"
- id="all-projects"
- onClick={[Function]}
- to={
- Object {
- "pathname": "/projects",
- "query": Object {
- "size": 1,
- },
- }
- }
- >
- all
- </IndexLink>
- </div>
-</div>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/NoFavoriteProjects-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/NoFavoriteProjects-test.tsx.snap
index 0b9b729d5f0..9c59832d3e6 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/NoFavoriteProjects-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/NoFavoriteProjects-test.tsx.snap
@@ -18,8 +18,6 @@ exports[`renders 1`] = `
>
<Link
className="button"
- onlyActiveOnIndex={false}
- style={Object {}}
to="/projects/all"
>
projects.explore_projects
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCreationMenu-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCreationMenu-test.tsx.snap
index 01db864b088..c2eea8f86d0 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCreationMenu-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCreationMenu-test.tsx.snap
@@ -19,8 +19,6 @@ exports[`should render correctly: default 1`] = `
>
<Link
className="display-flex-center"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/projects/create",
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCreationMenuItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCreationMenuItem-test.tsx.snap
index e66677e05de..b4e778a2161 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCreationMenuItem-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCreationMenuItem-test.tsx.snap
@@ -3,14 +3,10 @@
exports[`should render correctly: bitbucket 1`] = `
<Link
className="display-flex-center"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/projects/create",
- "query": Object {
- "mode": "bitbucket",
- },
+ "search": "?mode=bitbucket",
}
}
>
@@ -27,14 +23,10 @@ exports[`should render correctly: bitbucket 1`] = `
exports[`should render correctly: manual 1`] = `
<Link
className="display-flex-center"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/projects/create",
- "query": Object {
- "mode": "manual",
- },
+ "search": "?mode=manual",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx
index 473850bda5f..e7d95020d66 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx
@@ -20,7 +20,7 @@
import classNames from 'classnames';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import PrivacyBadgeContainer from '../../../../components/common/PrivacyBadgeContainer';
import Favorite from '../../../../components/controls/Favorite';
import Tooltip from '../../../../components/controls/Tooltip';
diff --git a/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCard-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCard-test.tsx.snap
index a326b9ecfcd..af69b9a3c92 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCard-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/__snapshots__/ProjectCard-test.tsx.snap
@@ -37,15 +37,10 @@ exports[`should display applications 1`] = `
title="Foo"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "id": "foo",
- },
+ "search": "?id=foo",
}
}
>
@@ -145,15 +140,10 @@ exports[`should display applications: with project count 1`] = `
title="Foo"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "id": "foo",
- },
+ "search": "?id=foo",
}
}
>
@@ -234,15 +224,10 @@ exports[`should display configure analysis button for logged in user: default 1`
title="Foo"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "id": "foo",
- },
+ "search": "?id=foo",
}
}
>
@@ -280,15 +265,10 @@ exports[`should display configure analysis button for logged in user: default 1`
</span>
<Link
className="button spacer-left"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "id": "foo",
- },
+ "search": "?id=foo",
}
}
>
@@ -458,15 +438,10 @@ exports[`should display not analyzed yet 1`] = `
title="Foo"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "id": "foo",
- },
+ "search": "?id=foo",
}
}
>
@@ -532,15 +507,10 @@ exports[`should display the overall measures and quality gate 1`] = `
title="Foo"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "id": "foo",
- },
+ "search": "?id=foo",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/projects/routes.ts b/server/sonar-web/src/main/js/apps/projects/routes.tsx
index 6b996e74891..99a97bf603d 100644
--- a/server/sonar-web/src/main/js/apps/projects/routes.ts
+++ b/server/sonar-web/src/main/js/apps/projects/routes.tsx
@@ -17,30 +17,27 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { RedirectFunction, RouterState } from 'react-router';
-import { lazyLoadComponent } from '../../components/lazyLoadComponent';
+import React from 'react';
+import { Navigate, Route } from 'react-router-dom';
import { save } from '../../helpers/storage';
+import CreateProjectPage from '../create/project/CreateProjectPage';
+import DefaultPageSelector from './components/DefaultPageSelector';
+import FavoriteProjectsContainer from './components/FavoriteProjectsContainer';
import { PROJECTS_ALL, PROJECTS_DEFAULT_FILTER } from './utils';
-const routes = [
- {
- indexRoute: { component: lazyLoadComponent(() => import('./components/DefaultPageSelector')) }
- },
- {
- path: 'all',
- onEnter(_: RouterState, replace: RedirectFunction) {
- save(PROJECTS_DEFAULT_FILTER, PROJECTS_ALL);
- replace('/projects');
- }
- },
- {
- path: 'favorite',
- component: lazyLoadComponent(() => import('./components/FavoriteProjectsContainer'))
- },
- {
- path: 'create',
- component: lazyLoadComponent(() => import('../create/project/CreateProjectPage'))
- }
-];
+function PersistNavigate() {
+ save(PROJECTS_DEFAULT_FILTER, PROJECTS_ALL);
+
+ return <Navigate to="/projects" replace={true} />;
+}
+
+const routes = () => (
+ <Route path="projects">
+ <Route index={true} element={<DefaultPageSelector />} />
+ <Route path="all" element={<PersistNavigate />} />
+ <Route path="favorite" element={<FavoriteProjectsContainer />} />
+ <Route path="create" element={<CreateProjectPage />} />
+ </Route>
+);
export default routes;
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx
index b416ffa2c1c..622329fc954 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.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 { createProject } from '../../api/components';
import VisibilitySelector from '../../components/common/VisibilitySelector';
import { ResetButtonLink, SubmitButton } from '../../components/controls/buttons';
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx
index 7f96d1b14ff..1cb7d26e0ba 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.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 { Project } from '../../api/components';
import PrivacyBadgeContainer from '../../components/common/PrivacyBadgeContainer';
import Checkbox from '../../components/controls/Checkbox';
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectManagementApp-it.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectManagementApp-it.tsx
index 70ab3aaae7e..36bbd9969e6 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectManagementApp-it.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectManagementApp-it.tsx
@@ -154,5 +154,5 @@ function mockComponents(n: number) {
}
function renderGlobalBackgroundTasksApp() {
- renderAdminApp('admin/background_tasks', routes, {});
+ renderAdminApp('admin/projects_management', routes, {});
}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap
index d7c8b42fb8f..6e3daf3a9bf 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap
@@ -313,15 +313,10 @@ exports[`creates project 4`] = `
values={
Object {
"project": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "id": "name",
- },
+ "search": "?id=name",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRow-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRow-test.tsx.snap
index eb4b5da27bb..69db1456cc3 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRow-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRow-test.tsx.snap
@@ -18,14 +18,10 @@ exports[`renders 1`] = `
>
<Link
className="link-with-icon"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "id": "project",
- },
+ "search": "?id=project",
}
}
>
@@ -114,14 +110,10 @@ exports[`renders: portfolio 1`] = `
>
<Link
className="link-with-icon"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/portfolio",
- "query": Object {
- "id": "project",
- },
+ "search": "?id=project",
}
}
>
@@ -210,14 +202,10 @@ exports[`renders: with lastAnalysisDate 1`] = `
>
<Link
className="link-with-icon"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "id": "project",
- },
+ "search": "?id=project",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/routes.ts b/server/sonar-web/src/main/js/apps/projectsManagement/routes.ts
deleted file mode 100644
index cabc724387d..00000000000
--- a/server/sonar-web/src/main/js/apps/projectsManagement/routes.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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 { lazyLoadComponent } from '../../components/lazyLoadComponent';
-
-const routes = [
- {
- indexRoute: { component: lazyLoadComponent(() => import('./ProjectManagementApp')) }
- }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/routes.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/routes.tsx
new file mode 100644
index 00000000000..f3caf1e7d8c
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/routes.tsx
@@ -0,0 +1,27 @@
+/*
+ * 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 React from 'react';
+import { Route } from 'react-router-dom';
+import ProjectManagementApp from './ProjectManagementApp';
+
+export const routes = () => <Route path="projects_management" element={<ProjectManagementApp />} />;
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/App.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/App.tsx
index c7cb7fcc3fb..2922b7371c1 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/App.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
-import { WithRouterProps } from 'react-router';
+import { NavigateFunction, useNavigate, useParams } from 'react-router-dom';
import { fetchQualityGates } from '../../../api/quality-gates';
import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
import Suggestions from '../../../components/embed-docs-modal/Suggestions';
@@ -39,13 +39,18 @@ import Details from './Details';
import List from './List';
import ListHeader from './ListHeader';
+interface Props {
+ id?: string;
+ navigate: NavigateFunction;
+}
+
interface State {
canCreate: boolean;
loading: boolean;
qualityGates: QualityGate[];
}
-class App extends React.PureComponent<Pick<WithRouterProps, 'params' | 'router'>, State> {
+class App extends React.PureComponent<Props, State> {
mounted = false;
state: State = { canCreate: false, loading: true, qualityGates: [] };
@@ -56,8 +61,8 @@ class App extends React.PureComponent<Pick<WithRouterProps, 'params' | 'router'>
addSideBarClass();
}
- componentDidUpdate(prevProps: WithRouterProps) {
- if (prevProps.params.id !== undefined && this.props.params.id === undefined) {
+ componentDidUpdate(prevProps: Props) {
+ if (prevProps.id !== undefined && this.props.id === undefined) {
this.openDefault(this.state.qualityGates);
}
}
@@ -74,7 +79,7 @@ class App extends React.PureComponent<Pick<WithRouterProps, 'params' | 'router'>
if (this.mounted) {
this.setState({ canCreate: actions.create, loading: false, qualityGates });
- if (!this.props.params.id) {
+ if (!this.props.id) {
this.openDefault(qualityGates);
}
}
@@ -89,7 +94,7 @@ class App extends React.PureComponent<Pick<WithRouterProps, 'params' | 'router'>
openDefault(qualityGates: QualityGate[]) {
const defaultQualityGate = qualityGates.find(gate => Boolean(gate.isDefault))!;
- this.props.router.replace(getQualityGateUrl(String(defaultQualityGate.id)));
+ this.props.navigate(getQualityGateUrl(String(defaultQualityGate.id)), { replace: true });
}
handleSetDefault = (qualityGate: QualityGate) => {
@@ -106,7 +111,7 @@ class App extends React.PureComponent<Pick<WithRouterProps, 'params' | 'router'>
};
render() {
- const { id } = this.props.params;
+ const { id } = this.props;
const { canCreate, qualityGates } = this.state;
const defaultTitle = translate('quality_gates.page');
@@ -148,4 +153,9 @@ class App extends React.PureComponent<Pick<WithRouterProps, 'params' | 'router'>
}
}
-export default App;
+export default function AppWrapper() {
+ const params = useParams();
+ const navigate = useNavigate();
+
+ return <App id={params['id']} navigate={navigate} />;
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/CopyQualityGateForm.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/CopyQualityGateForm.tsx
index a76b8214625..d65b53ca2a7 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/CopyQualityGateForm.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/CopyQualityGateForm.tsx
@@ -31,7 +31,7 @@ interface Props {
onClose: () => void;
onCopy: () => Promise<void>;
qualityGate: QualityGate;
- router: Pick<Router, 'push'>;
+ router: Router;
}
interface State {
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/CreateQualityGateForm.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/CreateQualityGateForm.tsx
index d8d874bd9de..1dab545b08e 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/CreateQualityGateForm.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/CreateQualityGateForm.tsx
@@ -29,7 +29,7 @@ import { getQualityGateUrl } from '../../../helpers/urls';
interface Props {
onClose: () => void;
onCreate: () => Promise<void>;
- router: Pick<Router, 'push'>;
+ router: Router;
}
interface State {
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteQualityGateForm.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteQualityGateForm.tsx
index d55af0c4c6a..dba0240f8de 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteQualityGateForm.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteQualityGateForm.tsx
@@ -29,7 +29,7 @@ import { QualityGate } from '../../../types/types';
interface Props {
onDelete: () => Promise<void>;
qualityGate: QualityGate;
- router: Pick<Router, 'push'>;
+ router: Router;
}
export class DeleteQualityGateForm extends React.PureComponent<Props> {
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/List.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/List.tsx
index b6d63e089e1..e2fdcd567ab 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/List.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/List.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 { NavLink } from 'react-router-dom';
import { translate } from '../../../helpers/l10n';
import { getQualityGateUrl } from '../../../helpers/urls';
import { QualityGate } from '../../../types/types';
@@ -32,8 +32,7 @@ export default function List({ qualityGates }: Props) {
return (
<div className="list-group" role="menu">
{qualityGates.map(qualityGate => (
- <Link
- activeClassName="active"
+ <NavLink
className="list-group-item display-flex-center"
role="menuitem"
data-id={qualityGate.id}
@@ -44,7 +43,7 @@ export default function List({ qualityGates }: Props) {
<span className="badge little-spacer-left">{translate('default')}</span>
)}
{qualityGate.isBuiltIn && <BuiltInQualityGateBadge className="little-spacer-left" />}
- </Link>
+ </NavLink>
))}
</div>
);
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/App-it.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/App-it.tsx
index fa001b90f96..a376ce36407 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/App-it.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/App-it.tsx
@@ -359,9 +359,16 @@ describe('The Project section', () => {
});
describe('The Permissions section', () => {
- it('should not show button to grant permission when user is not admin', () => {
+ it('should not show button to grant permission when user is not admin', async () => {
renderQualityGateApp();
+ // await just to make sure we've loaded the page
+ expect(
+ await screen.findByRole('menuitem', {
+ name: `${handler.getDefaultQualityGate().name} default`
+ })
+ ).toBeInTheDocument();
+
expect(screen.queryByText('quality_gates.permissions')).not.toBeInTheDocument();
});
it('should show button to grant permission when user is admin', async () => {
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/routes.tsx b/server/sonar-web/src/main/js/apps/quality-gates/routes.tsx
new file mode 100644
index 00000000000..98f0a4fedd7
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-gates/routes.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 React from 'react';
+import { Route } from 'react-router-dom';
+import App from './components/App';
+
+const routes = () => (
+ <Route path="quality_gates">
+ <Route index={true} element={<App />} />
+ <Route path="show/:id" element={<App />} />
+ </Route>
+);
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.tsx
index 227dfca2a77..367ee4be80b 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.tsx
@@ -20,7 +20,7 @@
import { isSameMinute } from 'date-fns';
import { sortBy } from 'lodash';
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
import { parseDate } from '../../../helpers/dates';
import { translate } from '../../../helpers/l10n';
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx
index 4e042153c4a..8b4956b25d9 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx
@@ -18,19 +18,21 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { WithRouterProps } from 'react-router';
import { getProfileChangelog } from '../../../api/quality-profiles';
-import { withRouter } from '../../../components/hoc/withRouter';
+import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
import { parseDate, toShortNotSoISOString } from '../../../helpers/dates';
import { translate } from '../../../helpers/l10n';
+import { withQualityProfilesContext } from '../qualityProfilesContext';
import { Profile, ProfileChangelogEvent } from '../types';
import { getProfileChangelogPath } from '../utils';
import Changelog from './Changelog';
import ChangelogEmpty from './ChangelogEmpty';
import ChangelogSearch from './ChangelogSearch';
-interface Props extends Pick<WithRouterProps, 'router' | 'location'> {
+interface Props {
profile: Profile;
+ location: Location;
+ router: Router;
}
interface State {
@@ -166,4 +168,4 @@ export class ChangelogContainer extends React.PureComponent<Props, State> {
}
}
-export default withRouter(ChangelogContainer);
+export default withQualityProfilesContext(withRouter(ChangelogContainer));
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/Changelog-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/Changelog-test.tsx
index 0a84d0af275..0aa89d41eec 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/Changelog-test.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/Changelog-test.tsx
@@ -68,7 +68,7 @@ it('should render action', () => {
it('should render rule', () => {
const events = [createEvent()];
const changelog = shallow(<Changelog events={events} />);
- expect(changelog.find('Link').prop('to')).toHaveProperty('query', { rule_key: 'squid1234' });
+ expect(changelog.find('Link').prop('to')).toHaveProperty('search', '?rule_key=squid1234');
});
it('should render ChangesList', () => {
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.tsx
index fa27be17394..ee9d0ce8e91 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.tsx
@@ -18,16 +18,19 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { withRouter, WithRouterProps } from 'react-router';
import { compareProfiles, CompareResponse } from '../../../api/quality-profiles';
+import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
+import { withQualityProfilesContext } from '../qualityProfilesContext';
import { Profile } from '../types';
import { getProfileComparePath } from '../utils';
import ComparisonForm from './ComparisonForm';
import ComparisonResults from './ComparisonResults';
-interface Props extends WithRouterProps {
+interface Props {
profile: Profile;
profiles: Profile[];
+ location: Location;
+ router: Router;
}
type State = { loading: boolean } & Partial<CompareResponse>;
@@ -123,4 +126,4 @@ class ComparisonContainer extends React.PureComponent<Props, State> {
}
}
-export default withRouter(ComparisonContainer);
+export default withQualityProfilesContext(withRouter(ComparisonContainer));
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResults.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResults.tsx
index 118fcb95397..4ced3242a18 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResults.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResults.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 { CompareResponse, Profile } from '../../../api/quality-profiles';
import ChevronLeftIcon from '../../../components/icons/ChevronLeftIcon';
import ChevronRightIcon from '../../../components/icons/ChevronRightIcon';
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonResults-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonResults-test.tsx
index 1e4f2a55f2d..bfab84c2975 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonResults-test.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonResults-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 { Profile } from '../../../../api/quality-profiles';
import ComparisonEmpty from '../ComparisonEmpty';
import ComparisonResults from '../ComparisonResults';
@@ -75,10 +75,7 @@ it('should compare', () => {
const leftDiffs = output.find('.js-comparison-in-left');
expect(leftDiffs.length).toBe(1);
expect(leftDiffs.find(Link).length).toBe(1);
- expect(leftDiffs.find(Link).prop('to')).toHaveProperty('query', {
- rule_key: 'rule1',
- open: 'rule1'
- });
+ expect(leftDiffs.find(Link).prop('to')).toHaveProperty('search', '?rule_key=rule1&open=rule1');
expect(leftDiffs.find(Link).prop('children')).toContain('rule1');
expect(leftDiffs.find('SeverityIcon').length).toBe(1);
expect(leftDiffs.find('SeverityIcon').prop('severity')).toBe('BLOCKER');
@@ -91,7 +88,7 @@ it('should compare', () => {
.at(0)
.find(Link)
.prop('to')
- ).toHaveProperty('query', { rule_key: 'rule2', open: 'rule2' });
+ ).toHaveProperty('search', '?rule_key=rule2&open=rule2');
expect(
rightDiffs
.at(0)
@@ -113,7 +110,7 @@ it('should compare', () => {
.find(Link)
.at(0)
.prop('to')
- ).toHaveProperty('query', { rule_key: 'rule4', open: 'rule4' });
+ ).toHaveProperty('search', '?rule_key=rule4&open=rule4');
expect(
modifiedDiffs
.find(Link)
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx
index 62fcbf30d0a..e3c36f91310 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx
@@ -44,7 +44,7 @@ import ProfileModalForm from './ProfileModalForm';
interface Props {
className?: string;
profile: Profile;
- router: Pick<Router, 'push' | 'replace'>;
+ router: Router;
updateProfiles: () => Promise<void>;
}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.tsx
index 94de28abc0c..a2a4be95c85 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.tsx
@@ -19,67 +19,53 @@
*/
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
-import { WithRouterProps } from 'react-router';
+import { Outlet, useSearchParams } from 'react-router-dom';
+import { useLocation } from '../../../components/hoc/withRouter';
import ProfileHeader from '../details/ProfileHeader';
-import { Profile } from '../types';
+import { QualityProfilesContextProps, withQualityProfilesContext } from '../qualityProfilesContext';
import ProfileNotFound from './ProfileNotFound';
-interface Props {
- children: React.ReactElement<any>;
- profiles: Profile[];
- updateProfiles: () => Promise<void>;
-}
+export function ProfileContainer(props: QualityProfilesContextProps) {
+ const [_, setSearchParams] = useSearchParams();
+ const location = useLocation();
-export default class ProfileContainer extends React.PureComponent<Props & WithRouterProps> {
- componentDidMount() {
- const { location, profiles, router } = this.props;
- if (location.query.key) {
- // try to find a quality profile with the given key
- // if managed to find one, redirect to a new version
- // otherwise do nothing, `render` will show not found page
- const profile = profiles.find(profile => profile.key === location.query.key);
- if (profile) {
- router.replace({
- pathname: location.pathname,
- query: { language: profile.language, name: profile.name }
- });
- }
- }
- }
+ const { key, language, name } = location.query;
- render() {
- const { profiles, location, ...other } = this.props;
- const { key, language, name } = location.query;
-
- if (key) {
- // if there is a `key` parameter,
- // then if we managed to find a quality profile with this key
- // then we will be redirected in `componentDidMount`
- // otherwise show `ProfileNotFound`
- const profile = profiles.find(profile => profile.key === location.query.key);
- return profile ? null : <ProfileNotFound />;
- }
+ const { profiles } = props;
- const profile = profiles.find(
- profile => profile.language === language && profile.name === name
- );
+ // try to find a quality profile with the given key
+ // if managed to find one, redirect to a new version
+ // otherwise show not found page
+ const profileForKey = key && profiles.find(p => p.key === location.query.key);
- if (!profile) {
- return <ProfileNotFound />;
+ React.useEffect(() => {
+ if (profileForKey) {
+ setSearchParams({ language: profileForKey.language, name: profileForKey.name });
}
+ });
- const child = React.cloneElement(this.props.children, {
- profile,
- profiles,
- ...other
- });
+ if (key) {
+ return profileForKey ? null : <ProfileNotFound />;
+ }
+
+ const profile = profiles.find(p => p.language === language && p.name === name);
- return (
- <div id="quality-profile">
- <Helmet defer={false} title={profile.name} />
- <ProfileHeader profile={profile} updateProfiles={this.props.updateProfiles} />
- {child}
- </div>
- );
+ if (!profile) {
+ return <ProfileNotFound />;
}
+
+ const context: QualityProfilesContextProps = {
+ profile,
+ ...props
+ };
+
+ return (
+ <div id="quality-profile">
+ <Helmet defer={false} title={profile.name} />
+ <ProfileHeader profile={profile} updateProfiles={props.updateProfiles} />
+ <Outlet context={context} />
+ </div>
+ );
}
+
+export default withQualityProfilesContext(ProfileContainer);
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileLink.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileLink.tsx
index 9bfaf8f101a..f9bacdde9b5 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileLink.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileLink.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 { NavLink } from 'react-router-dom';
import { getProfilePath } from '../utils';
interface Props {
@@ -30,8 +30,11 @@ interface Props {
export default function ProfileLink({ name, language, children, ...other }: Props) {
return (
- <Link activeClassName="link-no-underline" to={getProfilePath(name, language)} {...other}>
+ <NavLink
+ className={({ isActive }) => (isActive ? 'link-no-underline' : '')}
+ to={getProfilePath(name, language)}
+ {...other}>
{children}
- </Link>
+ </NavLink>
);
}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileNotFound.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileNotFound.tsx
index 2f7462b95ba..8c4e6f2c318 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileNotFound.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileNotFound.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { IndexLink } from 'react-router';
+import { NavLink } from 'react-router-dom';
import { translate } from '../../../helpers/l10n';
import { PROFILE_PATH } from '../utils';
@@ -26,9 +26,9 @@ export default function ProfileNotFound() {
return (
<div className="quality-profile-not-found">
<div className="note spacer-bottom">
- <IndexLink className="text-muted" to={PROFILE_PATH}>
+ <NavLink end={true} className="text-muted" to={PROFILE_PATH}>
{translate('quality_profiles.page')}
- </IndexLink>
+ </NavLink>
</div>
<div>{translate('quality_profiles.not_found')}</div>
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/QualityProfilesApp.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/QualityProfilesApp.tsx
index 7be27e7b99c..1603ff9e0f5 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/components/QualityProfilesApp.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/QualityProfilesApp.tsx
@@ -19,17 +19,18 @@
*/
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
+import { Outlet } from 'react-router-dom';
import { Actions, getExporters, searchQualityProfiles } from '../../../api/quality-profiles';
import withLanguagesContext from '../../../app/components/languages/withLanguagesContext';
import Suggestions from '../../../components/embed-docs-modal/Suggestions';
import { translate } from '../../../helpers/l10n';
import { Languages } from '../../../types/languages';
+import { QualityProfilesContextProps } from '../qualityProfilesContext';
import '../styles.css';
import { Exporter, Profile } from '../types';
import { sortProfiles } from '../utils';
interface Props {
- children: React.ReactElement;
languages: Languages;
}
@@ -87,18 +88,22 @@ export class QualityProfilesApp extends React.PureComponent<Props, State> {
};
renderChild() {
- if (this.state.loading) {
+ const { actions, loading, profiles, exporters } = this.state;
+
+ if (loading) {
return <i className="spinner" />;
}
const finalLanguages = Object.values(this.props.languages);
- return React.cloneElement(this.props.children, {
- actions: this.state.actions || {},
- profiles: this.state.profiles || [],
+ const context: QualityProfilesContextProps = {
+ actions: actions || {},
+ profiles: profiles || [],
languages: finalLanguages,
- exporters: this.state.exporters,
+ exporters: exporters || [],
updateProfiles: this.updateProfiles
- });
+ };
+
+ return <Outlet context={context} />;
}
render() {
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileActions-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileActions-test.tsx
index 780be44ce42..6f657c9da7f 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileActions-test.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileActions-test.tsx
@@ -29,6 +29,7 @@ import {
} from '../../../../api/quality-profiles';
import { mockQualityProfile, mockRouter } from '../../../../helpers/testMocks';
import { click, waitAndUpdate } from '../../../../helpers/testUtils';
+import { queryToSearch } from '../../../../helpers/urls';
import { ProfileActionModals } from '../../types';
import { PROFILE_PATH } from '../../utils';
import DeleteProfileForm from '../DeleteProfileForm';
@@ -119,7 +120,7 @@ describe('copy a profile', () => {
expect(updateProfiles).toBeCalled();
expect(push).toBeCalledWith({
pathname: '/profiles/show',
- query: { language: 'js', name }
+ search: queryToSearch({ name, language: 'js' })
});
expect(wrapper.find(ProfileModalForm).exists()).toBe(false);
});
@@ -182,7 +183,7 @@ describe('extend a profile', () => {
expect(push).toBeCalledWith({
pathname: '/profiles/show',
- query: { language: 'js', name }
+ search: queryToSearch({ name, language: 'js' })
});
expect(wrapper.find(ProfileModalForm).exists()).toBe(false);
});
@@ -234,7 +235,7 @@ describe('rename a profile', () => {
expect(updateProfiles).toBeCalled();
expect(push).toBeCalledWith({
pathname: '/profiles/show',
- query: { language: 'js', name }
+ search: queryToSearch({ name, language: 'js' })
});
expect(wrapper.find(ProfileModalForm).exists()).toBe(false);
});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileContainer-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileContainer-test.tsx
index ba092cbfe3e..48886252019 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileContainer-test.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileContainer-test.tsx
@@ -17,57 +17,77 @@
* 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 { Helmet } from 'react-helmet-async';
-import { WithRouterProps } from 'react-router';
-import { mockLocation, mockQualityProfile, mockRouter } from '../../../../helpers/testMocks';
-import ProfileHeader from '../../details/ProfileHeader';
-import ProfileContainer from '../ProfileContainer';
-import ProfileNotFound from '../ProfileNotFound';
+import { HelmetProvider } from 'react-helmet-async';
+import { MemoryRouter, Route, Routes } from 'react-router-dom';
+import { mockQualityProfile } from '../../../../helpers/testMocks';
+import {
+ QualityProfilesContextProps,
+ withQualityProfilesContext
+} from '../../qualityProfilesContext';
+import { Profile } from '../../types';
+import { ProfileContainer } from '../ProfileContainer';
-it('should render ProfileHeader', () => {
- const targetProfile = mockQualityProfile({ name: 'fake' });
- const profiles = [targetProfile, mockQualityProfile({ name: 'another' })];
- const updateProfiles = jest.fn();
- const location = mockLocation({ pathname: '', query: { language: 'js', name: 'fake' } });
+it('should render the header and child', () => {
+ const targetProfile = mockQualityProfile({ name: 'profile1' });
+ renderProfileContainer('/?language=js&name=profile1', {
+ profiles: [mockQualityProfile({ language: 'Java', name: 'profile1' }), targetProfile]
+ });
- const output = shallowRender({ profiles, updateProfiles, location });
-
- const header = output.find(ProfileHeader);
- expect(header.length).toBe(1);
- expect(header.prop('profile')).toBe(targetProfile);
- expect(header.prop('updateProfiles')).toBe(updateProfiles);
+ expect(screen.getByText('profile1')).toBeInTheDocument();
});
-it('should render ProfileNotFound', () => {
- const profiles = [mockQualityProfile({ name: 'fake' }), mockQualityProfile({ name: 'another' })];
- const location = mockLocation({ pathname: '', query: { language: 'js', name: 'random' } });
-
- const output = shallowRender({ profiles, location });
+it('should render "not found"', () => {
+ renderProfileContainer('/?language=java&name=profile2', {
+ profiles: [mockQualityProfile({ name: 'profile1' }), mockQualityProfile({ name: 'profile2' })]
+ });
- expect(output.is(ProfileNotFound)).toBe(true);
+ expect(screen.getByText('quality_profiles.not_found')).toBeInTheDocument();
});
-it('should render Helmet', () => {
- const name = 'First Profile';
- const profiles = [mockQualityProfile({ name })];
- const updateProfiles = jest.fn();
- const location = mockLocation({ pathname: '', query: { language: 'js', name } });
+it('should render "not found" for wrong key', () => {
+ renderProfileContainer('/?key=wrongKey', {
+ profiles: [mockQualityProfile({ key: 'profileKey' })]
+ });
+
+ expect(screen.getByText('quality_profiles.not_found')).toBeInTheDocument();
+});
- const output = shallowRender({ profiles, updateProfiles, location });
+it('should handle getting profile by key', () => {
+ renderProfileContainer('/?key=profileKey', {
+ profiles: [mockQualityProfile({ key: 'profileKey', name: 'found the profile' })]
+ });
- const helmet = output.find(Helmet);
- expect(helmet.length).toBe(1);
- expect(helmet.prop('title')).toContain(name);
+ expect(screen.getByText('found the profile')).toBeInTheDocument();
});
-function shallowRender(overrides: Partial<ProfileContainer['props']> = {}) {
- const routerProps = { router: mockRouter(), ...overrides } as WithRouterProps;
+function Child(props: { profile?: Profile }) {
+ return <div>{JSON.stringify(props.profile)}</div>;
+}
+
+const WrappedChild = withQualityProfilesContext(Child);
- return shallow(
- <ProfileContainer profiles={[]} updateProfiles={jest.fn()} {...routerProps} {...overrides}>
- <div />
- </ProfileContainer>
+function renderProfileContainer(path: string, overrides: Partial<QualityProfilesContextProps>) {
+ return render(
+ <HelmetProvider context={{}}>
+ <MemoryRouter initialEntries={[path]}>
+ <Routes>
+ <Route
+ element={
+ <ProfileContainer
+ actions={{}}
+ exporters={[]}
+ languages={[]}
+ profiles={[]}
+ updateProfiles={jest.fn()}
+ {...overrides}
+ />
+ }>
+ <Route path="*" element={<WrappedChild />} />
+ </Route>
+ </Routes>
+ </MemoryRouter>
+ </HelmetProvider>
);
}
diff --git a/server/sonar-web/src/main/js/app/components/extensions/__tests__/PortfolioPage-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileLink-test.tsx
index 1e1166e1807..7c78daea16a 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/__tests__/PortfolioPage-test.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileLink-test.tsx
@@ -17,25 +17,23 @@
* 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/react';
import * as React from 'react';
-import { mockComponent } from '../../../../helpers/mocks/component';
-import { mockLocation, mockRouter } from '../../../../helpers/testMocks';
-import { PortfolioPage, PortfolioPageProps } from '../PortfolioPage';
+import { renderComponent } from '../../../../helpers/testReactTestingUtils';
+import ProfileLink from '../ProfileLink';
-it('should render correctly', () => {
- expect(shallowRender()).toMatchSnapshot();
+it('should be active when on the right path', () => {
+ renderProfileLink('/profiles/show');
+
+ expect(screen.getByRole('link')).toHaveClass('link-no-underline');
+});
+
+it('should be inactive when on a different path', () => {
+ renderProfileLink('/toto');
+
+ expect(screen.getByRole('link')).not.toHaveClass('link-no-underline');
});
-function shallowRender(props?: Partial<PortfolioPageProps>) {
- return shallow<PortfolioPageProps>(
- <PortfolioPage
- component={mockComponent()}
- location={mockLocation()}
- router={mockRouter()}
- routes={[]}
- params={{}}
- {...props}
- />
- );
+function renderProfileLink(path: string) {
+ return renderComponent(<ProfileLink language="js" name="SonarWay" />, path);
}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/QualityProfilesApp-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/QualityProfilesApp-test.tsx
index f7e68bfae59..b8acc4ca54e 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/QualityProfilesApp-test.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/QualityProfilesApp-test.tsx
@@ -55,11 +55,13 @@ it('should render child with additional props', () => {
wrapper.setState({ loading: false, actions, profiles, exporters });
expect(wrapper.childAt(2).props()).toEqual({
- actions,
- profiles,
- languages: [language],
- exporters,
- updateProfiles: wrapper.instance().updateProfiles
+ context: {
+ actions,
+ profiles,
+ languages: [language],
+ exporters,
+ updateProfiles: wrapper.instance().updateProfiles
+ }
});
});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/ProfileActions-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/ProfileActions-test.tsx.snap
index ffb04e929d3..d131a69701d 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/ProfileActions-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/ProfileActions-test.tsx.snap
@@ -8,10 +8,7 @@ exports[`renders correctly: all permissions 1`] = `
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "false",
- "qprofile": "key",
- },
+ "search": "?qprofile=key&activation=false",
}
}
>
@@ -29,10 +26,7 @@ exports[`renders correctly: all permissions 1`] = `
to={
Object {
"pathname": "/profiles/compare",
- "query": Object {
- "language": "js",
- "name": "name",
- },
+ "search": "?language=js&name=name",
}
}
>
@@ -89,10 +83,7 @@ exports[`renders correctly: copy modal 1`] = `
to={
Object {
"pathname": "/profiles/compare",
- "query": Object {
- "language": "js",
- "name": "name",
- },
+ "search": "?language=js&name=name",
}
}
>
@@ -141,10 +132,7 @@ exports[`renders correctly: delete modal 1`] = `
to={
Object {
"pathname": "/profiles/compare",
- "query": Object {
- "language": "js",
- "name": "name",
- },
+ "search": "?language=js&name=name",
}
}
>
@@ -184,10 +172,7 @@ exports[`renders correctly: edit only 1`] = `
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "false",
- "qprofile": "key",
- },
+ "search": "?qprofile=key&activation=false",
}
}
>
@@ -205,10 +190,7 @@ exports[`renders correctly: edit only 1`] = `
to={
Object {
"pathname": "/profiles/compare",
- "query": Object {
- "language": "js",
- "name": "name",
- },
+ "search": "?language=js&name=name",
}
}
>
@@ -239,10 +221,7 @@ exports[`renders correctly: extend modal 1`] = `
to={
Object {
"pathname": "/profiles/compare",
- "query": Object {
- "language": "js",
- "name": "name",
- },
+ "search": "?language=js&name=name",
}
}
>
@@ -291,10 +270,7 @@ exports[`renders correctly: no permissions 1`] = `
to={
Object {
"pathname": "/profiles/compare",
- "query": Object {
- "language": "js",
- "name": "name",
- },
+ "search": "?language=js&name=name",
}
}
>
@@ -319,10 +295,7 @@ exports[`renders correctly: rename modal 1`] = `
to={
Object {
"pathname": "/profiles/compare",
- "query": Object {
- "language": "js",
- "name": "name",
- },
+ "search": "?language=js&name=name",
}
}
>
@@ -371,10 +344,7 @@ exports[`should not allow to set a profile as the default if the profile has no
to={
Object {
"pathname": "/profiles/compare",
- "query": Object {
- "language": "js",
- "name": "name",
- },
+ "search": "?language=js&name=name",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/QualityProfilesApp-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/QualityProfilesApp-test.tsx.snap
index 891a21afe2d..e362fd0a400 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/QualityProfilesApp-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/QualityProfilesApp-test.tsx.snap
@@ -13,12 +13,16 @@ exports[`should render correctly: full 1`] = `
prioritizeSeoTags={false}
title="quality_profiles.page"
/>
- <div
- actions={Object {}}
- exporters={Array []}
- languages={Array []}
- profiles={Array []}
- updateProfiles={[Function]}
+ <Outlet
+ context={
+ Object {
+ "actions": Object {},
+ "exporters": Array [],
+ "languages": Array [],
+ "profiles": Array [],
+ "updateProfiles": [Function],
+ }
+ }
/>
</div>
`;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.tsx
index 534ab5c4480..91f9445f768 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.tsx
@@ -20,6 +20,7 @@
import * as React from 'react';
import { Alert } from '../../../components/ui/Alert';
import { translate } from '../../../helpers/l10n';
+import { withQualityProfilesContext } from '../qualityProfilesContext';
import { Exporter, Profile } from '../types';
import ProfileExporters from './ProfileExporters';
import ProfileInheritance from './ProfileInheritance';
@@ -34,7 +35,7 @@ export interface ProfileDetailsProps {
updateProfiles: () => Promise<void>;
}
-export default function ProfileDetails(props: ProfileDetailsProps) {
+export function ProfileDetails(props: ProfileDetailsProps) {
const { profile } = props;
return (
<div>
@@ -69,3 +70,5 @@ export default function ProfileDetails(props: ProfileDetailsProps) {
</div>
);
}
+
+export default withQualityProfilesContext(ProfileDetails);
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.tsx
index 61a2caccda8..1d4b6ed5d4b 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { IndexLink, Link } from 'react-router';
+import { Link, NavLink } from 'react-router-dom';
import Tooltip from '../../../components/controls/Tooltip';
import DateFromNow from '../../../components/intl/DateFromNow';
import { translate } from '../../../helpers/l10n';
@@ -40,9 +40,9 @@ export default class ProfileHeader extends React.PureComponent<Props> {
return (
<header className="page-header quality-profile-header">
<div className="note spacer-bottom">
- <IndexLink className="text-muted" to={PROFILE_PATH}>
+ <NavLink end={true} className="text-muted" to={PROFILE_PATH}>
{translate('quality_profiles.page')}
- </IndexLink>
+ </NavLink>
{' / '}
<Link className="text-muted" to={getProfilesForLanguagePath(profile.language)}>
{profile.languageName}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx
index 087bfb2c45d..0dcc970ab01 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.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 { getProfileProjects } from '../../../api/quality-profiles';
import { Button } from '../../../components/controls/buttons';
import ListFooter from '../../../components/controls/ListFooter';
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx
index da78df791fc..cd5113bf17c 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx
@@ -19,7 +19,7 @@
*/
import { keyBy } from 'lodash';
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { getQualityProfile } from '../../../api/quality-profiles';
import { searchRules, takeFacet } from '../../../api/rules';
import { Button } from '../../../components/controls/buttons';
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesDeprecatedWarning.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesDeprecatedWarning.tsx
index 97ab2bba0b4..bde0c6a01f4 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesDeprecatedWarning.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesDeprecatedWarning.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 HelpTooltip from '../../../components/controls/HelpTooltip';
import { translate } from '../../../helpers/l10n';
import { getDeprecatedActiveRulesUrl } from '../../../helpers/urls';
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowOfType.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowOfType.tsx
index 668efb5b377..2243f339ffe 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowOfType.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowOfType.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 IssueTypeIcon from '../../../components/icons/IssueTypeIcon';
import { translate } from '../../../helpers/l10n';
import { formatMeasure } from '../../../helpers/measures';
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowTotal.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowTotal.tsx
index 488c6aa85fa..45bd13b3f6c 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowTotal.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowTotal.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 { formatMeasure } from '../../../helpers/measures';
import { getRulesUrl } from '../../../helpers/urls';
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesSonarWayComparison.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesSonarWayComparison.tsx
index dd2eab3bd6d..94831180faa 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesSonarWayComparison.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesSonarWayComparison.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 HelpTooltip from '../../../components/controls/HelpTooltip';
import { translate } from '../../../helpers/l10n';
import { getRulesUrl } from '../../../helpers/urls';
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileDetails-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileDetails-test.tsx
index 870e7d13da1..6d6b9d8eebf 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileDetails-test.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileDetails-test.tsx
@@ -20,7 +20,7 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { mockQualityProfile } from '../../../../helpers/testMocks';
-import ProfileDetails, { ProfileDetailsProps } from '../ProfileDetails';
+import { ProfileDetails, ProfileDetailsProps } from '../ProfileDetails';
it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileHeader-test.tsx.snap
index 41bb8222774..db6386f4230 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileHeader-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileHeader-test.tsx.snap
@@ -7,23 +7,20 @@ exports[`should render correctly 1`] = `
<div
className="note spacer-bottom"
>
- <IndexLink
+ <NavLink
className="text-muted"
+ end={true}
to="/profiles"
>
quality_profiles.page
- </IndexLink>
+ </NavLink>
/
<Link
className="text-muted"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/profiles",
- "query": Object {
- "language": "js",
- },
+ "search": "?language=js",
}
}
>
@@ -71,15 +68,10 @@ exports[`should render correctly 1`] = `
<li>
<Link
className="button"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/profiles/changelog",
- "query": Object {
- "language": "js",
- "name": "name",
- },
+ "search": "?language=js&name=name",
}
}
>
@@ -120,23 +112,20 @@ exports[`should render correctly: for default profile 1`] = `
<div
className="note spacer-bottom"
>
- <IndexLink
+ <NavLink
className="text-muted"
+ end={true}
to="/profiles"
>
quality_profiles.page
- </IndexLink>
+ </NavLink>
/
<Link
className="text-muted"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/profiles",
- "query": Object {
- "language": "js",
- },
+ "search": "?language=js",
}
}
>
@@ -193,15 +182,10 @@ exports[`should render correctly: for default profile 1`] = `
<li>
<Link
className="button"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/profiles/changelog",
- "query": Object {
- "language": "js",
- "name": "name",
- },
+ "search": "?language=js&name=name",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileProjects-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileProjects-test.tsx.snap
index d69b1c67fa8..f7310b99075 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileProjects-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileProjects-test.tsx.snap
@@ -37,15 +37,10 @@ exports[`should render correctly: default 1`] = `
>
<Link
className="link-with-icon"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "id": "org.sonarsource.xml:xml",
- },
+ "search": "?id=org.sonarsource.xml%3Axml",
}
}
>
@@ -142,15 +137,10 @@ exports[`should render correctly: no active rules, but associated projects 1`] =
>
<Link
className="link-with-icon"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "id": "org.sonarsource.xml:xml",
- },
+ "search": "?id=org.sonarsource.xml%3Axml",
}
}
>
@@ -232,15 +222,10 @@ exports[`should render correctly: no rights 1`] = `
>
<Link
className="link-with-icon"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "id": "org.sonarsource.xml:xml",
- },
+ "search": "?id=org.sonarsource.xml%3Axml",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRules-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRules-test.tsx.snap
index c2af3488e98..2bfdc44d131 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRules-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRules-test.tsx.snap
@@ -74,15 +74,10 @@ exports[`should render the quality profiles rules with sonarway comparison 1`] =
exports[`should show a button to activate more rules for admins 1`] = `
<Link
className="button js-activate-rules"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "false",
- "qprofile": "key",
- },
+ "search": "?qprofile=key&activation=false",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesDeprecatedWarning-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesDeprecatedWarning-test.tsx.snap
index 4a03b4e8a41..9b9d0902b5f 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesDeprecatedWarning-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesDeprecatedWarning-test.tsx.snap
@@ -19,16 +19,10 @@ exports[`should render correctly 1`] = `
</span>
<Link
className="pull-right"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "bar",
- "statuses": "DEPRECATED",
- },
+ "search": "?qprofile=bar&activation=true&statuses=DEPRECATED",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowOfType-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowOfType-test.tsx.snap
index c9bbffe4449..74a4d1abcf2 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowOfType-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowOfType-test.tsx.snap
@@ -15,16 +15,10 @@ exports[`should render correctly 1`] = `
className="thin nowrap text-right"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "bar",
- "types": "BUG",
- },
+ "search": "?qprofile=bar&activation=true&types=BUG",
}
}
>
@@ -36,16 +30,10 @@ exports[`should render correctly 1`] = `
>
<Link
className="small text-muted"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "false",
- "qprofile": "bar",
- "types": "BUG",
- },
+ "search": "?qprofile=bar&activation=false&types=BUG",
}
}
>
@@ -70,16 +58,10 @@ exports[`should render correctly if there is 0 rules 1`] = `
className="thin nowrap text-right"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "bar",
- "types": "VULNERABILITY",
- },
+ "search": "?qprofile=bar&activation=true&types=VULNERABILITY",
}
}
>
@@ -113,16 +95,10 @@ exports[`should render correctly if there is missing data 1`] = `
className="thin nowrap text-right"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "bar",
- "types": "VULNERABILITY",
- },
+ "search": "?qprofile=bar&activation=true&types=VULNERABILITY",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowTotal-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowTotal-test.tsx.snap
index 226dd57670d..5e18680e3b4 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowTotal-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowTotal-test.tsx.snap
@@ -11,15 +11,10 @@ exports[`should render correctly 1`] = `
className="thin nowrap text-right"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "bar",
- },
+ "search": "?qprofile=bar&activation=true",
}
}
>
@@ -33,15 +28,10 @@ exports[`should render correctly 1`] = `
>
<Link
className="small text-muted"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "false",
- "qprofile": "bar",
- },
+ "search": "?qprofile=bar&activation=false",
}
}
>
@@ -64,15 +54,10 @@ exports[`should render correctly if there is 0 rules 1`] = `
className="thin nowrap text-right"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "bar",
- },
+ "search": "?qprofile=bar&activation=true",
}
}
>
@@ -104,15 +89,10 @@ exports[`should render correctly if there is missing data 1`] = `
className="thin nowrap text-right"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "bar",
- },
+ "search": "?qprofile=bar&activation=true",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesSonarWayComparison-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesSonarWayComparison-test.tsx.snap
index 6567a60b75e..83633732e7b 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesSonarWayComparison-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesSonarWayComparison-test.tsx.snap
@@ -20,17 +20,10 @@ exports[`should render correctly 1`] = `
<Link
className="pull-right"
data-test="rules"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "false",
- "compareToProfile": "baz",
- "languages": "Java",
- "qprofile": "bar",
- },
+ "search": "?qprofile=bar&activation=false&compareToProfile=baz&languages=Java",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.tsx
index 8013f536b3f..18a7b1f33e3 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.tsx
@@ -19,7 +19,7 @@
*/
import { sortBy } from 'lodash';
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { getDeprecatedActiveRulesUrl } from '../../../helpers/urls';
import ProfileLink from '../components/ProfileLink';
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx
index 64bfa6654cf..155543ad1b0 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx
@@ -19,7 +19,7 @@
*/
import { sortBy } from 'lodash';
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { searchRules } from '../../../api/rules';
import { toShortNotSoISOString } from '../../../helpers/dates';
import { translate, translateWithParameters } from '../../../helpers/l10n';
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.tsx
index 13acc9e8a08..793033e8e6a 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.tsx
@@ -18,32 +18,28 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { Actions } from '../../../api/quality-profiles';
-import { Location } from '../../../components/hoc/withRouter';
-import { Profile } from '../types';
+import { useOutletContext, useSearchParams } from 'react-router-dom';
+import { QualityProfilesContextProps } from '../qualityProfilesContext';
import Evolution from './Evolution';
import PageHeader from './PageHeader';
import ProfilesList from './ProfilesList';
-interface Props {
- actions: Actions;
- languages: Array<{ key: string; name: string }>;
- location: Location;
- profiles: Profile[];
- updateProfiles: () => Promise<void>;
-}
+export default function HomeContainer() {
+ const context = useOutletContext<QualityProfilesContextProps>();
+ const [searchParams] = useSearchParams();
+
+ const selectedLanguage = searchParams.get('language') ?? undefined;
-export default function HomeContainer(props: Props) {
return (
<div>
- <PageHeader {...props} />
+ <PageHeader {...context} />
<div className="page-with-sidebar">
<div className="page-main">
- <ProfilesList {...props} />
+ <ProfilesList {...context} language={selectedLanguage} />
</div>
<div className="page-sidebar">
- <Evolution {...props} />
+ <Evolution {...context} />
</div>
</div>
</div>
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx
index 87a490fddb5..006b70a3490 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.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 { Actions } from '../../../api/quality-profiles';
import { Button } from '../../../components/controls/buttons';
import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
@@ -34,7 +34,7 @@ interface Props {
languages: Array<{ key: string; name: string }>;
location: Location;
profiles: Profile[];
- router: Pick<Router, 'push'>;
+ router: Router;
updateProfiles: () => Promise<void>;
}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.tsx
index 02fa9f9aeac..e749dcc9e12 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.tsx
@@ -17,7 +17,6 @@
* 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 { groupBy, pick, sortBy } from 'lodash';
import * as React from 'react';
import HelpTooltip from '../../../components/controls/HelpTooltip';
@@ -30,8 +29,8 @@ import ProfilesListHeader from './ProfilesListHeader';
import ProfilesListRow from './ProfilesListRow';
interface Props {
+ language?: string;
languages: Language[];
- location: Pick<Location, 'query'>;
profiles: Profile[];
updateProfiles: () => Promise<void>;
}
@@ -94,8 +93,7 @@ export default class ProfilesList extends React.PureComponent<Props> {
};
render() {
- const { profiles, languages } = this.props;
- const { language } = this.props.location.query;
+ const { profiles, languages, language } = this.props;
const profilesIndex: Dict<Profile[]> = groupBy<Profile>(profiles, profile => profile.language);
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListHeader.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListHeader.tsx
index b605ba81f5b..fe2bf6d96ff 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListHeader.tsx
@@ -26,7 +26,7 @@ import { getProfilesForLanguagePath, PROFILE_PATH } from '../utils';
interface Props {
currentFilter?: string;
languages: Array<{ key: string; name: string }>;
- router: Pick<Router, 'replace'>;
+ router: Router;
}
export class ProfilesListHeader extends React.PureComponent<Props> {
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.tsx
index 6bf8c8a38ec..72408e1905e 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.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 DateFromNow from '../../../components/intl/DateFromNow';
import { translate } from '../../../helpers/l10n';
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/ProfilesList-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/ProfilesList-test.tsx
index f835ff48328..3061318eac0 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/ProfilesList-test.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/ProfilesList-test.tsx
@@ -19,26 +19,21 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { mockLanguage, mockLocation, mockQualityProfile } from '../../../../helpers/testMocks';
+import { mockLanguage, mockQualityProfile } from '../../../../helpers/testMocks';
import ProfilesList from '../ProfilesList';
it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
- expect(
- shallowRender({ location: mockLocation({ query: { language: 'css' } }) })
- ).toMatchSnapshot();
+ expect(shallowRender({ language: 'css' })).toMatchSnapshot();
- expect(
- shallowRender({ location: mockLocation({ query: { language: 'unknown' } }) })
- ).toMatchSnapshot();
+ expect(shallowRender({ language: 'unknown' })).toMatchSnapshot();
});
function shallowRender(props: Partial<ProfilesList['props']> = {}) {
return shallow(
<ProfilesList
languages={[mockLanguage(), mockLanguage({ key: 'js', name: 'JS' })]}
- location={mockLocation()}
profiles={[
mockQualityProfile(),
mockQualityProfile({ language: 'css', languageName: 'CSS' })
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/EvolutionDeprecated-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/EvolutionDeprecated-test.tsx.snap
index 62a80256ecb..292172b31e5 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/EvolutionDeprecated-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/EvolutionDeprecated-test.tsx.snap
@@ -39,16 +39,10 @@ exports[`should render correctly 1`] = `
,
<Link
className="text-muted"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "qp-5",
- "statuses": "DEPRECATED",
- },
+ "search": "?qprofile=qp-5&activation=true&statuses=DEPRECATED",
}
}
>
@@ -92,16 +86,10 @@ exports[`should render correctly 1`] = `
,
<Link
className="text-muted"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "qp-4",
- "statuses": "DEPRECATED",
- },
+ "search": "?qprofile=qp-4&activation=true&statuses=DEPRECATED",
}
}
>
@@ -138,16 +126,10 @@ exports[`should render correctly 1`] = `
,
<Link
className="text-muted"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "qp-2",
- "statuses": "DEPRECATED",
- },
+ "search": "?qprofile=qp-2&activation=true&statuses=DEPRECATED",
}
}
>
@@ -177,16 +159,10 @@ exports[`should render correctly 1`] = `
,
<Link
className="text-muted"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "qp-3",
- "statuses": "DEPRECATED",
- },
+ "search": "?qprofile=qp-3&activation=true&statuses=DEPRECATED",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/PageHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/PageHeader-test.tsx.snap
index d880cd231c8..4cabe302e26 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/PageHeader-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/PageHeader-test.tsx.snap
@@ -17,8 +17,6 @@ exports[`should render correctly 1`] = `
quality_profiles.intro2
<Link
className="spacer-left"
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to={
Object {
@@ -67,8 +65,6 @@ exports[`should render correctly 2`] = `
quality_profiles.intro2
<Link
className="spacer-left"
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to={
Object {
@@ -123,8 +119,6 @@ exports[`should render correctly 3`] = `
quality_profiles.intro2
<Link
className="spacer-left"
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to={
Object {
@@ -173,8 +167,6 @@ exports[`should show a create form 1`] = `
quality_profiles.intro2
<Link
className="spacer-left"
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to={
Object {
@@ -196,7 +188,6 @@ exports[`should show a create form 1`] = `
}
location={
Object {
- "action": "PUSH",
"hash": "",
"key": "key",
"pathname": "/path",
@@ -264,8 +255,6 @@ exports[`should show a restore form 1`] = `
quality_profiles.intro2
<Link
className="spacer-left"
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to={
Object {
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/ProfilesListRow-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/ProfilesListRow-test.tsx.snap
index a19c915a89c..85d7ef07a13 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/ProfilesListRow-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/ProfilesListRow-test.tsx.snap
@@ -49,16 +49,10 @@ exports[`should render correctly: built-in profile 1`] = `
>
<Link
className="badge badge-error"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "key",
- "statuses": "DEPRECATED",
- },
+ "search": "?qprofile=key&activation=true&statuses=DEPRECATED",
}
}
>
@@ -67,15 +61,10 @@ exports[`should render correctly: built-in profile 1`] = `
</Tooltip>
</span>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "key",
- },
+ "search": "?qprofile=key&activation=true",
}
}
>
@@ -158,15 +147,10 @@ exports[`should render correctly: default 1`] = `
>
<div>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "key",
- },
+ "search": "?qprofile=key&activation=true",
}
}
>
@@ -262,16 +246,10 @@ exports[`should render correctly: default profile 1`] = `
>
<Link
className="badge badge-error"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "key",
- "statuses": "DEPRECATED",
- },
+ "search": "?qprofile=key&activation=true&statuses=DEPRECATED",
}
}
>
@@ -280,15 +258,10 @@ exports[`should render correctly: default profile 1`] = `
</Tooltip>
</span>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "key",
- },
+ "search": "?qprofile=key&activation=true",
}
}
>
@@ -378,16 +351,10 @@ exports[`should render correctly: with deprecated rules 1`] = `
>
<Link
className="badge badge-error"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "key",
- "statuses": "DEPRECATED",
- },
+ "search": "?qprofile=key&activation=true&statuses=DEPRECATED",
}
}
>
@@ -396,15 +363,10 @@ exports[`should render correctly: with deprecated rules 1`] = `
</Tooltip>
</span>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "activation": "true",
- "qprofile": "key",
- },
+ "search": "?qprofile=key&activation=true",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/qualityProfilesContext.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/qualityProfilesContext.tsx
new file mode 100644
index 00000000000..2aca327c296
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/qualityProfilesContext.tsx
@@ -0,0 +1,54 @@
+/*
+ * 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 React from 'react';
+import { useOutletContext } from 'react-router-dom';
+import { Actions } from '../../api/quality-profiles';
+import { Exporter, Profile } from '../../apps/quality-profiles/types';
+import { getWrappedDisplayName } from '../../components/hoc/utils';
+import { Language } from '../../types/languages';
+
+export interface QualityProfilesContextProps {
+ actions: Actions;
+ exporters: Exporter[];
+ languages: Language[];
+ profile?: Profile;
+ profiles: Profile[];
+ updateProfiles: () => Promise<void>;
+}
+
+export function withQualityProfilesContext<P extends Partial<QualityProfilesContextProps>>(
+ WrappedComponent: React.ComponentType<P>
+): React.ComponentType<Omit<P, keyof QualityProfilesContextProps>> {
+ function ComponentWithQualityProfilesProps(props: P) {
+ const context = useOutletContext<QualityProfilesContextProps>();
+ return <WrappedComponent {...props} {...context} />;
+ }
+
+ (ComponentWithQualityProfilesProps as React.FC<P>).displayName = getWrappedDisplayName(
+ WrappedComponent,
+ 'withQualityProfilesContext'
+ );
+
+ return ComponentWithQualityProfilesProps;
+}
+
+export function useQualityProfilesContext() {
+ return useOutletContext<QualityProfilesContextProps>();
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/routes.ts b/server/sonar-web/src/main/js/apps/quality-profiles/routes.ts
deleted file mode 100644
index 6e14d1a75af..00000000000
--- a/server/sonar-web/src/main/js/apps/quality-profiles/routes.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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 { lazyLoadComponent } from '../../components/lazyLoadComponent';
-
-const routes = [
- {
- component: lazyLoadComponent(() => import('./components/QualityProfilesApp')),
- indexRoute: { component: lazyLoadComponent(() => import('./home/HomeContainer')) },
- childRoutes: [
- {
- component: lazyLoadComponent(() => import('./components/ProfileContainer')),
- childRoutes: [
- {
- path: 'show',
- component: lazyLoadComponent(() => import('./details/ProfileDetails'))
- },
- {
- path: 'changelog',
- component: lazyLoadComponent(() => import('./changelog/ChangelogContainer'))
- },
- {
- path: 'compare',
- component: lazyLoadComponent(() => import('./compare/ComparisonContainer'))
- }
- ]
- }
- ]
- }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/routes.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/routes.tsx
new file mode 100644
index 00000000000..71b57cbee0f
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/routes.tsx
@@ -0,0 +1,40 @@
+/*
+ * 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 React from 'react';
+import { Route } from 'react-router-dom';
+import ChangelogContainer from './changelog/ChangelogContainer';
+import ComparisonContainer from './compare/ComparisonContainer';
+import ProfileContainer from './components/ProfileContainer';
+import QualityProfilesApp from './components/QualityProfilesApp';
+import ProfileDetails from './details/ProfileDetails';
+import HomeContainer from './home/HomeContainer';
+
+const routes = () => (
+ <Route path="profiles" element={<QualityProfilesApp />}>
+ <Route index={true} element={<HomeContainer />} />
+ <Route element={<ProfileContainer />}>
+ <Route path="show" element={<ProfileDetails />} />
+ <Route path="changelog" element={<ChangelogContainer />} />
+ <Route path="compare" element={<ComparisonContainer />} />
+ </Route>
+ </Route>
+);
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/utils.ts b/server/sonar-web/src/main/js/apps/quality-profiles/utils.ts
index 7e3040f20fd..5bed7860f04 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/utils.ts
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/utils.ts
@@ -21,6 +21,7 @@ import { differenceInYears } from 'date-fns';
import { sortBy } from 'lodash';
import { Profile as BaseProfile } from '../../api/quality-profiles';
import { isValidDate, parseDate } from '../../helpers/dates';
+import { queryToSearch } from '../../helpers/urls';
import { Profile } from './types';
export function sortProfiles(profiles: BaseProfile[]): Profile[] {
@@ -66,12 +67,12 @@ export const PROFILE_PATH = '/profiles';
export const getProfilesForLanguagePath = (language: string) => ({
pathname: PROFILE_PATH,
- query: { language }
+ search: queryToSearch({ language })
});
export const getProfilePath = (name: string, language: string) => ({
pathname: `${PROFILE_PATH}/show`,
- query: { name, language }
+ search: queryToSearch({ name, language })
});
export const getProfileComparePath = (name: string, language: string, withKey?: string) => {
@@ -81,7 +82,7 @@ export const getProfileComparePath = (name: string, language: string, withKey?:
}
return {
pathname: `${PROFILE_PATH}/compare`,
- query
+ search: queryToSearch(query)
};
};
@@ -101,6 +102,6 @@ export const getProfileChangelogPath = (
}
return {
pathname: `${PROFILE_PATH}/changelog`,
- query
+ search: queryToSearch(query)
};
};
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx
index fce568a6aed..83b805bd143 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx
@@ -17,14 +17,14 @@
* 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 { flatMap, range } from 'lodash';
import * as React from 'react';
import { getMeasures } from '../../api/measures';
import { getSecurityHotspotList, getSecurityHotspots } from '../../api/security-hotspots';
import withBranchStatusActions from '../../app/components/branch-status/withBranchStatusActions';
+import withComponentContext from '../../app/components/componentContext/withComponentContext';
import withCurrentUserContext from '../../app/components/current-user/withCurrentUserContext';
-import { Router } from '../../components/hoc/withRouter';
+import { Location, Router, withRouter } from '../../components/hoc/withRouter';
import { getLeakValue } from '../../components/measure/utils';
import { getBranchLikeQuery, isPullRequest, isSameBranchLike } from '../../helpers/branch-like';
import { KeyboardKeys } from '../../helpers/keycodes';
@@ -550,4 +550,6 @@ export class SecurityHotspotsApp extends React.PureComponent<Props, State> {
}
}
-export default withCurrentUserContext(withBranchStatusActions(SecurityHotspotsApp));
+export default withRouter(
+ withComponentContext(withCurrentUserContext(withBranchStatusActions(SecurityHotspotsApp)))
+);
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/EmptyHotspotsPage.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/EmptyHotspotsPage.tsx
index a4c025191a9..92c6afdd267 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/EmptyHotspotsPage.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/EmptyHotspotsPage.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';
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotHeader.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotHeader.tsx
index 6e3aa2bfd65..7dd943896f6 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotHeader.tsx
@@ -19,7 +19,7 @@
*/
import React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { translate } from '../../../helpers/l10n';
import { getRuleUrl } from '../../../helpers/urls';
import { Hotspot, HotspotStatusOption } from '../../../types/security-hotspots';
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/EmptyHotspotsPage-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/EmptyHotspotsPage-test.tsx.snap
index 9ea274c206a..5ca002b074f 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/EmptyHotspotsPage-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/EmptyHotspotsPage-test.tsx.snap
@@ -22,8 +22,6 @@ exports[`should render correctly 1`] = `
</div>
<Link
className="big-spacer-top"
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to={
Object {
@@ -58,8 +56,6 @@ exports[`should render correctly: file 1`] = `
</div>
<Link
className="big-spacer-top"
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to={
Object {
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotHeader-test.tsx.snap
index eb76f5685b9..47b62f17a02 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotHeader-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotHeader-test.tsx.snap
@@ -22,16 +22,11 @@ exports[`should render correctly 1`] = `
</span>
<Link
className="small"
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to={
Object {
"pathname": "/coding_rules",
- "query": Object {
- "open": "squid:S2077",
- "rule_key": "squid:S2077",
- },
+ "search": "?open=squid%3AS2077&rule_key=squid%3AS2077",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/sessions/components/LoginContainer.tsx b/server/sonar-web/src/main/js/apps/sessions/components/LoginContainer.tsx
index bb7fcf719c3..460ce54cd4a 100644
--- a/server/sonar-web/src/main/js/apps/sessions/components/LoginContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/sessions/components/LoginContainer.tsx
@@ -17,10 +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 { Location } from 'history';
import * as React from 'react';
import { logIn } from '../../../api/auth';
import { getIdentityProviders } from '../../../api/users';
+import { Location, withRouter } from '../../../components/hoc/withRouter';
import { addGlobalErrorMessage } from '../../../helpers/globalMessages';
import { translate } from '../../../helpers/l10n';
import { getReturnUrl } from '../../../helpers/urls';
@@ -28,9 +28,7 @@ import { IdentityProvider } from '../../../types/types';
import Login from './Login';
interface Props {
- location: Pick<Location, 'hash' | 'pathname' | 'query'> & {
- query: { advanced?: string; return_to?: string };
- };
+ location: Location;
}
interface State {
identityProviders?: IdentityProvider[];
@@ -90,4 +88,4 @@ export class LoginContainer extends React.PureComponent<Props, State> {
}
}
-export default LoginContainer;
+export default withRouter(LoginContainer);
diff --git a/server/sonar-web/src/main/js/apps/sessions/routes.ts b/server/sonar-web/src/main/js/apps/sessions/routes.ts
deleted file mode 100644
index a465a04c533..00000000000
--- a/server/sonar-web/src/main/js/apps/sessions/routes.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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 { lazyLoadComponent } from '../../components/lazyLoadComponent';
-
-const routes = [
- {
- path: 'new',
- component: lazyLoadComponent(() => import('./components/LoginContainer'))
- },
- {
- path: 'logout',
- component: lazyLoadComponent(() => import('./components/Logout'))
- },
- {
- path: 'unauthorized',
- component: lazyLoadComponent(() => import('./components/Unauthorized'))
- }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/sessions/routes.tsx b/server/sonar-web/src/main/js/apps/sessions/routes.tsx
new file mode 100644
index 00000000000..2d82a8a4e0c
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/sessions/routes.tsx
@@ -0,0 +1,35 @@
+/*
+ * 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 React from 'react';
+import { Route } from 'react-router-dom';
+import SimpleSessionsContainer from '../../app/components/SimpleSessionsContainer';
+import LoginContainer from './components/LoginContainer';
+import Logout from './components/Logout';
+import Unauthorized from './components/Unauthorized';
+
+const routes = () => (
+ <Route path="sessions" element={<SimpleSessionsContainer />}>
+ <Route path="new" element={<LoginContainer />} />
+ <Route path="logout" element={<Logout />} />
+ <Route path="unauthorized" element={<Unauthorized />} />
+ </Route>
+);
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.ts
index 8314209db8c..417758c8932 100644
--- a/server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.ts
+++ b/server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.ts
@@ -92,7 +92,7 @@ describe('buildSettingLink', () => {
[
mockDefinition({ key: 'anykey' }),
undefined,
- { hash: '#anykey', pathname: '/admin/settings', query: { category: 'foo category' } }
+ { hash: '#anykey', pathname: '/admin/settings', search: '?category=foo+category' }
],
[
mockDefinition({ key: 'sonar.auth.gitlab.name' }),
@@ -100,7 +100,7 @@ describe('buildSettingLink', () => {
{
hash: '#sonar.auth.gitlab.name',
pathname: '/admin/settings',
- query: { alm: 'gitlab', category: 'foo category' }
+ search: '?category=foo+category&alm=gitlab'
}
],
[
@@ -109,7 +109,7 @@ describe('buildSettingLink', () => {
{
hash: '#sonar.auth.github.token',
pathname: '/admin/settings',
- query: { alm: 'github', category: 'foo category' }
+ search: '?category=foo+category&alm=github'
}
],
[
@@ -118,7 +118,7 @@ describe('buildSettingLink', () => {
{
hash: '#sonar.almintegration.azure',
pathname: '/admin/settings',
- query: { alm: 'azure', category: 'foo category' }
+ search: '?category=foo+category&alm=azure'
}
],
[
@@ -127,7 +127,7 @@ describe('buildSettingLink', () => {
{
hash: '#defKey',
pathname: '/project/settings',
- query: { id: 'componentKey', category: 'foo category' }
+ search: '?id=componentKey&category=foo+category'
}
]
])('should work as expected', (definition, component, expectedUrl) => {
diff --git a/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx b/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx
index 91c8ced89c1..d0b0c01a8a7 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx
@@ -20,7 +20,7 @@
import classNames from 'classnames';
import { sortBy } from 'lodash';
import * as React from 'react';
-import { IndexLink } from 'react-router';
+import { NavLink } from 'react-router-dom';
import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import { getGlobalSettingsUrl, getProjectSettingsUrl } from '../../../helpers/urls';
import { AppState } from '../../../types/appstate';
@@ -65,10 +65,13 @@ export function CategoriesList(props: CategoriesListProps) {
const category = c.key !== defaultCategory ? c.key.toLowerCase() : undefined;
return (
<li key={c.key}>
- <IndexLink
- className={classNames({
- active: c.key.toLowerCase() === selectedCategory.toLowerCase()
- })}
+ <NavLink
+ end={true}
+ className={_ =>
+ classNames({
+ active: c.key.toLowerCase() === selectedCategory.toLowerCase()
+ })
+ }
title={c.name}
to={
component
@@ -76,7 +79,7 @@ export function CategoriesList(props: CategoriesListProps) {
: getGlobalSettingsUrl(category)
}>
{c.name}
- </IndexLink>
+ </NavLink>
</li>
);
})}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/AnalysisScope.tsx b/server/sonar-web/src/main/js/apps/settings/components/AnalysisScope.tsx
index eb196609ea8..0fc1f42e352 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/AnalysisScope.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/AnalysisScope.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 { AdditionalCategoryComponentProps } from './AdditionalCategories';
import CategoryDefinitionsList from './CategoryDefinitionsList';
diff --git a/server/sonar-web/src/main/js/apps/settings/components/NewCodePeriod.tsx b/server/sonar-web/src/main/js/apps/settings/components/NewCodePeriod.tsx
index 197bafeba93..22068cf074b 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/NewCodePeriod.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/NewCodePeriod.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 { getNewCodePeriod, setNewCodePeriod } from '../../../api/newCodePeriod';
import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons';
import AlertSuccessIcon from '../../../components/icons/AlertSuccessIcon';
diff --git a/server/sonar-web/src/main/js/apps/settings/components/SettingsApp.tsx b/server/sonar-web/src/main/js/apps/settings/components/SettingsApp.tsx
index 9018e7d9264..4315e5f760e 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/SettingsApp.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/SettingsApp.tsx
@@ -19,6 +19,7 @@
*/
import * as React from 'react';
import { getDefinitions } from '../../../api/settings';
+import withComponentContext from '../../../app/components/componentContext/withComponentContext';
import {
addSideBarClass,
addWhitePageClass,
@@ -39,7 +40,7 @@ interface State {
loading: boolean;
}
-export default class SettingsApp extends React.PureComponent<Props, State> {
+export class SettingsApp extends React.PureComponent<Props, State> {
mounted = false;
state: State = { definitions: [], loading: true };
@@ -79,3 +80,5 @@ export default class SettingsApp extends React.PureComponent<Props, State> {
return <SettingsAppRenderer component={component} {...this.state} />;
}
}
+
+export default withComponentContext(SettingsApp);
diff --git a/server/sonar-web/src/main/js/apps/settings/components/SettingsSearch.tsx b/server/sonar-web/src/main/js/apps/settings/components/SettingsSearch.tsx
index b78a17052e3..21af1e9ac28 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/SettingsSearch.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/SettingsSearch.tsx
@@ -20,8 +20,7 @@
import { debounce, keyBy } from 'lodash';
import lunr, { LunrIndex } from 'lunr';
import * as React from 'react';
-import { InjectedRouter } from 'react-router';
-import { withRouter } from '../../../components/hoc/withRouter';
+import { Router, withRouter } from '../../../components/hoc/withRouter';
import { KeyboardKeys } from '../../../helpers/keycodes';
import { ExtendedSettingDefinition } from '../../../types/settings';
import { Component, Dict } from '../../../types/types';
@@ -36,7 +35,7 @@ interface Props {
className?: string;
component?: Component;
definitions: ExtendedSettingDefinition[];
- router: InjectedRouter;
+ router: Router;
}
interface State {
diff --git a/server/sonar-web/src/main/js/apps/settings/components/SettingsSearchRenderer.tsx b/server/sonar-web/src/main/js/apps/settings/components/SettingsSearchRenderer.tsx
index 29e268b6e63..fa9dac8fb38 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/SettingsSearchRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/SettingsSearchRenderer.tsx
@@ -19,7 +19,7 @@
*/
import classNames from 'classnames';
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { DropdownOverlay } from '../../../components/controls/Dropdown';
import OutsideClickHandler from '../../../components/controls/OutsideClickHandler';
import SearchBox from '../../../components/controls/SearchBox';
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/AllCategoriesList-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/AllCategoriesList-test.tsx
index 3d7a1401872..94ff4c16fcc 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/AllCategoriesList-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/AllCategoriesList-test.tsx
@@ -17,10 +17,11 @@
* 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 { mockComponent } from '../../../../helpers/mocks/component';
import { mockAppState } from '../../../../helpers/testMocks';
+import { renderComponent } from '../../../../helpers/testReactTestingUtils';
import { AdditionalCategory } from '../AdditionalCategories';
import { CategoriesList, CategoriesListProps } from '../AllCategoriesList';
@@ -63,16 +64,34 @@ jest.mock('../AdditionalCategories', () => ({
}));
it('should render correctly', () => {
- expect(shallowRender()).toMatchSnapshot('global mode');
- expect(shallowRender({ selectedCategory: 'CAT_2' })).toMatchSnapshot('selected category');
- expect(shallowRender({ component: mockComponent() })).toMatchSnapshot('project mode');
- expect(shallowRender({ appState: mockAppState({ branchesEnabled: false }) })).toMatchSnapshot(
- 'branches disabled'
- );
+ renderCategoriesList({ selectedCategory: 'CAT_2' });
+
+ expect(screen.getByText('CAT_1_NAME')).toBeInTheDocument();
+ expect(screen.getByText('CAT_2_NAME')).toBeInTheDocument();
+ expect(screen.queryByText('CAT_3_NAME')).not.toBeInTheDocument();
+ expect(screen.queryByText('CAT_4_NAME')).not.toBeInTheDocument();
+ expect(screen.getByText('CAT_2_NAME').className).toBe('active');
+});
+
+it('should correctly for project', () => {
+ renderCategoriesList({ component: mockComponent() });
+
+ expect(screen.getByText('CAT_1_NAME')).toBeInTheDocument();
+ expect(screen.queryByText('CAT_2_NAME')).not.toBeInTheDocument();
+ expect(screen.getByText('CAT_3_NAME')).toBeInTheDocument();
+ expect(screen.queryByText('CAT_4_NAME')).not.toBeInTheDocument();
+});
+
+it('should render correctly when branches are disabled', () => {
+ renderCategoriesList({ appState: mockAppState({ branchesEnabled: false }) });
+
+ expect(screen.queryByText('CAT_1_NAME')).not.toBeInTheDocument();
+ expect(screen.getByText('CAT_2_NAME')).toBeInTheDocument();
+ expect(screen.queryByText('CAT_4_NAME')).not.toBeInTheDocument();
});
-function shallowRender(props?: Partial<CategoriesListProps>) {
- return shallow<CategoriesListProps>(
+function renderCategoriesList(props?: Partial<CategoriesListProps>) {
+ return renderComponent(
<CategoriesList
appState={mockAppState({ branchesEnabled: true })}
categories={['general']}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsApp-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsApp-test.tsx
index 72bb8ce2bef..005d1e1a961 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsApp-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsApp-test.tsx
@@ -28,7 +28,7 @@ import {
removeWhitePageClass
} from '../../../../helpers/pages';
import { waitAndUpdate } from '../../../../helpers/testUtils';
-import SettingsApp from '../SettingsApp';
+import { SettingsApp } from '../SettingsApp';
jest.mock('../../../../helpers/pages', () => ({
addSideBarClass: jest.fn(),
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsSearch-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsSearch-test.tsx
index 5cc275bc881..ba04565ca31 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsSearch-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsSearch-test.tsx
@@ -24,6 +24,7 @@ import { mockComponent } from '../../../../helpers/mocks/component';
import { mockDefinition } from '../../../../helpers/mocks/settings';
import { mockRouter } from '../../../../helpers/testMocks';
import { mockEvent, waitAndUpdate } from '../../../../helpers/testUtils';
+import { queryToSearch } from '../../../../helpers/urls';
import { SettingsSearch } from '../SettingsSearch';
jest.mock('lunr', () =>
@@ -107,7 +108,7 @@ describe('instance', () => {
expect(router.push).toBeCalledWith({
hash: '#foo',
pathname: '/admin/settings',
- query: { category: 'foo category' }
+ search: queryToSearch({ category: 'foo category' })
});
});
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AllCategoriesList-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AllCategoriesList-test.tsx.snap
deleted file mode 100644
index 1b46b821b3f..00000000000
--- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AllCategoriesList-test.tsx.snap
+++ /dev/null
@@ -1,230 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly: branches disabled 1`] = `
-<ul
- className="side-tabs-menu"
->
- <li
- key="CAT_2"
- >
- <IndexLink
- className=""
- title="CAT_2_NAME"
- to={
- Object {
- "pathname": "/admin/settings",
- "query": Object {
- "category": "cat_2",
- },
- }
- }
- >
- CAT_2_NAME
- </IndexLink>
- </li>
- <li
- key="general"
- >
- <IndexLink
- className=""
- title="property.category.general"
- to={
- Object {
- "pathname": "/admin/settings",
- "query": Object {
- "category": undefined,
- },
- }
- }
- >
- property.category.general
- </IndexLink>
- </li>
-</ul>
-`;
-
-exports[`should render correctly: global mode 1`] = `
-<ul
- className="side-tabs-menu"
->
- <li
- key="CAT_1"
- >
- <IndexLink
- className=""
- title="CAT_1_NAME"
- to={
- Object {
- "pathname": "/admin/settings",
- "query": Object {
- "category": "cat_1",
- },
- }
- }
- >
- CAT_1_NAME
- </IndexLink>
- </li>
- <li
- key="CAT_2"
- >
- <IndexLink
- className=""
- title="CAT_2_NAME"
- to={
- Object {
- "pathname": "/admin/settings",
- "query": Object {
- "category": "cat_2",
- },
- }
- }
- >
- CAT_2_NAME
- </IndexLink>
- </li>
- <li
- key="general"
- >
- <IndexLink
- className=""
- title="property.category.general"
- to={
- Object {
- "pathname": "/admin/settings",
- "query": Object {
- "category": undefined,
- },
- }
- }
- >
- property.category.general
- </IndexLink>
- </li>
-</ul>
-`;
-
-exports[`should render correctly: project mode 1`] = `
-<ul
- className="side-tabs-menu"
->
- <li
- key="CAT_1"
- >
- <IndexLink
- className=""
- title="CAT_1_NAME"
- to={
- Object {
- "pathname": "/project/settings",
- "query": Object {
- "category": "cat_1",
- "id": "my-project",
- },
- }
- }
- >
- CAT_1_NAME
- </IndexLink>
- </li>
- <li
- key="CAT_3"
- >
- <IndexLink
- className=""
- title="CAT_3_NAME"
- to={
- Object {
- "pathname": "/project/settings",
- "query": Object {
- "category": "cat_3",
- "id": "my-project",
- },
- }
- }
- >
- CAT_3_NAME
- </IndexLink>
- </li>
- <li
- key="general"
- >
- <IndexLink
- className=""
- title="property.category.general"
- to={
- Object {
- "pathname": "/project/settings",
- "query": Object {
- "category": undefined,
- "id": "my-project",
- },
- }
- }
- >
- property.category.general
- </IndexLink>
- </li>
-</ul>
-`;
-
-exports[`should render correctly: selected category 1`] = `
-<ul
- className="side-tabs-menu"
->
- <li
- key="CAT_1"
- >
- <IndexLink
- className=""
- title="CAT_1_NAME"
- to={
- Object {
- "pathname": "/admin/settings",
- "query": Object {
- "category": "cat_1",
- },
- }
- }
- >
- CAT_1_NAME
- </IndexLink>
- </li>
- <li
- key="CAT_2"
- >
- <IndexLink
- className="active"
- title="CAT_2_NAME"
- to={
- Object {
- "pathname": "/admin/settings",
- "query": Object {
- "category": "cat_2",
- },
- }
- }
- >
- CAT_2_NAME
- </IndexLink>
- </li>
- <li
- key="general"
- >
- <IndexLink
- className=""
- title="property.category.general"
- to={
- Object {
- "pathname": "/admin/settings",
- "query": Object {
- "category": undefined,
- },
- }
- }
- >
- property.category.general
- </IndexLink>
- </li>
-</ul>
-`;
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AnalysisScope-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AnalysisScope-test.tsx.snap
index 5e237ce1b23..7fccfe01e2f 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AnalysisScope-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AnalysisScope-test.tsx.snap
@@ -8,8 +8,6 @@ exports[`should render correctly 1`] = `
settings.analysis_scope.wildcards.introduction
<Link
className="spacer-left"
- onlyActiveOnIndex={false}
- style={Object {}}
to="/documentation/project-administration/narrowing-the-focus/"
>
learn_more
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/NewCodePeriod-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/NewCodePeriod-test.tsx.snap
index 1154b4e91f5..d5aaaf819a7 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/NewCodePeriod-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/NewCodePeriod-test.tsx.snap
@@ -30,8 +30,6 @@ exports[`should render correctly 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to="/documentation/project-administration/new-code-period/"
>
learn_more
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SettingsSearchRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SettingsSearchRenderer-test.tsx.snap
index ba2d014d06b..334d84a3098 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SettingsSearchRenderer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SettingsSearchRenderer-test.tsx.snap
@@ -76,15 +76,11 @@ exports[`should render correctly when open: results 1`] = `
<Link
onClick={[MockFunction]}
onMouseEnter={[Function]}
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"hash": "#foo",
"pathname": "/admin/settings",
- "query": Object {
- "category": "foo category",
- },
+ "search": "?category=foo+category",
}
}
>
@@ -109,15 +105,11 @@ exports[`should render correctly when open: results 1`] = `
<Link
onClick={[MockFunction]}
onMouseEnter={[Function]}
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"hash": "#bar",
"pathname": "/admin/settings",
- "query": Object {
- "category": "foo category",
- },
+ "search": "?category=foo+category",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionBox.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionBox.tsx
index 6327c6fa11a..1e5cd961d7d 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionBox.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionBox.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 { Button } from '../../../../components/controls/buttons';
import HelpTooltip from '../../../../components/controls/HelpTooltip';
import Tooltip from '../../../../components/controls/Tooltip';
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionFormField.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionFormField.tsx
index ade370d1e75..0fdefb5e2dc 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionFormField.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionFormField.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 { ButtonLink } from '../../../../components/controls/buttons';
import ValidationInput, {
ValidationInputErrorPlacement
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegration.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegration.tsx
index dd57a2c7464..127c0c1ca5b 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegration.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegration.tsx
@@ -18,7 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { WithRouterProps } from 'react-router';
import {
countBindedProjects,
deleteConfiguration,
@@ -26,7 +25,7 @@ import {
validateAlmSettings
} from '../../../../api/alm-settings';
import withAppStateContext from '../../../../app/components/app-state/withAppStateContext';
-import { withRouter } from '../../../../components/hoc/withRouter';
+import { Location, Router, withRouter } from '../../../../components/hoc/withRouter';
import {
AlmBindingDefinitionBase,
AlmKeys,
@@ -39,9 +38,11 @@ import { ExtendedSettingDefinition } from '../../../../types/settings';
import { Dict } from '../../../../types/types';
import AlmIntegrationRenderer from './AlmIntegrationRenderer';
-interface Props extends Pick<WithRouterProps, 'location' | 'router'> {
+interface Props {
appState: AppState;
definitions: ExtendedSettingDefinition[];
+ location: Location;
+ router: Router;
}
export type AlmTabs = AlmKeys.Azure | AlmKeys.GitHub | AlmKeys.GitLab | AlmKeys.BitbucketServer;
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AzureForm.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AzureForm.tsx
index ae8d550eb5c..3751017ab6a 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AzureForm.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AzureForm.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 { ALM_DOCUMENTATION_PATHS } from '../../../../helpers/constants';
import { translate } from '../../../../helpers/l10n';
import { AlmKeys, AzureBindingDefinition } from '../../../../types/alm-settings';
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketCloudForm.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketCloudForm.tsx
index cdbcbfdda4b..e281ea2f167 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketCloudForm.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketCloudForm.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 { ALM_DOCUMENTATION_PATHS } from '../../../../helpers/constants';
import { translate } from '../../../../helpers/l10n';
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketServerForm.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketServerForm.tsx
index 6db54060f6d..cf472ab7740 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketServerForm.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketServerForm.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 { ALM_DOCUMENTATION_PATHS } from '../../../../helpers/constants';
import { translate } from '../../../../helpers/l10n';
import { AlmKeys, BitbucketServerBindingDefinition } from '../../../../types/alm-settings';
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/GithubForm.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/GithubForm.tsx
index b196f643606..4aacacfba9d 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/GithubForm.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/GithubForm.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 { ALM_DOCUMENTATION_PATHS } from '../../../../helpers/constants';
import { translate } from '../../../../helpers/l10n';
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/GitlabForm.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/GitlabForm.tsx
index d3ed87922bd..ac2ab98c35d 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/GitlabForm.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/GitlabForm.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 { ALM_DOCUMENTATION_PATHS } from '../../../../helpers/constants';
import { translate } from '../../../../helpers/l10n';
import { AlmKeys, GitlabBindingDefinition } from '../../../../types/alm-settings';
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionBox-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionBox-test.tsx.snap
index 1937189579d..6ea758b9ec2 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionBox-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionBox-test.tsx.snap
@@ -523,8 +523,6 @@ exports[`should render correctly: success with alert 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/github-integration/"
>
@@ -652,8 +650,6 @@ exports[`should render correctly: success with branches disabled 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/github-integration/"
>
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionFormField-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionFormField-test.tsx.snap
index daf6cf08860..0b37d9683d2 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionFormField-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionFormField-test.tsx.snap
@@ -82,8 +82,6 @@ exports[`should render correctly: encryptable 1`] = `
values={
Object {
"learn_more": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to={
Object {
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AzureForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AzureForm-test.tsx.snap
index a13b6b6ead1..cade0289c9e 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AzureForm-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AzureForm-test.tsx.snap
@@ -42,8 +42,6 @@ exports[`should render correctly: create 1`] = `
values={
Object {
"doc_link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/azuredevops-integration/"
>
@@ -117,8 +115,6 @@ exports[`should render correctly: edit 1`] = `
values={
Object {
"doc_link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/azuredevops-integration/"
>
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketCloudForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketCloudForm-test.tsx.snap
index 64876591a64..9d990ce41b2 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketCloudForm-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketCloudForm-test.tsx.snap
@@ -46,8 +46,6 @@ exports[`should render correctly: default 1`] = `
values={
Object {
"doc_link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/bitbucket-cloud-integration/"
>
@@ -135,8 +133,6 @@ exports[`should render correctly: invalid workspace ID 1`] = `
values={
Object {
"doc_link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/bitbucket-cloud-integration/"
>
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketServerForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketServerForm-test.tsx.snap
index f8bf5e2ce02..ff7ec2649b4 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketServerForm-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketServerForm-test.tsx.snap
@@ -37,8 +37,6 @@ exports[`should render correctly 1`] = `
values={
Object {
"doc_link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/bitbucket-integration/"
>
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GithubForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GithubForm-test.tsx.snap
index e921eb46af9..2945edf3857 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GithubForm-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GithubForm-test.tsx.snap
@@ -44,8 +44,6 @@ exports[`should render correctly 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/github-integration/"
>
@@ -139,8 +137,6 @@ exports[`should render correctly 2`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/github-integration/"
>
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GitlabForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GitlabForm-test.tsx.snap
index 335a2f039bc..cec311aa194 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GitlabForm-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GitlabForm-test.tsx.snap
@@ -35,8 +35,6 @@ exports[`should render correctly 1`] = `
values={
Object {
"doc_link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/gitlab-integration/"
>
@@ -106,8 +104,6 @@ exports[`should render correctly 2`] = `
values={
Object {
"doc_link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/gitlab-integration/"
>
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx
index 2da3d4765a7..6f05ba109f1 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.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 withAppStateContext from '../../../../app/components/app-state/withAppStateContext';
import Toggle from '../../../../components/controls/Toggle';
import { Alert } from '../../../../components/ui/Alert';
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx
index 3a4f0b7d07c..cf65a0a9d5e 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.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 { components, OptionProps, SingleValueProps } from 'react-select';
import { Button, SubmitButton } from '../../../../components/controls/buttons';
import Select from '../../../../components/controls/Select';
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/AlmSpecificForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/AlmSpecificForm-test.tsx.snap
index 77a82861402..82c1f025f65 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/AlmSpecificForm-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/AlmSpecificForm-test.tsx.snap
@@ -819,8 +819,6 @@ exports[`should render the monorepo field when the feature is supported 1`] = `
values={
Object {
"doc_link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/azuredevops-integration/"
>
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBindingRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBindingRenderer-test.tsx.snap
index b72c9f4121e..958ef5a7534 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBindingRenderer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBindingRenderer-test.tsx.snap
@@ -265,15 +265,10 @@ exports[`should render correctly: when there are configuration errors (admin use
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/admin/settings",
- "query": Object {
- "alm": "github",
- "category": "almintegration",
- },
+ "search": "?category=almintegration&alm=github",
}
}
>
@@ -911,14 +906,10 @@ exports[`should render correctly: with no ALM instances (admin user) 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/admin/settings",
- "query": Object {
- "category": "almintegration",
- },
+ "search": "?category=almintegration",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/settings/encryption/EncryptionForm.tsx b/server/sonar-web/src/main/js/apps/settings/encryption/EncryptionForm.tsx
index 065dccac260..381a843057b 100644
--- a/server/sonar-web/src/main/js/apps/settings/encryption/EncryptionForm.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/encryption/EncryptionForm.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 { encryptValue } from '../../../api/settings';
import { SubmitButton } from '../../../components/controls/buttons';
import { ClipboardButton } from '../../../components/controls/clipboard';
diff --git a/server/sonar-web/src/main/js/apps/settings/encryption/GenerateSecretKeyForm.tsx b/server/sonar-web/src/main/js/apps/settings/encryption/GenerateSecretKeyForm.tsx
index 0201bba7b40..6874d6563b7 100644
--- a/server/sonar-web/src/main/js/apps/settings/encryption/GenerateSecretKeyForm.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/encryption/GenerateSecretKeyForm.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 { SubmitButton } from '../../../components/controls/buttons';
import { ClipboardButton } from '../../../components/controls/clipboard';
import DeferredSpinner from '../../../components/ui/DeferredSpinner';
diff --git a/server/sonar-web/src/main/js/apps/settings/encryption/__tests__/__snapshots__/EncryptionForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/encryption/__tests__/__snapshots__/EncryptionForm-test.tsx.snap
index 29a34bb6671..c6af518e5fb 100644
--- a/server/sonar-web/src/main/js/apps/settings/encryption/__tests__/__snapshots__/EncryptionForm-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/encryption/__tests__/__snapshots__/EncryptionForm-test.tsx.snap
@@ -51,8 +51,6 @@ exports[`should render correctly 1`] = `
values={
Object {
"moreInformationLink": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/instance-administration/security/"
>
diff --git a/server/sonar-web/src/main/js/apps/settings/encryption/__tests__/__snapshots__/GenerateSecretKeyForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/encryption/__tests__/__snapshots__/GenerateSecretKeyForm-test.tsx.snap
index 53251a408ba..4eed71722b2 100644
--- a/server/sonar-web/src/main/js/apps/settings/encryption/__tests__/__snapshots__/GenerateSecretKeyForm-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/encryption/__tests__/__snapshots__/GenerateSecretKeyForm-test.tsx.snap
@@ -17,8 +17,6 @@ exports[`should render correctly 1`] = `
values={
Object {
"moreInformationLink": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/instance-administration/security/"
>
diff --git a/server/sonar-web/src/main/js/apps/settings/routes.ts b/server/sonar-web/src/main/js/apps/settings/routes.ts
deleted file mode 100644
index a16832835e3..00000000000
--- a/server/sonar-web/src/main/js/apps/settings/routes.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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 { lazyLoadComponent } from '../../components/lazyLoadComponent';
-
-const routes = [
- {
- indexRoute: { component: lazyLoadComponent(() => import('./components/SettingsApp')) }
- },
- {
- path: 'encryption',
- component: lazyLoadComponent(() => import('./encryption/EncryptionApp'))
- }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/settings/routes.tsx b/server/sonar-web/src/main/js/apps/settings/routes.tsx
new file mode 100644
index 00000000000..67e534f5e6f
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/settings/routes.tsx
@@ -0,0 +1,32 @@
+/*
+ * 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 React from 'react';
+import { Route } from 'react-router-dom';
+import SettingsApp from './components/SettingsApp';
+import EncryptionApp from './encryption/EncryptionApp';
+
+const routes = () => (
+ <Route path="settings">
+ <Route index={true} element={<SettingsApp />} />
+ <Route path="encryption" element={<EncryptionApp />} />
+ </Route>
+);
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/settings/utils.ts b/server/sonar-web/src/main/js/apps/settings/utils.ts
index 3cec4487f83..fd26233e62d 100644
--- a/server/sonar-web/src/main/js/apps/settings/utils.ts
+++ b/server/sonar-web/src/main/js/apps/settings/utils.ts
@@ -17,8 +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 { LocationDescriptor } from 'history';
import { sortBy } from 'lodash';
+import { Path } from 'react-router-dom';
import { hasMessage, translate } from '../../helpers/l10n';
import { getGlobalSettingsUrl, getProjectSettingsUrl } from '../../helpers/urls';
import { AlmKeys } from '../../types/alm-settings';
@@ -212,7 +212,7 @@ export function isRealSettingKey(key: string) {
export function buildSettingLink(
definition: ExtendedSettingDefinition,
component?: Component
-): LocationDescriptor {
+): Partial<Path> {
const { category, key } = definition;
if (component !== undefined) {
diff --git a/server/sonar-web/src/main/js/apps/system/components/App.tsx b/server/sonar-web/src/main/js/apps/system/components/App.tsx
index 9beb5cee6c6..8ba55c260de 100644
--- a/server/sonar-web/src/main/js/apps/system/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/system/components/App.tsx
@@ -19,10 +19,10 @@
*/
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
-import { withRouter, WithRouterProps } from 'react-router';
import { getSystemInfo } from '../../../api/system';
import UpdateNotification from '../../../app/components/update-notification/UpdateNotification';
import Suggestions from '../../../components/embed-docs-modal/Suggestions';
+import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
import { translate } from '../../../helpers/l10n';
import { SysInfoCluster, SysInfoStandalone } from '../../../types/types';
import '../styles.css';
@@ -40,7 +40,10 @@ import ClusterSysInfos from './ClusterSysInfos';
import PageHeader from './PageHeader';
import StandaloneSysInfos from './StandaloneSysInfos';
-type Props = WithRouterProps;
+interface Props {
+ location: Location;
+ router: Router;
+}
interface State {
loading: boolean;
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/system/components/__tests__/App-test.tsx
index dd866fc1692..e231ef41d7d 100644
--- a/server/sonar-web/src/main/js/apps/system/components/__tests__/App-test.tsx
+++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/App-test.tsx
@@ -80,7 +80,5 @@ it('should toggle cards and update the URL', () => {
});
function shallowRender(props: Partial<App['props']> = {}) {
- return shallow<App>(
- <App location={mockLocation()} params={{}} router={mockRouter()} routes={[]} {...props} />
- );
+ return shallow<App>(<App location={mockLocation()} router={mockRouter()} {...props} />);
}
diff --git a/server/sonar-web/src/main/js/apps/system/routes.ts b/server/sonar-web/src/main/js/apps/system/routes.ts
deleted file mode 100644
index 9d8f2bd42e4..00000000000
--- a/server/sonar-web/src/main/js/apps/system/routes.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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 { lazyLoadComponent } from '../../components/lazyLoadComponent';
-
-const routes = [
- {
- indexRoute: { component: lazyLoadComponent(() => import('./components/App')) }
- }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/system/routes.tsx b/server/sonar-web/src/main/js/apps/system/routes.tsx
new file mode 100644
index 00000000000..5087a4ec38a
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/routes.tsx
@@ -0,0 +1,26 @@
+/*
+ * 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 React from 'react';
+import { Route } from 'react-router-dom';
+import App from './components/App';
+
+export const routes = () => <Route path="system" element={<App />} />;
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/components/TutorialsApp.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/TutorialsApp.tsx
index bce81dc134d..4174e269a3e 100644
--- a/server/sonar-web/src/main/js/apps/tutorials/components/TutorialsApp.tsx
+++ b/server/sonar-web/src/main/js/apps/tutorials/components/TutorialsApp.tsx
@@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import withComponentContext from '../../../app/components/componentContext/withComponentContext';
import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext';
import TutorialSelection from '../../../components/tutorials/TutorialSelection';
import handleRequiredAuthentication from '../../../helpers/handleRequiredAuthentication';
@@ -50,4 +51,4 @@ export function TutorialsApp(props: TutorialsAppProps) {
);
}
-export default withCurrentUserContext(TutorialsApp);
+export default withComponentContext(withCurrentUserContext(TutorialsApp));
diff --git a/server/sonar-web/src/main/js/apps/tutorials/routes.ts b/server/sonar-web/src/main/js/apps/tutorials/routes.ts
deleted file mode 100644
index 6f1e2f0be31..00000000000
--- a/server/sonar-web/src/main/js/apps/tutorials/routes.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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 { lazyLoadComponent } from '../../components/lazyLoadComponent';
-
-const routes = [
- {
- indexRoute: { component: lazyLoadComponent(() => import('./components/TutorialsApp')) }
- }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/audit-logs/routes.ts b/server/sonar-web/src/main/js/apps/tutorials/routes.tsx
index cf3bd5ca8ef..59c48641c5f 100644
--- a/server/sonar-web/src/main/js/apps/audit-logs/routes.ts
+++ b/server/sonar-web/src/main/js/apps/tutorials/routes.tsx
@@ -17,12 +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 { lazyLoadComponent } from '../../components/lazyLoadComponent';
+import React from 'react';
+import { Route } from 'react-router-dom';
+import TutorialsApp from './components/TutorialsApp';
-const routes = [
- {
- indexRoute: { component: lazyLoadComponent(() => import('./components/AuditApp')) }
- }
-];
+const routes = () => <Route path="tutorials" element={<TutorialsApp />} />;
export default routes;
diff --git a/server/sonar-web/src/main/js/apps/users/UsersApp.tsx b/server/sonar-web/src/main/js/apps/users/UsersApp.tsx
index e788902f316..3a1b0cf85eb 100644
--- a/server/sonar-web/src/main/js/apps/users/UsersApp.tsx
+++ b/server/sonar-web/src/main/js/apps/users/UsersApp.tsx
@@ -34,8 +34,8 @@ import { parseQuery, Query, serializeQuery } from './utils';
interface Props {
currentUser: { isLoggedIn: boolean; login?: string };
- location: Pick<Location, 'query'>;
- router: Pick<Router, 'push'>;
+ location: Location;
+ router: Router;
}
interface State {
diff --git a/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-test.tsx b/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-test.tsx
index 96cd752c072..0855aa622f0 100644
--- a/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-test.tsx
+++ b/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-test.tsx
@@ -20,6 +20,7 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { Location } from '../../../components/hoc/withRouter';
+import { mockRouter } from '../../../helpers/testMocks';
import { waitAndUpdate } from '../../../helpers/testUtils';
import { UsersApp } from '../UsersApp';
@@ -78,11 +79,6 @@ it('should render correctly', async () => {
function getWrapper(props: Partial<UsersApp['props']> = {}) {
return shallow(
- <UsersApp
- currentUser={currentUser}
- location={location}
- router={{ push: jest.fn() }}
- {...props}
- />
+ <UsersApp currentUser={currentUser} location={location} router={mockRouter()} {...props} />
);
}
diff --git a/server/sonar-web/src/main/js/apps/users/routes.tsx b/server/sonar-web/src/main/js/apps/users/routes.tsx
new file mode 100644
index 00000000000..5805fafc9e9
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/users/routes.tsx
@@ -0,0 +1,26 @@
+/*
+ * 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 React from 'react';
+import { Route } from 'react-router-dom';
+import UsersApp from './UsersApp';
+
+export const routes = () => <Route path="users" element={<UsersApp />} />;
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/web-api/components/Action.tsx b/server/sonar-web/src/main/js/apps/web-api/components/Action.tsx
index 47ab504988c..1b7cfafa7ff 100644
--- a/server/sonar-web/src/main/js/apps/web-api/components/Action.tsx
+++ b/server/sonar-web/src/main/js/apps/web-api/components/Action.tsx
@@ -19,9 +19,10 @@
*/
import classNames from 'classnames';
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import LinkIcon from '../../../components/icons/LinkIcon';
import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { queryToSearch } from '../../../helpers/urls';
import { WebApi } from '../../../types/types';
import { getActionKey, serializeQuery } from '../utils';
import ActionChangelog from './ActionChangelog';
@@ -136,10 +137,12 @@ export default class Action extends React.PureComponent<Props, State> {
className="spacer-right link-no-underline"
to={{
pathname: '/web_api/' + actionKey,
- query: serializeQuery({
- deprecated: Boolean(action.deprecatedSince),
- internal: Boolean(action.internal)
- })
+ search: queryToSearch(
+ serializeQuery({
+ deprecated: Boolean(action.deprecatedSince),
+ internal: Boolean(action.internal)
+ })
+ )
}}>
<LinkIcon />
</Link>
diff --git a/server/sonar-web/src/main/js/apps/web-api/components/Menu.tsx b/server/sonar-web/src/main/js/apps/web-api/components/Menu.tsx
index a70bfa86e8a..8cf0c9623ff 100644
--- a/server/sonar-web/src/main/js/apps/web-api/components/Menu.tsx
+++ b/server/sonar-web/src/main/js/apps/web-api/components/Menu.tsx
@@ -19,7 +19,8 @@
*/
import classNames from 'classnames';
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
+import { queryToSearch } from '../../../helpers/urls';
import { WebApi } from '../../../types/types';
import { actionsFilter, isDomainPathActive, Query, serializeQuery } from '../utils';
import DeprecatedBadge from './DeprecatedBadge';
@@ -48,7 +49,7 @@ export default function Menu(props: Props) {
active: isDomainPathActive(domain.path, splat)
})}
key={domain.path}
- to={{ pathname: '/web_api/' + domain.path, query: serializeQuery(query) }}>
+ to={{ pathname: '/web_api/' + domain.path, search: queryToSearch(serializeQuery(query)) }}>
<h3 className="list-group-item-heading">
{domain.path}
{domain.deprecatedSince && <DeprecatedBadge since={domain.deprecatedSince} />}
diff --git a/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx b/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx
index c295e6438c2..b872548ae65 100644
--- a/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx
+++ b/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx
@@ -20,11 +20,12 @@
import { maxBy } from 'lodash';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
-import { Link, withRouter, WithRouterProps } from 'react-router';
+import { Link, Params, useParams } from 'react-router-dom';
import { fetchWebApi } from '../../../api/web-api';
import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
import Suggestions from '../../../components/embed-docs-modal/Suggestions';
+import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
import { translate } from '../../../helpers/l10n';
import { addSideBarClass, removeSideBarClass } from '../../../helpers/pages';
import { scrollToElement } from '../../../helpers/scrolling';
@@ -42,7 +43,11 @@ import Domain from './Domain';
import Menu from './Menu';
import Search from './Search';
-type Props = WithRouterProps;
+interface Props {
+ location: Location;
+ params: Params;
+ router: Router;
+}
interface State {
domains: WebApi.Domain[];
@@ -197,7 +202,13 @@ export class WebApiApp extends React.PureComponent<Props, State> {
}
}
-export default withRouter(WebApiApp);
+function WebApiAppWithParams(props: { router: Router; location: Location }) {
+ const params = useParams();
+
+ return <WebApiApp {...props} params={{ splat: params['*'] }} />;
+}
+
+export default withRouter(WebApiAppWithParams);
/** Checks if all actions are deprecated, and returns the latest deprecated one */
function getLatestDeprecatedAction(domain: Pick<WebApi.Domain, 'actions'>) {
diff --git a/server/sonar-web/src/main/js/apps/web-api/components/__tests__/WebApiApp-test.tsx b/server/sonar-web/src/main/js/apps/web-api/components/__tests__/WebApiApp-test.tsx
index 774e5bc5cfb..faa0f6ce70e 100644
--- a/server/sonar-web/src/main/js/apps/web-api/components/__tests__/WebApiApp-test.tsx
+++ b/server/sonar-web/src/main/js/apps/web-api/components/__tests__/WebApiApp-test.tsx
@@ -66,7 +66,6 @@ function shallowRender(props: Partial<WebApiApp['props']> = {}) {
location={mockLocation()}
params={{ splat: 'foo/bar' }}
router={mockRouter()}
- routes={[]}
{...props}
/>
);
diff --git a/server/sonar-web/src/main/js/apps/web-api/components/__tests__/__snapshots__/Action-test.tsx.snap b/server/sonar-web/src/main/js/apps/web-api/components/__tests__/__snapshots__/Action-test.tsx.snap
index 5d5142d1c66..af905ee7f55 100644
--- a/server/sonar-web/src/main/js/apps/web-api/components/__tests__/__snapshots__/Action-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/web-api/components/__tests__/__snapshots__/Action-test.tsx.snap
@@ -99,12 +99,10 @@ exports[`should render correctly 1`] = `
>
<Link
className="spacer-right link-no-underline"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/web_api/foo/foo",
- "query": Object {},
+ "search": "?",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/web-api/components/__tests__/__snapshots__/Menu-test.tsx.snap b/server/sonar-web/src/main/js/apps/web-api/components/__tests__/__snapshots__/Menu-test.tsx.snap
index 64b8d0e4bd2..629742ef918 100644
--- a/server/sonar-web/src/main/js/apps/web-api/components/__tests__/__snapshots__/Menu-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/web-api/components/__tests__/__snapshots__/Menu-test.tsx.snap
@@ -10,14 +10,10 @@ exports[`should also render domains with an actions description matching the que
<Link
className="list-group-item"
key="bar"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/web_api/bar",
- "query": Object {
- "query": "Bar",
- },
+ "search": "?query=Bar",
}
}
>
@@ -30,14 +26,10 @@ exports[`should also render domains with an actions description matching the que
<Link
className="list-group-item"
key="baz"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/web_api/baz",
- "query": Object {
- "query": "Bar",
- },
+ "search": "?query=Bar",
}
}
>
@@ -61,12 +53,10 @@ exports[`should not render deprecated domains 1`] = `
<Link
className="list-group-item"
key="foo"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/web_api/foo",
- "query": Object {},
+ "search": "?",
}
}
>
@@ -90,12 +80,10 @@ exports[`should not render internal domains 1`] = `
<Link
className="list-group-item"
key="foo"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/web_api/foo",
- "query": Object {},
+ "search": "?",
}
}
>
@@ -119,14 +107,10 @@ exports[`should render deprecated domains 1`] = `
<Link
className="list-group-item"
key="foo"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/web_api/foo",
- "query": Object {
- "deprecated": true,
- },
+ "search": "?deprecated=true",
}
}
>
@@ -139,14 +123,10 @@ exports[`should render deprecated domains 1`] = `
<Link
className="list-group-item"
key="bar"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/web_api/bar",
- "query": Object {
- "deprecated": true,
- },
+ "search": "?deprecated=true",
}
}
>
@@ -173,14 +153,10 @@ exports[`should render internal domains 1`] = `
<Link
className="list-group-item"
key="foo"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/web_api/foo",
- "query": Object {
- "internal": true,
- },
+ "search": "?internal=true",
}
}
>
@@ -193,14 +169,10 @@ exports[`should render internal domains 1`] = `
<Link
className="list-group-item"
key="bar"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/web_api/bar",
- "query": Object {
- "internal": true,
- },
+ "search": "?internal=true",
}
}
>
@@ -225,14 +197,10 @@ exports[`should render only domains with an action matching the query 1`] = `
<Link
className="list-group-item"
key="foo"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/web_api/foo",
- "query": Object {
- "query": "Foo",
- },
+ "search": "?query=Foo",
}
}
>
diff --git a/server/sonar-web/src/main/js/apps/web-api/components/__tests__/__snapshots__/WebApiApp-test.tsx.snap b/server/sonar-web/src/main/js/apps/web-api/components/__tests__/__snapshots__/WebApiApp-test.tsx.snap
index 1ff1ab2615a..1fc0062503f 100644
--- a/server/sonar-web/src/main/js/apps/web-api/components/__tests__/__snapshots__/WebApiApp-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/web-api/components/__tests__/__snapshots__/WebApiApp-test.tsx.snap
@@ -71,8 +71,6 @@ exports[`should render correctly 2`] = `
className="web-api-page-header"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to="/web_api/"
>
<h1>
diff --git a/server/sonar-web/src/main/js/apps/web-api/routes.ts b/server/sonar-web/src/main/js/apps/web-api/routes.ts
deleted file mode 100644
index f950de012bb..00000000000
--- a/server/sonar-web/src/main/js/apps/web-api/routes.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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 { lazyLoadComponent } from '../../components/lazyLoadComponent';
-
-const routes = [
- {
- indexRoute: { component: lazyLoadComponent(() => import('./components/WebApiApp')) }
- },
- {
- path: '**',
- component: lazyLoadComponent(() => import('./components/WebApiApp'))
- }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/documentation/routes.ts b/server/sonar-web/src/main/js/apps/web-api/routes.tsx
index f396663b15b..56be0a16a19 100644
--- a/server/sonar-web/src/main/js/apps/documentation/routes.ts
+++ b/server/sonar-web/src/main/js/apps/web-api/routes.tsx
@@ -17,10 +17,15 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { lazyLoadComponent } from '../../components/lazyLoadComponent';
+import React from 'react';
+import { Route } from 'react-router-dom';
+import WebApiApp from './components/WebApiApp';
-const App = lazyLoadComponent(() => import(/* webpackChunkName: "docs" */ './components/App'));
-
-const routes = [{ indexRoute: { component: App } }, { path: '**', indexRoute: { component: App } }];
+const routes = () => (
+ <Route path="web_api">
+ <Route index={true} element={<WebApiApp />} />
+ <Route path="*" element={<WebApiApp />} />
+ </Route>
+);
export default routes;
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/App.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/App.tsx
index d2875d66824..d495d911cd2 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/App.tsx
@@ -20,16 +20,17 @@
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import { createWebhook, deleteWebhook, searchWebhooks, updateWebhook } from '../../../api/webhooks';
+import withComponentContext from '../../../app/components/componentContext/withComponentContext';
import Suggestions from '../../../components/embed-docs-modal/Suggestions';
import { translate } from '../../../helpers/l10n';
-import { LightComponent, Webhook } from '../../../types/types';
+import { Component, Webhook } from '../../../types/types';
import PageActions from './PageActions';
import PageHeader from './PageHeader';
import WebhooksList from './WebhooksList';
interface Props {
// eslint-disable-next-line react/no-unused-prop-types
- component?: LightComponent;
+ component?: Component;
}
interface State {
@@ -37,7 +38,7 @@ interface State {
webhooks: Webhook[];
}
-export default class App extends React.PureComponent<Props, State> {
+export class App extends React.PureComponent<Props, State> {
mounted = false;
state: State = { loading: true, webhooks: [] };
@@ -148,3 +149,5 @@ export default class App extends React.PureComponent<Props, State> {
);
}
}
+
+export default withComponentContext(App);
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/PageHeader.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/PageHeader.tsx
index 0a6c7d92b61..aa994d2db64 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/PageHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/PageHeader.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 { translate } from '../../../helpers/l10n';
interface Props {
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/App-test.tsx
index c3ba2f55ede..96d3cd899cc 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/App-test.tsx
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/App-test.tsx
@@ -25,7 +25,8 @@ import {
searchWebhooks,
updateWebhook
} from '../../../../api/webhooks';
-import App from '../App';
+import { mockComponent } from '../../../../helpers/mocks/component';
+import { App } from '../App';
jest.mock('../../../../api/webhooks', () => ({
createWebhook: jest.fn(() =>
@@ -43,7 +44,7 @@ jest.mock('../../../../api/webhooks', () => ({
updateWebhook: jest.fn(() => Promise.resolve())
}));
-const component = { key: 'bar', qualifier: 'TRK' };
+const component = mockComponent({ key: 'bar', qualifier: 'TRK' });
beforeEach(() => {
(createWebhook as jest.Mock<any>).mockClear();
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/PageHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/PageHeader-test.tsx.snap
index f8a71f5b264..62a0abeeed9 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/PageHeader-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/PageHeader-test.tsx.snap
@@ -22,8 +22,6 @@ exports[`should render correctly 1`] = `
values={
Object {
"url": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
to="/documentation/project-administration/webhooks/"
>
webhooks.documentation_link
diff --git a/server/sonar-web/src/main/js/apps/webhooks/routes.ts b/server/sonar-web/src/main/js/apps/webhooks/routes.ts
deleted file mode 100644
index 9d8f2bd42e4..00000000000
--- a/server/sonar-web/src/main/js/apps/webhooks/routes.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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 { lazyLoadComponent } from '../../components/lazyLoadComponent';
-
-const routes = [
- {
- indexRoute: { component: lazyLoadComponent(() => import('./components/App')) }
- }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/webhooks/routes.tsx b/server/sonar-web/src/main/js/apps/webhooks/routes.tsx
new file mode 100644
index 00000000000..b472e935d45
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/webhooks/routes.tsx
@@ -0,0 +1,26 @@
+/*
+ * 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 React from 'react';
+import { Route } from 'react-router-dom';
+import App from './components/App';
+
+export const routes = () => <Route path="webhooks" element={<App />} />;
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx
index 569775ece00..c04ccc4f6fd 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.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 { ButtonIcon } from '../../components/controls/buttons';
import { ClipboardIconButton } from '../../components/controls/clipboard';
import Dropdown from '../../components/controls/Dropdown';
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeaderSlim.tsx b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeaderSlim.tsx
index 38ebd8cbce5..b6a6ac4774f 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeaderSlim.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeaderSlim.tsx
@@ -19,7 +19,7 @@
*/
import classNames from 'classnames';
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { ButtonIcon } from '../../components/controls/buttons';
import { ClipboardIconButton } from '../../components/controls/clipboard';
import ExpandSnippetIcon from '../../components/icons/ExpandSnippetIcon';
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerHeader-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerHeader-test.tsx.snap
index 58ecb2f6e0b..f5c25d061ba 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerHeader-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerHeader-test.tsx.snap
@@ -71,18 +71,12 @@ exports[`should render correctly for a regular file 1`] = `
<li>
<Link
className="js-new-window"
- onlyActiveOnIndex={false}
rel="noopener noreferrer"
- style={Object {}}
target="_blank"
to={
Object {
"pathname": "/code",
- "query": Object {
- "id": "project",
- "line": undefined,
- "selected": "project:foo/bar.ts",
- },
+ "search": "?id=project&selected=project%3Afoo%2Fbar.ts",
}
}
>
@@ -210,18 +204,12 @@ exports[`should render correctly for a unit test 1`] = `
<li>
<Link
className="js-new-window"
- onlyActiveOnIndex={false}
rel="noopener noreferrer"
- style={Object {}}
target="_blank"
to={
Object {
"pathname": "/code",
- "query": Object {
- "id": "my-project",
- "line": undefined,
- "selected": "my-project:foo/bar.ts",
- },
+ "search": "?id=my-project&selected=my-project%3Afoo%2Fbar.ts",
}
}
>
@@ -374,17 +362,11 @@ exports[`should render correctly if issue details are passed 1`] = `
className="source-viewer-header-measure-value"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
+ "hash": "",
"pathname": "/project/issues",
- "query": Object {
- "files": "foo/bar.ts",
- "id": "project",
- "resolved": "false",
- "types": "BUG",
- },
+ "search": "?files=foo%2Fbar.ts&resolved=false&types=BUG&id=project",
}
}
>
@@ -405,17 +387,11 @@ exports[`should render correctly if issue details are passed 1`] = `
className="source-viewer-header-measure-value"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
+ "hash": "",
"pathname": "/project/issues",
- "query": Object {
- "files": "foo/bar.ts",
- "id": "project",
- "resolved": "false",
- "types": "VULNERABILITY",
- },
+ "search": "?files=foo%2Fbar.ts&resolved=false&types=VULNERABILITY&id=project",
}
}
>
@@ -436,17 +412,11 @@ exports[`should render correctly if issue details are passed 1`] = `
className="source-viewer-header-measure-value"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
+ "hash": "",
"pathname": "/project/issues",
- "query": Object {
- "files": "foo/bar.ts",
- "id": "project",
- "resolved": "false",
- "types": "CODE_SMELL",
- },
+ "search": "?files=foo%2Fbar.ts&resolved=false&types=CODE_SMELL&id=project",
}
}
>
@@ -467,17 +437,11 @@ exports[`should render correctly if issue details are passed 1`] = `
className="source-viewer-header-measure-value"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
+ "hash": "",
"pathname": "/project/issues",
- "query": Object {
- "files": "foo/bar.ts",
- "id": "project",
- "resolved": "false",
- "types": "SECURITY_HOTSPOT",
- },
+ "search": "?files=foo%2Fbar.ts&resolved=false&types=SECURITY_HOTSPOT&id=project",
}
}
>
@@ -504,18 +468,12 @@ exports[`should render correctly if issue details are passed 1`] = `
<li>
<Link
className="js-new-window"
- onlyActiveOnIndex={false}
rel="noopener noreferrer"
- style={Object {}}
target="_blank"
to={
Object {
"pathname": "/code",
- "query": Object {
- "id": "project",
- "line": undefined,
- "selected": "project:foo/bar.ts",
- },
+ "search": "?id=project&selected=project%3Afoo%2Fbar.ts",
}
}
>
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerHeaderSlim-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerHeaderSlim-test.tsx.snap
index 1a2badc509e..0b6e42a0fb3 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerHeaderSlim-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerHeaderSlim-test.tsx.snap
@@ -52,16 +52,11 @@ exports[`should render correctly 1`] = `
className="flex-0 big-spacer-left"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
+ "hash": "",
"pathname": "/project/issues",
- "query": Object {
- "files": "foo/bar.ts",
- "id": "my-project",
- "resolved": "false",
- },
+ "search": "?files=foo%2Fbar.ts&resolved=false&id=my-project",
}
}
>
@@ -132,16 +127,11 @@ exports[`should render correctly: no link to project 1`] = `
className="flex-0 big-spacer-left"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
+ "hash": "",
"pathname": "/project/issues",
- "query": Object {
- "files": "foo/bar.ts",
- "id": "my-project",
- "resolved": "false",
- },
+ "search": "?files=foo%2Fbar.ts&resolved=false&id=my-project",
}
}
>
@@ -201,16 +191,11 @@ exports[`should render correctly: no project name 1`] = `
className="flex-0 big-spacer-left"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
+ "hash": "",
"pathname": "/project/issues",
- "query": Object {
- "files": "foo/bar.ts",
- "id": "my-project",
- "resolved": "false",
- },
+ "search": "?files=foo%2Fbar.ts&resolved=false&id=my-project",
}
}
>
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx
index cb0778b2fef..2716924b6c2 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx
@@ -19,7 +19,7 @@
*/
import { groupBy, sortBy } 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 { Alert } from '../../../components/ui/Alert';
import { isPullRequest } from '../../../helpers/branch-like';
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx
index 3456b9b3577..86c8a90e4fd 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx
@@ -19,7 +19,7 @@
*/
import { groupBy, keyBy, sortBy } from 'lodash';
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { getFacets } from '../../../api/issues';
import { getMeasures } from '../../../api/measures';
import { getAllMetrics } from '../../../api/metrics';
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlay-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlay-test.tsx.snap
index eaa091a84af..6b09bf1ea0b 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlay-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlay-test.tsx.snap
@@ -25,15 +25,10 @@ exports[`should render source file 1`] = `
qualifier="TRK"
/>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": "feature",
- "id": "project-key",
- },
+ "search": "?branch=feature&id=project-key",
}
}
>
@@ -594,15 +589,10 @@ exports[`should render source file 2`] = `
qualifier="TRK"
/>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": "feature",
- "id": "project-key",
- },
+ "search": "?branch=feature&id=project-key",
}
}
>
@@ -1746,15 +1736,10 @@ exports[`should render test file 1`] = `
qualifier="TRK"
/>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/dashboard",
- "query": Object {
- "branch": "feature",
- "id": "project-key",
- },
+ "search": "?branch=feature&id=project-key",
}
}
>
diff --git a/server/sonar-web/src/main/js/components/charts/BubbleChart.tsx b/server/sonar-web/src/main/js/components/charts/BubbleChart.tsx
index 89af3278c2f..bdb4504a50b 100644
--- a/server/sonar-web/src/main/js/components/charts/BubbleChart.tsx
+++ b/server/sonar-web/src/main/js/components/charts/BubbleChart.tsx
@@ -24,10 +24,10 @@ import { event, select } from 'd3-selection';
import { zoom, ZoomBehavior, zoomIdentity } from 'd3-zoom';
import { sortBy, uniq } from 'lodash';
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer';
import { translate } from '../../helpers/l10n';
-import { Location } from '../../helpers/urls';
+import { convertToTo, Location } from '../../helpers/urls';
import Tooltip from '../controls/Tooltip';
import './BubbleChart.css';
@@ -117,7 +117,7 @@ export default class BubbleChart<T> extends React.PureComponent<Props<T>, State>
});
};
- resetZoom = (e: React.MouseEvent<Link>) => {
+ resetZoom = (e: React.MouseEvent) => {
e.stopPropagation();
e.preventDefault();
if (this.zoom && this.node) {
@@ -377,7 +377,7 @@ function Bubble<T>(props: BubbleProps<T>) {
);
if (props.link && !props.onClick) {
- circle = <Link to={props.link}>{circle}</Link>;
+ circle = <Link to={convertToTo(props.link)}>{circle}</Link>;
}
return (
diff --git a/server/sonar-web/src/main/js/components/charts/TreeMapRect.tsx b/server/sonar-web/src/main/js/components/charts/TreeMapRect.tsx
index 49b8825bce4..3e10e369ac0 100644
--- a/server/sonar-web/src/main/js/components/charts/TreeMapRect.tsx
+++ b/server/sonar-web/src/main/js/components/charts/TreeMapRect.tsx
@@ -20,8 +20,8 @@
import classNames from 'classnames';
import { scaleLinear } from 'd3-scale';
import * as React from 'react';
-import { Link } from 'react-router';
-import { Location } from '../../helpers/urls';
+import { Link } from 'react-router-dom';
+import { convertToTo, Location } from '../../helpers/urls';
import Tooltip, { Placement } from '../controls/Tooltip';
import LinkIcon from '../icons/LinkIcon';
@@ -69,8 +69,9 @@ export default class TreeMapRect extends React.PureComponent<Props> {
if (!hasMinSize || link == null) {
return null;
}
+
return (
- <Link className="treemap-link" onClick={this.handleLinkClick} to={link}>
+ <Link className="treemap-link" onClick={this.handleLinkClick} to={convertToTo(link)}>
<LinkIcon />
</Link>
);
diff --git a/server/sonar-web/src/main/js/components/charts/__tests__/BubbleChart-test.tsx b/server/sonar-web/src/main/js/components/charts/__tests__/BubbleChart-test.tsx
index f83cff62df4..d47c0ebfcd1 100644
--- a/server/sonar-web/src/main/js/components/charts/__tests__/BubbleChart-test.tsx
+++ b/server/sonar-web/src/main/js/components/charts/__tests__/BubbleChart-test.tsx
@@ -21,7 +21,7 @@ import { select } from 'd3-selection';
import { zoom } from 'd3-zoom';
import { shallow } from 'enzyme';
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import { AutoSizer, AutoSizerProps } from 'react-virtualized/dist/commonjs/AutoSizer';
import { mockComponentMeasureEnhanced } from '../../../helpers/mocks/component';
import { mockHtmlElement } from '../../../helpers/mocks/dom';
diff --git a/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/BubbleChart-test.tsx.snap b/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/BubbleChart-test.tsx.snap
index 8adfc888cd0..1e0feb80ef5 100644
--- a/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/BubbleChart-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/BubbleChart-test.tsx.snap
@@ -40,8 +40,6 @@ exports[`should render bubble links 1`] = `
<Tooltip>
<g>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to="foo"
>
<circle
@@ -64,8 +62,6 @@ exports[`should render bubble links 2`] = `
<Tooltip>
<g>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to="bar"
>
<circle
diff --git a/server/sonar-web/src/main/js/components/common/ActivityLink.tsx b/server/sonar-web/src/main/js/components/common/ActivityLink.tsx
index b231d18438d..0a4f8fc8698 100644
--- a/server/sonar-web/src/main/js/components/common/ActivityLink.tsx
+++ b/server/sonar-web/src/main/js/components/common/ActivityLink.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 HistoryIcon from '../../components/icons/HistoryIcon';
import { translate } from '../../helpers/l10n';
import { getActivityUrl, getMeasureHistoryUrl } from '../../helpers/urls';
diff --git a/server/sonar-web/src/main/js/components/common/DocumentationTooltip.tsx b/server/sonar-web/src/main/js/components/common/DocumentationTooltip.tsx
index 7d9b51b0744..e2150e74794 100644
--- a/server/sonar-web/src/main/js/components/common/DocumentationTooltip.tsx
+++ b/server/sonar-web/src/main/js/components/common/DocumentationTooltip.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 { isWebUri } from 'valid-url';
import HelpTooltip from '../../components/controls/HelpTooltip';
import DetachIcon from '../../components/icons/DetachIcon';
diff --git a/server/sonar-web/src/main/js/components/common/MeasuresLink.tsx b/server/sonar-web/src/main/js/components/common/MeasuresLink.tsx
index aeb02bc74e1..c3515fc8bfa 100644
--- a/server/sonar-web/src/main/js/components/common/MeasuresLink.tsx
+++ b/server/sonar-web/src/main/js/components/common/MeasuresLink.tsx
@@ -19,7 +19,7 @@
*/
import classNames from 'classnames';
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import MeasuresIcon from '../../components/icons/MeasuresIcon';
import { translate } from '../../helpers/l10n';
import { getComponentDrilldownUrl } from '../../helpers/urls';
diff --git a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/ActivityLink-test.tsx.snap b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/ActivityLink-test.tsx.snap
index e61f19c53df..3a792609d92 100644
--- a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/ActivityLink-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/ActivityLink-test.tsx.snap
@@ -3,15 +3,10 @@
exports[`renders correctly 1`] = `
<Link
className="activity-link"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/project/activity",
- "query": Object {
- "graph": undefined,
- "id": "foo",
- },
+ "search": "?id=foo",
}
}
>
@@ -28,16 +23,10 @@ exports[`renders correctly 1`] = `
exports[`renders correctly 2`] = `
<Link
className="activity-link"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/project/activity",
- "query": Object {
- "branch": "branch-6.7",
- "graph": undefined,
- "id": "foo",
- },
+ "search": "?id=foo&branch=branch-6.7",
}
}
>
@@ -54,15 +43,10 @@ exports[`renders correctly 2`] = `
exports[`renders correctly 3`] = `
<Link
className="activity-link"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/project/activity",
- "query": Object {
- "graph": "coverage",
- "id": "foo",
- },
+ "search": "?id=foo&graph=coverage",
}
}
>
@@ -79,16 +63,10 @@ exports[`renders correctly 3`] = `
exports[`renders correctly 4`] = `
<Link
className="activity-link"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/project/activity",
- "query": Object {
- "custom_metrics": "new_bugs,bugs",
- "graph": "custom",
- "id": "foo",
- },
+ "search": "?id=foo&graph=custom&custom_metrics=new_bugs%2Cbugs",
}
}
>
diff --git a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/DocumentationTooltip-test.tsx.snap b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/DocumentationTooltip-test.tsx.snap
index 6dcf00334b3..3755fb97cf0 100644
--- a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/DocumentationTooltip-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/DocumentationTooltip-test.tsx.snap
@@ -78,9 +78,7 @@ exports[`renders correctly: with links 1`] = `
>
<Link
className="display-inline-flex-center link-with-icon"
- onlyActiveOnIndex={false}
rel="noopener noreferrer"
- style={Object {}}
target="_blank"
to="http://link.tosome.place"
>
@@ -98,9 +96,7 @@ exports[`renders correctly: with links 1`] = `
>
<Link
className="display-inline-flex-center link-with-icon"
- onlyActiveOnIndex={false}
rel="noopener noreferrer"
- style={Object {}}
target="_blank"
to="/documentation/guide"
>
@@ -113,8 +109,6 @@ exports[`renders correctly: with links 1`] = `
className="little-spacer-bottom"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to="/projects"
>
<span>
diff --git a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MeasuresLink-test.tsx.snap b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MeasuresLink-test.tsx.snap
index e874481330c..80224fda5a4 100644
--- a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MeasuresLink-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MeasuresLink-test.tsx.snap
@@ -3,15 +3,10 @@
exports[`renders 1`] = `
<Link
className="measures-link"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/component_measures",
- "query": Object {
- "id": "foo",
- "metric": "security_rating",
- },
+ "search": "?id=foo&metric=security_rating",
}
}
>
@@ -28,15 +23,10 @@ exports[`renders 1`] = `
exports[`renders 2`] = `
<Link
className="measures-link"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/component_measures",
- "query": Object {
- "id": "foo",
- "metric": "security_rating",
- },
+ "search": "?id=foo&metric=security_rating",
}
}
>
@@ -53,15 +43,10 @@ exports[`renders 2`] = `
exports[`renders 3`] = `
<Link
className="measures-link"
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/component_measures",
- "query": Object {
- "id": "foo",
- "metric": "security_rating",
- },
+ "search": "?id=foo&metric=security_rating",
}
}
>
diff --git a/server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx b/server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx
index ac124ce01ad..1a87832a2f2 100644
--- a/server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx
+++ b/server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx
@@ -18,9 +18,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import classNames from 'classnames';
-import { LocationDescriptor } from 'history';
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link, To } from 'react-router-dom';
import { translate } from '../../helpers/l10n';
import DropdownIcon from '../icons/DropdownIcon';
import SettingsIcon from '../icons/SettingsIcon';
@@ -68,7 +67,7 @@ interface ItemProps {
download?: string;
id?: string;
onClick?: () => void;
- to?: LocationDescriptor;
+ to?: To;
}
export class ActionsDropdownItem extends React.PureComponent<ItemProps> {
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ActionsDropdown-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ActionsDropdown-test.tsx.snap
index 42636f02080..dbb20219cbe 100644
--- a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ActionsDropdown-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ActionsDropdown-test.tsx.snap
@@ -81,8 +81,6 @@ exports[`ActionsDropdownItem should render correctly 2`] = `
<Link
className="foo text-danger"
id="baz"
- onlyActiveOnIndex={false}
- style={Object {}}
to="path/name"
>
<span>
diff --git a/server/sonar-web/src/main/js/components/docs/DocLink.tsx b/server/sonar-web/src/main/js/components/docs/DocLink.tsx
index 1e1c556665a..493a652f0fc 100644
--- a/server/sonar-web/src/main/js/components/docs/DocLink.tsx
+++ b/server/sonar-web/src/main/js/components/docs/DocLink.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 withAppStateContext from '../../app/components/app-state/withAppStateContext';
import DetachIcon from '../../components/icons/DetachIcon';
import { AppState } from '../../types/appstate';
diff --git a/server/sonar-web/src/main/js/components/docs/DocTooltipLink.tsx b/server/sonar-web/src/main/js/components/docs/DocTooltipLink.tsx
index eb29f9be7aa..4074ac970aa 100644
--- a/server/sonar-web/src/main/js/components/docs/DocTooltipLink.tsx
+++ b/server/sonar-web/src/main/js/components/docs/DocTooltipLink.tsx
@@ -19,7 +19,7 @@
*/
import { forEach } from 'lodash';
import * as React from 'react';
-import { Link } from 'react-router';
+import { Link } from 'react-router-dom';
import DetachIcon from '../../components/icons/DetachIcon';
import { Dict } from '../../types/types';
diff --git a/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocLink-test.tsx.snap b/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocLink-test.tsx.snap
index a9918c0b752..2ec3dbe8289 100644
--- a/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocLink-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocLink-test.tsx.snap
@@ -17,8 +17,6 @@ exports[`should render documentation anchor 1`] = `
exports[`should render documentation link 1`] = `
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to="/documentation/foo/bar"
>
link text
@@ -52,8 +50,6 @@ exports[`should render sonarqube admin link on sonarqube for admin 1`] = `
exports[`should render sonarqube admin link on sonarqube for admin 2`] = `
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/foo/bar"
>
@@ -71,8 +67,6 @@ exports[`should render sonarqube link on sonarqube 1`] = `
exports[`should render sonarqube link on sonarqube 2`] = `
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/foo/bar"
>
diff --git a/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocTooltipLink-test.tsx.snap b/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocTooltipLink-test.tsx.snap
index 4d7250e364e..851e357803a 100644
--- a/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocTooltipLink-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocTooltipLink-test.tsx.snap
@@ -2,9 +2,7 @@
exports[`should render internal link 1`] = `
<Link
- onlyActiveOnIndex={false}
rel="noopener noreferrer"
- style={Object {}}
target="_blank"
to="/documentation/foo/bar"
/>
@@ -12,9 +10,7 @@ exports[`should render internal link 1`] = `
exports[`should render links with custom props 1`] = `
<Link
- onlyActiveOnIndex={false}
rel="noopener noreferrer"
- style={Object {}}
target="_blank"
to="/documentation/foo/baz"
/>
diff --git a/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.tsx b/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.tsx
index 0f8d1db103c..699c03b9521 100644
--- a/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.tsx
+++ b/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.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 { SuggestionLink } from '../../types/types';
diff --git a/server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/__snapshots__/EmbedDocsPopup-test.tsx.snap b/server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/__snapshots__/EmbedDocsPopup-test.tsx.snap
index 29a379acaca..da58a8ccb00 100644
--- a/server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/__snapshots__/EmbedDocsPopup-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/__snapshots__/EmbedDocsPopup-test.tsx.snap
@@ -11,8 +11,6 @@ exports[`should render 1`] = `
<li>
<Link
onClick={[MockFunction]}
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation"
>
@@ -22,8 +20,6 @@ exports[`should render 1`] = `
<li>
<Link
onClick={[MockFunction]}
- onlyActiveOnIndex={false}
- style={Object {}}
to="/web_api"
>
api_documentation.page
diff --git a/server/sonar-web/src/main/js/components/hoc/withLocation.tsx b/server/sonar-web/src/main/js/components/hoc/withLocation.tsx
new file mode 100644
index 00000000000..4b6f25401a7
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/hoc/withLocation.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 { Location, useLocation } from 'react-router-dom';
+
+export default function withLocation<P>(
+ WrappedComponent: React.ComponentType<P & { location: Location }>
+) {
+ return function WithLocation(props: Omit<P, 'location'>) {
+ const location = useLocation();
+
+ return <WrappedComponent location={location} {...(props as P)} />;
+ };
+}
diff --git a/server/sonar-web/src/main/js/components/hoc/withRouter.tsx b/server/sonar-web/src/main/js/components/hoc/withRouter.tsx
index ca8509036b1..650d6db725c 100644
--- a/server/sonar-web/src/main/js/components/hoc/withRouter.tsx
+++ b/server/sonar-web/src/main/js/components/hoc/withRouter.tsx
@@ -18,18 +18,78 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { withRouter as originalWithRouter, WithRouterProps } from 'react-router';
+import {
+ Location as LocationRouter,
+ Params,
+ useLocation as useLocationRouter,
+ useNavigate,
+ useParams,
+ useSearchParams
+} from 'react-router-dom';
+import { queryToSearch, searchParamsToQuery } from '../../helpers/urls';
+import { RawQuery } from '../../types/types';
+import { getWrappedDisplayName } from './utils';
-export type Location = WithRouterProps['location'];
-export type Router = WithRouterProps['router'];
+export interface Location extends LocationRouter {
+ query: RawQuery;
+}
+
+export interface Router {
+ replace: (location: string | Partial<Location>) => void;
+ push: (location: string | Partial<Location>) => void;
+}
-interface InjectedProps {
- location?: Partial<Location>;
- router?: Partial<Router>;
+export interface WithRouterProps {
+ location: Location;
+ params: Params;
+ router: Router;
}
-export function withRouter<P extends InjectedProps>(
- WrappedComponent: React.ComponentType<P & InjectedProps>
-): React.ComponentType<Omit<P, keyof InjectedProps>> {
- return originalWithRouter(WrappedComponent as any);
+export function withRouter<P extends Partial<WithRouterProps>>(
+ WrappedComponent: React.ComponentType<P>
+): React.ComponentType<Omit<P, keyof WithRouterProps>> {
+ function ComponentWithRouterProp(props: P) {
+ const locationRouter = useLocationRouter();
+ const navigate = useNavigate();
+ const params = useParams();
+ const [searchParams] = useSearchParams();
+
+ const router = React.useMemo(
+ () => ({
+ replace: (path: string | Partial<Location>) => {
+ if ((path as Location).query) {
+ path.search = queryToSearch((path as Location).query);
+ }
+ navigate(path, { replace: true });
+ },
+ push: (path: string | Partial<Location>) => {
+ if ((path as Location).query) {
+ path.search = queryToSearch((path as Location).query);
+ }
+ navigate(path);
+ }
+ }),
+ [navigate]
+ );
+
+ const location = {
+ ...locationRouter,
+ query: searchParamsToQuery(searchParams)
+ };
+
+ return <WrappedComponent {...props} location={location} params={params} router={router} />;
+ }
+
+ (ComponentWithRouterProp as React.FC<P>).displayName = getWrappedDisplayName(
+ WrappedComponent,
+ 'withRouter'
+ );
+
+ return ComponentWithRouterProp;
+}
+
+export function useLocation() {
+ const location = useLocationRouter();
+
+ return { ...location, query: searchParamsToQuery(new URLSearchParams(location.search)) };
}
diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx
index b010d31c688..1e39ea8bd65 100644
--- a/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx
+++ b/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.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 LinkIcon from '../../../components/icons/LinkIcon';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap
index 8b2455b7c45..3cb4d435d76 100644
--- a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap
@@ -73,19 +73,13 @@ exports[`should render correctly: default 1`] = `
>
<Link
className="js-issue-permalink link-no-underline"
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
title="permalink"
to={
Object {
+ "hash": "",
"pathname": "/project/issues",
- "query": Object {
- "id": "myproject",
- "issues": "AVsae-CQS-9G3txfbFN2",
- "open": "AVsae-CQS-9G3txfbFN2",
- "types": undefined,
- },
+ "search": "?issues=AVsae-CQS-9G3txfbFN2&open=AVsae-CQS-9G3txfbFN2&id=myproject",
}
}
>
@@ -170,19 +164,13 @@ exports[`should render correctly: with filter 1`] = `
>
<Link
className="js-issue-permalink link-no-underline"
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
title="permalink"
to={
Object {
+ "hash": "",
"pathname": "/project/issues",
- "query": Object {
- "id": "myproject",
- "issues": "AVsae-CQS-9G3txfbFN2",
- "open": "AVsae-CQS-9G3txfbFN2",
- "types": undefined,
- },
+ "search": "?issues=AVsae-CQS-9G3txfbFN2&open=AVsae-CQS-9G3txfbFN2&id=myproject",
}
}
>
@@ -386,19 +374,13 @@ exports[`should render correctly: with multi locations 1`] = `
>
<Link
className="js-issue-permalink link-no-underline"
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
title="permalink"
to={
Object {
+ "hash": "",
"pathname": "/project/issues",
- "query": Object {
- "id": "myproject",
- "issues": "AVsae-CQS-9G3txfbFN2",
- "open": "AVsae-CQS-9G3txfbFN2",
- "types": undefined,
- },
+ "search": "?issues=AVsae-CQS-9G3txfbFN2&open=AVsae-CQS-9G3txfbFN2&id=myproject",
}
}
>
@@ -549,19 +531,12 @@ exports[`should render correctly: with multi locations and link 1`] = `
className="issue-meta"
>
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to={
Object {
+ "hash": "",
"pathname": "/project/issues",
- "query": Object {
- "branch": "branch-6.7",
- "id": "myproject",
- "issues": "AVsae-CQS-9G3txfbFN2",
- "open": "AVsae-CQS-9G3txfbFN2",
- "types": undefined,
- },
+ "search": "?branch=branch-6.7&issues=AVsae-CQS-9G3txfbFN2&open=AVsae-CQS-9G3txfbFN2&id=myproject",
}
}
>
@@ -579,20 +554,13 @@ exports[`should render correctly: with multi locations and link 1`] = `
>
<Link
className="js-issue-permalink link-no-underline"
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
title="permalink"
to={
Object {
+ "hash": "",
"pathname": "/project/issues",
- "query": Object {
- "branch": "branch-6.7",
- "id": "myproject",
- "issues": "AVsae-CQS-9G3txfbFN2",
- "open": "AVsae-CQS-9G3txfbFN2",
- "types": undefined,
- },
+ "search": "?branch=branch-6.7&issues=AVsae-CQS-9G3txfbFN2&open=AVsae-CQS-9G3txfbFN2&id=myproject",
}
}
>
diff --git a/server/sonar-web/src/main/js/components/shared/DrilldownLink.tsx b/server/sonar-web/src/main/js/components/shared/DrilldownLink.tsx
index e4a36009c39..bd368a08cf7 100644
--- a/server/sonar-web/src/main/js/components/shared/DrilldownLink.tsx
+++ b/server/sonar-web/src/main/js/components/shared/DrilldownLink.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 { getBranchLikeQuery } from '../../helpers/branch-like';
import { getComponentDrilldownUrl, getComponentIssuesUrl } from '../../helpers/urls';
import { BranchLike } from '../../types/branch-like';
diff --git a/server/sonar-web/src/main/js/components/shared/__tests__/__snapshots__/DrilldownLink-test.tsx.snap b/server/sonar-web/src/main/js/components/shared/__tests__/__snapshots__/DrilldownLink-test.tsx.snap
index cb653af58a1..e09cd6d4cd1 100644
--- a/server/sonar-web/src/main/js/components/shared/__tests__/__snapshots__/DrilldownLink-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/shared/__tests__/__snapshots__/DrilldownLink-test.tsx.snap
@@ -2,17 +2,10 @@
exports[`should render correctly 1`] = `
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
"pathname": "/component_measures",
- "query": Object {
- "asc": undefined,
- "id": "project123",
- "metric": "other",
- "view": "list",
- },
+ "search": "?id=project123&metric=other&view=list",
}
}
>
@@ -22,15 +15,11 @@ exports[`should render correctly 1`] = `
exports[`should render issuesLink correctly 1`] = `
<Link
- onlyActiveOnIndex={false}
- style={Object {}}
to={
Object {
+ "hash": "",
"pathname": "/project/issues",
- "query": Object {
- "id": "project123",
- "resolved": "false",
- },
+ "search": "?resolved=false&id=project123",
}
}
>
diff --git a/server/sonar-web/src/main/js/components/tutorials/TutorialSelection.tsx b/server/sonar-web/src/main/js/components/tutorials/TutorialSelection.tsx
index beaccfa713e..ae972acfd0e 100644
--- a/server/sonar-web/src/main/js/components/tutorials/TutorialSelection.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/TutorialSelection.tsx
@@ -18,7 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { WithRouterProps } from 'react-router';
import { getAlmSettingsNoCatch } from '../../api/alm-settings';
import { getScannableProjects } from '../../api/components';
import { getValues } from '../../api/settings';
@@ -29,15 +28,17 @@ import { Permissions } from '../../types/permissions';
import { SettingsKey } from '../../types/settings';
import { Component } from '../../types/types';
import { LoggedInUser } from '../../types/users';
-import { withRouter } from '../hoc/withRouter';
+import { Location, Router, withRouter } from '../hoc/withRouter';
import TutorialSelectionRenderer from './TutorialSelectionRenderer';
import { TutorialModes } from './types';
-interface Props extends Pick<WithRouterProps, 'router' | 'location'> {
+interface Props {
component: Component;
currentUser: LoggedInUser;
projectBinding?: ProjectAlmBindingResponse;
willRefreshAutomatically?: boolean;
+ location: Location;
+ router: Router;
}
interface State {
diff --git a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/AlertClassicEditor.tsx b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/AlertClassicEditor.tsx
index cd23d453df5..ae64c89960d 100644
--- a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/AlertClassicEditor.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/AlertClassicEditor.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 { ALM_DOCUMENTATION_PATHS } from '../../../../helpers/constants';
import { translate } from '../../../../helpers/l10n';
diff --git a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/PublishSteps.tsx b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/PublishSteps.tsx
index 13d10fb06ec..665d729a0d5 100644
--- a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/PublishSteps.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/PublishSteps.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 withAppStateContext from '../../../../app/components/app-state/withAppStateContext';
import { Alert } from '../../../../components/ui/Alert';
import { ALM_DOCUMENTATION_PATHS } from '../../../../helpers/constants';
diff --git a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/__tests__/__snapshots__/AlertClassicEditor-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/__tests__/__snapshots__/AlertClassicEditor-test.tsx.snap
index 287d3d1a78a..a9a6864ee29 100644
--- a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/__tests__/__snapshots__/AlertClassicEditor-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/__tests__/__snapshots__/AlertClassicEditor-test.tsx.snap
@@ -11,8 +11,6 @@ exports[`should render correctly 1`] = `
values={
Object {
"doc_link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/azuredevops-integration/"
>
diff --git a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/__tests__/__snapshots__/PublishSteps-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/__tests__/__snapshots__/PublishSteps-test.tsx.snap
index 346a354101d..13c0f03e6f9 100644
--- a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/__tests__/__snapshots__/PublishSteps-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/__tests__/__snapshots__/PublishSteps-test.tsx.snap
@@ -68,8 +68,6 @@ exports[`should render correctly 2`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/azuredevops-integration/"
>
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/EditTokenModal.tsx b/server/sonar-web/src/main/js/components/tutorials/components/EditTokenModal.tsx
index 9d5774dd572..f60578becaa 100644
--- a/server/sonar-web/src/main/js/components/tutorials/components/EditTokenModal.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/components/EditTokenModal.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 { generateToken, getTokens, revokeToken } from '../../../api/user-tokens';
import { Button, DeleteButton } from '../../../components/controls/buttons';
import { ClipboardIconButton } from '../../../components/controls/clipboard';
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/PreRequisitesStep.tsx b/server/sonar-web/src/main/js/components/tutorials/jenkins/PreRequisitesStep.tsx
index 0f391b0bf53..6e068484065 100644
--- a/server/sonar-web/src/main/js/components/tutorials/jenkins/PreRequisitesStep.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/jenkins/PreRequisitesStep.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 { rawSizes } from '../../../app/theme';
import { Button } from '../../../components/controls/buttons';
import ChevronRightIcon from '../../../components/icons/ChevronRightIcon';
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/__snapshots__/PreRequisitesStep-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/__snapshots__/PreRequisitesStep-test.tsx.snap
index 33d44479d74..091b72035d8 100644
--- a/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/__snapshots__/PreRequisitesStep-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/__snapshots__/PreRequisitesStep-test.tsx.snap
@@ -47,8 +47,6 @@ exports[`should render correctly: content 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/jenkins/"
>
@@ -109,8 +107,6 @@ exports[`should render correctly: content for branches disabled 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/jenkins/"
>
@@ -174,8 +170,6 @@ exports[`should render correctly: content for branches disabled, gitlab 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/jenkins/"
>
diff --git a/server/sonar-web/src/main/js/components/tutorials/manual/DoneNextSteps.tsx b/server/sonar-web/src/main/js/components/tutorials/manual/DoneNextSteps.tsx
index 4cfcf0dc1b0..df037f76a29 100644
--- a/server/sonar-web/src/main/js/components/tutorials/manual/DoneNextSteps.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/manual/DoneNextSteps.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 { translate } from '../../../helpers/l10n';
import { Component } from '../../../types/types';
diff --git a/server/sonar-web/src/main/js/components/tutorials/manual/TokenStep.tsx b/server/sonar-web/src/main/js/components/tutorials/manual/TokenStep.tsx
index c9ba982bada..e0f47ce2425 100644
--- a/server/sonar-web/src/main/js/components/tutorials/manual/TokenStep.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/manual/TokenStep.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 { generateToken, getTokens, revokeToken } from '../../../api/user-tokens';
import { Button, DeleteButton, SubmitButton } from '../../../components/controls/buttons';
import Radio from '../../../components/controls/Radio';
diff --git a/server/sonar-web/src/main/js/components/tutorials/manual/__tests__/__snapshots__/DoneNextSteps-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/manual/__tests__/__snapshots__/DoneNextSteps-test.tsx.snap
index 782a7ba254b..31694bcce7b 100644
--- a/server/sonar-web/src/main/js/components/tutorials/manual/__tests__/__snapshots__/DoneNextSteps-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/tutorials/manual/__tests__/__snapshots__/DoneNextSteps-test.tsx.snap
@@ -26,18 +26,14 @@ exports[`should render correctly: default 1`] = `
values={
Object {
"link_branches": <Link
- onlyActiveOnIndex={false}
rel="noopener noreferrer"
- style={Object {}}
target="_blank"
to="/documentation/branches/overview/"
>
onboarding.analysis.auto_refresh_after_analysis.check_these_links.branches
</Link>,
"link_pr_analysis": <Link
- onlyActiveOnIndex={false}
rel="noopener noreferrer"
- style={Object {}}
target="_blank"
to="/documentation/analysis/pull-request/"
>
@@ -76,18 +72,14 @@ exports[`should render correctly: project admin 1`] = `
values={
Object {
"link_branches": <Link
- onlyActiveOnIndex={false}
rel="noopener noreferrer"
- style={Object {}}
target="_blank"
to="/documentation/branches/overview/"
>
onboarding.analysis.auto_refresh_after_analysis.check_these_links.branches
</Link>,
"link_pr_analysis": <Link
- onlyActiveOnIndex={false}
rel="noopener noreferrer"
- style={Object {}}
target="_blank"
to="/documentation/analysis/pull-request/"
>
diff --git a/server/sonar-web/src/main/js/components/tutorials/manual/__tests__/__snapshots__/TokenStep-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/manual/__tests__/__snapshots__/TokenStep-test.tsx.snap
index 269c233855d..e5217b8c6f2 100644
--- a/server/sonar-web/src/main/js/components/tutorials/manual/__tests__/__snapshots__/TokenStep-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/tutorials/manual/__tests__/__snapshots__/TokenStep-test.tsx.snap
@@ -97,8 +97,6 @@ exports[`generates token 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/account/security"
>
@@ -207,8 +205,6 @@ exports[`generates token 2`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/account/security"
>
@@ -273,8 +269,6 @@ exports[`generates token 3`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/account/security"
>
@@ -349,8 +343,6 @@ exports[`revokes token 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/account/security"
>
@@ -424,8 +416,6 @@ exports[`revokes token 2`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/account/security"
>
@@ -547,8 +537,6 @@ exports[`revokes token 3`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/account/security"
>
diff --git a/server/sonar-web/src/main/js/components/tutorials/manual/commands/DotNetExecute.tsx b/server/sonar-web/src/main/js/components/tutorials/manual/commands/DotNetExecute.tsx
index e3b2906cc0c..b2840c55b8e 100644
--- a/server/sonar-web/src/main/js/components/tutorials/manual/commands/DotNetExecute.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/manual/commands/DotNetExecute.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 { translate } from '../../../../helpers/l10n';
import { Component } from '../../../../types/types';
import CodeSnippet from '../../../common/CodeSnippet';
diff --git a/server/sonar-web/src/main/js/components/tutorials/manual/commands/ExecBuildWrapper.tsx b/server/sonar-web/src/main/js/components/tutorials/manual/commands/ExecBuildWrapper.tsx
index 233cfcf0c19..a359e2bae1a 100644
--- a/server/sonar-web/src/main/js/components/tutorials/manual/commands/ExecBuildWrapper.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/manual/commands/ExecBuildWrapper.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 { translate } from '../../../../helpers/l10n';
import CodeSnippet from '../../../common/CodeSnippet';
import { OSs } from '../../types';
diff --git a/server/sonar-web/src/main/js/components/tutorials/manual/commands/ExecScanner.tsx b/server/sonar-web/src/main/js/components/tutorials/manual/commands/ExecScanner.tsx
index 19751ebba7c..3bcb8b538bf 100644
--- a/server/sonar-web/src/main/js/components/tutorials/manual/commands/ExecScanner.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/manual/commands/ExecScanner.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 { translate } from '../../../../helpers/l10n';
import { Component } from '../../../../types/types';
import CodeSnippet from '../../../common/CodeSnippet';
diff --git a/server/sonar-web/src/main/js/components/tutorials/manual/commands/JavaGradle.tsx b/server/sonar-web/src/main/js/components/tutorials/manual/commands/JavaGradle.tsx
index 854d76fdb1b..b640f69b70a 100644
--- a/server/sonar-web/src/main/js/components/tutorials/manual/commands/JavaGradle.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/manual/commands/JavaGradle.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 { translate } from '../../../../helpers/l10n';
import { Component } from '../../../../types/types';
import CodeSnippet from '../../../common/CodeSnippet';
diff --git a/server/sonar-web/src/main/js/components/tutorials/manual/commands/JavaMaven.tsx b/server/sonar-web/src/main/js/components/tutorials/manual/commands/JavaMaven.tsx
index 8477b611393..f56b9be740e 100644
--- a/server/sonar-web/src/main/js/components/tutorials/manual/commands/JavaMaven.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/manual/commands/JavaMaven.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 { translate } from '../../../../helpers/l10n';
import { Component } from '../../../../types/types';
import CodeSnippet from '../../../common/CodeSnippet';
diff --git a/server/sonar-web/src/main/js/components/tutorials/manual/commands/__tests__/__snapshots__/DotNetExecute-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/manual/commands/__tests__/__snapshots__/DotNetExecute-test.tsx.snap
index 7ff51cbcb67..a921c78ff66 100644
--- a/server/sonar-web/src/main/js/components/tutorials/manual/commands/__tests__/__snapshots__/DotNetExecute-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/tutorials/manual/commands/__tests__/__snapshots__/DotNetExecute-test.tsx.snap
@@ -29,8 +29,6 @@ exports[`should render correctly 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/scan/sonarscanner-for-msbuild/"
>
diff --git a/server/sonar-web/src/main/js/components/tutorials/manual/commands/__tests__/__snapshots__/ExecBuildWrapper-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/manual/commands/__tests__/__snapshots__/ExecBuildWrapper-test.tsx.snap
index 7e7f188ffe3..68e585ff320 100644
--- a/server/sonar-web/src/main/js/components/tutorials/manual/commands/__tests__/__snapshots__/ExecBuildWrapper-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/tutorials/manual/commands/__tests__/__snapshots__/ExecBuildWrapper-test.tsx.snap
@@ -24,8 +24,6 @@ exports[`Shoud renders for "linux" correctly 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/languages/cfamily/"
>
@@ -62,8 +60,6 @@ exports[`Shoud renders for "mac" correctly 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/languages/cfamily/"
>
@@ -100,8 +96,6 @@ exports[`Shoud renders for "win" correctly 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/languages/cfamily/"
>
diff --git a/server/sonar-web/src/main/js/components/tutorials/manual/commands/__tests__/__snapshots__/ExecScanner-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/manual/commands/__tests__/__snapshots__/ExecScanner-test.tsx.snap
index b48bd7b66eb..c979aedbd8a 100644
--- a/server/sonar-web/src/main/js/components/tutorials/manual/commands/__tests__/__snapshots__/ExecScanner-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/tutorials/manual/commands/__tests__/__snapshots__/ExecScanner-test.tsx.snap
@@ -34,8 +34,6 @@ exports[`should render correctly for "linux" 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/scan/sonarscanner/"
>
@@ -106,8 +104,6 @@ exports[`should render correctly for "mac" 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/scan/sonarscanner/"
>
@@ -178,8 +174,6 @@ exports[`should render correctly for "win" 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/scan/sonarscanner/"
>
@@ -250,8 +244,6 @@ exports[`should render correctly for cfamily 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/scan/sonarscanner/"
>
diff --git a/server/sonar-web/src/main/js/components/tutorials/manual/commands/__tests__/__snapshots__/JavaGradle-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/manual/commands/__tests__/__snapshots__/JavaGradle-test.tsx.snap
index ca9adca4c17..fc132dddb80 100644
--- a/server/sonar-web/src/main/js/components/tutorials/manual/commands/__tests__/__snapshots__/JavaGradle-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/tutorials/manual/commands/__tests__/__snapshots__/JavaGradle-test.tsx.snap
@@ -29,8 +29,6 @@ exports[`renders correctly 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/scan/sonarscanner-for-gradle/"
>
@@ -65,8 +63,6 @@ exports[`renders correctly 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/scan/sonarscanner-for-gradle/"
>
diff --git a/server/sonar-web/src/main/js/components/tutorials/manual/commands/__tests__/__snapshots__/JavaMaven-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/manual/commands/__tests__/__snapshots__/JavaMaven-test.tsx.snap
index 741cf44262a..970c7f620cc 100644
--- a/server/sonar-web/src/main/js/components/tutorials/manual/commands/__tests__/__snapshots__/JavaMaven-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/tutorials/manual/commands/__tests__/__snapshots__/JavaMaven-test.tsx.snap
@@ -33,8 +33,6 @@ exports[`renders correctly 1`] = `
values={
Object {
"link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
target="_blank"
to="/documentation/analysis/scan/sonarscanner-for-maven/"
>
diff --git a/server/sonar-web/src/main/js/helpers/__tests__/handleRequiredAuthentication-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/handleRequiredAuthentication-test.ts
index e27c6f2465a..48ce7ea3071 100644
--- a/server/sonar-web/src/main/js/helpers/__tests__/handleRequiredAuthentication-test.ts
+++ b/server/sonar-web/src/main/js/helpers/__tests__/handleRequiredAuthentication-test.ts
@@ -17,14 +17,34 @@
* 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 '../getHistory';
import handleRequiredAuthentication from '../handleRequiredAuthentication';
-jest.mock('../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 });
handleRequiredAuthentication();
- expect(replace).toBeCalledWith(expect.objectContaining({ pathname: '/sessions/new' }));
+ expect(replace).toBeCalledWith('/sessions/new?return_to=%2Fpath%3Fid%3D12%23tag');
});
diff --git a/server/sonar-web/src/main/js/helpers/__tests__/urls-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/urls-test.ts
index bdcb878c1b3..a881a16e4a1 100644
--- a/server/sonar-web/src/main/js/helpers/__tests__/urls-test.ts
+++ b/server/sonar-web/src/main/js/helpers/__tests__/urls-test.ts
@@ -22,14 +22,18 @@ import { ComponentQualifier } from '../../types/component';
import { IssueType } from '../../types/issues';
import { SecurityStandard } from '../../types/security';
import { mockBranch, mockMainBranch, mockPullRequest } from '../mocks/branch-like';
+import { mockLocation } from '../testMocks';
import {
CodeScope,
convertGithubApiUrlToLink,
+ convertToTo,
+ getComponentAdminUrl,
getComponentDrilldownUrl,
getComponentDrilldownUrlWithSelection,
getComponentIssuesUrl,
getComponentOverviewUrl,
getComponentSecurityHotspotsUrl,
+ getDeprecatedActiveRulesUrl,
getGlobalSettingsUrl,
getIssuesUrl,
getPathUrlAsString,
@@ -38,6 +42,8 @@ import {
getQualityGateUrl,
getReturnUrl,
isRelativeUrl,
+ queryToSearch,
+ searchParamsToQuery,
stripTrailingSlash
} from '../urls';
@@ -62,28 +68,55 @@ describe('#stripTrailingSlash', () => {
});
});
+describe('getComponentAdminUrl', () => {
+ it.each([
+ [
+ 'Portfolio',
+ ComponentQualifier.Portfolio,
+ { pathname: '/project/admin/extension/governance/console', search: '?id=key&qualifier=VW' }
+ ],
+ [
+ 'Application',
+ ComponentQualifier.Application,
+ {
+ pathname: '/project/admin/extension/developer-server/application-console',
+ search: '?id=key'
+ }
+ ],
+ ['Project', ComponentQualifier.Project, { pathname: '/dashboard', search: '?id=key' }]
+ ])('should work for %s', (_qualifierName, qualifier, result) => {
+ expect(getComponentAdminUrl('key', qualifier)).toEqual(result);
+ });
+});
+
describe('#getComponentIssuesUrl', () => {
it('should work without parameters', () => {
- expect(getComponentIssuesUrl(SIMPLE_COMPONENT_KEY, {})).toEqual({
- pathname: '/project/issues',
- query: { id: SIMPLE_COMPONENT_KEY }
- });
+ expect(getComponentIssuesUrl(SIMPLE_COMPONENT_KEY)).toEqual(
+ expect.objectContaining({
+ pathname: '/project/issues',
+ search: queryToSearch({ id: SIMPLE_COMPONENT_KEY })
+ })
+ );
});
it('should work with parameters', () => {
- expect(getComponentIssuesUrl(SIMPLE_COMPONENT_KEY, { resolved: 'false' })).toEqual({
- pathname: '/project/issues',
- query: { id: SIMPLE_COMPONENT_KEY, resolved: 'false' }
- });
+ expect(getComponentIssuesUrl(SIMPLE_COMPONENT_KEY, { resolved: 'false' })).toEqual(
+ expect.objectContaining({
+ pathname: '/project/issues',
+ search: queryToSearch({ resolved: 'false', id: SIMPLE_COMPONENT_KEY })
+ })
+ );
});
});
describe('#getComponentSecurityHotspotsUrl', () => {
it('should work with no extra parameters', () => {
- expect(getComponentSecurityHotspotsUrl(SIMPLE_COMPONENT_KEY, {})).toEqual({
- pathname: '/security_hotspots',
- query: { id: SIMPLE_COMPONENT_KEY }
- });
+ expect(getComponentSecurityHotspotsUrl(SIMPLE_COMPONENT_KEY)).toEqual(
+ expect.objectContaining({
+ pathname: '/security_hotspots',
+ search: queryToSearch({ id: SIMPLE_COMPONENT_KEY })
+ })
+ );
});
it('should forward some query parameters', () => {
@@ -97,39 +130,47 @@ describe('#getComponentSecurityHotspotsUrl', () => {
[SecurityStandard.SONARSOURCE]: 'a1',
ignoredParam: '1234'
})
- ).toEqual({
- pathname: '/security_hotspots',
- query: {
- id: SIMPLE_COMPONENT_KEY,
- [SecurityStandard.OWASP_TOP10_2021]: 'a1',
- [SecurityStandard.CWE]: 'a1',
- [SecurityStandard.OWASP_TOP10]: 'a1',
- [SecurityStandard.SANS_TOP25]: 'a1',
- [SecurityStandard.SONARSOURCE]: 'a1',
- sinceLeakPeriod: 'true'
- }
- });
+ ).toEqual(
+ expect.objectContaining({
+ pathname: '/security_hotspots',
+ search: queryToSearch({
+ id: SIMPLE_COMPONENT_KEY,
+ sinceLeakPeriod: 'true',
+ [SecurityStandard.OWASP_TOP10_2021]: 'a1',
+ [SecurityStandard.SONARSOURCE]: 'a1',
+ [SecurityStandard.OWASP_TOP10]: 'a1',
+ [SecurityStandard.SANS_TOP25]: 'a1',
+ [SecurityStandard.CWE]: 'a1'
+ })
+ })
+ );
});
});
describe('#getComponentOverviewUrl', () => {
it('should return a portfolio url for a portfolio', () => {
- expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.Portfolio)).toEqual({
- pathname: '/portfolio',
- query: { id: SIMPLE_COMPONENT_KEY }
- });
+ expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.Portfolio)).toEqual(
+ expect.objectContaining({
+ pathname: '/portfolio',
+ search: queryToSearch({ id: SIMPLE_COMPONENT_KEY })
+ })
+ );
});
it('should return a portfolio url for a subportfolio', () => {
- expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.SubPortfolio)).toEqual({
- pathname: '/portfolio',
- query: { id: SIMPLE_COMPONENT_KEY }
- });
+ expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.SubPortfolio)).toEqual(
+ expect.objectContaining({
+ pathname: '/portfolio',
+ search: queryToSearch({ id: SIMPLE_COMPONENT_KEY })
+ })
+ );
});
it('should return a dashboard url for a project', () => {
- expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.Project)).toEqual({
- pathname: '/dashboard',
- query: { id: SIMPLE_COMPONENT_KEY }
- });
+ expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.Project)).toEqual(
+ expect.objectContaining({
+ pathname: '/dashboard',
+ search: queryToSearch({ id: SIMPLE_COMPONENT_KEY })
+ })
+ );
});
it('should return correct dashboard url for a project when navigating from new code', () => {
expect(
@@ -139,10 +180,12 @@ describe('#getComponentOverviewUrl', () => {
undefined,
CodeScope.New
)
- ).toEqual({
- pathname: '/dashboard',
- query: { id: SIMPLE_COMPONENT_KEY, code_scope: 'new' }
- });
+ ).toEqual(
+ expect.objectContaining({
+ pathname: '/dashboard',
+ search: queryToSearch({ id: SIMPLE_COMPONENT_KEY, code_scope: 'new' })
+ })
+ );
});
it('should return correct dashboard url for a project when navigating from overall code', () => {
expect(
@@ -152,16 +195,20 @@ describe('#getComponentOverviewUrl', () => {
undefined,
CodeScope.Overall
)
- ).toEqual({
- pathname: '/dashboard',
- query: { id: SIMPLE_COMPONENT_KEY, code_scope: 'overall' }
- });
+ ).toEqual(
+ expect.objectContaining({
+ pathname: '/dashboard',
+ search: queryToSearch({ id: SIMPLE_COMPONENT_KEY, code_scope: 'overall' })
+ })
+ );
});
it('should return a dashboard url for an app', () => {
- expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.Application)).toEqual({
- pathname: '/dashboard',
- query: { id: SIMPLE_COMPONENT_KEY }
- });
+ expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.Application)).toEqual(
+ expect.objectContaining({
+ pathname: '/dashboard',
+ search: queryToSearch({ id: SIMPLE_COMPONENT_KEY })
+ })
+ );
});
});
@@ -169,28 +216,34 @@ describe('#getComponentDrilldownUrl', () => {
it('should return component drilldown url', () => {
expect(
getComponentDrilldownUrl({ componentKey: SIMPLE_COMPONENT_KEY, metric: METRIC })
- ).toEqual({
- pathname: '/component_measures',
- query: { id: SIMPLE_COMPONENT_KEY, metric: METRIC }
- });
+ ).toEqual(
+ expect.objectContaining({
+ pathname: '/component_measures',
+ search: queryToSearch({ id: SIMPLE_COMPONENT_KEY, metric: METRIC })
+ })
+ );
});
it('should not encode component key', () => {
expect(
getComponentDrilldownUrl({ componentKey: COMPLEX_COMPONENT_KEY, metric: METRIC })
- ).toEqual({
- pathname: '/component_measures',
- query: { id: COMPLEX_COMPONENT_KEY, metric: METRIC }
- });
+ ).toEqual(
+ expect.objectContaining({
+ pathname: '/component_measures',
+ search: queryToSearch({ id: COMPLEX_COMPONENT_KEY, metric: METRIC })
+ })
+ );
});
it('should add asc param only when its list view', () => {
expect(
getComponentDrilldownUrl({ componentKey: SIMPLE_COMPONENT_KEY, metric: METRIC, asc: false })
- ).toEqual({
- pathname: '/component_measures',
- query: { id: SIMPLE_COMPONENT_KEY, metric: METRIC }
- });
+ ).toEqual(
+ expect.objectContaining({
+ pathname: '/component_measures',
+ search: queryToSearch({ id: SIMPLE_COMPONENT_KEY, metric: METRIC })
+ })
+ );
expect(
getComponentDrilldownUrl({
@@ -199,10 +252,17 @@ describe('#getComponentDrilldownUrl', () => {
listView: true,
asc: false
})
- ).toEqual({
- pathname: '/component_measures',
- query: { id: SIMPLE_COMPONENT_KEY, metric: METRIC, asc: 'false', view: 'list' }
- });
+ ).toEqual(
+ expect.objectContaining({
+ pathname: '/component_measures',
+ search: queryToSearch({
+ id: SIMPLE_COMPONENT_KEY,
+ metric: METRIC,
+ view: 'list',
+ asc: 'false'
+ })
+ })
+ );
});
});
@@ -210,10 +270,16 @@ describe('#getComponentDrilldownUrlWithSelection', () => {
it('should return component drilldown url with selection', () => {
expect(
getComponentDrilldownUrlWithSelection(SIMPLE_COMPONENT_KEY, COMPLEX_COMPONENT_KEY, METRIC)
- ).toEqual({
- pathname: '/component_measures',
- query: { id: SIMPLE_COMPONENT_KEY, metric: METRIC, selected: COMPLEX_COMPONENT_KEY }
- });
+ ).toEqual(
+ expect.objectContaining({
+ pathname: '/component_measures',
+ search: queryToSearch({
+ id: SIMPLE_COMPONENT_KEY,
+ metric: METRIC,
+ selected: COMPLEX_COMPONENT_KEY
+ })
+ })
+ );
});
it('should return component drilldown url with branchLike', () => {
@@ -224,15 +290,17 @@ describe('#getComponentDrilldownUrlWithSelection', () => {
METRIC,
mockBranch({ name: 'foo' })
)
- ).toEqual({
- pathname: '/component_measures',
- query: {
- id: SIMPLE_COMPONENT_KEY,
- metric: METRIC,
- selected: COMPLEX_COMPONENT_KEY,
- branch: 'foo'
- }
- });
+ ).toEqual(
+ expect.objectContaining({
+ pathname: '/component_measures',
+ search: queryToSearch({
+ id: SIMPLE_COMPONENT_KEY,
+ metric: METRIC,
+ branch: 'foo',
+ selected: COMPLEX_COMPONENT_KEY
+ })
+ })
+ );
});
it('should return component drilldown url with view parameter', () => {
@@ -244,15 +312,17 @@ describe('#getComponentDrilldownUrlWithSelection', () => {
undefined,
'list'
)
- ).toEqual({
- pathname: '/component_measures',
- query: {
- id: SIMPLE_COMPONENT_KEY,
- metric: METRIC,
- selected: COMPLEX_COMPONENT_KEY,
- view: 'list'
- }
- });
+ ).toEqual(
+ expect.objectContaining({
+ pathname: '/component_measures',
+ search: queryToSearch({
+ id: SIMPLE_COMPONENT_KEY,
+ metric: METRIC,
+ view: 'list',
+ selected: COMPLEX_COMPONENT_KEY
+ })
+ })
+ );
expect(
getComponentDrilldownUrlWithSelection(
@@ -262,15 +332,17 @@ describe('#getComponentDrilldownUrlWithSelection', () => {
mockMainBranch(),
'treemap'
)
- ).toEqual({
- pathname: '/component_measures',
- query: {
- id: SIMPLE_COMPONENT_KEY,
- metric: METRIC,
- selected: COMPLEX_COMPONENT_KEY,
- view: 'treemap'
- }
- });
+ ).toEqual(
+ expect.objectContaining({
+ pathname: '/component_measures',
+ search: queryToSearch({
+ id: SIMPLE_COMPONENT_KEY,
+ metric: METRIC,
+ view: 'treemap',
+ selected: COMPLEX_COMPONENT_KEY
+ })
+ })
+ );
expect(
getComponentDrilldownUrlWithSelection(
@@ -280,14 +352,31 @@ describe('#getComponentDrilldownUrlWithSelection', () => {
mockPullRequest({ key: '1' }),
'tree'
)
- ).toEqual({
- pathname: '/component_measures',
- query: {
- id: SIMPLE_COMPONENT_KEY,
- metric: METRIC,
- selected: COMPLEX_COMPONENT_KEY,
- pullRequest: '1'
- }
+ ).toEqual(
+ expect.objectContaining({
+ pathname: '/component_measures',
+ search: queryToSearch({
+ id: SIMPLE_COMPONENT_KEY,
+ metric: METRIC,
+ pullRequest: '1',
+ selected: COMPLEX_COMPONENT_KEY
+ })
+ })
+ );
+ });
+});
+
+describe('getDeprecatedActiveRulesUrl', () => {
+ it('should include query params', () => {
+ expect(getDeprecatedActiveRulesUrl({ languages: 'js' })).toEqual({
+ pathname: '/coding_rules',
+ search: '?languages=js&activation=true&statuses=DEPRECATED'
+ });
+ });
+ it('should handle empty query', () => {
+ expect(getDeprecatedActiveRulesUrl()).toEqual({
+ pathname: '/coding_rules',
+ search: '?activation=true&statuses=DEPRECATED'
});
});
});
@@ -304,7 +393,7 @@ describe('#getIssuesUrl', () => {
const type = IssueType.Bug;
expect(getIssuesUrl({ type })).toEqual({
pathname: '/issues',
- query: { type }
+ search: queryToSearch({ type })
});
});
});
@@ -313,11 +402,11 @@ describe('#getGlobalSettingsUrl', () => {
it('should work as expected', () => {
expect(getGlobalSettingsUrl('foo')).toEqual({
pathname: '/admin/settings',
- query: { category: 'foo' }
+ search: queryToSearch({ category: 'foo' })
});
expect(getGlobalSettingsUrl('foo', { alm: AlmKeys.GitHub })).toEqual({
pathname: '/admin/settings',
- query: { category: 'foo', alm: AlmKeys.GitHub }
+ search: queryToSearch({ category: 'foo', alm: AlmKeys.GitHub })
});
});
});
@@ -326,11 +415,11 @@ describe('#getProjectSettingsUrl', () => {
it('should work as expected', () => {
expect(getProjectSettingsUrl('foo')).toEqual({
pathname: '/project/settings',
- query: { id: 'foo' }
+ search: queryToSearch({ id: 'foo' })
});
expect(getProjectSettingsUrl('foo', 'bar')).toEqual({
pathname: '/project/settings',
- query: { id: 'foo', category: 'bar' }
+ search: queryToSearch({ id: 'foo', category: 'bar' })
});
});
});
@@ -338,15 +427,25 @@ describe('#getProjectSettingsUrl', () => {
describe('#getPathUrlAsString', () => {
it('should return component url', () => {
expect(
- getPathUrlAsString({ pathname: '/dashboard', query: { id: SIMPLE_COMPONENT_KEY } })
+ getPathUrlAsString({
+ pathname: '/dashboard',
+ search: queryToSearch({ id: SIMPLE_COMPONENT_KEY })
+ })
).toBe('/dashboard?id=' + SIMPLE_COMPONENT_KEY);
});
it('should encode component key', () => {
expect(
- getPathUrlAsString({ pathname: '/dashboard', query: { id: COMPLEX_COMPONENT_KEY } })
+ getPathUrlAsString({
+ pathname: '/dashboard',
+ search: queryToSearch({ id: COMPLEX_COMPONENT_KEY })
+ })
).toBe('/dashboard?id=' + COMPLEX_COMPONENT_KEY_ENCODED);
});
+
+ it('should handle partial arguments', () => {
+ expect(getPathUrlAsString({}, true)).toBe('/');
+ });
});
describe('#getReturnUrl', () => {
@@ -394,3 +493,52 @@ describe('#getHostUrl', () => {
);
});
});
+
+describe('searchParamsToQuery', () => {
+ it('should handle arrays and single params', () => {
+ const searchParams = new URLSearchParams([
+ ['a', 'v1'],
+ ['a', 'v2'],
+ ['b', 'awesome'],
+ ['a', 'v3']
+ ]);
+
+ const result = searchParamsToQuery(searchParams);
+
+ expect(result).toEqual({ a: ['v1', 'v2', 'v3'], b: 'awesome' });
+ });
+});
+
+describe('queryToSearch', () => {
+ it('should handle all types', () => {
+ const query = {
+ author: ['GRRM', 'JKR', 'Stross'],
+ b1: true,
+ b2: false,
+ emptyArray: [],
+ normalString: 'hello',
+ undef: undefined
+ };
+
+ expect(queryToSearch(query)).toBe(
+ '?b1=true&b2=false&normalString=hello&author=GRRM&author=JKR&author=Stross'
+ );
+ });
+
+ it('should handle an missing query', () => {
+ expect(queryToSearch()).toBe('?');
+ });
+});
+
+describe('convertToTo', () => {
+ it('should handle locations with a query', () => {
+ expect(convertToTo(mockLocation({ pathname: '/account', query: { id: 1 } }))).toEqual({
+ pathname: '/account',
+ search: '?id=1'
+ });
+ });
+
+ it('should forward strings', () => {
+ expect(convertToTo('/whatever')).toBe('/whatever');
+ });
+});
diff --git a/server/sonar-web/src/main/js/helpers/branch-like.ts b/server/sonar-web/src/main/js/helpers/branch-like.ts
index 8df94c21614..261bea49cae 100644
--- a/server/sonar-web/src/main/js/helpers/branch-like.ts
+++ b/server/sonar-web/src/main/js/helpers/branch-like.ts
@@ -120,9 +120,8 @@ export function getBranchLikeQuery(branchLike?: BranchLike): BranchParameters {
return { branch: branchLike.name };
} else if (isPullRequest(branchLike)) {
return { pullRequest: branchLike.key };
- } else {
- return {};
}
+ return {};
}
// Create branch object from branch name or pull request key
diff --git a/server/sonar-web/src/main/js/helpers/handleRequiredAuthentication.ts b/server/sonar-web/src/main/js/helpers/handleRequiredAuthentication.ts
index 75a046a24f9..fbf9c568551 100644
--- a/server/sonar-web/src/main/js/helpers/handleRequiredAuthentication.ts
+++ b/server/sonar-web/src/main/js/helpers/handleRequiredAuthentication.ts
@@ -17,11 +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 './getHistory';
-
export default function handleRequiredAuthentication() {
- const history = getHistory();
const returnTo = window.location.pathname + window.location.search + window.location.hash;
- // eslint-disable-next-line camelcase
- history.replace({ pathname: '/sessions/new', query: { return_to: returnTo } });
+ const searchParams = new URLSearchParams({ return_to: returnTo });
+ window.location.replace(`/sessions/new?${searchParams.toString()}`);
}
diff --git a/server/sonar-web/src/main/js/helpers/testMocks.ts b/server/sonar-web/src/main/js/helpers/testMocks.ts
index 1875185fa23..a71f5cb3a08 100644
--- a/server/sonar-web/src/main/js/helpers/testMocks.ts
+++ b/server/sonar-web/src/main/js/helpers/testMocks.ts
@@ -17,10 +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 { Location, LocationDescriptor } from 'history';
-import { InjectedRouter } from 'react-router';
+import { To } from 'react-router-dom';
import { DocumentationEntry } from '../apps/documentation/utils';
import { Exporter, Profile } from '../apps/quality-profiles/types';
+import { Location, Router } from '../components/hoc/withRouter';
import { AppState } from '../types/appstate';
import { RuleRepository } from '../types/coding-rules';
import { EditionKey } from '../types/editions';
@@ -402,7 +402,6 @@ export function mockIssue(withLocations = false, overrides: Partial<Issue> = {})
export function mockLocation(overrides: Partial<Location> = {}): Location {
return {
- action: 'PUSH',
hash: '',
key: 'key',
pathname: '/path',
@@ -518,8 +517,8 @@ export function mockQualityProfileExporter(override?: Partial<Exporter>): Export
export function mockRouter(
overrides: {
- push?: (loc: LocationDescriptor) => void;
- replace?: (loc: LocationDescriptor) => void;
+ push?: (loc: To) => void;
+ replace?: (loc: To) => void;
} = {}
) {
return {
@@ -533,7 +532,7 @@ export function mockRouter(
replace: jest.fn(),
setRouteLeaveHook: jest.fn(),
...overrides
- } as InjectedRouter;
+ } as Router;
}
export function mockRule(overrides: Partial<Rule> = {}): Rule {
diff --git a/server/sonar-web/src/main/js/helpers/testReactTestingUtils.tsx b/server/sonar-web/src/main/js/helpers/testReactTestingUtils.tsx
index 6cbbe7ee4ce..840ade86c20 100644
--- a/server/sonar-web/src/main/js/helpers/testReactTestingUtils.tsx
+++ b/server/sonar-web/src/main/js/helpers/testReactTestingUtils.tsx
@@ -18,26 +18,19 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { render, RenderResult } from '@testing-library/react';
-import { History } from 'history';
import * as React from 'react';
import { HelmetProvider } from 'react-helmet-async';
import { IntlProvider } from 'react-intl';
-import {
- createMemoryHistory,
- Route,
- RouteComponent,
- RouteConfig,
- Router,
- withRouter,
- WithRouterProps
-} from 'react-router';
+
+import { MemoryRouter, Outlet, parsePath, Route, Routes } from 'react-router-dom';
import AdminContext from '../app/components/AdminContext';
import AppStateContextProvider from '../app/components/app-state/AppStateContextProvider';
import CurrentUserContextProvider from '../app/components/current-user/CurrentUserContextProvider';
import GlobalMessagesContainer from '../app/components/GlobalMessagesContainer';
+import IndexationContextProvider from '../app/components/indexation/IndexationContextProvider';
import { LanguagesContext } from '../app/components/languages/LanguagesContext';
import { MetricsContext } from '../app/components/metrics/MetricsContext';
-import { RouteWithChildRoutes } from '../app/utils/startReactApp';
+import { useLocation } from '../components/hoc/withRouter';
import { AppState } from '../types/appstate';
import { Dict, Extension, Languages, Metric, SysStatus } from '../types/types';
import { CurrentUser } from '../types/users';
@@ -46,7 +39,6 @@ import { mockAppState, mockCurrentUser } from './testMocks';
interface RenderContext {
metrics?: Dict<Metric>;
- history?: History;
appState?: AppState;
languages?: Languages;
currentUser?: CurrentUser;
@@ -55,11 +47,11 @@ interface RenderContext {
export function renderAdminApp(
indexPath: string,
- routes: RouteConfig,
+ routes: () => JSX.Element,
context: RenderContext = {},
overrides: { systemStatus?: SysStatus; adminPages?: Extension[] } = {}
): RenderResult {
- function MockAdminContainer(props: { children: React.ReactElement }) {
+ function MockAdminContainer() {
return (
<AdminContext.Provider
value={{
@@ -72,29 +64,33 @@ export function renderAdminApp(
pendingPlugins: { installing: [], removing: [], updating: [] },
systemStatus: overrides.systemStatus ?? 'UP'
}}>
- {React.cloneElement(props.children, {
- adminPages: overrides.adminPages ?? []
- })}
+ <Outlet
+ context={{
+ adminPages: overrides.adminPages ?? []
+ }}
+ />
</AdminContext.Provider>
);
}
- const innerPath = indexPath.split('admin/').pop();
-
return renderRoutedApp(
- <Route component={MockAdminContainer} path="admin">
- <RouteWithChildRoutes path={innerPath} childRoutes={routes} />
+ <Route element={<MockAdminContainer />} path="admin">
+ {routes()}
</Route>,
indexPath,
context
);
}
-export function renderComponent(component: React.ReactElement) {
+export function renderComponent(component: React.ReactElement, pathname = '/') {
function Wrapper({ children }: { children: React.ReactElement }) {
return (
<IntlProvider defaultLocale="en" locale="en">
- {children}
+ <MemoryRouter initialEntries={[pathname]}>
+ <Routes>
+ <Route path="*" element={children} />
+ </Routes>
+ </MemoryRouter>
</IntlProvider>
);
}
@@ -104,31 +100,25 @@ export function renderComponent(component: React.ReactElement) {
export function renderComponentApp(
indexPath: string,
- component: RouteComponent,
+ component: JSX.Element,
context: RenderContext = {}
): RenderResult {
- return renderRoutedApp(<Route path={indexPath} component={component} />, indexPath, context);
+ return renderRoutedApp(<Route path={indexPath} element={component} />, indexPath, context);
}
export function renderApp(
indexPath: string,
- routes: RouteConfig,
+ routes: () => JSX.Element,
context?: RenderContext
): RenderResult {
- return renderRoutedApp(
- <RouteWithChildRoutes path={indexPath} childRoutes={routes} />,
- indexPath,
- context
- );
+ return renderRoutedApp(routes(), indexPath, context);
}
-const CatchAll = withRouter((props: WithRouterProps) => {
- return (
- <div>{`${props.location.pathname}?${new URLSearchParams(
- props.location.query
- ).toString()}`}</div>
- );
-});
+export function CatchAll() {
+ const location = useLocation();
+
+ return <div>{`${location.pathname}${location.search}`}</div>;
+}
function renderRoutedApp(
children: React.ReactElement,
@@ -138,11 +128,12 @@ function renderRoutedApp(
navigateTo = indexPath,
metrics = DEFAULT_METRICS,
appState = mockAppState(),
- history = createMemoryHistory(),
languages = {}
}: RenderContext = {}
): RenderResult {
- history.push(`/${navigateTo}`);
+ const path = parsePath(navigateTo);
+ path.pathname = `/${path.pathname}`;
+
return render(
<HelmetProvider context={{}}>
<IntlProvider defaultLocale="en" locale="en">
@@ -150,11 +141,15 @@ function renderRoutedApp(
<LanguagesContext.Provider value={languages}>
<CurrentUserContextProvider currentUser={currentUser}>
<AppStateContextProvider appState={appState}>
- <GlobalMessagesContainer />
- <Router history={history}>
- {children}
- <Route path="*" component={CatchAll} />
- </Router>
+ <IndexationContextProvider>
+ <GlobalMessagesContainer />
+ <MemoryRouter initialEntries={[path]}>
+ <Routes>
+ {children}
+ <Route path="*" element={<CatchAll />} />
+ </Routes>
+ </MemoryRouter>
+ </IndexationContextProvider>
</AppStateContextProvider>
</CurrentUserContextProvider>
</LanguagesContext.Provider>
diff --git a/server/sonar-web/src/main/js/helpers/urls.ts b/server/sonar-web/src/main/js/helpers/urls.ts
index 38013879e05..6dbf21e8d9e 100644
--- a/server/sonar-web/src/main/js/helpers/urls.ts
+++ b/server/sonar-web/src/main/js/helpers/urls.ts
@@ -17,14 +17,15 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { isNil, omitBy, pick } from 'lodash';
+import { isArray, mapValues, omitBy, pick } from 'lodash';
+import { Path, To } from 'react-router-dom';
import { getProfilePath } from '../apps/quality-profiles/utils';
import { BranchLike, BranchParameters } from '../types/branch-like';
import { ComponentQualifier, isApplication, isPortfolioLike } from '../types/component';
import { MeasurePageView } from '../types/measures';
import { GraphType } from '../types/project-activity';
import { SecurityStandard } from '../types/security';
-import { Dict } from '../types/types';
+import { Dict, RawQuery } from '../types/types';
import { HomePage } from '../types/users';
import { getBranchLikeQuery, isBranch, isMainBranch, isPullRequest } from './branch-like';
import { IS_SSR } from './browser';
@@ -47,6 +48,35 @@ type Query = Location['query'];
const PROJECT_BASE_URL = '/dashboard';
+export function queryToSearch(query: RawQuery = {}) {
+ const arrayParams: Array<{ key: string; values: string[] }> = [];
+
+ const stringParams = mapValues(query, (value, key) => {
+ // array values are added afterwards
+ if (isArray(value)) {
+ arrayParams.push({ key, values: value });
+ return '';
+ }
+
+ return value != null ? `${value}` : '';
+ });
+ const filteredParams = omitBy(stringParams, (v: string) => v.length === 0);
+ const searchParams = new URLSearchParams(filteredParams);
+
+ /*
+ * Add each value separately
+ * e.g. author: ['a', 'b'] should be serialized as
+ * author=a&author=b
+ */
+ arrayParams.forEach(({ key, values }) => {
+ values.forEach(value => {
+ searchParams.append(key, value);
+ });
+ });
+
+ return `?${searchParams.toString()}`;
+}
+
export function getComponentOverviewUrl(
componentKey: string,
componentQualifier: ComponentQualifier | string,
@@ -66,19 +96,18 @@ export function getComponentAdminUrl(
return getPortfolioAdminUrl(componentKey);
} else if (isApplication(componentQualifier)) {
return getApplicationAdminUrl(componentKey);
- } else {
- return getProjectUrl(componentKey);
}
+ return getProjectUrl(componentKey);
}
export function getProjectUrl(
project: string,
branch?: string,
codeScope?: CodeScopeType
-): Location {
+): Partial<Path> {
return {
pathname: PROJECT_BASE_URL,
- query: { id: project, branch, ...(codeScope && { code_scope: codeScope }) }
+ search: queryToSearch({ id: project, branch, ...(codeScope && { code_scope: codeScope }) })
};
}
@@ -86,32 +115,32 @@ export function getProjectQueryUrl(
project: string,
branchParameters?: BranchParameters,
codeScope?: CodeScopeType
-): Location {
+): To {
return {
pathname: PROJECT_BASE_URL,
- query: {
+ search: queryToSearch({
id: project,
...branchParameters,
...(codeScope && { code_scope: codeScope })
- }
+ })
};
}
-export function getPortfolioUrl(key: string): Location {
- return { pathname: '/portfolio', query: { id: key } };
+export function getPortfolioUrl(key: string): To {
+ return { pathname: '/portfolio', search: queryToSearch({ id: key }) };
}
-export function getPortfolioAdminUrl(key: string) {
+export function getPortfolioAdminUrl(key: string): To {
return {
pathname: '/project/admin/extension/governance/console',
- query: { id: key, qualifier: ComponentQualifier.Portfolio }
+ search: queryToSearch({ id: key, qualifier: ComponentQualifier.Portfolio })
};
}
-export function getApplicationAdminUrl(key: string) {
+export function getApplicationAdminUrl(key: string): To {
return {
pathname: '/project/admin/extension/developer-server/application-console',
- query: { id: key }
+ search: queryToSearch({ id: key })
};
}
@@ -119,51 +148,58 @@ export function getComponentBackgroundTaskUrl(
componentKey: string,
status?: string,
taskType?: string
-): Location {
- return { pathname: '/project/background_tasks', query: { id: componentKey, status, taskType } };
+): Path {
+ return {
+ pathname: '/project/background_tasks',
+ search: queryToSearch({ id: componentKey, status, taskType }),
+ hash: ''
+ };
}
-export function getBranchLikeUrl(project: string, branchLike?: BranchLike): Location {
+export function getBranchLikeUrl(project: string, branchLike?: BranchLike): Partial<Path> {
if (isPullRequest(branchLike)) {
return getPullRequestUrl(project, branchLike.key);
} else if (isBranch(branchLike) && !isMainBranch(branchLike)) {
return getBranchUrl(project, branchLike.name);
- } else {
- return getProjectUrl(project);
}
+ return getProjectUrl(project);
}
-export function getBranchUrl(project: string, branch: string): Location {
- return { pathname: PROJECT_BASE_URL, query: { branch, id: project } };
+export function getBranchUrl(project: string, branch: string): Partial<Path> {
+ return { pathname: PROJECT_BASE_URL, search: queryToSearch({ branch, id: project }) };
}
-export function getPullRequestUrl(project: string, pullRequest: string): Location {
- return { pathname: PROJECT_BASE_URL, query: { id: project, pullRequest } };
+export function getPullRequestUrl(project: string, pullRequest: string): Partial<Path> {
+ return { pathname: PROJECT_BASE_URL, search: queryToSearch({ id: project, pullRequest }) };
}
/**
* Generate URL for a global issues page
*/
-export function getIssuesUrl(query: Query): Location {
+export function getIssuesUrl(query: Query): To {
const pathname = '/issues';
- return { pathname, query };
+ return { pathname, search: queryToSearch(query) };
}
/**
* Generate URL for a component's issues page
*/
-export function getComponentIssuesUrl(componentKey: string, query?: Query): Location {
- return { pathname: '/project/issues', query: { ...(query || {}), id: componentKey } };
+export function getComponentIssuesUrl(componentKey: string, query?: Query): Path {
+ return {
+ pathname: '/project/issues',
+ search: queryToSearch({ ...(query || {}), id: componentKey }),
+ hash: ''
+ };
}
/**
* Generate URL for a component's security hotspot page
*/
-export function getComponentSecurityHotspotsUrl(componentKey: string, query: Query = {}): Location {
+export function getComponentSecurityHotspotsUrl(componentKey: string, query: Query = {}): Path {
const { branch, pullRequest, sinceLeakPeriod, hotspots, assignedToMe, file } = query;
return {
pathname: '/security_hotspots',
- query: {
+ search: queryToSearch({
id: componentKey,
branch,
pullRequest,
@@ -178,7 +214,8 @@ export function getComponentSecurityHotspotsUrl(componentKey: string, query: Que
SecurityStandard.SANS_TOP25,
SecurityStandard.CWE
])
- }
+ }),
+ hash: ''
};
}
@@ -193,7 +230,7 @@ export function getComponentDrilldownUrl(options: {
treemapView?: boolean;
listView?: boolean;
asc?: boolean;
-}): Location {
+}): To {
const { componentKey, metric, branchLike, selectionKey, treemapView, listView, asc } = options;
const query: Query = { id: componentKey, metric, ...getBranchLikeQuery(branchLike) };
if (treemapView) {
@@ -206,7 +243,7 @@ export function getComponentDrilldownUrl(options: {
if (selectionKey) {
query.selected = selectionKey;
}
- return { pathname: '/component_measures', query };
+ return { pathname: '/component_measures', search: queryToSearch(query) };
}
export function getComponentDrilldownUrlWithSelection(
@@ -215,7 +252,7 @@ export function getComponentDrilldownUrlWithSelection(
metric: string,
branchLike?: BranchLike,
view?: MeasurePageView
-): Location {
+): To {
return getComponentDrilldownUrl({
componentKey,
selectionKey,
@@ -233,7 +270,7 @@ export function getMeasureTreemapUrl(componentKey: string, metric: string) {
export function getActivityUrl(component: string, branchLike?: BranchLike, graph?: GraphType) {
return {
pathname: '/project/activity',
- query: { id: component, graph, ...getBranchLikeQuery(branchLike) }
+ search: queryToSearch({ id: component, graph, ...getBranchLikeQuery(branchLike) })
};
}
@@ -243,36 +280,36 @@ export function getActivityUrl(component: string, branchLike?: BranchLike, graph
export function getMeasureHistoryUrl(component: string, metric: string, branchLike?: BranchLike) {
return {
pathname: '/project/activity',
- query: {
+ search: queryToSearch({
id: component,
graph: 'custom',
custom_metrics: metric,
...getBranchLikeQuery(branchLike)
- }
+ })
};
}
/**
* Generate URL for a component's permissions page
*/
-export function getComponentPermissionsUrl(componentKey: string): Location {
- return { pathname: '/project_roles', query: { id: componentKey } };
+export function getComponentPermissionsUrl(componentKey: string): To {
+ return { pathname: '/project_roles', search: queryToSearch({ id: componentKey }) };
}
/**
* Generate URL for a quality profile
*/
-export function getQualityProfileUrl(name: string, language: string): Location {
+export function getQualityProfileUrl(name: string, language: string): To {
return getProfilePath(name, language);
}
-export function getQualityGateUrl(key: string): Location {
+export function getQualityGateUrl(key: string): To {
return {
pathname: '/quality_gates/show/' + encodeURIComponent(key)
};
}
-export function getQualityGatesUrl(): Location {
+export function getQualityGatesUrl(): To {
return {
pathname: '/quality_gates'
};
@@ -281,31 +318,31 @@ export function getQualityGatesUrl(): Location {
export function getGlobalSettingsUrl(
category?: string,
query?: Dict<string | undefined | number>
-): Location {
+): Partial<Path> {
return {
pathname: '/admin/settings',
- query: { category, ...query }
+ search: queryToSearch({ category, ...query })
};
}
-export function getProjectSettingsUrl(id: string, category?: string): Location {
+export function getProjectSettingsUrl(id: string, category?: string): Partial<Path> {
return {
pathname: '/project/settings',
- query: { id, category }
+ search: queryToSearch({ id, category })
};
}
/**
* Generate URL for the rules page
*/
-export function getRulesUrl(query: Query): Location {
- return { pathname: '/coding_rules', query };
+export function getRulesUrl(query: Query): To {
+ return { pathname: '/coding_rules', search: queryToSearch(query) };
}
/**
* Generate URL for the rules page filtering only active deprecated rules
*/
-export function getDeprecatedActiveRulesUrl(query: Query = {}): Location {
+export function getDeprecatedActiveRulesUrl(query: Query = {}): To {
const baseQuery = { activation: 'true', statuses: 'DEPRECATED' };
return getRulesUrl({ ...query, ...baseQuery });
}
@@ -323,10 +360,15 @@ export function getCodeUrl(
branchLike?: BranchLike,
selected?: string,
line?: number
-): Location {
+): Partial<Path> {
return {
pathname: '/code',
- query: { id: project, ...getBranchLikeQuery(branchLike), selected, line: line?.toFixed() }
+ search: queryToSearch({
+ id: project,
+ ...getBranchLikeQuery(branchLike),
+ selected,
+ line: line?.toFixed()
+ })
};
}
@@ -372,14 +414,13 @@ export function getHostUrl(): string {
return window.location.origin + getBaseUrl();
}
-export function getPathUrlAsString(path: Location, internal = true): string {
- return `${internal ? getBaseUrl() : getHostUrl()}${path.pathname}?${new URLSearchParams(
- omitBy(path.query, isNil)
- ).toString()}`;
+export function getPathUrlAsString(path: Partial<Path>, internal = true): string {
+ return `${internal ? getBaseUrl() : getHostUrl()}${path.pathname ?? '/'}${path.search ?? ''}`;
}
export function getReturnUrl(location: { hash?: string; query?: { return_to?: string } }) {
const returnTo = location.query && location.query['return_to'];
+
if (isRelativeUrl(returnTo)) {
return returnTo + (location.hash ? location.hash : '');
}
@@ -390,3 +431,28 @@ export function isRelativeUrl(url?: string): boolean {
const regex = new RegExp(/^\/[^/\\]/);
return Boolean(url && regex.test(url));
}
+
+export function searchParamsToQuery(searchParams: URLSearchParams) {
+ const result: RawQuery = {};
+
+ searchParams.forEach((value, key) => {
+ if (result[key]) {
+ result[key] = ([] as string[]).concat(result[key], value);
+ } else {
+ result[key] = value;
+ }
+ });
+
+ return result;
+}
+
+export function convertToTo(link: string | Location) {
+ if (linkIsLocation(link)) {
+ return { pathname: link.pathname, search: queryToSearch(link.query) } as Partial<Path>;
+ }
+ return link;
+}
+
+function linkIsLocation(link: string | Location): link is Location {
+ return (link as Location).query !== undefined;
+}
diff --git a/server/sonar-web/src/main/js/types/admin.ts b/server/sonar-web/src/main/js/types/admin.ts
new file mode 100644
index 00000000000..53e4d768cd3
--- /dev/null
+++ b/server/sonar-web/src/main/js/types/admin.ts
@@ -0,0 +1,24 @@
+/*
+ * 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 { Extension } from './types';
+
+export interface AdminPagesContext {
+ adminPages: Extension[];
+}
diff --git a/server/sonar-web/src/main/js/types/component.ts b/server/sonar-web/src/main/js/types/component.ts
index 020a455fb94..b1faecc307c 100644
--- a/server/sonar-web/src/main/js/types/component.ts
+++ b/server/sonar-web/src/main/js/types/component.ts
@@ -17,7 +17,9 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { LightComponent } from './types';
+import { ProjectAlmBindingResponse } from './alm-settings';
+import { BranchLike } from './branch-like';
+import { Component, LightComponent } from './types';
export enum Visibility {
Public = 'public',
@@ -94,3 +96,14 @@ export function isView(componentQualifier?: string | ComponentQualifier): boolea
ComponentQualifier.Application
].includes(componentQualifier as ComponentQualifier);
}
+
+export interface ComponentContextShape {
+ branchLike?: BranchLike;
+ branchLikes: BranchLike[];
+ component?: Component;
+ isInProgress?: boolean;
+ isPending?: boolean;
+ onBranchesChange: (updateBranches?: boolean, updatePRs?: boolean) => void;
+ onComponentChange: (changes: Partial<Component>) => void;
+ projectBinding?: ProjectAlmBindingResponse;
+}
diff --git a/server/sonar-web/yarn.lock b/server/sonar-web/yarn.lock
index 974673267ff..8292592e03d 100644
--- a/server/sonar-web/yarn.lock
+++ b/server/sonar-web/yarn.lock
@@ -609,6 +609,15 @@ __metadata:
languageName: node
linkType: hard
+"@babel/runtime@npm:^7.7.6":
+ version: 7.18.3
+ resolution: "@babel/runtime@npm:7.18.3"
+ dependencies:
+ regenerator-runtime: ^0.13.4
+ checksum: db8526226aa02cfa35a5a7ac1a34b5f303c62a1f000c7db48cb06c6290e616483e5036ab3c4e7a84d0f3be6d4e2148d5fe5cec9564bf955f505c3e764b83d7f1
+ languageName: node
+ linkType: hard
+
"@babel/template@npm:^7.1.0, @babel/template@npm:^7.4.4":
version: 7.4.4
resolution: "@babel/template@npm:7.4.4"
@@ -1823,13 +1832,6 @@ __metadata:
languageName: node
linkType: hard
-"@types/history@npm:^3":
- version: 3.2.3
- resolution: "@types/history@npm:3.2.3"
- checksum: da2e2ca56921ef86703f1a19317bf4af409e425b90d174892fe2fb4a6b8c5856cc5113096ccd94e9436e11720be38920452a612a0e1b2fce1670956a68a3a0a1
- languageName: node
- linkType: hard
-
"@types/hoist-non-react-statics@npm:^3.3.1":
version: 3.3.1
resolution: "@types/hoist-non-react-statics@npm:3.3.1"
@@ -2007,16 +2009,6 @@ __metadata:
languageName: node
linkType: hard
-"@types/react-router@npm:3.0.20":
- version: 3.0.20
- resolution: "@types/react-router@npm:3.0.20"
- dependencies:
- "@types/history": ^3
- "@types/react": "*"
- checksum: fe9b2b00feb6fb6911b47e89109f434c0673878734ad906ceeafa8bc1a99202d6e654bef90cf8216d3f1bb9f00e3b2994982c4307ce87af841db3b67a5d0e793
- languageName: node
- linkType: hard
-
"@types/react-select@npm:4.0.16":
version: 4.0.16
resolution: "@types/react-select@npm:4.0.16"
@@ -2301,7 +2293,6 @@ __metadata:
"@types/react-dom": 16.8.4
"@types/react-helmet": 5.0.15
"@types/react-modal": 3.13.1
- "@types/react-router": 3.0.20
"@types/react-select": 4.0.16
"@types/react-virtualized": 9.21.20
"@types/valid-url": 1.0.3
@@ -2338,7 +2329,6 @@ __metadata:
fs-extra: 10.0.1
glob: 7.2.0
glob-promise: 4.2.2
- history: 3.3.0
http-proxy: 1.18.1
jest: 27.5.1
jest-emotion: 10.0.32
@@ -2359,7 +2349,7 @@ __metadata:
react-helmet-async: 1.2.3
react-intl: 3.12.1
react-modal: 3.14.4
- react-router: 3.2.6
+ react-router-dom: 6.3.0
react-select: 4.3.1
react-select-event: 5.4.0
react-virtualized: 9.22.3
@@ -3478,17 +3468,6 @@ __metadata:
languageName: node
linkType: hard
-"create-react-class@npm:^15.5.1":
- version: 15.6.3
- resolution: "create-react-class@npm:15.6.3"
- dependencies:
- fbjs: ^0.8.9
- loose-envify: ^1.3.1
- object-assign: ^4.1.1
- checksum: 8ad00603815efafe44d511dc39beb0e2d03177c99c60c85978c2d791db880e83be64042e0ee718ccdeb596cd850f3649333adbbd08783980ba3882488bb2bf7d
- languageName: node
- linkType: hard
-
"create-react-context@npm:^0.2.2":
version: 0.2.3
resolution: "create-react-context@npm:0.2.3"
@@ -5143,7 +5122,7 @@ __metadata:
languageName: node
linkType: hard
-"fbjs@npm:^0.8.0, fbjs@npm:^0.8.9":
+"fbjs@npm:^0.8.0":
version: 0.8.17
resolution: "fbjs@npm:0.8.17"
dependencies:
@@ -5842,15 +5821,12 @@ __metadata:
languageName: node
linkType: hard
-"history@npm:3.3.0, history@npm:^3.0.0":
- version: 3.3.0
- resolution: "history@npm:3.3.0"
+"history@npm:^5.2.0":
+ version: 5.3.0
+ resolution: "history@npm:5.3.0"
dependencies:
- invariant: ^2.2.1
- loose-envify: ^1.2.0
- query-string: ^4.2.2
- warning: ^3.0.0
- checksum: 1570a278a9821d81fba37db7460eb06416c13805df0a9802432ce3161b0fa6d3d4e50fb7c538d1bf84de0f1c697771e5f252d684a4931b25ccb0128cffbc08aa
+ "@babel/runtime": ^7.7.6
+ checksum: d73c35df49d19ac172f9547d30a21a26793e83f16a78386d99583b5bf1429cc980799fcf1827eb215d31816a6600684fba9686ce78104e23bd89ec239e7c726f
languageName: node
linkType: hard
@@ -6185,7 +6161,7 @@ __metadata:
languageName: node
linkType: hard
-"invariant@npm:^2.2.1, invariant@npm:^2.2.4":
+"invariant@npm:^2.2.4":
version: 2.2.4
resolution: "invariant@npm:2.2.4"
dependencies:
@@ -7629,7 +7605,7 @@ __metadata:
languageName: node
linkType: hard
-"loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.2.0, loose-envify@npm:^1.3.1, loose-envify@npm:^1.4.0":
+"loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0":
version: 1.4.0
resolution: "loose-envify@npm:1.4.0"
dependencies:
@@ -8952,16 +8928,6 @@ __metadata:
languageName: node
linkType: hard
-"query-string@npm:^4.2.2":
- version: 4.3.4
- resolution: "query-string@npm:4.3.4"
- dependencies:
- object-assign: ^4.1.0
- strict-uri-encode: ^1.0.0
- checksum: 3b2bae6a8454cf0edf11cf1aa4d1f920398bbdabc1c39222b9bb92147e746fcd97faf00e56f494728fb66b2961b495ba0fde699d5d3bd06b11472d664b36c6cf
- languageName: node
- linkType: hard
-
"raf@npm:^3.4.1":
version: 3.4.1
resolution: "raf@npm:3.4.1"
@@ -9089,7 +9055,7 @@ __metadata:
languageName: node
linkType: hard
-"react-is@npm:^16.12.0, react-is@npm:^16.13.0":
+"react-is@npm:^16.12.0":
version: 16.13.0
resolution: "react-is@npm:16.13.0"
checksum: 9da7d02ebeb5f2bedb781db5427097dbff9a23d7800b06f0a788bd557a47cd863ebf80de21348207edb66d7667c1adbd65a434e81a3b84c3fdae2597bb697ac5
@@ -9139,21 +9105,27 @@ __metadata:
languageName: node
linkType: hard
-"react-router@npm:3.2.6":
- version: 3.2.6
- resolution: "react-router@npm:3.2.6"
+"react-router-dom@npm:6.3.0":
+ version: 6.3.0
+ resolution: "react-router-dom@npm:6.3.0"
dependencies:
- create-react-class: ^15.5.1
- history: ^3.0.0
- hoist-non-react-statics: ^3.3.2
- invariant: ^2.2.1
- loose-envify: ^1.2.0
- prop-types: ^15.7.2
- react-is: ^16.13.0
- warning: ^3.0.0
+ history: ^5.2.0
+ react-router: 6.3.0
peerDependencies:
- react: ^0.14.0 || ^15.0.0 || ^16.0.0
- checksum: 1604dab702ce2dbf2b9754fc62e0ce78ad9ec359e8414a282e633d162bc7294b46fd85ef54fd2edf179a5ea299775d06c35d334edad0798174fd88e9f871f4ed
+ react: ">=16.8"
+ react-dom: ">=16.8"
+ checksum: 77603a654f8a8dc7f65535a2e5917a65f8d9ffcb06546d28dd297e52adcc4b8a84377e0115f48dca330b080af2da3e78f29d590c89307094d36927d2b1751ec3
+ languageName: node
+ linkType: hard
+
+"react-router@npm:6.3.0":
+ version: 6.3.0
+ resolution: "react-router@npm:6.3.0"
+ dependencies:
+ history: ^5.2.0
+ peerDependencies:
+ react: ">=16.8"
+ checksum: 7be673f5e72104be01e6ab274516bdb932efd93305243170690f6560e3bd1035dd1df3d3c9ce1e0f452638a2529f43a1e77dcf0934fc8031c4783da657be13ca
languageName: node
linkType: hard
@@ -10066,13 +10038,6 @@ resolve@^1.3.2:
languageName: node
linkType: hard
-"strict-uri-encode@npm:^1.0.0":
- version: 1.1.0
- resolution: "strict-uri-encode@npm:1.1.0"
- checksum: 9466d371f7b36768d43f7803f26137657559e4c8b0161fb9e320efb8edba3ae22f8e99d4b0d91da023b05a13f62ec5412c3f4f764b5788fac11d1fea93720bb3
- languageName: node
- linkType: hard
-
"string-hash@npm:^1.1.1":
version: 1.1.3
resolution: "string-hash@npm:1.1.3"