aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>2018-03-12 12:06:11 +0100
committerTeryk Bellahsene <teryk@users.noreply.github.com>2018-03-13 14:05:36 +0100
commit913c82c8772fd4747626a1fbe665ccda2e5ca9f1 (patch)
treed48784851df80905ce125cc60ac8aec8570751a9
parent751e4000e40a4af66b80767d632b1bef64dc5647 (diff)
downloadsonarqube-913c82c8772fd4747626a1fbe665ccda2e5ca9f1.tar.gz
sonarqube-913c82c8772fd4747626a1fbe665ccda2e5ca9f1.zip
SONAR-10374 Support pull request in the web app
-rw-r--r--server/sonar-web/src/main/js/api/branches.ts20
-rw-r--r--server/sonar-web/src/main/js/api/components.ts48
-rw-r--r--server/sonar-web/src/main/js/api/measures.ts10
-rw-r--r--server/sonar-web/src/main/js/api/nav.ts7
-rw-r--r--server/sonar-web/src/main/js/api/projectActivity.ts12
-rw-r--r--server/sonar-web/src/main/js/api/settings.ts37
-rw-r--r--server/sonar-web/src/main/js/api/tests.ts21
-rw-r--r--server/sonar-web/src/main/js/api/time-machine.ts39
-rw-r--r--server/sonar-web/src/main/js/app/components/ComponentContainer.tsx72
-rw-r--r--server/sonar-web/src/main/js/app/components/ProjectAdminContainer.tsx6
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx33
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css4
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx17
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx91
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenu.tsx109
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenuItem.tsx42
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavHeader.tsx12
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx104
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx76
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNav-test.tsx9
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx58
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenu-test.tsx37
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenuItem-test.tsx7
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavHeader-test.tsx17
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMenu-test.tsx10
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMeta-test.tsx34
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranch-test.tsx.snap65
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenu-test.tsx.snap79
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenuItem-test.tsx.snap18
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavHeader-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap39
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMeta-test.tsx.snap47
-rw-r--r--server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.js.snap8
-rw-r--r--server/sonar-web/src/main/js/app/types.ts44
-rw-r--r--server/sonar-web/src/main/js/apps/about/actions.js2
-rw-r--r--server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/ProjectNotifications-test.js.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/account/organizations/actions.ts2
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.tsx34
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/TaskComponent-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/types.ts2
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/App.tsx37
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/Breadcrumbs.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/Component.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/ComponentLink.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/Components.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/Search.tsx25
-rw-r--r--server/sonar-web/src/main/js/apps/code/utils.ts70
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/App.js20
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.js5
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.js15
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js19
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.js10
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureFavoriteContainer.js2
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js6
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.js18
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.js9
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.js5
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.js.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.js6
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.js11
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.js6
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsListRow.js6
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.js4
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.js8
-rw-r--r--server/sonar-web/src/main/js/apps/component/components/App.tsx21
-rw-r--r--server/sonar-web/src/main/js/apps/component/components/__tests__/__snapshots__/App-test.tsx.snap9
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/App.d.ts4
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/App.js33
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/ComponentBreadcrumbs.tsx21
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/IssuesList.js6
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.js4
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/ListItem.js8
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/__tests__/ComponentBreadcrumbs-test.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/ComponentBreadcrumbs-test.tsx.snap26
-rw-r--r--server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/overview/badges/utils.ts7
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/App.tsx22
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx55
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/overview/events/AnalysesList.tsx20
-rw-r--r--server/sonar-web/src/main/js/apps/overview/events/__tests__/AnalysesList-test.tsx38
-rw-r--r--server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/AnalysesList-test.tsx.snap18
-rw-r--r--server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/overview/main/CodeSmells.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/overview/main/Coverage.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/overview/main/Duplications.tsx12
-rw-r--r--server/sonar-web/src/main/js/apps/overview/main/enhance.tsx23
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/MetaSize.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGate.js10
-rw-r--r--server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js13
-rw-r--r--server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateConditions.js10
-rw-r--r--server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGateProject-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGateCondition-test.js.snap7
-rw-r--r--server/sonar-web/src/main/js/apps/portfolio/components/Activity.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/portfolio/components/App.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Activity-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/portfolio/components/__tests__/App-test.tsx22
-rw-r--r--server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Effort-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/HistoryButtonLink-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MainRating-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MeasuresButtonLink-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/ReleasabilityBox-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Summary-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/WorstProjects-test.tsx.snap3
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js21
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/components/App.tsx48
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/components/BranchRow.tsx76
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/components/DeleteBranchModal.tsx31
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/components/LeakPeriodForm.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/components/LongBranchesPattern.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/components/RenameBranchModal.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/components/SettingForm.tsx25
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/App-test.tsx26
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/BranchRow-test.tsx34
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx73
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/LongBranchesPattern-test.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/SettingForm-test.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/App-test.tsx.snap54
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchRow-test.tsx.snap59
-rw-r--r--server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Risk-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/SimpleBubbleChart-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/settings/store/actions.js12
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx62
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.tsx13
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx30
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/CoveragePopup.tsx16
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx7
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx10
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx6
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/LineCoverage.tsx8
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesList.tsx6
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/LineNumber.tsx11
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/LineOptionsPopup.tsx10
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx40
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlayTestCases.tsx13
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.tsx10
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCoverage-test.tsx8
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesList-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineNumber-test.tsx4
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineOptionsPopup-test.tsx9
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/MeasuresOverlay-test.tsx17
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/MeasuresOverlayTestCases-test.tsx10
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap9
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCoverage-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineNumber-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlay-test.tsx.snap39
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlayCoveredFiles-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/helpers/loadIssues.tsx13
-rw-r--r--server/sonar-web/src/main/js/components/common/BranchStatus.css3
-rw-r--r--server/sonar-web/src/main/js/components/common/BranchStatus.tsx28
-rw-r--r--server/sonar-web/src/main/js/components/common/__tests__/BranchStatus-test.tsx6
-rw-r--r--server/sonar-web/src/main/js/components/icons-components/BranchIcon.tsx22
-rw-r--r--server/sonar-web/src/main/js/components/icons-components/PullRequestIcon.tsx5
-rw-r--r--server/sonar-web/src/main/js/components/issue/Issue.d.ts4
-rw-r--r--server/sonar-web/src/main/js/components/issue/Issue.js4
-rw-r--r--server/sonar-web/src/main/js/components/issue/IssueView.js4
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.js5
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTitleBar-test.js2
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.js.snap1
-rw-r--r--server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.d.ts4
-rw-r--r--server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.js5
-rw-r--r--server/sonar-web/src/main/js/components/shared/DrilldownLink.tsx8
-rw-r--r--server/sonar-web/src/main/js/components/workspace/views/viewer-view.js4
-rw-r--r--server/sonar-web/src/main/js/helpers/__tests__/branches-test.ts97
-rw-r--r--server/sonar-web/src/main/js/helpers/__tests__/urls-test.ts4
-rw-r--r--server/sonar-web/src/main/js/helpers/branches.ts123
-rw-r--r--server/sonar-web/src/main/js/helpers/urls.ts77
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties18
175 files changed, 2135 insertions, 1242 deletions
diff --git a/server/sonar-web/src/main/js/api/branches.ts b/server/sonar-web/src/main/js/api/branches.ts
index 02ce5fe693e..9cb5dbaec27 100644
--- a/server/sonar-web/src/main/js/api/branches.ts
+++ b/server/sonar-web/src/main/js/api/branches.ts
@@ -18,16 +18,28 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { getJSON, post } from '../helpers/request';
+import { Branch, PullRequest } from '../app/types';
import throwGlobalError from '../app/utils/throwGlobalError';
-export function getBranches(project: string): Promise<any> {
+export function getBranches(project: string): Promise<Branch[]> {
return getJSON('/api/project_branches/list', { project }).then(r => r.branches, throwGlobalError);
}
-export function deleteBranch(project: string, branch: string): Promise<void | Response> {
- return post('/api/project_branches/delete', { project, branch }).catch(throwGlobalError);
+export function getPullRequests(project: string): Promise<PullRequest[]> {
+ return getJSON('/api/project_pull_requests/list', { project }).then(
+ r => r.pullRequests,
+ throwGlobalError
+ );
}
-export function renameBranch(project: string, name: string): Promise<void | Response> {
+export function deleteBranch(data: { branch: string; project: string }) {
+ return post('/api/project_branches/delete', data).catch(throwGlobalError);
+}
+
+export function deletePullRequest(data: { project: string; pullRequest: string }) {
+ return post('/api/project_pull_requests/delete', data).catch(throwGlobalError);
+}
+
+export function renameBranch(project: string, name: string) {
return post('/api/project_branches/rename', { project, name }).catch(throwGlobalError);
}
diff --git a/server/sonar-web/src/main/js/api/components.ts b/server/sonar-web/src/main/js/api/components.ts
index db1e32cc643..60f7af8b14b 100644
--- a/server/sonar-web/src/main/js/api/components.ts
+++ b/server/sonar-web/src/main/js/api/components.ts
@@ -19,7 +19,7 @@
*/
import { getJSON, postJSON, post, RequestData } from '../helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';
-import { Paging, Visibility } from '../app/types';
+import { Paging, Visibility, BranchParameters } from '../app/types';
export interface BaseSearchProjectsParameters {
analyzedBefore?: string;
@@ -118,11 +118,8 @@ export function getComponentLeaves(
}
export function getComponent(
- componentKey: string,
- metrics: string[] = [],
- branch?: string
+ data: { componentKey: string; metricKeys: string } & BranchParameters
): Promise<any> {
- const data = { branch, componentKey, metricKeys: metrics.join(',') };
return getJSON('/api/measures/component', data).then(r => r.component);
}
@@ -130,23 +127,23 @@ export function getTree(component: string, options: RequestData = {}): Promise<a
return getJSON('/api/components/tree', { ...options, component });
}
-export function getComponentShow(component: string, branch?: string): Promise<any> {
- return getJSON('/api/components/show', { component, branch });
+export function getComponentShow(data: { component: string } & BranchParameters): Promise<any> {
+ return getJSON('/api/components/show', data);
}
export function getParents(component: string): Promise<any> {
- return getComponentShow(component).then(r => r.ancestors);
+ return getComponentShow({ component }).then(r => r.ancestors);
}
-export function getBreadcrumbs(component: string, branch?: string): Promise<any> {
- return getComponentShow(component, branch).then(r => {
+export function getBreadcrumbs(data: { component: string } & BranchParameters): Promise<any> {
+ return getComponentShow(data).then(r => {
const reversedAncestors = [...r.ancestors].reverse();
return [...reversedAncestors, r.component];
});
}
-export function getComponentData(component: string, branch?: string): Promise<any> {
- return getComponentShow(component, branch).then(r => r.component);
+export function getComponentData(data: { component: string } & BranchParameters): Promise<any> {
+ return getComponentShow(data).then(r => r.component);
}
export function getMyProjects(data: RequestData): Promise<any> {
@@ -246,31 +243,24 @@ export function getSuggestions(
return getJSON('/api/components/suggestions', data);
}
-export function getComponentForSourceViewer(component: string, branch?: string): Promise<any> {
- return getJSON('/api/components/app', { component, branch });
+export function getComponentForSourceViewer(
+ data: { component: string } & BranchParameters
+): Promise<any> {
+ return getJSON('/api/components/app', data);
}
export function getSources(
- component: string,
- from?: number,
- to?: number,
- branch?: string
+ data: { key: string; from?: number; to?: number } & BranchParameters
): Promise<any> {
- const data: RequestData = { key: component, branch };
- if (from) {
- Object.assign(data, { from });
- }
- if (to) {
- Object.assign(data, { to });
- }
return getJSON('/api/sources/lines', data).then(r => r.sources);
}
-export function getDuplications(component: string, branch?: string): Promise<any> {
- return getJSON('/api/duplications/show', { key: component, branch });
+export function getDuplications(data: { key: string } & BranchParameters): Promise<any> {
+ return getJSON('/api/duplications/show', data);
}
-export function getTests(component: string, line: number | string, branch?: string): Promise<any> {
- const data = { sourceFileKey: component, sourceFileLineNumber: line, branch };
+export function getTests(
+ data: { sourceFileKey: string; sourceFileLineNumber: number | string } & BranchParameters
+): Promise<any> {
return getJSON('/api/tests/list', data).then(r => r.tests);
}
diff --git a/server/sonar-web/src/main/js/api/measures.ts b/server/sonar-web/src/main/js/api/measures.ts
index bf6f8712090..40d219fcfa3 100644
--- a/server/sonar-web/src/main/js/api/measures.ts
+++ b/server/sonar-web/src/main/js/api/measures.ts
@@ -20,17 +20,13 @@
import { getJSON, RequestData, postJSON, post } from '../helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';
import { Measure, MeasurePeriod } from '../helpers/measures';
-import { Metric, CustomMeasure, Paging } from '../app/types';
+import { Metric, CustomMeasure, Paging, BranchParameters } from '../app/types';
import { Period } from '../helpers/periods';
export function getMeasures(
- componentKey: string,
- metrics: string[],
- branch?: string
+ data: { componentKey: string; metricKeys: string } & BranchParameters
): Promise<{ metric: string; value?: string }[]> {
- const url = '/api/measures/component';
- const data = { componentKey, metricKeys: metrics.join(','), branch };
- return getJSON(url, data).then(r => r.component.measures, throwGlobalError);
+ return getJSON('/api/measures/component', data).then(r => r.component.measures, throwGlobalError);
}
interface MeasureComponent {
diff --git a/server/sonar-web/src/main/js/api/nav.ts b/server/sonar-web/src/main/js/api/nav.ts
index 43a2340cabb..7dcf404ad71 100644
--- a/server/sonar-web/src/main/js/api/nav.ts
+++ b/server/sonar-web/src/main/js/api/nav.ts
@@ -18,14 +18,17 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { getJSON, parseJSON, request } from '../helpers/request';
+import { BranchParameters } from '../app/types';
import throwGlobalError from '../app/utils/throwGlobalError';
export function getGlobalNavigation(): Promise<any> {
return getJSON('/api/navigation/global');
}
-export function getComponentNavigation(componentKey: string, branch?: string): Promise<any> {
- return getJSON('/api/navigation/component', { componentKey, branch }).catch(throwGlobalError);
+export function getComponentNavigation(
+ data: { componentKey: string } & BranchParameters
+): Promise<any> {
+ return getJSON('/api/navigation/component', data).catch(throwGlobalError);
}
export function getSettingsNavigation(): Promise<any> {
diff --git a/server/sonar-web/src/main/js/api/projectActivity.ts b/server/sonar-web/src/main/js/api/projectActivity.ts
index b931ea5a6f6..fad7562500f 100644
--- a/server/sonar-web/src/main/js/api/projectActivity.ts
+++ b/server/sonar-web/src/main/js/api/projectActivity.ts
@@ -19,7 +19,7 @@
*/
import { getJSON, postJSON, post, RequestData } from '../helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';
-import { Paging } from '../app/types';
+import { Paging, BranchParameters } from '../app/types';
export interface Event {
key: string;
@@ -34,13 +34,9 @@ export interface Analysis {
events: Event[];
}
-export function getProjectActivity(data: {
- branch?: string;
- project: string;
- category?: string;
- p?: number;
- ps?: number;
-}): Promise<{ analyses: Analysis[]; paging: Paging }> {
+export function getProjectActivity(
+ data: { project: string; category?: string; p?: number; ps?: number } & BranchParameters
+): Promise<{ analyses: Analysis[]; paging: Paging }> {
return getJSON('/api/project_analyses/search', data).catch(throwGlobalError);
}
diff --git a/server/sonar-web/src/main/js/api/settings.ts b/server/sonar-web/src/main/js/api/settings.ts
index 880d37d65e4..46c0ec67cf8 100644
--- a/server/sonar-web/src/main/js/api/settings.ts
+++ b/server/sonar-web/src/main/js/api/settings.ts
@@ -20,10 +20,11 @@
import { omitBy } from 'lodash';
import { getJSON, RequestData, post, postJSON } from '../helpers/request';
import { TYPE_PROPERTY_SET } from '../apps/settings/constants';
+import { BranchParameters } from '../app/types';
import throwGlobalError from '../app/utils/throwGlobalError';
-export function getDefinitions(component: string | null, branch?: string): Promise<any> {
- return getJSON('/api/settings/list_definitions', { branch, component }).then(r => r.definitions);
+export function getDefinitions(component?: string): Promise<any> {
+ return getJSON('/api/settings/list_definitions', { component }).then(r => r.definitions);
}
export interface SettingValue {
@@ -36,21 +37,14 @@ export interface SettingValue {
}
export function getValues(
- keys: string,
- component?: string,
- branch?: string
+ data: { keys: string; component?: string } & BranchParameters
): Promise<SettingValue[]> {
- return getJSON('/api/settings/values', { keys, component, branch }).then(r => r.settings);
+ return getJSON('/api/settings/values', data).then(r => r.settings);
}
-export function setSettingValue(
- definition: any,
- value: any,
- component?: string,
- branch?: string
-): Promise<void> {
+export function setSettingValue(definition: any, value: any, component?: string): Promise<void> {
const { key } = definition;
- const data: RequestData = { key, component, branch };
+ const data: RequestData = { key, component };
if (definition.multiValues) {
data.values = value;
@@ -65,17 +59,16 @@ export function setSettingValue(
return post('/api/settings/set', data);
}
-export function setSimpleSettingValue(parameters: {
- branch?: string;
- component?: string;
- value: string;
- key: string;
-}): Promise<void | Response> {
- return post('/api/settings/set', parameters).catch(throwGlobalError);
+export function setSimpleSettingValue(
+ data: { component?: string; value: string; key: string } & BranchParameters
+): Promise<void | Response> {
+ return post('/api/settings/set', data).catch(throwGlobalError);
}
-export function resetSettingValue(key: string, component?: string, branch?: string): Promise<void> {
- return post('/api/settings/reset', { keys: key, component, branch });
+export function resetSettingValue(
+ data: { keys: string; component?: string } & BranchParameters
+): Promise<void> {
+ return post('/api/settings/reset', data);
}
export function sendTestEmail(to: string, subject: string, message: string): Promise<void> {
diff --git a/server/sonar-web/src/main/js/api/tests.ts b/server/sonar-web/src/main/js/api/tests.ts
index 1ac56218630..5b649d1d99b 100644
--- a/server/sonar-web/src/main/js/api/tests.ts
+++ b/server/sonar-web/src/main/js/api/tests.ts
@@ -18,18 +18,19 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import throwGlobalError from '../app/utils/throwGlobalError';
-import { Paging, TestCase, CoveredFile } from '../app/types';
+import { Paging, TestCase, CoveredFile, BranchParameters } from '../app/types';
import { getJSON } from '../helpers/request';
-export function getTests(parameters: {
- branch?: string;
- p?: number;
- ps?: number;
- sourceFileKey?: string;
- sourceFileLineNumber?: number;
- testFileKey: string;
- testId?: string;
-}): Promise<{ paging: Paging; tests: TestCase[] }> {
+export function getTests(
+ parameters: {
+ p?: number;
+ ps?: number;
+ sourceFileKey?: string;
+ sourceFileLineNumber?: number;
+ testFileKey: string;
+ testId?: string;
+ } & BranchParameters
+): Promise<{ paging: Paging; tests: TestCase[] }> {
return getJSON('/api/tests/list', parameters).catch(throwGlobalError);
}
diff --git a/server/sonar-web/src/main/js/api/time-machine.ts b/server/sonar-web/src/main/js/api/time-machine.ts
index 2f25912b191..ac77c7c8b41 100644
--- a/server/sonar-web/src/main/js/api/time-machine.ts
+++ b/server/sonar-web/src/main/js/api/time-machine.ts
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { getJSON } from '../helpers/request';
-import { Paging } from '../app/types';
+import { Paging, BranchParameters } from '../app/types';
import throwGlobalError from '../app/utils/throwGlobalError';
export interface HistoryItem {
@@ -39,25 +39,29 @@ interface TimeMachineResponse {
}
export function getTimeMachineData(
- component: string,
- metrics: string[],
- other?: { branch?: string; p?: number; ps?: number; from?: string; to?: string }
+ data: {
+ component: string;
+ from?: string;
+ metrics: string;
+ p?: number;
+ ps?: number;
+ to?: string;
+ } & BranchParameters
): Promise<TimeMachineResponse> {
- return getJSON('/api/measures/search_history', {
- component,
- metrics: metrics.join(),
- ps: 1000,
- ...other
- }).catch(throwGlobalError);
+ return getJSON('/api/measures/search_history', data).catch(throwGlobalError);
}
export function getAllTimeMachineData(
- component: string,
- metrics: Array<string>,
- other?: { branch?: string; p?: number; from?: string; to?: string },
+ data: {
+ component: string;
+ metrics: string;
+ from?: string;
+ p?: number;
+ to?: string;
+ } & BranchParameters,
prev?: TimeMachineResponse
): Promise<TimeMachineResponse> {
- return getTimeMachineData(component, metrics, { ...other, ps: 1000 }).then(r => {
+ return getTimeMachineData({ ...data, ps: 1000 }).then(r => {
const result = prev
? {
measures: prev.measures.map((measure, idx) => ({
@@ -71,11 +75,6 @@ export function getAllTimeMachineData(
if (result.paging.pageIndex * result.paging.pageSize >= result.paging.total) {
return result;
}
- return getAllTimeMachineData(
- component,
- metrics,
- { ...other, p: result.paging.pageIndex + 1 },
- result
- );
+ return getAllTimeMachineData({ ...data, p: result.paging.pageIndex + 1 }, result);
});
}
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 af04b9229fb..530bb5f63d4 100644
--- a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
@@ -22,25 +22,26 @@ import * as PropTypes from 'prop-types';
import { connect } from 'react-redux';
import ComponentContainerNotFound from './ComponentContainerNotFound';
import ComponentNav from './nav/component/ComponentNav';
-import { Branch, Component } from '../types';
+import { Component, BranchLike } from '../types';
import handleRequiredAuthorization from '../utils/handleRequiredAuthorization';
-import { getBranches } from '../../api/branches';
+import { getBranches, getPullRequests } from '../../api/branches';
import { Task, getTasksForComponent } from '../../api/ce';
import { getComponentData } from '../../api/components';
import { getComponentNavigation } from '../../api/nav';
import { fetchOrganizations } from '../../store/rootActions';
import { STATUSES } from '../../apps/background-tasks/constants';
+import { isPullRequest, isBranch } from '../../helpers/branches';
interface Props {
children: any;
fetchOrganizations: (organizations: string[]) => void;
location: {
- query: { branch?: string; id: string };
+ query: { branch?: string; id: string; pullRequest?: string };
};
}
interface State {
- branches: Branch[];
+ branchLikes: BranchLike[];
loading: boolean;
component?: Component;
currentTask?: Task;
@@ -57,7 +58,7 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
- this.state = { branches: [], loading: true };
+ this.state = { branchLikes: [], loading: true };
}
componentDidMount() {
@@ -68,7 +69,8 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
componentWillReceiveProps(nextProps: Props) {
if (
nextProps.location.query.id !== this.props.location.query.id ||
- nextProps.location.query.branch !== this.props.location.query.branch
+ nextProps.location.query.branch !== this.props.location.query.branch ||
+ nextProps.location.query.pullRequest !== this.props.location.query.pullRequest
) {
this.fetchComponent(nextProps);
}
@@ -84,7 +86,7 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
});
fetchComponent(props: Props) {
- const { branch, id } = props.location.query;
+ const { branch, id: key, pullRequest } = props.location.query;
this.setState({ loading: true });
const onError = (error: any) => {
@@ -97,29 +99,33 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
}
};
- Promise.all([getComponentNavigation(id, branch), getComponentData(id, branch)]).then(
- ([nav, data]) => {
- const component = this.addQualifier({ ...nav, ...data });
+ Promise.all([
+ getComponentNavigation({ componentKey: key, branch, pullRequest }),
+ getComponentData({ component: key, branch, pullRequest })
+ ]).then(([nav, data]) => {
+ const component = this.addQualifier({ ...nav, ...data });
- if (this.context.organizationsEnabled) {
- this.props.fetchOrganizations([component.organization]);
- }
+ if (this.context.organizationsEnabled) {
+ this.props.fetchOrganizations([component.organization]);
+ }
- this.fetchBranches(component).then(branches => {
- if (this.mounted) {
- this.setState({ loading: false, branches, component });
- }
- }, onError);
+ this.fetchBranches(component).then(branchLikes => {
+ if (this.mounted) {
+ this.setState({ loading: false, branchLikes, component });
+ }
+ }, onError);
- this.fetchStatus(component);
- },
- onError
- );
+ this.fetchStatus(component);
+ }, onError);
}
- fetchBranches = (component: Component) => {
+ fetchBranches = (component: Component): Promise<BranchLike[]> => {
const project = component.breadcrumbs.find(({ qualifier }) => qualifier === 'TRK');
- return project ? getBranches(project.key) : Promise.resolve([]);
+ return project
+ ? Promise.all([getBranches(project.key), getPullRequests(project.key)]).then(
+ ([branches, pullRequests]) => [...branches, ...pullRequests]
+ )
+ : Promise.resolve([]);
};
fetchStatus = (component: Component) => {
@@ -146,9 +152,9 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
handleBranchesChange = () => {
if (this.mounted && this.state.component) {
this.fetchBranches(this.state.component).then(
- branches => {
+ branchLikes => {
if (this.mounted) {
- this.setState({ branches });
+ this.setState({ branchLikes });
}
},
() => {}
@@ -158,22 +164,24 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
render() {
const { query } = this.props.location;
- const { branches, component, loading } = this.state;
+ const { branchLikes, component, loading } = this.state;
if (!loading && !component) {
return <ComponentContainerNotFound />;
}
- const branch = branches.find(b => (query.branch ? b.name === query.branch : b.isMain));
+ const branchLike = query.pullRequest
+ ? branchLikes.find(b => isPullRequest(b) && b.key === query.pullRequest)
+ : branchLikes.find(b => isBranch(b) && (query.branch ? b.name === query.branch : b.isMain));
return (
<div>
{component &&
!['FIL', 'UTS'].includes(component.qualifier) && (
<ComponentNav
- branches={branches}
- currentBranch={branch}
+ branchLikes={branchLikes}
component={component}
+ currentBranchLike={branchLike}
currentTask={this.state.currentTask}
isInProgress={this.state.isInProgress}
isPending={this.state.isPending}
@@ -186,8 +194,8 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
</div>
) : (
React.cloneElement(this.props.children, {
- branch,
- branches,
+ branchLike,
+ branchLikes,
component,
isInProgress: this.state.isInProgress,
isPending: this.state.isPending,
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 612f4bb043f..e2293316ea9 100644
--- a/server/sonar-web/src/main/js/app/components/ProjectAdminContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/ProjectAdminContainer.tsx
@@ -19,12 +19,12 @@
*/
import * as React from 'react';
import handleRequiredAuthorization from '../utils/handleRequiredAuthorization';
-import { Component, Branch } from '../types';
+import { BranchLike, Component } from '../types';
interface Props {
children: JSX.Element;
- branch?: Branch;
- branches: Branch[];
+ branchLike?: BranchLike;
+ branchLikes: BranchLike[];
component: Component;
isInProgress?: boolean;
isPending?: boolean;
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 b66a2aff8da..b0917c9d1c7 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
@@ -20,18 +20,24 @@
import * as React from 'react';
import { shallow, mount } from 'enzyme';
import { ComponentContainer } from '../ComponentContainer';
-import { getBranches } from '../../../api/branches';
+import { getBranches, getPullRequests } from '../../../api/branches';
import { getTasksForComponent } from '../../../api/ce';
import { getComponentData } from '../../../api/components';
import { getComponentNavigation } from '../../../api/nav';
-jest.mock('../../../api/branches', () => ({ getBranches: jest.fn(() => Promise.resolve([])) }));
+jest.mock('../../../api/branches', () => ({
+ getBranches: jest.fn(() => Promise.resolve([])),
+ getPullRequests: jest.fn(() => Promise.resolve([]))
+}));
+
jest.mock('../../../api/ce', () => ({
getTasksForComponent: jest.fn(() => Promise.resolve({ queue: [] }))
}));
+
jest.mock('../../../api/components', () => ({
getComponentData: jest.fn(() => Promise.resolve({}))
}));
+
jest.mock('../../../api/nav', () => ({
getComponentNavigation: jest.fn(() =>
Promise.resolve({
@@ -49,10 +55,11 @@ jest.mock('../nav/component/ComponentNav', () => ({
const Inner = () => <div />;
beforeEach(() => {
- (getBranches as jest.Mock<any>).mockClear();
- (getComponentData as jest.Mock<any>).mockClear();
- (getComponentNavigation as jest.Mock<any>).mockClear();
- (getTasksForComponent as jest.Mock<any>).mockClear();
+ (getBranches as jest.Mock).mockClear();
+ (getPullRequests as jest.Mock).mockClear();
+ (getComponentData as jest.Mock).mockClear();
+ (getComponentNavigation as jest.Mock).mockClear();
+ (getTasksForComponent as jest.Mock).mockClear();
});
it('changes component', () => {
@@ -90,8 +97,9 @@ it("loads branches for module's project", async () => {
await new Promise(setImmediate);
expect(getBranches).toBeCalledWith('projectKey');
- expect(getComponentData).toBeCalledWith('moduleKey', undefined);
- expect(getComponentNavigation).toBeCalledWith('moduleKey', undefined);
+ expect(getPullRequests).toBeCalledWith('projectKey');
+ expect(getComponentData).toBeCalledWith({ component: 'moduleKey', branch: undefined });
+ expect(getComponentNavigation).toBeCalledWith({ componentKey: 'moduleKey', branch: undefined });
});
it("doesn't load branches portfolio", async () => {
@@ -103,8 +111,12 @@ it("doesn't load branches portfolio", async () => {
await new Promise(setImmediate);
expect(getBranches).not.toBeCalled();
- expect(getComponentData).toBeCalledWith('portfolioKey', undefined);
- expect(getComponentNavigation).toBeCalledWith('portfolioKey', undefined);
+ expect(getPullRequests).not.toBeCalled();
+ expect(getComponentData).toBeCalledWith({ component: 'portfolioKey', branch: undefined });
+ expect(getComponentNavigation).toBeCalledWith({
+ componentKey: 'portfolioKey',
+ branch: undefined
+ });
wrapper.update();
expect(wrapper.find(Inner).exists()).toBeTruthy();
});
@@ -123,6 +135,7 @@ it('updates branches on change', () => {
});
(wrapper.find(Inner).prop('onBranchesChange') as Function)();
expect(getBranches).toBeCalledWith('projectKey');
+ expect(getPullRequests).toBeCalledWith('projectKey');
});
it('loads organization', async () => {
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css
index 8b13a6ef075..ac7ce95e3f3 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css
@@ -25,6 +25,10 @@
font-size: var(--baseFontSize);
}
+.navbar-context-meta-branch-menu-title {
+ padding-left: calc(3 * var(--gridSize));
+}
+
.navbar-context-meta-branch-menu-item {
display: flex !important;
justify-content: space-between;
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx
index 71380876edf..38fbd68f644 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx
@@ -24,15 +24,15 @@ import ComponentNavMenu from './ComponentNavMenu';
import ComponentNavBgTaskNotif from './ComponentNavBgTaskNotif';
import RecentHistory from '../../RecentHistory';
import * as theme from '../../../theme';
-import { Branch, Component } from '../../../types';
+import { BranchLike, Component } from '../../../types';
import ContextNavBar from '../../../../components/nav/ContextNavBar';
import { Task } from '../../../../api/ce';
import { STATUSES } from '../../../../apps/background-tasks/constants';
import './ComponentNav.css';
interface Props {
- branches: Branch[];
- currentBranch?: Branch;
+ branchLikes: BranchLike[];
+ currentBranchLike: BranchLike | undefined;
component: Component;
currentTask?: Task;
isInProgress?: boolean;
@@ -85,15 +85,18 @@ export default class ComponentNav extends React.PureComponent<Props> {
height={notifComponent ? theme.contextNavHeightRaw + 20 : theme.contextNavHeightRaw}
notif={notifComponent}>
<ComponentNavHeader
- branches={this.props.branches}
+ branchLikes={this.props.branchLikes}
component={this.props.component}
- currentBranch={this.props.currentBranch}
+ currentBranchLike={this.props.currentBranchLike}
// to close dropdown on any location change
location={this.props.location}
/>
- <ComponentNavMeta branch={this.props.currentBranch} component={this.props.component} />
+ <ComponentNavMeta
+ branchLike={this.props.currentBranchLike}
+ component={this.props.component}
+ />
<ComponentNavMenu
- branch={this.props.currentBranch}
+ branchLike={this.props.currentBranchLike}
component={this.props.component}
// to re-render selected menu item
location={this.props.location}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx
index 8c600143589..d51dadbd73d 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx
@@ -20,22 +20,28 @@
import * as React from 'react';
import * as classNames from 'classnames';
import * as PropTypes from 'prop-types';
+import { FormattedMessage } from 'react-intl';
import ComponentNavBranchesMenu from './ComponentNavBranchesMenu';
import SingleBranchHelperPopup from './SingleBranchHelperPopup';
import NoBranchSupportPopup from './NoBranchSupportPopup';
-import { Branch, Component } from '../../../types';
+import { BranchLike, Component } from '../../../types';
import * as theme from '../../../theme';
import BranchIcon from '../../../../components/icons-components/BranchIcon';
-import { isShortLivingBranch } from '../../../../helpers/branches';
+import {
+ isShortLivingBranch,
+ isSameBranchLike,
+ getBranchLikeDisplayName,
+ isPullRequest
+} from '../../../../helpers/branches';
import { translate } from '../../../../helpers/l10n';
import HelpIcon from '../../../../components/icons-components/HelpIcon';
import BubblePopupHelper from '../../../../components/common/BubblePopupHelper';
import Tooltip from '../../../../components/controls/Tooltip';
interface Props {
- branches: Branch[];
+ branchLikes: BranchLike[];
component: Component;
- currentBranch: Branch;
+ currentBranchLike: BranchLike;
location?: any;
}
@@ -69,7 +75,7 @@ export default class ComponentNavBranch extends React.PureComponent<Props, State
componentWillReceiveProps(nextProps: Props) {
if (
nextProps.component !== this.props.component ||
- this.differentBranches(nextProps.currentBranch, this.props.currentBranch) ||
+ !isSameBranchLike(nextProps.currentBranchLike, this.props.currentBranchLike) ||
nextProps.location !== this.props.location
) {
this.setState({ dropdownOpen: false, singleBranchPopupOpen: false });
@@ -80,11 +86,6 @@ export default class ComponentNavBranch extends React.PureComponent<Props, State
this.mounted = false;
}
- differentBranches(a: Branch, b: Branch) {
- // if main branch changes name, we should not close the dropdown
- return a.isMain && b.isMain ? false : a.name !== b.name;
- }
-
handleClick = (event: React.SyntheticEvent<HTMLElement>) => {
event.preventDefault();
event.stopPropagation();
@@ -130,32 +131,46 @@ export default class ComponentNavBranch extends React.PureComponent<Props, State
const { configuration } = this.props.component;
return this.state.dropdownOpen ? (
<ComponentNavBranchesMenu
- branches={this.props.branches}
+ branchLikes={this.props.branchLikes}
canAdmin={configuration && configuration.showSettings}
component={this.props.component}
- currentBranch={this.props.currentBranch}
+ currentBranchLike={this.props.currentBranchLike}
onClose={this.closeDropdown}
/>
) : null;
};
renderMergeBranch = () => {
- const { currentBranch } = this.props;
- if (!isShortLivingBranch(currentBranch)) {
+ const { currentBranchLike } = this.props;
+ if (isShortLivingBranch(currentBranchLike)) {
+ return currentBranchLike.isOrphan ? (
+ <span className="note big-spacer-left text-lowercase">
+ {translate('branches.orphan_branch')}
+ <Tooltip overlay={translate('branches.orphan_branches.tooltip')}>
+ <i className="icon-help spacer-left" />
+ </Tooltip>
+ </span>
+ ) : (
+ <span className="note big-spacer-left text-lowercase">
+ {translate('from')} <strong>{currentBranchLike.mergeBranch}</strong>
+ </span>
+ );
+ } else if (isPullRequest(currentBranchLike)) {
+ return (
+ <span className="note big-spacer-left text-lowercase">
+ <FormattedMessage
+ defaultMessage={translate('branches.pull_request.for_merge_into_x_from_y')}
+ id="branches.pull_request.for_merge_into_x_from_y"
+ values={{
+ base: <strong>{currentBranchLike.base}</strong>,
+ branch: <strong>{currentBranchLike.branch}</strong>
+ }}
+ />
+ </span>
+ );
+ } else {
return null;
}
- return currentBranch.isOrphan ? (
- <span className="note big-spacer-left text-lowercase">
- {translate('branches.orphan_branch')}
- <Tooltip overlay={translate('branches.orphan_branches.tooltip')}>
- <i className="icon-help spacer-left" />
- </Tooltip>
- </span>
- ) : (
- <span className="note big-spacer-left text-lowercase">
- {translate('from')} <strong>{currentBranch.mergeBranch}</strong>
- </span>
- );
};
renderSingleBranchPopup = () => (
@@ -187,27 +202,33 @@ export default class ComponentNavBranch extends React.PureComponent<Props, State
);
render() {
- const { branches, currentBranch } = this.props;
+ const { branchLikes, currentBranchLike } = this.props;
if (this.context.onSonarCloud && !this.context.branchesEnabled) {
return null;
}
+ const displayName = getBranchLikeDisplayName(currentBranchLike);
+
if (!this.context.branchesEnabled) {
return (
<div className="navbar-context-branches">
- <BranchIcon branch={currentBranch} className="little-spacer-right" fill={theme.gray80} />
- <span className="note">{currentBranch.name}</span>
+ <BranchIcon
+ branchLike={currentBranchLike}
+ className="little-spacer-right"
+ fill={theme.gray80}
+ />
+ <span className="note">{displayName}</span>
{this.renderNoBranchSupportPopup()}
</div>
);
}
- if (branches.length < 2) {
+ if (branchLikes.length < 2) {
return (
<div className="navbar-context-branches">
- <BranchIcon branch={currentBranch} className="little-spacer-right" />
- <span className="note">{currentBranch.name}</span>
+ <BranchIcon branchLike={currentBranchLike} className="little-spacer-right" />
+ <span className="note">{displayName}</span>
{this.renderSingleBranchPopup()}
</div>
);
@@ -219,9 +240,9 @@ export default class ComponentNavBranch extends React.PureComponent<Props, State
open: this.state.dropdownOpen
})}>
<a className="link-base-color link-no-underline" href="#" onClick={this.handleClick}>
- <BranchIcon branch={currentBranch} className="little-spacer-right" />
- <Tooltip overlay={currentBranch.name} mouseEnterDelay={1}>
- <span className="text-limited text-top">{currentBranch.name}</span>
+ <BranchIcon branchLike={currentBranchLike} className="little-spacer-right" />
+ <Tooltip overlay={displayName} mouseEnterDelay={1}>
+ <span className="text-limited text-top">{displayName}</span>
</Tooltip>
<i className="icon-dropdown little-spacer-left" />
</a>
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenu.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenu.tsx
index b46da2bf13d..4f2849bdfd8 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenu.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenu.tsx
@@ -21,28 +21,32 @@ import * as React from 'react';
import * as PropTypes from 'prop-types';
import { Link } from 'react-router';
import ComponentNavBranchesMenuItem from './ComponentNavBranchesMenuItem';
-import { Branch, Component } from '../../../types';
+import { BranchLike, Component } from '../../../types';
import {
sortBranchesAsTree,
isLongLivingBranch,
- isShortLivingBranch
+ isShortLivingBranch,
+ isSameBranchLike,
+ getBranchLikeKey,
+ isPullRequest,
+ isBranch
} from '../../../../helpers/branches';
import { translate } from '../../../../helpers/l10n';
-import { getProjectBranchUrl } from '../../../../helpers/urls';
+import { getBranchLikeUrl } from '../../../../helpers/urls';
import SearchBox from '../../../../components/controls/SearchBox';
import Tooltip from '../../../../components/controls/Tooltip';
interface Props {
- branches: Branch[];
+ branchLikes: BranchLike[];
canAdmin?: boolean;
component: Component;
- currentBranch: Branch;
+ currentBranchLike: BranchLike;
onClose: () => void;
}
interface State {
query: string;
- selected: string | null;
+ selected: BranchLike | undefined;
}
export default class ComponentNavBranchesMenu extends React.PureComponent<Props, State> {
@@ -54,7 +58,7 @@ export default class ComponentNavBranchesMenu extends React.PureComponent<Props,
constructor(props: Props) {
super(props);
- this.state = { query: '', selected: null };
+ this.state = { query: '', selected: undefined };
}
componentDidMount() {
@@ -65,10 +69,16 @@ export default class ComponentNavBranchesMenu extends React.PureComponent<Props,
window.removeEventListener('click', this.handleClickOutside);
}
- getFilteredBranches = () =>
- sortBranchesAsTree(this.props.branches).filter(branch =>
- branch.name.toLowerCase().includes(this.state.query.toLowerCase())
- );
+ getFilteredBranchLikes = () => {
+ const query = this.state.query.toLowerCase();
+ return sortBranchesAsTree(this.props.branchLikes).filter(branchLike => {
+ const matchBranchName = isBranch(branchLike) && branchLike.name.toLowerCase().includes(query);
+ const matchPullRequestTitleOrId =
+ isPullRequest(branchLike) &&
+ (branchLike.title.includes(query) || branchLike.key.includes(query));
+ return matchBranchName || matchPullRequestTitleOrId;
+ });
+ };
handleClickOutside = (event: Event) => {
if (!this.node || !this.node.contains(event.target as HTMLElement)) {
@@ -76,7 +86,7 @@ export default class ComponentNavBranchesMenu extends React.PureComponent<Props,
}
};
- handleSearchChange = (query: string) => this.setState({ query, selected: null });
+ handleSearchChange = (query: string) => this.setState({ query, selected: undefined });
handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
switch (event.keyCode) {
@@ -99,32 +109,31 @@ export default class ComponentNavBranchesMenu extends React.PureComponent<Props,
openSelected = () => {
const selected = this.getSelected();
- const branch = this.getFilteredBranches().find(branch => branch.name === selected);
- if (branch) {
- this.context.router.push(this.getProjectBranchUrl(branch));
+ if (selected) {
+ this.context.router.push(this.getProjectBranchUrl(selected));
}
};
selectPrevious = () => {
const selected = this.getSelected();
- const branches = this.getFilteredBranches();
- const index = branches.findIndex(branch => branch.name === selected);
+ const branchLikes = this.getFilteredBranchLikes();
+ const index = branchLikes.findIndex(b => isSameBranchLike(b, selected));
if (index > 0) {
- this.setState({ selected: branches[index - 1].name });
+ this.setState({ selected: branchLikes[index - 1] });
}
};
selectNext = () => {
const selected = this.getSelected();
- const branches = this.getFilteredBranches();
- const index = branches.findIndex(branch => branch.name === selected);
+ const branches = this.getFilteredBranchLikes();
+ const index = branches.findIndex(b => isSameBranchLike(b, selected));
if (index >= 0 && index < branches.length - 1) {
- this.setState({ selected: branches[index + 1].name });
+ this.setState({ selected: branches[index + 1] });
}
};
- handleSelect = (branch: Branch) => {
- this.setState({ selected: branch.name });
+ handleSelect = (branchLike: BranchLike) => {
+ this.setState({ selected: branchLike });
};
getSelected = () => {
@@ -132,21 +141,24 @@ export default class ComponentNavBranchesMenu extends React.PureComponent<Props,
return this.state.selected;
}
- const branches = this.getFilteredBranches();
- if (branches.find(b => b.name === this.props.currentBranch.name)) {
- return this.props.currentBranch.name;
+ const branchLikes = this.getFilteredBranchLikes();
+ if (branchLikes.find(b => isSameBranchLike(b, this.props.currentBranchLike))) {
+ return this.props.currentBranchLike;
}
- if (branches.length > 0) {
- return branches[0].name;
+ if (branchLikes.length > 0) {
+ return branchLikes[0];
}
return undefined;
};
- getProjectBranchUrl = (branch: Branch) => getProjectBranchUrl(this.props.component.key, branch);
+ getProjectBranchUrl = (branchLike: BranchLike) =>
+ getBranchLikeUrl(this.props.component.key, branchLike);
- isSelected = (branch: Branch) => branch.name === this.getSelected();
+ isOrphan = (branchLike: BranchLike) => {
+ return (isShortLivingBranch(branchLike) || isPullRequest(branchLike)) && branchLike.isOrphan;
+ };
renderSearch = () => (
<div className="menu-search">
@@ -161,21 +173,26 @@ export default class ComponentNavBranchesMenu extends React.PureComponent<Props,
);
renderBranchesList = () => {
- const branches = this.getFilteredBranches();
+ const branchLikes = this.getFilteredBranchLikes();
const selected = this.getSelected();
- if (branches.length === 0) {
+ if (branchLikes.length === 0) {
return <div className="menu-message note">{translate('no_results')}</div>;
}
- const items = branches.map((branch, index) => {
- const isOrphan = isShortLivingBranch(branch) && branch.isOrphan;
- const previous = index > 0 ? branches[index - 1] : undefined;
- const isPreviousOrphan = isShortLivingBranch(previous) ? previous.isOrphan : false;
- const showDivider = isLongLivingBranch(branch) || (isOrphan && !isPreviousOrphan);
+ const items = branchLikes.map((branchLike, index) => {
+ const isOrphan = this.isOrphan(branchLike);
+ const previous = index > 0 ? branchLikes[index - 1] : undefined;
+ const isPreviousOrphan = previous !== undefined && this.isOrphan(previous);
+ const showDivider = isLongLivingBranch(branchLike) || (isOrphan && !isPreviousOrphan);
const showOrphanHeader = isOrphan && !isPreviousOrphan;
+ const showPullRequestHeader =
+ !showOrphanHeader && isPullRequest(branchLike) && !isPullRequest(previous);
+ const showShortLivingBranchHeader =
+ !showOrphanHeader && isShortLivingBranch(branchLike) && !isShortLivingBranch(previous);
+
return (
- <React.Fragment key={branch.name}>
+ <React.Fragment key={getBranchLikeKey(branchLike)}>
{showDivider && <li className="divider" />}
{showOrphanHeader && (
<li className="dropdown-header">
@@ -185,12 +202,22 @@ export default class ComponentNavBranchesMenu extends React.PureComponent<Props,
</Tooltip>
</li>
)}
+ {showPullRequestHeader && (
+ <li className="dropdown-header navbar-context-meta-branch-menu-title">
+ {translate('branches.pull_requests')}
+ </li>
+ )}
+ {showShortLivingBranchHeader && (
+ <li className="dropdown-header navbar-context-meta-branch-menu-title">
+ {translate('branches.short_lived_branches')}
+ </li>
+ )}
<ComponentNavBranchesMenuItem
- branch={branch}
+ branchLike={branchLike}
component={this.props.component}
- key={branch.name}
+ key={getBranchLikeKey(branchLike)}
onSelect={this.handleSelect}
- selected={branch.name === selected}
+ selected={isSameBranchLike(branchLike, selected)}
/>
</React.Fragment>
);
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenuItem.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenuItem.tsx
index b98de6bba7b..9375bbf356c 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenuItem.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenuItem.tsx
@@ -21,47 +21,55 @@ import * as React from 'react';
import { Link } from 'react-router';
import * as classNames from 'classnames';
import BranchStatus from '../../../../components/common/BranchStatus';
-import { Branch, Component } from '../../../types';
+import { BranchLike, Component } from '../../../types';
import BranchIcon from '../../../../components/icons-components/BranchIcon';
-import { isShortLivingBranch } from '../../../../helpers/branches';
+import {
+ isShortLivingBranch,
+ getBranchLikeDisplayName,
+ getBranchLikeKey,
+ isMainBranch,
+ isPullRequest
+} from '../../../../helpers/branches';
import { translate } from '../../../../helpers/l10n';
-import { getProjectBranchUrl } from '../../../../helpers/urls';
+import { getBranchLikeUrl } from '../../../../helpers/urls';
import Tooltip from '../../../../components/controls/Tooltip';
export interface Props {
- branch: Branch;
+ branchLike: BranchLike;
component: Component;
- onSelect: (branch: Branch) => void;
+ onSelect: (branchLike: BranchLike) => void;
selected: boolean;
}
-export default function ComponentNavBranchesMenuItem({ branch, ...props }: Props) {
+export default function ComponentNavBranchesMenuItem({ branchLike, ...props }: Props) {
const handleMouseEnter = () => {
- props.onSelect(branch);
+ props.onSelect(branchLike);
};
+ const displayName = getBranchLikeDisplayName(branchLike);
+ const shouldBeIndented =
+ (isShortLivingBranch(branchLike) && !branchLike.isOrphan) || isPullRequest(branchLike);
+
return (
- <li key={branch.name} onMouseEnter={handleMouseEnter}>
- <Tooltip mouseEnterDelay={0.5} overlay={branch.name} placement="right">
+ <li key={getBranchLikeKey(branchLike)} onMouseEnter={handleMouseEnter}>
+ <Tooltip mouseEnterDelay={0.5} overlay={displayName} placement="right">
<Link
className={classNames('navbar-context-meta-branch-menu-item', {
active: props.selected
})}
- to={getProjectBranchUrl(props.component.key, branch)}>
+ to={getBranchLikeUrl(props.component.key, branchLike)}>
<div className="navbar-context-meta-branch-menu-item-name text-ellipsis">
<BranchIcon
- branch={branch}
- className={classNames('little-spacer-right', {
- 'big-spacer-left': isShortLivingBranch(branch) && !branch.isOrphan
- })}
+ branchLike={branchLike}
+ className={classNames('little-spacer-right', { 'big-spacer-left': shouldBeIndented })}
/>
- {branch.name}
- {branch.isMain && (
+ {displayName}
+ {isMainBranch(branchLike) && (
<div className="outline-badge spacer-left">{translate('branches.main_branch')}</div>
)}
</div>
<div className="big-spacer-left note">
- <BranchStatus branch={branch} concise={true} />
+ <BranchStatus branchLike={branchLike} concise={true} />
</div>
</Link>
</Tooltip>
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavHeader.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavHeader.tsx
index ede0242991a..b0d387e4fa7 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavHeader.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavHeader.tsx
@@ -21,7 +21,7 @@ import * as React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import ComponentNavBranch from './ComponentNavBranch';
-import { Component, Organization, Branch, Breadcrumb } from '../../../types';
+import { Component, Organization, BranchLike, Breadcrumb } from '../../../types';
import QualifierIcon from '../../../../components/shared/QualifierIcon';
import { getOrganizationByKey, areThereCustomOrganizations } from '../../../../store/rootReducer';
import OrganizationAvatar from '../../../../components/common/OrganizationAvatar';
@@ -37,9 +37,9 @@ interface StateProps {
}
interface OwnProps {
- branches: Branch[];
+ branchLikes: BranchLike[];
component: Component;
- currentBranch?: Branch;
+ currentBranchLike: BranchLike | undefined;
location?: any;
}
@@ -70,11 +70,11 @@ export function ComponentNavHeader(props: Props) {
{component.visibility === 'private' && (
<PrivateBadge className="spacer-left" qualifier={component.qualifier} />
)}
- {props.currentBranch && (
+ {props.currentBranchLike && (
<ComponentNavBranch
- branches={props.branches}
+ branchLikes={props.branchLikes}
component={component}
- currentBranch={props.currentBranch}
+ currentBranchLike={props.currentBranchLike}
// to close dropdown on any location change
location={props.location}
/>
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx
index a52d9a31a51..80f9ebb31de 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx
@@ -21,12 +21,13 @@ import * as React from 'react';
import { Link } from 'react-router';
import * as classNames from 'classnames';
import * as PropTypes from 'prop-types';
-import { Branch, Component, Extension } from '../../../types';
+import { BranchLike, Component, Extension } from '../../../types';
import NavBarTabs from '../../../../components/nav/NavBarTabs';
import {
isShortLivingBranch,
- getBranchName,
- isLongLivingBranch
+ isPullRequest,
+ isMainBranch,
+ getBranchLikeQuery
} from '../../../../helpers/branches';
import { translate } from '../../../../helpers/l10n';
@@ -47,7 +48,7 @@ const SETTINGS_URLS = [
];
interface Props {
- branch?: Branch;
+ branchLike: BranchLike | undefined;
component: Component;
location?: any;
}
@@ -78,23 +79,21 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
return this.props.component.configuration || {};
}
+ getQuery = () => {
+ return { id: this.props.component.key, ...getBranchLikeQuery(this.props.branchLike) };
+ };
+
renderDashboardLink() {
- if (isShortLivingBranch(this.props.branch)) {
+ const { branchLike } = this.props;
+
+ if (isShortLivingBranch(branchLike) || isPullRequest(branchLike)) {
return null;
}
const pathname = this.isPortfolio() ? '/portfolio' : '/dashboard';
return (
<li>
- <Link
- to={{
- pathname,
- query: {
- branch: getBranchName(this.props.branch),
- id: this.props.component.key
- }
- }}
- activeClassName="active">
+ <Link activeClassName="active" to={{ pathname, query: this.getQuery() }}>
{translate('overview.page')}
</Link>
</li>
@@ -108,15 +107,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
return (
<li>
- <Link
- to={{
- pathname: '/code',
- query: {
- branch: getBranchName(this.props.branch),
- id: this.props.component.key
- }
- }}
- activeClassName="active">
+ <Link to={{ pathname: '/code', query: this.getQuery() }} activeClassName="active">
{this.isPortfolio() || this.isApplication()
? translate('view_projects.page')
: translate('code.page')}
@@ -126,20 +117,16 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
}
renderActivityLink() {
- if (isShortLivingBranch(this.props.branch)) {
+ const { branchLike } = this.props;
+
+ if (isShortLivingBranch(branchLike) || isPullRequest(branchLike)) {
return null;
}
return (
<li>
<Link
- to={{
- pathname: '/project/activity',
- query: {
- branch: getBranchName(this.props.branch),
- id: this.props.component.key
- }
- }}
+ to={{ pathname: '/project/activity', query: this.getQuery() }}
activeClassName="active">
{translate('project_activity.page')}
</Link>
@@ -153,16 +140,9 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
return (
<li>
<Link
- to={{
- pathname: '/project/issues',
- query: {
- branch: getBranchName(this.props.branch),
- id: this.props.component.key,
- resolved: 'false'
- }
- }}
+ activeClassName="active"
className={classNames({ active: isIssuesActive })}
- activeClassName="active">
+ to={{ pathname: '/project/issues', query: { ...this.getQuery(), resolved: 'false' } }}>
{translate('issues.page')}
</Link>
</li>
@@ -170,20 +150,16 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
}
renderComponentMeasuresLink() {
- if (isShortLivingBranch(this.props.branch)) {
+ const { branchLike } = this.props;
+
+ if (isShortLivingBranch(branchLike) || isPullRequest(branchLike)) {
return null;
}
return (
<li>
<Link
- to={{
- pathname: '/component_measures',
- query: {
- branch: getBranchName(this.props.branch),
- id: this.props.component.key
- }
- }}
+ to={{ pathname: '/component_measures', query: this.getQuery() }}
activeClassName="active">
{translate('layout.measures')}
</Link>
@@ -192,30 +168,14 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
}
renderAdministration() {
- const { branch } = this.props;
+ const { branchLike } = this.props;
- if (!this.getConfiguration().showSettings || (branch && !branch.isMain)) {
+ if (!this.getConfiguration().showSettings || (branchLike && !isMainBranch(branchLike))) {
return null;
}
const isSettingsActive = SETTINGS_URLS.some(url => window.location.href.indexOf(url) !== -1);
- if (isLongLivingBranch(branch)) {
- return (
- <li>
- <Link
- className={classNames({ active: isSettingsActive })}
- id="component-navigation-admin"
- to={{
- pathname: '/project/settings',
- query: { branch: getBranchName(branch), id: this.props.component.key }
- }}>
- {translate('branches.branch_settings')}
- </Link>
- </li>
- );
- }
-
const adminLinks = this.renderAdministrationLinks();
if (!adminLinks.some(link => link != null)) {
return null;
@@ -260,13 +220,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
return (
<li key="settings">
<Link
- to={{
- pathname: '/project/settings',
- query: {
- branch: getBranchName(this.props.branch),
- id: this.props.component.key
- }
- }}
+ to={{ pathname: '/project/settings', query: this.getQuery() }}
activeClassName="active">
{translate('project_settings.page')}
</Link>
@@ -448,7 +402,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
};
renderAdminExtensions() {
- if (this.props.branch && !this.props.branch.isMain) {
+ if (this.props.branchLike && !isMainBranch(this.props.branchLike)) {
return [];
}
const extensions = this.getConfiguration().extensions || [];
@@ -457,7 +411,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> {
renderExtensions() {
const extensions = this.props.component.extensions || [];
- if (!extensions.length || (this.props.branch && !this.props.branch.isMain)) {
+ if (!extensions.length || (this.props.branchLike && !isMainBranch(this.props.branchLike))) {
return null;
}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx
index 9bee1a224fe..57f41ffeb3d 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx
@@ -19,7 +19,14 @@
*/
import * as React from 'react';
import { connect } from 'react-redux';
-import { Branch, Component, CurrentUser, isLoggedIn, HomePageType, HomePage } from '../../../types';
+import {
+ BranchLike,
+ Component,
+ CurrentUser,
+ isLoggedIn,
+ HomePageType,
+ HomePage
+} from '../../../types';
import BranchStatus from '../../../../components/common/BranchStatus';
import DateTimeFormatter from '../../../../components/intl/DateTimeFormatter';
import Favorite from '../../../../components/controls/Favorite';
@@ -28,7 +35,8 @@ import Tooltip from '../../../../components/controls/Tooltip';
import {
isShortLivingBranch,
isLongLivingBranch,
- getBranchName
+ isMainBranch,
+ isPullRequest
} from '../../../../helpers/branches';
import { translate } from '../../../../helpers/l10n';
import { getCurrentUser } from '../../../../store/rootReducer';
@@ -38,27 +46,15 @@ interface StateProps {
}
interface Props extends StateProps {
- branch?: Branch;
+ branchLike?: BranchLike;
component: Component;
}
-export function ComponentNavMeta({ branch, component, currentUser }: Props) {
- const shortBranch = isShortLivingBranch(branch);
- const mainBranch = !branch || branch.isMain;
- const longBranch = isLongLivingBranch(branch);
-
- let currentPage: HomePage | undefined;
- if (component.qualifier === 'VW' || component.qualifier === 'SVW') {
- currentPage = { type: HomePageType.Portfolio, component: component.key };
- } else if (component.qualifier === 'APP') {
- currentPage = { type: HomePageType.Application, component: component.key };
- } else if (component.qualifier === 'TRK') {
- currentPage = {
- type: HomePageType.Project,
- component: component.key,
- branch: getBranchName(branch)
- };
- }
+export function ComponentNavMeta({ branchLike, component, currentUser }: Props) {
+ const mainBranch = !branchLike || isMainBranch(branchLike);
+ const longBranch = isLongLivingBranch(branchLike);
+ const currentPage = getCurrentPage(component, branchLike);
+ const displayVersion = component.version !== undefined && (mainBranch || longBranch);
return (
<div className="navbar-context-meta">
@@ -67,14 +63,13 @@ export function ComponentNavMeta({ branch, component, currentUser }: Props) {
<DateTimeFormatter date={component.analysisDate} />
</div>
)}
- {component.version &&
- !shortBranch && (
- <Tooltip mouseEnterDelay={0.5} overlay={`${translate('version')} ${component.version}`}>
- <div className="spacer-left text-limited">
- {translate('version')} {component.version}
- </div>
- </Tooltip>
- )}
+ {displayVersion && (
+ <Tooltip mouseEnterDelay={0.5} overlay={`${translate('version')} ${component.version}`}>
+ <div className="spacer-left text-limited">
+ {translate('version')} {component.version}
+ </div>
+ </Tooltip>
+ )}
{isLoggedIn(currentUser) && (
<div className="navbar-context-meta-secondary">
{mainBranch && (
@@ -90,15 +85,36 @@ export function ComponentNavMeta({ branch, component, currentUser }: Props) {
)}
</div>
)}
- {shortBranch && (
+ {(isShortLivingBranch(branchLike) || isPullRequest(branchLike)) && (
<div className="navbar-context-meta-secondary">
- <BranchStatus branch={branch!} />
+ {isPullRequest(branchLike) &&
+ branchLike.url !== undefined && (
+ <a className="big-spacer-right" href={branchLike.url} rel="nofollow" target="_blank">
+ {translate('branches.see_the_pr')}
+ <i className="icon-detach little-spacer-left" />
+ </a>
+ )}
+ <BranchStatus branchLike={branchLike} />
</div>
)}
</div>
);
}
+function getCurrentPage(component: Component, branchLike: BranchLike | undefined) {
+ let currentPage: HomePage | undefined;
+ if (component.qualifier === 'VW' || component.qualifier === 'SVW') {
+ currentPage = { type: HomePageType.Portfolio, component: component.key };
+ } else if (component.qualifier === 'APP') {
+ currentPage = { type: HomePageType.Application, component: component.key };
+ } else if (component.qualifier === 'TRK') {
+ const branch =
+ isMainBranch(branchLike) || isLongLivingBranch(branchLike) ? branchLike.name : undefined;
+ currentPage = { type: HomePageType.Project, component: component.key, branch };
+ }
+ return currentPage;
+}
+
const mapStateToProps = (state: any): StateProps => ({
currentUser: getCurrentUser(state)
});
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNav-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNav-test.tsx
index e4b72cb3564..b0f633badb5 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNav-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNav-test.tsx
@@ -30,7 +30,14 @@ const component = {
};
it('renders', () => {
- const wrapper = shallow(<ComponentNav branches={[]} component={component} location={{}} />);
+ const wrapper = shallow(
+ <ComponentNav
+ branchLikes={[]}
+ component={component}
+ currentBranchLike={undefined}
+ location={{}}
+ />
+ );
wrapper.setState({ isInProgress: true, isPending: true });
expect(wrapper).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx
index dee0d5b79ba..7eac067d8dc 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx
@@ -25,21 +25,22 @@ import {
ShortLivingBranch,
MainBranch,
Component,
- LongLivingBranch
+ LongLivingBranch,
+ PullRequest
} from '../../../../types';
import { click } from '../../../../../helpers/testUtils';
+const mainBranch: MainBranch = { isMain: true, name: 'master' };
const fooBranch: LongLivingBranch = { isMain: false, name: 'foo', type: BranchType.LONG };
it('renders main branch', () => {
- const branch: MainBranch = { isMain: true, name: 'master' };
const component = {} as Component;
expect(
shallow(
<ComponentNavBranch
- branches={[branch, fooBranch]}
+ branchLikes={[mainBranch, fooBranch]}
component={component}
- currentBranch={branch}
+ currentBranchLike={mainBranch}
/>,
{ context: { branchesEnabled: true } }
)
@@ -58,9 +59,30 @@ it('renders short-living branch', () => {
expect(
shallow(
<ComponentNavBranch
- branches={[branch, fooBranch]}
+ branchLikes={[branch, fooBranch]}
component={component}
- currentBranch={branch}
+ currentBranchLike={branch}
+ />,
+ { context: { branchesEnabled: true } }
+ )
+ ).toMatchSnapshot();
+});
+
+it('renders pull request', () => {
+ const pullRequest: PullRequest = {
+ base: 'master',
+ branch: 'feature',
+ key: '1234',
+ title: 'Feature PR',
+ url: 'https://example.com/pull/1234'
+ };
+ const component = {} as Component;
+ expect(
+ shallow(
+ <ComponentNavBranch
+ branchLikes={[pullRequest, fooBranch]}
+ component={component}
+ currentBranchLike={pullRequest}
/>,
{ context: { branchesEnabled: true } }
)
@@ -68,13 +90,12 @@ it('renders short-living branch', () => {
});
it('opens menu', () => {
- const branch: MainBranch = { isMain: true, name: 'master' };
const component = {} as Component;
const wrapper = shallow(
<ComponentNavBranch
- branches={[branch, fooBranch]}
+ branchLikes={[mainBranch, fooBranch]}
component={component}
- currentBranch={branch}
+ currentBranchLike={mainBranch}
/>,
{ context: { branchesEnabled: true } }
);
@@ -84,10 +105,13 @@ it('opens menu', () => {
});
it('renders single branch popup', () => {
- const branch: MainBranch = { isMain: true, name: 'master' };
const component = {} as Component;
const wrapper = shallow(
- <ComponentNavBranch branches={[branch]} component={component} currentBranch={branch} />,
+ <ComponentNavBranch
+ branchLikes={[mainBranch]}
+ component={component}
+ currentBranchLike={mainBranch}
+ />,
{ context: { branchesEnabled: true } }
);
expect(wrapper).toMatchSnapshot();
@@ -97,13 +121,12 @@ it('renders single branch popup', () => {
});
it('renders no branch support popup', () => {
- const branch: MainBranch = { isMain: true, name: 'master' };
const component = {} as Component;
const wrapper = shallow(
<ComponentNavBranch
- branches={[branch, fooBranch]}
+ branchLikes={[mainBranch, fooBranch]}
component={component}
- currentBranch={branch}
+ currentBranchLike={mainBranch}
/>,
{ context: { branchesEnabled: false } }
);
@@ -114,10 +137,13 @@ it('renders no branch support popup', () => {
});
it('renders nothing on SonarCloud without branch support', () => {
- const branch: MainBranch = { isMain: true, name: 'master' };
const component = {} as Component;
const wrapper = shallow(
- <ComponentNavBranch branches={[branch]} component={component} currentBranch={branch} />,
+ <ComponentNavBranch
+ branchLikes={[mainBranch]}
+ component={component}
+ currentBranchLike={mainBranch}
+ />,
{ context: { branchesEnabled: false, onSonarCloud: true } }
);
expect(wrapper.type()).toBeNull();
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenu-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenu-test.tsx
index ec4843e138f..bb43ec60c63 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenu-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenu-test.tsx
@@ -25,7 +25,8 @@ import {
MainBranch,
ShortLivingBranch,
LongLivingBranch,
- Component
+ Component,
+ PullRequest
} from '../../../../types';
import { elementKeydown } from '../../../../../helpers/testUtils';
@@ -35,9 +36,15 @@ it('renders list', () => {
expect(
shallow(
<ComponentNavBranchesMenu
- branches={[mainBranch(), shortBranch('foo'), longBranch('bar'), shortBranch('baz', true)]}
+ branchLikes={[
+ mainBranch(),
+ shortBranch('foo'),
+ longBranch('bar'),
+ shortBranch('baz', true),
+ pullRequest('qux')
+ ]}
component={component}
- currentBranch={mainBranch()}
+ currentBranchLike={mainBranch()}
onClose={jest.fn()}
/>
)
@@ -47,9 +54,9 @@ it('renders list', () => {
it('searches', () => {
const wrapper = shallow(
<ComponentNavBranchesMenu
- branches={[mainBranch(), shortBranch('foo'), shortBranch('foobar'), longBranch('bar')]}
+ branchLikes={[mainBranch(), shortBranch('foo'), shortBranch('foobar'), longBranch('bar')]}
component={component}
- currentBranch={mainBranch()}
+ currentBranchLike={mainBranch()}
onClose={jest.fn()}
/>
);
@@ -60,21 +67,21 @@ it('searches', () => {
it('selects next & previous', () => {
const wrapper = shallow(
<ComponentNavBranchesMenu
- branches={[mainBranch(), shortBranch('foo'), shortBranch('foobar'), longBranch('bar')]}
+ branchLikes={[mainBranch(), shortBranch('foo'), shortBranch('foobar'), longBranch('bar')]}
component={component}
- currentBranch={mainBranch()}
+ currentBranchLike={mainBranch()}
onClose={jest.fn()}
/>
);
elementKeydown(wrapper.find('SearchBox'), 40);
wrapper.update();
- expect(wrapper.state().selected).toBe('foo');
+ expect(wrapper.state().selected).toEqual(shortBranch('foo'));
elementKeydown(wrapper.find('SearchBox'), 40);
wrapper.update();
- expect(wrapper.state().selected).toBe('foobar');
+ expect(wrapper.state().selected).toEqual(shortBranch('foobar'));
elementKeydown(wrapper.find('SearchBox'), 38);
wrapper.update();
- expect(wrapper.state().selected).toBe('foo');
+ expect(wrapper.state().selected).toEqual(shortBranch('foo'));
});
function mainBranch(): MainBranch {
@@ -95,3 +102,13 @@ function shortBranch(name: string, isOrphan?: true): ShortLivingBranch {
function longBranch(name: string): LongLivingBranch {
return { isMain: false, name, type: BranchType.LONG };
}
+
+function pullRequest(title: string): PullRequest {
+ return {
+ base: 'master',
+ branch: 'feature',
+ key: '1234',
+ status: { bugs: 0, codeSmells: 0, vulnerabilities: 0 },
+ title
+ };
+}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenuItem-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenuItem-test.tsx
index b411df4578a..0635bea82a8 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenuItem-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenuItem-test.tsx
@@ -35,7 +35,7 @@ const shortBranch: ShortLivingBranch = {
const mainBranch: MainBranch = { isMain: true, name: 'master' };
it('renders main branch', () => {
- expect(shallowRender({ branch: mainBranch })).toMatchSnapshot();
+ expect(shallowRender({ branchLike: mainBranch })).toMatchSnapshot();
});
it('renders short-living branch', () => {
@@ -43,13 +43,14 @@ it('renders short-living branch', () => {
});
it('renders short-living orhpan branch', () => {
- expect(shallowRender({ branch: { ...shortBranch, isOrphan: true } })).toMatchSnapshot();
+ const orhpan: ShortLivingBranch = { ...shortBranch, isOrphan: true };
+ expect(shallowRender({ branchLike: orhpan })).toMatchSnapshot();
});
function shallowRender(props?: { [P in keyof Props]?: Props[P] }) {
return shallow(
<ComponentNavBranchesMenuItem
- branch={shortBranch}
+ branchLike={shortBranch}
component={component}
onSelect={jest.fn()}
selected={false}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavHeader-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavHeader-test.tsx
index 912973b4558..676a38a577b 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavHeader-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavHeader-test.tsx
@@ -32,7 +32,12 @@ it('should not render breadcrumbs with one element', () => {
visibility: 'public'
};
const result = shallow(
- <ComponentNavHeader branches={[]} component={component} shouldOrganizationBeDisplayed={false} />
+ <ComponentNavHeader
+ branchLikes={[]}
+ component={component}
+ currentBranchLike={undefined}
+ shouldOrganizationBeDisplayed={false}
+ />
);
expect(result).toMatchSnapshot();
});
@@ -53,8 +58,9 @@ it('should render organization', () => {
};
const result = shallow(
<ComponentNavHeader
- branches={[]}
+ branchLikes={[]}
component={component}
+ currentBranchLike={undefined}
organization={organization}
shouldOrganizationBeDisplayed={true}
/>
@@ -72,7 +78,12 @@ it('renders private badge', () => {
visibility: 'private'
};
const result = shallow(
- <ComponentNavHeader branches={[]} component={component} shouldOrganizationBeDisplayed={false} />
+ <ComponentNavHeader
+ branchLikes={[]}
+ component={component}
+ currentBranchLike={undefined}
+ shouldOrganizationBeDisplayed={false}
+ />
);
expect(result.find('PrivateBadge')).toHaveLength(1);
});
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMenu-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMenu-test.tsx
index eb400536ed1..ef502e33aad 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMenu-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMenu-test.tsx
@@ -39,7 +39,7 @@ it('should work with extensions', () => {
extensions: [{ key: 'component-foo', name: 'ComponentFoo' }]
};
expect(
- shallow(<ComponentNavMenu branch={mainBranch} component={component} />, {
+ shallow(<ComponentNavMenu branchLike={mainBranch} component={component} />, {
context: { branchesEnabled: true }
})
).toMatchSnapshot();
@@ -58,7 +58,7 @@ it('should work with multiple extensions', () => {
]
};
expect(
- shallow(<ComponentNavMenu branch={mainBranch} component={component} />, {
+ shallow(<ComponentNavMenu branchLike={mainBranch} component={component} />, {
context: { branchesEnabled: true }
})
).toMatchSnapshot();
@@ -77,7 +77,7 @@ it('should work for short-living branches', () => {
extensions: [{ key: 'component-foo', name: 'ComponentFoo' }]
};
expect(
- shallow(<ComponentNavMenu branch={branch} component={component} />, {
+ shallow(<ComponentNavMenu branchLike={branch} component={component} />, {
context: { branchesEnabled: true }
})
).toMatchSnapshot();
@@ -89,7 +89,7 @@ it('should work for long-living branches', () => {
expect(
shallow(
<ComponentNavMenu
- branch={branch}
+ branchLike={branch}
component={{
...baseComponent,
configuration: { showSettings },
@@ -109,7 +109,7 @@ it('should work for all qualifiers', () => {
function checkWithQualifier(qualifier: string) {
const component = { ...baseComponent, configuration: { showSettings: true }, qualifier };
expect(
- shallow(<ComponentNavMenu branch={mainBranch} component={component} />, {
+ shallow(<ComponentNavMenu branchLike={mainBranch} component={component} />, {
context: { branchesEnabled: true }
})
).toMatchSnapshot();
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMeta-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMeta-test.tsx
index c964e23552a..62bdbdbb715 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMeta-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMeta-test.tsx
@@ -20,7 +20,7 @@
import * as React from 'react';
import { shallow } from 'enzyme';
import { ComponentNavMeta } from '../ComponentNavMeta';
-import { BranchType, ShortLivingBranch, LongLivingBranch } from '../../../../types';
+import { BranchType, ShortLivingBranch, LongLivingBranch, PullRequest } from '../../../../types';
const component = {
analysisDate: '2017-01-02T00:00:00.000Z',
@@ -42,7 +42,11 @@ it('renders status of short-living branch', () => {
};
expect(
shallow(
- <ComponentNavMeta branch={branch} component={component} currentUser={{ isLoggedIn: false }} />
+ <ComponentNavMeta
+ branchLike={branch}
+ component={component}
+ currentUser={{ isLoggedIn: false }}
+ />
)
).toMatchSnapshot();
});
@@ -56,7 +60,31 @@ it('renders meta for long-living branch', () => {
};
expect(
shallow(
- <ComponentNavMeta branch={branch} component={component} currentUser={{ isLoggedIn: false }} />
+ <ComponentNavMeta
+ branchLike={branch}
+ component={component}
+ currentUser={{ isLoggedIn: false }}
+ />
+ )
+ ).toMatchSnapshot();
+});
+
+it('renders meta for pull request', () => {
+ const pullRequest: PullRequest = {
+ base: 'master',
+ branch: 'feature',
+ key: '1234',
+ status: { bugs: 0, codeSmells: 2, vulnerabilities: 3 },
+ title: 'Feature PR',
+ url: 'https://example.com/pull/1234'
+ };
+ expect(
+ shallow(
+ <ComponentNavMeta
+ branchLike={pullRequest}
+ component={component}
+ currentUser={{ isLoggedIn: false }}
+ />
)
).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap
index e52606979cf..2d5fe354989 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap
@@ -6,7 +6,7 @@ exports[`renders 1`] = `
id="context-navigation"
>
<Connect(ComponentNavHeader)
- branches={Array []}
+ branchLikes={Array []}
component={
Object {
"breadcrumbs": Array [
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranch-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranch-test.tsx.snap
index 3571893cc96..6e45772bb56 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranch-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranch-test.tsx.snap
@@ -10,7 +10,7 @@ exports[`renders main branch 1`] = `
onClick={[Function]}
>
<BranchIcon
- branch={
+ branchLike={
Object {
"isMain": true,
"name": "master",
@@ -41,7 +41,7 @@ exports[`renders no branch support popup 1`] = `
className="navbar-context-branches"
>
<BranchIcon
- branch={
+ branchLike={
Object {
"isMain": true,
"name": "master",
@@ -77,6 +77,63 @@ exports[`renders no branch support popup 1`] = `
</div>
`;
+exports[`renders pull request 1`] = `
+<div
+ className="navbar-context-branches dropdown"
+>
+ <a
+ className="link-base-color link-no-underline"
+ href="#"
+ onClick={[Function]}
+ >
+ <BranchIcon
+ branchLike={
+ Object {
+ "base": "master",
+ "branch": "feature",
+ "key": "1234",
+ "title": "Feature PR",
+ "url": "https://example.com/pull/1234",
+ }
+ }
+ className="little-spacer-right"
+ />
+ <Tooltip
+ mouseEnterDelay={1}
+ overlay="1234 – Feature PR"
+ placement="bottom"
+ >
+ <span
+ className="text-limited text-top"
+ >
+ 1234 – Feature PR
+ </span>
+ </Tooltip>
+ <i
+ className="icon-dropdown little-spacer-left"
+ />
+ </a>
+ <span
+ className="note big-spacer-left text-lowercase"
+ >
+ <FormattedMessage
+ defaultMessage="branches.pull_request.for_merge_into_x_from_y"
+ id="branches.pull_request.for_merge_into_x_from_y"
+ values={
+ Object {
+ "base": <strong>
+ master
+ </strong>,
+ "branch": <strong>
+ feature
+ </strong>,
+ }
+ }
+ />
+ </span>
+</div>
+`;
+
exports[`renders short-living branch 1`] = `
<div
className="navbar-context-branches dropdown"
@@ -87,7 +144,7 @@ exports[`renders short-living branch 1`] = `
onClick={[Function]}
>
<BranchIcon
- branch={
+ branchLike={
Object {
"isMain": false,
"mergeBranch": "master",
@@ -134,7 +191,7 @@ exports[`renders single branch popup 1`] = `
className="navbar-context-branches"
>
<BranchIcon
- branch={
+ branchLike={
Object {
"isMain": true,
"name": "master",
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenu-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenu-test.tsx.snap
index 6946e02b328..cc51efd1976 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenu-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenu-test.tsx.snap
@@ -19,10 +19,10 @@ exports[`renders list 1`] = `
className="menu menu-vertically-limited"
>
<React.Fragment
- key="master"
+ key="branch-master"
>
<ComponentNavBranchesMenuItem
- branch={
+ branchLike={
Object {
"isMain": true,
"name": "master",
@@ -33,13 +33,45 @@ exports[`renders list 1`] = `
"key": "component",
}
}
- key="master"
+ key="branch-master"
onSelect={[Function]}
selected={true}
/>
</React.Fragment>
<React.Fragment
- key="baz"
+ key="pull-request-1234"
+ >
+ <li
+ className="dropdown-header navbar-context-meta-branch-menu-title"
+ >
+ branches.pull_requests
+ </li>
+ <ComponentNavBranchesMenuItem
+ branchLike={
+ Object {
+ "base": "master",
+ "branch": "feature",
+ "key": "1234",
+ "status": Object {
+ "bugs": 0,
+ "codeSmells": 0,
+ "vulnerabilities": 0,
+ },
+ "title": "qux",
+ }
+ }
+ component={
+ Object {
+ "key": "component",
+ }
+ }
+ key="pull-request-1234"
+ onSelect={[Function]}
+ selected={false}
+ />
+ </React.Fragment>
+ <React.Fragment
+ key="branch-baz"
>
<li
className="divider"
@@ -58,7 +90,7 @@ exports[`renders list 1`] = `
</Tooltip>
</li>
<ComponentNavBranchesMenuItem
- branch={
+ branchLike={
Object {
"isMain": false,
"isOrphan": true,
@@ -77,16 +109,16 @@ exports[`renders list 1`] = `
"key": "component",
}
}
- key="baz"
+ key="branch-baz"
onSelect={[Function]}
selected={false}
/>
</React.Fragment>
<React.Fragment
- key="foo"
+ key="branch-foo"
>
<ComponentNavBranchesMenuItem
- branch={
+ branchLike={
Object {
"isMain": false,
"isOrphan": undefined,
@@ -105,19 +137,19 @@ exports[`renders list 1`] = `
"key": "component",
}
}
- key="foo"
+ key="branch-foo"
onSelect={[Function]}
selected={false}
/>
</React.Fragment>
<React.Fragment
- key="bar"
+ key="branch-bar"
>
<li
className="divider"
/>
<ComponentNavBranchesMenuItem
- branch={
+ branchLike={
Object {
"isMain": false,
"name": "bar",
@@ -129,13 +161,13 @@ exports[`renders list 1`] = `
"key": "component",
}
}
- key="bar"
+ key="branch-bar"
onSelect={[Function]}
selected={false}
/>
</React.Fragment>
<React.Fragment
- key="baz"
+ key="branch-baz"
>
<li
className="divider"
@@ -154,7 +186,7 @@ exports[`renders list 1`] = `
</Tooltip>
</li>
<ComponentNavBranchesMenuItem
- branch={
+ branchLike={
Object {
"isMain": false,
"isOrphan": true,
@@ -173,7 +205,7 @@ exports[`renders list 1`] = `
"key": "component",
}
}
- key="baz"
+ key="branch-baz"
onSelect={[Function]}
selected={false}
/>
@@ -201,10 +233,15 @@ exports[`searches 1`] = `
className="menu menu-vertically-limited"
>
<React.Fragment
- key="foobar"
+ key="branch-foobar"
>
+ <li
+ className="dropdown-header navbar-context-meta-branch-menu-title"
+ >
+ branches.short_lived_branches
+ </li>
<ComponentNavBranchesMenuItem
- branch={
+ branchLike={
Object {
"isMain": false,
"isOrphan": undefined,
@@ -223,19 +260,19 @@ exports[`searches 1`] = `
"key": "component",
}
}
- key="foobar"
+ key="branch-foobar"
onSelect={[Function]}
selected={true}
/>
</React.Fragment>
<React.Fragment
- key="bar"
+ key="branch-bar"
>
<li
className="divider"
/>
<ComponentNavBranchesMenuItem
- branch={
+ branchLike={
Object {
"isMain": false,
"name": "bar",
@@ -247,7 +284,7 @@ exports[`searches 1`] = `
"key": "component",
}
}
- key="bar"
+ key="branch-bar"
onSelect={[Function]}
selected={false}
/>
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenuItem-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenuItem-test.tsx.snap
index ceba812e99c..781da6de591 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenuItem-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenuItem-test.tsx.snap
@@ -2,7 +2,7 @@
exports[`renders main branch 1`] = `
<li
- key="master"
+ key="branch-master"
onMouseEnter={[Function]}
>
<Tooltip
@@ -27,7 +27,7 @@ exports[`renders main branch 1`] = `
className="navbar-context-meta-branch-menu-item-name text-ellipsis"
>
<BranchIcon
- branch={
+ branchLike={
Object {
"isMain": true,
"name": "master",
@@ -46,7 +46,7 @@ exports[`renders main branch 1`] = `
className="big-spacer-left note"
>
<BranchStatus
- branch={
+ branchLike={
Object {
"isMain": true,
"name": "master",
@@ -62,7 +62,7 @@ exports[`renders main branch 1`] = `
exports[`renders short-living branch 1`] = `
<li
- key="foo"
+ key="branch-foo"
onMouseEnter={[Function]}
>
<Tooltip
@@ -89,7 +89,7 @@ exports[`renders short-living branch 1`] = `
className="navbar-context-meta-branch-menu-item-name text-ellipsis"
>
<BranchIcon
- branch={
+ branchLike={
Object {
"isMain": false,
"mergeBranch": "master",
@@ -110,7 +110,7 @@ exports[`renders short-living branch 1`] = `
className="big-spacer-left note"
>
<BranchStatus
- branch={
+ branchLike={
Object {
"isMain": false,
"mergeBranch": "master",
@@ -133,7 +133,7 @@ exports[`renders short-living branch 1`] = `
exports[`renders short-living orhpan branch 1`] = `
<li
- key="foo"
+ key="branch-foo"
onMouseEnter={[Function]}
>
<Tooltip
@@ -160,7 +160,7 @@ exports[`renders short-living orhpan branch 1`] = `
className="navbar-context-meta-branch-menu-item-name text-ellipsis"
>
<BranchIcon
- branch={
+ branchLike={
Object {
"isMain": false,
"isOrphan": true,
@@ -182,7 +182,7 @@ exports[`renders short-living orhpan branch 1`] = `
className="big-spacer-left note"
>
<BranchStatus
- branch={
+ branchLike={
Object {
"isMain": false,
"isOrphan": true,
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavHeader-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavHeader-test.tsx.snap
index eb990ac6401..dd13db8a75c 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavHeader-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavHeader-test.tsx.snap
@@ -23,7 +23,6 @@ exports[`should not render breadcrumbs with one element 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "my-project",
},
}
@@ -91,7 +90,6 @@ exports[`should render organization 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "my-project",
},
}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap
index 22c56c67930..5665e5ab9b4 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap
@@ -11,7 +11,6 @@ exports[`should work for all qualifiers 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -30,7 +29,6 @@ exports[`should work for all qualifiers 1`] = `
Object {
"pathname": "/project/issues",
"query": Object {
- "branch": undefined,
"id": "foo",
"resolved": "false",
},
@@ -49,7 +47,6 @@ exports[`should work for all qualifiers 1`] = `
Object {
"pathname": "/component_measures",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -67,7 +64,6 @@ exports[`should work for all qualifiers 1`] = `
Object {
"pathname": "/code",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -85,7 +81,6 @@ exports[`should work for all qualifiers 1`] = `
Object {
"pathname": "/project/activity",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -123,7 +118,6 @@ exports[`should work for all qualifiers 1`] = `
Object {
"pathname": "/project/settings",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -205,7 +199,6 @@ exports[`should work for all qualifiers 2`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -224,7 +217,6 @@ exports[`should work for all qualifiers 2`] = `
Object {
"pathname": "/project/issues",
"query": Object {
- "branch": undefined,
"id": "foo",
"resolved": "false",
},
@@ -243,7 +235,6 @@ exports[`should work for all qualifiers 2`] = `
Object {
"pathname": "/component_measures",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -261,7 +252,6 @@ exports[`should work for all qualifiers 2`] = `
Object {
"pathname": "/code",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -279,7 +269,6 @@ exports[`should work for all qualifiers 2`] = `
Object {
"pathname": "/project/activity",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -317,7 +306,6 @@ exports[`should work for all qualifiers 2`] = `
Object {
"pathname": "/project/settings",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -342,7 +330,6 @@ exports[`should work for all qualifiers 3`] = `
Object {
"pathname": "/portfolio",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -361,7 +348,6 @@ exports[`should work for all qualifiers 3`] = `
Object {
"pathname": "/project/issues",
"query": Object {
- "branch": undefined,
"id": "foo",
"resolved": "false",
},
@@ -380,7 +366,6 @@ exports[`should work for all qualifiers 3`] = `
Object {
"pathname": "/component_measures",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -398,7 +383,6 @@ exports[`should work for all qualifiers 3`] = `
Object {
"pathname": "/code",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -416,7 +400,6 @@ exports[`should work for all qualifiers 3`] = `
Object {
"pathname": "/project/activity",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -478,7 +461,6 @@ exports[`should work for all qualifiers 4`] = `
Object {
"pathname": "/portfolio",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -497,7 +479,6 @@ exports[`should work for all qualifiers 4`] = `
Object {
"pathname": "/project/issues",
"query": Object {
- "branch": undefined,
"id": "foo",
"resolved": "false",
},
@@ -516,7 +497,6 @@ exports[`should work for all qualifiers 4`] = `
Object {
"pathname": "/component_measures",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -534,7 +514,6 @@ exports[`should work for all qualifiers 4`] = `
Object {
"pathname": "/code",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -552,7 +531,6 @@ exports[`should work for all qualifiers 4`] = `
Object {
"pathname": "/project/activity",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -575,7 +553,6 @@ exports[`should work for all qualifiers 5`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -594,7 +571,6 @@ exports[`should work for all qualifiers 5`] = `
Object {
"pathname": "/project/issues",
"query": Object {
- "branch": undefined,
"id": "foo",
"resolved": "false",
},
@@ -613,7 +589,6 @@ exports[`should work for all qualifiers 5`] = `
Object {
"pathname": "/component_measures",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -631,7 +606,6 @@ exports[`should work for all qualifiers 5`] = `
Object {
"pathname": "/code",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -649,7 +623,6 @@ exports[`should work for all qualifiers 5`] = `
Object {
"pathname": "/project/activity",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -948,7 +921,6 @@ exports[`should work with extensions 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -967,7 +939,6 @@ exports[`should work with extensions 1`] = `
Object {
"pathname": "/project/issues",
"query": Object {
- "branch": undefined,
"id": "foo",
"resolved": "false",
},
@@ -986,7 +957,6 @@ exports[`should work with extensions 1`] = `
Object {
"pathname": "/component_measures",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -1004,7 +974,6 @@ exports[`should work with extensions 1`] = `
Object {
"pathname": "/code",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -1022,7 +991,6 @@ exports[`should work with extensions 1`] = `
Object {
"pathname": "/project/activity",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -1060,7 +1028,6 @@ exports[`should work with extensions 1`] = `
Object {
"pathname": "/project/settings",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -1200,7 +1167,6 @@ exports[`should work with multiple extensions 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -1219,7 +1185,6 @@ exports[`should work with multiple extensions 1`] = `
Object {
"pathname": "/project/issues",
"query": Object {
- "branch": undefined,
"id": "foo",
"resolved": "false",
},
@@ -1238,7 +1203,6 @@ exports[`should work with multiple extensions 1`] = `
Object {
"pathname": "/component_measures",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -1256,7 +1220,6 @@ exports[`should work with multiple extensions 1`] = `
Object {
"pathname": "/code",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -1274,7 +1237,6 @@ exports[`should work with multiple extensions 1`] = `
Object {
"pathname": "/project/activity",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -1312,7 +1274,6 @@ exports[`should work with multiple extensions 1`] = `
Object {
"pathname": "/project/settings",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMeta-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMeta-test.tsx.snap
index 437ab7a3477..46e1c4becd7 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMeta-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMeta-test.tsx.snap
@@ -27,6 +27,51 @@ exports[`renders meta for long-living branch 1`] = `
</div>
`;
+exports[`renders meta for pull request 1`] = `
+<div
+ className="navbar-context-meta"
+>
+ <div
+ className="spacer-left"
+ >
+ <DateTimeFormatter
+ date="2017-01-02T00:00:00.000Z"
+ />
+ </div>
+ <div
+ className="navbar-context-meta-secondary"
+ >
+ <a
+ className="big-spacer-right"
+ href="https://example.com/pull/1234"
+ rel="nofollow"
+ target="_blank"
+ >
+ branches.see_the_pr
+ <i
+ className="icon-detach little-spacer-left"
+ />
+ </a>
+ <BranchStatus
+ branchLike={
+ Object {
+ "base": "master",
+ "branch": "feature",
+ "key": "1234",
+ "status": Object {
+ "bugs": 0,
+ "codeSmells": 2,
+ "vulnerabilities": 3,
+ },
+ "title": "Feature PR",
+ "url": "https://example.com/pull/1234",
+ }
+ }
+ />
+ </div>
+</div>
+`;
+
exports[`renders status of short-living branch 1`] = `
<div
className="navbar-context-meta"
@@ -42,7 +87,7 @@ exports[`renders status of short-living branch 1`] = `
className="navbar-context-meta-secondary"
>
<BranchStatus
- branch={
+ branchLike={
Object {
"isMain": false,
"mergeBranch": "master",
diff --git a/server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.js.snap b/server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.js.snap
index 0a9d6781bf0..4ad1c1e2e2d 100644
--- a/server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.js.snap
+++ b/server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.js.snap
@@ -21,7 +21,6 @@ exports[`renders favorite 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -70,7 +69,6 @@ exports[`renders match 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -118,7 +116,6 @@ exports[`renders organizations 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -171,7 +168,6 @@ exports[`renders organizations 2`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -219,7 +215,6 @@ exports[`renders projects 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "qwe",
},
}
@@ -272,7 +267,6 @@ exports[`renders recently browsed 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -320,7 +314,6 @@ exports[`renders selected 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -366,7 +359,6 @@ exports[`renders selected 2`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts
index 92136630ff4..106d76e679f 100644
--- a/server/sonar-web/src/main/js/app/types.ts
+++ b/server/sonar-web/src/main/js/app/types.ts
@@ -36,7 +36,15 @@ export interface AppState {
qualifiers: string[];
}
-export type Branch = MainBranch | LongLivingBranch | ShortLivingBranch;
+export interface Branch {
+ analysisDate?: string;
+ isMain: boolean;
+ name: string;
+}
+
+export type BranchLike = Branch | PullRequest;
+
+export type BranchParameters = { branch?: string } | { pullRequest?: string };
export enum BranchType {
LONG = 'LONG',
@@ -273,23 +281,14 @@ export interface LoggedInUser extends CurrentUser {
name: string;
}
-export interface LongLivingBranch {
- analysisDate?: string;
+export interface LongLivingBranch extends Branch {
isMain: false;
- name: string;
- status?: {
- qualityGateStatus: string;
- };
+ status?: { qualityGateStatus: string };
type: BranchType.LONG;
}
-export interface MainBranch {
- analysisDate?: string;
+export interface MainBranch extends Branch {
isMain: true;
- name: string;
- status?: {
- qualityGateStatus: string;
- };
}
export interface Metric {
@@ -352,6 +351,21 @@ export interface ProjectLink {
url: string;
}
+export interface PullRequest {
+ analysisDate?: string;
+ base: string;
+ branch: string;
+ key: string;
+ isOrphan?: true;
+ status?: {
+ bugs: number;
+ codeSmells: number;
+ vulnerabilities: number;
+ };
+ title: string;
+ url?: string;
+}
+
export interface Rule {
isTemplate?: boolean;
key: string;
@@ -419,12 +433,10 @@ export enum RuleScope {
All = 'ALL'
}
-export interface ShortLivingBranch {
- analysisDate?: string;
+export interface ShortLivingBranch extends Branch {
isMain: false;
isOrphan?: true;
mergeBranch: string;
- name: string;
status?: {
bugs: number;
codeSmells: number;
diff --git a/server/sonar-web/src/main/js/apps/about/actions.js b/server/sonar-web/src/main/js/apps/about/actions.js
index 2c63a8553b9..a60d730c30a 100644
--- a/server/sonar-web/src/main/js/apps/about/actions.js
+++ b/server/sonar-web/src/main/js/apps/about/actions.js
@@ -23,7 +23,7 @@ import { receiveValues } from '../settings/store/values/actions';
export const fetchAboutPageSettings = () => dispatch => {
const keys = ['sonar.lf.aboutText'];
- return getValues(keys.join()).then(values => {
+ return getValues({ keys: keys.join() }).then(values => {
dispatch(receiveValues(values));
});
};
diff --git a/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/ProjectNotifications-test.js.snap b/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/ProjectNotifications-test.js.snap
index 2fb0f7c15d1..33c554244f1 100644
--- a/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/ProjectNotifications-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/ProjectNotifications-test.js.snap
@@ -23,7 +23,6 @@ exports[`should match snapshot 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
diff --git a/server/sonar-web/src/main/js/apps/account/organizations/actions.ts b/server/sonar-web/src/main/js/apps/account/organizations/actions.ts
index 89dbf7c9705..4361e0d0849 100644
--- a/server/sonar-web/src/main/js/apps/account/organizations/actions.ts
+++ b/server/sonar-web/src/main/js/apps/account/organizations/actions.ts
@@ -30,7 +30,7 @@ export const fetchMyOrganizations = () => (dispatch: Dispatch<any>) => {
};
export const fetchIfAnyoneCanCreateOrganizations = () => (dispatch: Dispatch<any>) => {
- return getValues('sonar.organizations.anyoneCanCreate').then(values => {
+ return getValues({ keys: 'sonar.organizations.anyoneCanCreate' }).then(values => {
dispatch(receiveValues(values, undefined));
});
};
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 61fba1730da..a81919215b1 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
@@ -23,9 +23,16 @@ import TaskType from './TaskType';
import { Task } from '../types';
import QualifierIcon from '../../../components/shared/QualifierIcon';
import Organization from '../../../components/shared/Organization';
-import { getProjectUrl } from '../../../helpers/urls';
+import {
+ getProjectUrl,
+ getShortLivingBranchUrl,
+ getLongLivingBranchUrl,
+ getPullRequestUrl
+} from '../../../helpers/urls';
import ShortLivingBranchIcon from '../../../components/icons-components/ShortLivingBranchIcon';
import LongLivingBranchIcon from '../../../components/icons-components/LongLivingBranchIcon';
+import PullRequestIcon from '../../../components/icons-components/PullRequestIcon';
+import Tooltip from '../../../components/controls/Tooltip';
interface Props {
task: Task;
@@ -45,8 +52,10 @@ export default function TaskComponent({ task }: Props) {
<td>
{task.branchType === 'SHORT' && <ShortLivingBranchIcon className="little-spacer-right" />}
{task.branchType === 'LONG' && <LongLivingBranchIcon className="little-spacer-right" />}
+ {task.pullRequest !== undefined && <PullRequestIcon className="little-spacer-right" />}
{!task.branchType &&
+ !task.pullRequest &&
task.componentQualifier && (
<span className="little-spacer-right">
<QualifierIcon qualifier={task.componentQualifier} />
@@ -56,7 +65,7 @@ export default function TaskComponent({ task }: Props) {
{task.organization && <Organization organizationKey={task.organization} />}
{task.componentName && (
- <Link className="spacer-right" to={getProjectUrl(task.componentKey, task.branch)}>
+ <Link className="spacer-right" to={getTaskComponentUrl(task.componentKey, task)}>
{task.componentName}
{task.branch && (
@@ -65,6 +74,15 @@ export default function TaskComponent({ task }: Props) {
{task.branch}
</span>
)}
+
+ {task.pullRequest && (
+ <Tooltip overlay={task.pullRequestTitle}>
+ <span className="text-limited text-text-top">
+ <span style={{ marginLeft: 5, marginRight: 5 }}>/</span>
+ {task.pullRequest}
+ </span>
+ </Tooltip>
+ )}
</Link>
)}
@@ -72,3 +90,15 @@ export default function TaskComponent({ task }: Props) {
</td>
);
}
+
+function getTaskComponentUrl(componentKey: string, task: Task) {
+ if (task.branch && task.branchType === 'SHORT') {
+ return getShortLivingBranchUrl(componentKey, task.branchType);
+ } else if (task.branchType && task.branchType === 'LONG') {
+ return getLongLivingBranchUrl(componentKey, task.branchType);
+ } else if (task.pullRequest) {
+ return getPullRequestUrl(componentKey, task.pullRequest);
+ } else {
+ return getProjectUrl(componentKey);
+ }
+}
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 93a63cdafd4..f71a484187b 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
@@ -20,7 +20,6 @@ exports[`renders 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -67,7 +66,6 @@ exports[`renders 3`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": "feature",
"id": "foo",
},
}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/types.ts b/server/sonar-web/src/main/js/apps/background-tasks/types.ts
index b52783299ce..239c4581d61 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/types.ts
+++ b/server/sonar-web/src/main/js/apps/background-tasks/types.ts
@@ -29,6 +29,8 @@ export interface Task {
hasScannerContext?: boolean;
id: string;
organization?: string;
+ pullRequest?: string;
+ pullRequestTitle?: string;
startedAt?: string;
status: string;
submittedAt: string;
diff --git a/server/sonar-web/src/main/js/apps/code/components/App.tsx b/server/sonar-web/src/main/js/apps/code/components/App.tsx
index b6941e56388..937a66ee751 100644
--- a/server/sonar-web/src/main/js/apps/code/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/code/components/App.tsx
@@ -26,16 +26,16 @@ import Search from './Search';
import { addComponent, addComponentBreadcrumbs, clearBucket } from '../bucket';
import { Component as CodeComponent } from '../types';
import { retrieveComponentChildren, retrieveComponent, loadMoreChildren } from '../utils';
+import { Component, BranchLike } from '../../../app/types';
import ListFooter from '../../../components/controls/ListFooter';
import SourceViewer from '../../../components/SourceViewer/SourceViewer';
-import { parseError } from '../../../helpers/request';
-import { getBranchName } from '../../../helpers/branches';
+import { isSameBranchLike } from '../../../helpers/branches';
import { translate } from '../../../helpers/l10n';
-import { Component, Branch } from '../../../app/types';
+import { parseError } from '../../../helpers/request';
import '../code.css';
interface Props {
- branch?: Branch;
+ branchLike?: BranchLike;
component: Component;
location: { query: { [x: string]: string } };
}
@@ -67,7 +67,10 @@ export default class App extends React.PureComponent<Props, State> {
}
componentDidUpdate(prevProps: Props) {
- if (prevProps.component !== this.props.component || prevProps.branch !== this.props.branch) {
+ if (
+ prevProps.component !== this.props.component ||
+ !isSameBranchLike(prevProps.branchLike, this.props.branchLike)
+ ) {
this.handleComponentChange();
} else if (prevProps.location !== this.props.location) {
this.handleUpdate();
@@ -80,14 +83,14 @@ export default class App extends React.PureComponent<Props, State> {
}
handleComponentChange() {
- const { branch, component } = this.props;
+ const { branchLike, component } = this.props;
// we already know component's breadcrumbs,
addComponentBreadcrumbs(component.key, component.breadcrumbs);
this.setState({ loading: true });
const isPortfolio = ['VW', 'SVW'].includes(component.qualifier);
- retrieveComponentChildren(component.key, isPortfolio, getBranchName(branch))
+ retrieveComponentChildren(component.key, isPortfolio, branchLike)
.then(() => {
addComponent(component);
if (this.mounted) {
@@ -106,7 +109,7 @@ export default class App extends React.PureComponent<Props, State> {
this.setState({ loading: true });
const isPortfolio = ['VW', 'SVW'].includes(this.props.component.qualifier);
- retrieveComponent(componentKey, isPortfolio, getBranchName(this.props.branch))
+ retrieveComponent(componentKey, isPortfolio, this.props.branchLike)
.then(r => {
if (this.mounted) {
if (['FIL', 'UTS'].includes(r.component.qualifier)) {
@@ -152,7 +155,7 @@ export default class App extends React.PureComponent<Props, State> {
return;
}
const isPortfolio = ['VW', 'SVW'].includes(this.props.component.qualifier);
- loadMoreChildren(baseComponent.key, page + 1, isPortfolio, getBranchName(this.props.branch))
+ loadMoreChildren(baseComponent.key, page + 1, isPortfolio, this.props.branchLike)
.then(r => {
if (this.mounted) {
this.setState({
@@ -177,7 +180,7 @@ export default class App extends React.PureComponent<Props, State> {
};
render() {
- const { branch, component, location } = this.props;
+ const { branchLike, component, location } = this.props;
const {
loading,
error,
@@ -187,8 +190,6 @@ export default class App extends React.PureComponent<Props, State> {
total,
sourceViewer
} = this.state;
- const branchName = getBranchName(branch);
-
const shouldShowBreadcrumbs = breadcrumbs.length > 1;
const componentsClassName = classNames('boxed-group', 'boxed-group-inner', 'spacer-top', {
@@ -202,7 +203,7 @@ export default class App extends React.PureComponent<Props, State> {
{error && <div className="alert alert-danger">{error}</div>}
<Search
- branch={branchName}
+ branchLike={branchLike}
component={component}
location={location}
onError={this.handleError}
@@ -210,7 +211,11 @@ export default class App extends React.PureComponent<Props, State> {
<div className="code-components">
{shouldShowBreadcrumbs && (
- <Breadcrumbs branch={branchName} breadcrumbs={breadcrumbs} rootComponent={component} />
+ <Breadcrumbs
+ branchLike={branchLike}
+ breadcrumbs={breadcrumbs}
+ rootComponent={component}
+ />
)}
{sourceViewer === undefined &&
@@ -218,7 +223,7 @@ export default class App extends React.PureComponent<Props, State> {
<div className={componentsClassName}>
<Components
baseComponent={baseComponent}
- branch={branchName}
+ branchLike={branchLike}
components={components}
rootComponent={component}
/>
@@ -232,7 +237,7 @@ export default class App extends React.PureComponent<Props, State> {
{sourceViewer !== undefined && (
<div className="spacer-top">
- <SourceViewer branch={branchName} component={sourceViewer.key} />
+ <SourceViewer branchLike={branchLike} component={sourceViewer.key} />
</div>
)}
</div>
diff --git a/server/sonar-web/src/main/js/apps/code/components/Breadcrumbs.tsx b/server/sonar-web/src/main/js/apps/code/components/Breadcrumbs.tsx
index 783b7126fa5..7ea212cd7ee 100644
--- a/server/sonar-web/src/main/js/apps/code/components/Breadcrumbs.tsx
+++ b/server/sonar-web/src/main/js/apps/code/components/Breadcrumbs.tsx
@@ -20,20 +20,21 @@
import * as React from 'react';
import ComponentName from './ComponentName';
import { Component } from '../types';
+import { BranchLike } from '../../../app/types';
interface Props {
- branch?: string;
+ branchLike?: BranchLike;
breadcrumbs: Component[];
rootComponent: Component;
}
-export default function Breadcrumbs({ branch, breadcrumbs, rootComponent }: Props) {
+export default function Breadcrumbs({ branchLike, breadcrumbs, rootComponent }: Props) {
return (
<ul className="code-breadcrumbs">
{breadcrumbs.map((component, index) => (
<li key={component.key}>
<ComponentName
- branch={branch}
+ branchLike={branchLike}
canBrowse={index < breadcrumbs.length - 1}
component={component}
rootComponent={rootComponent}
diff --git a/server/sonar-web/src/main/js/apps/code/components/Component.tsx b/server/sonar-web/src/main/js/apps/code/components/Component.tsx
index 841d96baee7..df873439506 100644
--- a/server/sonar-web/src/main/js/apps/code/components/Component.tsx
+++ b/server/sonar-web/src/main/js/apps/code/components/Component.tsx
@@ -24,12 +24,13 @@ import ComponentMeasure from './ComponentMeasure';
import ComponentLink from './ComponentLink';
import ComponentPin from './ComponentPin';
import { Component as IComponent } from '../types';
+import { BranchLike } from '../../../app/types';
const TOP_OFFSET = 200;
const BOTTOM_OFFSET = 10;
interface Props {
- branch?: string;
+ branchLike?: BranchLike;
canBrowse?: boolean;
component: IComponent;
previous?: IComponent;
@@ -73,7 +74,7 @@ export default class Component extends React.PureComponent<Props> {
render() {
const {
- branch,
+ branchLike,
component,
rootComponent,
selected = false,
@@ -89,10 +90,10 @@ export default class Component extends React.PureComponent<Props> {
switch (component.qualifier) {
case 'FIL':
case 'UTS':
- componentAction = <ComponentPin branch={branch} component={component} />;
+ componentAction = <ComponentPin branchLike={branchLike} component={component} />;
break;
default:
- componentAction = <ComponentLink branch={branch} component={component} />;
+ componentAction = <ComponentLink branchLike={branchLike} component={component} />;
}
}
@@ -121,7 +122,7 @@ export default class Component extends React.PureComponent<Props> {
</td>
<td className="code-name-cell">
<ComponentName
- branch={branch}
+ branchLike={branchLike}
component={component}
rootComponent={rootComponent}
previous={previous}
diff --git a/server/sonar-web/src/main/js/apps/code/components/ComponentLink.tsx b/server/sonar-web/src/main/js/apps/code/components/ComponentLink.tsx
index 599dabdc412..60927947472 100644
--- a/server/sonar-web/src/main/js/apps/code/components/ComponentLink.tsx
+++ b/server/sonar-web/src/main/js/apps/code/components/ComponentLink.tsx
@@ -20,21 +20,22 @@
import * as React from 'react';
import { Link } from 'react-router';
import { Component } from '../types';
+import { BranchLike } from '../../../app/types';
import LinkIcon from '../../../components/icons-components/LinkIcon';
import { translate } from '../../../helpers/l10n';
-import { getProjectUrl } from '../../../helpers/urls';
+import { getBranchLikeUrl } from '../../../helpers/urls';
interface Props {
- branch?: string;
+ branchLike?: BranchLike;
component: Component;
}
-export default function ComponentLink({ component, branch }: Props) {
+export default function ComponentLink({ component, branchLike }: Props) {
return (
<Link
className="link-no-underline"
title={translate('code.open_component_page')}
- to={getProjectUrl(component.refKey || component.key, branch)}>
+ to={getBranchLikeUrl(component.refKey || component.key, branchLike)}>
<LinkIcon />
</Link>
);
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 7abb5e51a2d..0bd1290d662 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
@@ -20,9 +20,11 @@
import * as React from 'react';
import { Link } from 'react-router';
import Truncated from './Truncated';
+import { Component } from '../types';
import * as theme from '../../../app/theme';
+import { BranchLike } from '../../../app/types';
import QualifierIcon from '../../../components/shared/QualifierIcon';
-import { Component } from '../types';
+import { getBranchLikeQuery } from '../../../helpers/branches';
function getTooltip(component: Component) {
const isFile = component.qualifier === 'FIL' || component.qualifier === 'UTS';
@@ -49,7 +51,7 @@ function mostCommitPrefix(strings: string[]) {
}
interface Props {
- branch?: string;
+ branchLike?: BranchLike;
canBrowse?: boolean;
component: Component;
previous?: Component;
@@ -57,7 +59,7 @@ interface Props {
}
export default function ComponentName(props: Props) {
- const { branch, component, rootComponent, previous, canBrowse = false } = props;
+ const { branchLike, component, rootComponent, previous, canBrowse = false } = props;
const areBothDirs = component.qualifier === 'DIR' && previous && previous.qualifier === 'DIR';
const prefix =
areBothDirs && previous !== undefined
@@ -83,7 +85,7 @@ export default function ComponentName(props: Props) {
</Link>
);
} else if (canBrowse) {
- const query = { id: rootComponent.key, branch };
+ const query = { id: rootComponent.key, ...getBranchLikeQuery(branchLike) };
if (component.key !== rootComponent.key) {
Object.assign(query, { selected: component.key });
}
diff --git a/server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx b/server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx
index eb61f75da93..867f4bd476f 100644
--- a/server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx
+++ b/server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx
@@ -18,20 +18,21 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import Workspace from '../../../components/workspace/main';
+import { Component } from '../types';
+import { BranchLike } from '../../../app/types';
import PinIcon from '../../../components/shared/pin-icon';
+import Workspace from '../../../components/workspace/main';
import { translate } from '../../../helpers/l10n';
-import { Component } from '../types';
interface Props {
- branch?: string;
+ branchLike?: BranchLike;
component: Component;
}
-export default function ComponentPin({ branch, component }: Props) {
+export default function ComponentPin({ branchLike, component }: Props) {
const handleClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
event.preventDefault();
- Workspace.openComponent({ branch, key: component.key });
+ Workspace.openComponent({ branchLike, key: component.key });
};
return (
diff --git a/server/sonar-web/src/main/js/apps/code/components/Components.tsx b/server/sonar-web/src/main/js/apps/code/components/Components.tsx
index 25e99051994..98d3b569e71 100644
--- a/server/sonar-web/src/main/js/apps/code/components/Components.tsx
+++ b/server/sonar-web/src/main/js/apps/code/components/Components.tsx
@@ -22,24 +22,25 @@ import Component from './Component';
import ComponentsEmpty from './ComponentsEmpty';
import ComponentsHeader from './ComponentsHeader';
import { Component as IComponent } from '../types';
+import { BranchLike } from '../../../app/types';
interface Props {
baseComponent?: IComponent;
- branch?: string;
+ branchLike?: BranchLike;
components: IComponent[];
rootComponent: IComponent;
selected?: IComponent;
}
export default function Components(props: Props) {
- const { baseComponent, branch, components, rootComponent, selected } = props;
+ const { baseComponent, branchLike, components, rootComponent, selected } = props;
return (
<table className="data zebra">
<ComponentsHeader baseComponent={baseComponent} rootComponent={rootComponent} />
{baseComponent && (
<tbody>
<Component
- branch={branch}
+ branchLike={branchLike}
component={baseComponent}
key={baseComponent.key}
rootComponent={rootComponent}
@@ -53,7 +54,7 @@ export default function Components(props: Props) {
{components.length ? (
components.map((component, index, list) => (
<Component
- branch={branch}
+ branchLike={branchLike}
canBrowse={true}
component={component}
key={component.key}
diff --git a/server/sonar-web/src/main/js/apps/code/components/Search.tsx b/server/sonar-web/src/main/js/apps/code/components/Search.tsx
index 4a5536ed3de..abeeacc511a 100644
--- a/server/sonar-web/src/main/js/apps/code/components/Search.tsx
+++ b/server/sonar-web/src/main/js/apps/code/components/Search.tsx
@@ -21,15 +21,17 @@ import * as React from 'react';
import * as PropTypes from 'prop-types';
import * as classNames from 'classnames';
import Components from './Components';
-import { getTree } from '../../../api/components';
-import { parseError } from '../../../helpers/request';
-import { getProjectUrl } from '../../../helpers/urls';
import { Component } from '../types';
+import { getTree } from '../../../api/components';
+import { BranchLike } from '../../../app/types';
import SearchBox from '../../../components/controls/SearchBox';
+import { getBranchLikeQuery } from '../../../helpers/branches';
import { translate } from '../../../helpers/l10n';
+import { parseError } from '../../../helpers/request';
+import { getProjectUrl } from '../../../helpers/urls';
interface Props {
- branch?: string;
+ branchLike?: BranchLike;
component: Component;
location: {};
onError: (error: string) => void;
@@ -89,7 +91,7 @@ export default class Search extends React.PureComponent<Props, State> {
}
handleSelectCurrent() {
- const { branch, component } = this.props;
+ const { branchLike, component } = this.props;
const { results, selectedIndex } = this.state;
if (results != null && selectedIndex != null) {
const selected = results[selectedIndex];
@@ -99,7 +101,7 @@ export default class Search extends React.PureComponent<Props, State> {
} else {
this.context.router.push({
pathname: '/code',
- query: { branch, id: component.key, selected: selected.key }
+ query: { id: component.key, selected: selected.key, ...getBranchLikeQuery(branchLike) }
});
}
}
@@ -125,13 +127,18 @@ export default class Search extends React.PureComponent<Props, State> {
handleSearch = (query: string) => {
if (this.mounted) {
- const { branch, component, onError } = this.props;
+ const { branchLike, component, onError } = this.props;
this.setState({ loading: true });
const isPortfolio = ['VW', 'SVW', 'APP'].includes(component.qualifier);
const qualifiers = isPortfolio ? 'SVW,TRK' : 'BRC,UTS,FIL';
- getTree(component.key, { branch, q: query, s: 'qualifier,name', qualifiers })
+ getTree(component.key, {
+ q: query,
+ s: 'qualifier,name',
+ qualifiers,
+ ...getBranchLikeQuery(branchLike)
+ })
.then(r => {
if (this.mounted) {
this.setState({
@@ -184,7 +191,7 @@ export default class Search extends React.PureComponent<Props, State> {
{results != null && (
<div className="boxed-group boxed-group-inner spacer-top">
<Components
- branch={this.props.branch}
+ branchLike={this.props.branchLike}
components={results}
rootComponent={component}
selected={selected}
diff --git a/server/sonar-web/src/main/js/apps/code/utils.ts b/server/sonar-web/src/main/js/apps/code/utils.ts
index 80fdbadea8c..75ef812c977 100644
--- a/server/sonar-web/src/main/js/apps/code/utils.ts
+++ b/server/sonar-web/src/main/js/apps/code/utils.ts
@@ -28,6 +28,8 @@ import {
} from './bucket';
import { Breadcrumb, Component } from './types';
import { getChildren, getComponent, getBreadcrumbs } from '../../api/components';
+import { BranchLike } from '../../app/types';
+import { getBranchLikeQuery } from '../../helpers/branches';
const METRICS = [
'ncloc',
@@ -54,11 +56,15 @@ function requestChildren(
componentKey: string,
metrics: string[],
page: number,
- branch?: string
+ branchLike?: BranchLike
): Promise<Component[]> {
- return getChildren(componentKey, metrics, { branch, p: page, ps: PAGE_SIZE }).then(r => {
+ return getChildren(componentKey, metrics, {
+ p: page,
+ ps: PAGE_SIZE,
+ ...getBranchLikeQuery(branchLike)
+ }).then(r => {
if (r.paging.total > r.paging.pageSize * r.paging.pageIndex) {
- return requestChildren(componentKey, metrics, page + 1, branch).then(moreComponents => {
+ return requestChildren(componentKey, metrics, page + 1, branchLike).then(moreComponents => {
return [...r.components, ...moreComponents];
});
}
@@ -69,9 +75,9 @@ function requestChildren(
function requestAllChildren(
componentKey: string,
metrics: string[],
- branch?: string
+ branchLike?: BranchLike
): Promise<Component[]> {
- return requestChildren(componentKey, metrics, 1, branch);
+ return requestChildren(componentKey, metrics, 1, branchLike);
}
interface Children {
@@ -84,13 +90,13 @@ interface ExpandRootDirFunc {
(children: Children): Promise<Children>;
}
-function expandRootDir(metrics: string[], branch?: string): ExpandRootDirFunc {
+function expandRootDir(metrics: string[], branchLike?: BranchLike): ExpandRootDirFunc {
return function({ components, total, ...other }) {
const rootDir = components.find(
(component: Component) => component.qualifier === 'DIR' && component.name === '/'
);
if (rootDir) {
- return requestAllChildren(rootDir.key, metrics, branch).then(rootDirComponents => {
+ return requestAllChildren(rootDir.key, metrics, branchLike).then(rootDirComponents => {
const nextComponents = without([...rootDirComponents, ...components], rootDir);
const nextTotal = total + rootDirComponents.length - /* root dir */ 1;
return { components: nextComponents, total: nextTotal, ...other };
@@ -133,7 +139,11 @@ function getMetrics(isPortfolio: boolean) {
return isPortfolio ? PORTFOLIO_METRICS : METRICS;
}
-function retrieveComponentBase(componentKey: string, isPortfolio: boolean, branch?: string) {
+function retrieveComponentBase(
+ componentKey: string,
+ isPortfolio: boolean,
+ branchLike?: BranchLike
+) {
const existing = getComponentFromBucket(componentKey);
if (existing) {
return Promise.resolve(existing);
@@ -141,7 +151,11 @@ function retrieveComponentBase(componentKey: string, isPortfolio: boolean, branc
const metrics = getMetrics(isPortfolio);
- return getComponent(componentKey, metrics, branch).then(component => {
+ return getComponent({
+ componentKey,
+ metricKeys: metrics.join(),
+ ...getBranchLikeQuery(branchLike)
+ }).then(component => {
addComponent(component);
return component;
});
@@ -150,7 +164,7 @@ function retrieveComponentBase(componentKey: string, isPortfolio: boolean, branc
export function retrieveComponentChildren(
componentKey: string,
isPortfolio: boolean,
- branch?: string
+ branchLike?: BranchLike
): Promise<{ components: Component[]; page: number; total: number }> {
const existing = getComponentChildren(componentKey);
if (existing) {
@@ -163,9 +177,13 @@ export function retrieveComponentChildren(
const metrics = getMetrics(isPortfolio);
- return getChildren(componentKey, metrics, { branch, ps: PAGE_SIZE, s: 'qualifier,name' })
+ return getChildren(componentKey, metrics, {
+ ps: PAGE_SIZE,
+ s: 'qualifier,name',
+ ...getBranchLikeQuery(branchLike)
+ })
.then(prepareChildren)
- .then(expandRootDir(metrics, branch))
+ .then(expandRootDir(metrics, branchLike))
.then(r => {
addComponentChildren(componentKey, r.components, r.total, r.page);
storeChildrenBase(r.components);
@@ -175,18 +193,18 @@ export function retrieveComponentChildren(
}
function retrieveComponentBreadcrumbs(
- componentKey: string,
- branch?: string
+ component: string,
+ branchLike?: BranchLike
): Promise<Breadcrumb[]> {
- const existing = getComponentBreadcrumbs(componentKey);
+ const existing = getComponentBreadcrumbs(component);
if (existing) {
return Promise.resolve(existing);
}
- return getBreadcrumbs(componentKey, branch)
+ return getBreadcrumbs({ component, ...getBranchLikeQuery(branchLike) })
.then(skipRootDir)
.then(breadcrumbs => {
- addComponentBreadcrumbs(componentKey, breadcrumbs);
+ addComponentBreadcrumbs(component, breadcrumbs);
return breadcrumbs;
});
}
@@ -194,7 +212,7 @@ function retrieveComponentBreadcrumbs(
export function retrieveComponent(
componentKey: string,
isPortfolio: boolean,
- branch?: string
+ branchLike?: BranchLike
): Promise<{
breadcrumbs: Component[];
component: Component;
@@ -203,9 +221,9 @@ export function retrieveComponent(
total: number;
}> {
return Promise.all([
- retrieveComponentBase(componentKey, isPortfolio, branch),
- retrieveComponentChildren(componentKey, isPortfolio, branch),
- retrieveComponentBreadcrumbs(componentKey, branch)
+ retrieveComponentBase(componentKey, isPortfolio, branchLike),
+ retrieveComponentChildren(componentKey, isPortfolio, branchLike),
+ retrieveComponentBreadcrumbs(componentKey, branchLike)
]).then(r => {
return {
component: r[0],
@@ -221,13 +239,17 @@ export function loadMoreChildren(
componentKey: string,
page: number,
isPortfolio: boolean,
- branch?: string
+ branchLike?: BranchLike
): Promise<Children> {
const metrics = getMetrics(isPortfolio);
- return getChildren(componentKey, metrics, { branch, ps: PAGE_SIZE, p: page })
+ return getChildren(componentKey, metrics, {
+ ps: PAGE_SIZE,
+ p: page,
+ ...getBranchLikeQuery(branchLike)
+ })
.then(prepareChildren)
- .then(expandRootDir(metrics, branch))
+ .then(expandRootDir(metrics, branchLike))
.then(r => {
addComponentChildren(componentKey, r.components, r.total, r.page);
storeChildrenBase(r.components);
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/App.js b/server/sonar-web/src/main/js/apps/component-measures/components/App.js
index 4198c6689fd..d357ff4c15a 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/App.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/App.js
@@ -26,7 +26,7 @@ import MeasureOverviewContainer from './MeasureOverviewContainer';
import Sidebar from '../sidebar/Sidebar';
import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
import { hasBubbleChart, parseQuery, serializeQuery } from '../utils';
-import { getBranchName } from '../../../helpers/branches';
+import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches';
import { translate } from '../../../helpers/l10n';
import { getDisplayMetrics } from '../../../helpers/measures';
/*:: import type { Component, Query, Period } from '../types'; */
@@ -36,14 +36,14 @@ import { getDisplayMetrics } from '../../../helpers/measures';
import '../style.css';
/*:: type Props = {|
- branch?: {},
+ branchLike?: { id?: string; name: string },
component: Component,
currentUser: { isLoggedIn: boolean },
location: { pathname: string, query: RawQuery },
fetchMeasures: (
component: string,
metricsKey: Array<string>,
- branch?: string
+ branchLike?: { id?: string; name: string }
) => Promise<{ component: Component, measures: Array<MeasureEnhanced>, leakPeriod: ?Period }>,
fetchMetrics: () => void,
metrics: { [string]: Metric },
@@ -88,7 +88,7 @@ export default class App extends React.PureComponent {
componentWillReceiveProps(nextProps /*: Props */) {
if (
- nextProps.branch !== this.props.branch ||
+ !isSameBranchLike(nextProps.branchLike, this.props.branchLike) ||
nextProps.component.key !== this.props.component.key ||
nextProps.metrics !== this.props.metrics
) {
@@ -107,10 +107,10 @@ export default class App extends React.PureComponent {
}
}
- fetchMeasures = ({ branch, component, fetchMeasures, metrics } /*: Props */) => {
+ fetchMeasures = ({ branchLike, component, fetchMeasures, metrics } /*: Props */) => {
this.setState({ loading: true });
const filteredKeys = getDisplayMetrics(Object.values(metrics)).map(metric => metric.key);
- fetchMeasures(component.key, filteredKeys, getBranchName(branch)).then(
+ fetchMeasures(component.key, filteredKeys, branchLike).then(
({ measures, leakPeriod }) => {
if (this.mounted) {
this.setState({
@@ -137,7 +137,7 @@ export default class App extends React.PureComponent {
pathname: this.props.location.pathname,
query: {
...query,
- branch: getBranchName(this.props.branch),
+ ...getBranchLikeQuery(this.props.branchLike),
id: this.props.component.key
}
});
@@ -148,7 +148,7 @@ export default class App extends React.PureComponent {
if (isLoading) {
return <i className="spinner spinner-margin" />;
}
- const { branch, component, fetchMeasures, metrics } = this.props;
+ const { branchLike, component, fetchMeasures, metrics } = this.props;
const { leakPeriod } = this.state;
const query = parseQuery(this.props.location.query);
const metric = metrics[query.metric];
@@ -174,7 +174,7 @@ export default class App extends React.PureComponent {
{metric != null && (
<MeasureContentContainer
- branch={getBranchName(branch)}
+ branchLike={branchLike}
className="layout-page-main"
currentUser={this.props.currentUser}
rootComponent={component}
@@ -191,7 +191,7 @@ export default class App extends React.PureComponent {
{metric == null &&
hasBubbleChart(query.metric) && (
<MeasureOverviewContainer
- branch={getBranchName(branch)}
+ branchLike={branchLike}
className="layout-page-main"
rootComponent={component}
currentUser={this.props.currentUser}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.js b/server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.js
index a9122702894..54d63a4dddd 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.js
@@ -27,6 +27,7 @@ import { fetchMetrics } from '../../../store/rootActions';
import { getMeasuresAndMeta } from '../../../api/measures';
import { getLeakPeriod } from '../../../helpers/periods';
import { enhanceMeasure } from '../../../components/measure/utils';
+import { getBranchLikeQuery } from '../../../helpers/branches';
/*:: import type { Component, Period } from '../types'; */
/*:: import type { Measure, MeasureEnhanced } from '../../../components/measure/types'; */
@@ -50,7 +51,7 @@ function banQualityGate(component /*: Component */) /*: Array<Measure> */ {
const fetchMeasures = (
component /*: string */,
metricsKey /*: Array<string> */,
- branch /*: string | void */
+ branchLike /*: { id?: string; name: string } | void */
) => (dispatch, getState) => {
if (metricsKey.length <= 0) {
return Promise.resolve({ component: {}, measures: [], leakPeriod: null });
@@ -58,7 +59,7 @@ const fetchMeasures = (
return getMeasuresAndMeta(component, metricsKey, {
additionalFields: 'periods',
- branch
+ ...getBranchLikeQuery(branchLike)
}).then(r => {
const measures = banQualityGate(r.component).map(measure =>
enhanceMeasure(measure, getMetrics(getState()))
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.js b/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.js
index fedd4d52dfe..7709a31d257 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.js
@@ -22,11 +22,12 @@ import React from 'react';
import key from 'keymaster';
import Breadcrumb from './Breadcrumb';
import { getBreadcrumbs } from '../../../api/components';
+import { getBranchLikeQuery } from '../../../helpers/branches';
/*:: import type { Component } from '../types'; */
/*:: type Props = {|
backToFirst: boolean,
- branch?: string,
+ branchLike?: { id?: string, name: string },
className?: string,
component: Component,
handleSelect: string => void,
@@ -76,7 +77,7 @@ export default class Breadcrumbs extends React.PureComponent {
key.unbind('left', 'measures-files');
}
- fetchBreadcrumbs = ({ branch, component, rootComponent } /*: Props */) => {
+ fetchBreadcrumbs = ({ branchLike, component, rootComponent } /*: Props */) => {
const isRoot = component.key === rootComponent.key;
if (isRoot) {
if (this.mounted) {
@@ -84,11 +85,13 @@ export default class Breadcrumbs extends React.PureComponent {
}
return;
}
- getBreadcrumbs(component.key, branch).then(breadcrumbs => {
- if (this.mounted) {
- this.setState({ breadcrumbs });
+ getBreadcrumbs({ component: component.key, ...getBranchLikeQuery(branchLike) }).then(
+ breadcrumbs => {
+ if (this.mounted) {
+ this.setState({ breadcrumbs });
+ }
}
- });
+ );
};
render() {
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js
index a9d9f81c35d..8feb83e1167 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js
@@ -34,6 +34,7 @@ import { complementary } from '../config/complementary';
import { enhanceComponent, isFileType, isViewType } from '../utils';
import { getProjectUrl } from '../../../helpers/urls';
import { isDiffMetric } from '../../../helpers/measures';
+import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches';
/*:: import type { Component, ComponentEnhanced, Paging, Period } from '../types'; */
/*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */
/*:: import type { Metric } from '../../../store/metrics/actions'; */
@@ -42,7 +43,7 @@ import { isDiffMetric } from '../../../helpers/measures';
// https://github.com/facebook/flow/issues/3147
// router: { push: ({ pathname: string, query?: RawQuery }) => void }
/*:: type Props = {|
- branch?: string,
+ branchLike?: { id?: string; name: string },
className?: string,
component: Component,
currentUser: { isLoggedIn: boolean },
@@ -87,7 +88,7 @@ export default class MeasureContent extends React.PureComponent {
componentWillReceiveProps(nextProps /*: Props */) {
if (
- nextProps.branch !== this.props.branch ||
+ !isSameBranchLike(nextProps.branchLike, this.props.branchLike) ||
nextProps.component !== this.props.component ||
nextProps.metric !== this.props.metric
) {
@@ -115,7 +116,7 @@ export default class MeasureContent extends React.PureComponent {
const strategy = view === 'list' ? 'leaves' : 'children';
const metricKeys = [metric.key];
const opts /*: Object */ = {
- branch: this.props.branch,
+ ...getBranchLikeQuery(this.props.branchLike),
metricSortFilter: 'withMeasuresOnly'
};
const isDiff = isDiffMetric(metric.key);
@@ -225,7 +226,7 @@ export default class MeasureContent extends React.PureComponent {
return (
<div className="measure-details-viewer">
<CodeView
- branch={this.props.branch}
+ branchLike={this.props.branchLike}
component={this.props.component}
components={this.state.components}
leakPeriod={this.props.leakPeriod}
@@ -244,7 +245,7 @@ export default class MeasureContent extends React.PureComponent {
const selectedIdx = this.getSelectedIndex();
return (
<FilesView
- branch={this.props.branch}
+ branchLike={this.props.branchLike}
components={this.state.components}
fetchMore={this.fetchMoreComponents}
handleOpen={this.onOpenComponent}
@@ -261,7 +262,7 @@ export default class MeasureContent extends React.PureComponent {
if (view === 'treemap') {
return (
<TreeMapView
- branch={this.props.branch}
+ branchLike={this.props.branchLike}
components={this.state.components}
handleSelect={this.onOpenComponent}
metric={metric}
@@ -274,7 +275,7 @@ export default class MeasureContent extends React.PureComponent {
}
render() {
- const { branch, component, currentUser, measure, metric, rootComponent, view } = this.props;
+ const { branchLike, component, currentUser, measure, metric, rootComponent, view } = this.props;
const isLoggedIn = currentUser && currentUser.isLoggedIn;
const isFile = isFileType(component);
const selectedIdx = this.getSelectedIndex();
@@ -288,7 +289,7 @@ export default class MeasureContent extends React.PureComponent {
<div className="layout-page-main-inner">
<Breadcrumbs
backToFirst={view === 'list'}
- branch={branch}
+ branchLike={branchLike}
className="measure-breadcrumbs spacer-right text-ellipsis"
component={component}
handleSelect={this.onOpenComponent}
@@ -327,7 +328,7 @@ export default class MeasureContent extends React.PureComponent {
measure != null && (
<div className="layout-page-main-inner measure-details-content">
<MeasureHeader
- branch={branch}
+ branchLike={branchLike}
component={component}
components={this.state.components}
leakPeriod={this.props.leakPeriod}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.js
index beaca1bd6e1..1998460996e 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.js
@@ -26,14 +26,14 @@ import MeasureContent from './MeasureContent';
/*:: import type { RawQuery } from '../../../helpers/query'; */
/*:: type Props = {|
- branch?: string,
+ branchLike?: { id?: string; name: string },
className?: string,
currentUser: { isLoggedIn: boolean },
rootComponent: Component,
fetchMeasures: (
component: string,
metricsKey: Array<string>,
- branch?: string
+ branchLike?: { id?: string; name: string }
) => Promise<{ component: Component, measures: Array<MeasureEnhanced> }>,
leakPeriod?: Period,
metric: Metric,
@@ -89,7 +89,7 @@ export default class MeasureContentContainer extends React.PureComponent {
this.mounted = false;
}
- fetchMeasure = ({ branch, rootComponent, fetchMeasures, metric, selected } /*: Props */) => {
+ fetchMeasure = ({ branchLike, rootComponent, fetchMeasures, metric, selected } /*: Props */) => {
this.updateLoading({ measure: true });
const metricKeys = [metric.key];
@@ -101,7 +101,7 @@ export default class MeasureContentContainer extends React.PureComponent {
metricKeys.push('file_complexity_distribution');
}
- fetchMeasures(selected || rootComponent.key, metricKeys, branch).then(
+ fetchMeasures(selected || rootComponent.key, metricKeys, branchLike).then(
({ component, measures }) => {
if (this.mounted) {
const measure = measures.find(measure => measure.metric.key === metric.key);
@@ -134,7 +134,7 @@ export default class MeasureContentContainer extends React.PureComponent {
return (
<MeasureContent
- branch={this.props.branch}
+ branchLike={this.props.branchLike}
className={this.props.className}
component={this.state.component}
currentUser={this.props.currentUser}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureFavoriteContainer.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureFavoriteContainer.js
index 1690866ffde..f1aac5309b1 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureFavoriteContainer.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureFavoriteContainer.js
@@ -57,7 +57,7 @@ class MeasureFavoriteContainer extends React.PureComponent {
}
fetchComponentFavorite({ component, onReceiveComponent } /*: Props */) {
- getComponentForSourceViewer(component).then(component => {
+ getComponentForSourceViewer({ component }).then(component => {
this.setState({ component });
onReceiveComponent(component);
});
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js
index 87c543aaa76..5c809374492 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js
@@ -34,7 +34,7 @@ import { isDiffMetric } from '../../../helpers/measures';
/*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */
/*:: type Props = {|
- branch?: string,
+ branchLike?: { id?: string; name: string },
component: Component,
components: Array<Component>,
leakPeriod?: Period,
@@ -43,7 +43,7 @@ import { isDiffMetric } from '../../../helpers/measures';
|}; */
export default function MeasureHeader(props /*: Props*/) {
- const { branch, component, leakPeriod, measure, secondaryMeasure } = props;
+ const { branchLike, component, leakPeriod, measure, secondaryMeasure } = props;
const { metric } = measure;
const isDiff = isDiffMetric(metric.key);
return (
@@ -72,7 +72,7 @@ export default function MeasureHeader(props /*: Props*/) {
overlay={translate('component_measures.show_metric_history')}>
<Link
className="js-show-history spacer-left button button-small"
- to={getMeasureHistoryUrl(component.key, metric.key, branch)}>
+ to={getMeasureHistoryUrl(component.key, metric.key, branchLike)}>
<HistoryIcon />
</Link>
</Tooltip>
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.js
index a5e79b7a134..0128f7b8b1f 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.js
@@ -27,11 +27,12 @@ import BubbleChart from '../drilldown/BubbleChart';
import SourceViewer from '../../../components/SourceViewer/SourceViewer';
import { getComponentLeaves } from '../../../api/components';
import { enhanceComponent, getBubbleMetrics, isFileType } from '../utils';
+import { getBranchLikeQuery } from '../../../helpers/branches';
/*:: import type { Component, ComponentEnhanced, Paging, Period } from '../types'; */
/*:: import type { Metric } from '../../../store/metrics/actions'; */
/*:: type Props = {|
- branch?: string,
+ branchLike?: { id?: string; name: string },
className?: string,
component: Component,
currentUser: { isLoggedIn: boolean },
@@ -79,9 +80,10 @@ export default class MeasureOverview extends React.PureComponent {
}
fetchComponents = (props /*: Props */) => {
- const { branch, component, domain, metrics } = props;
+ const { branchLike, component, domain, metrics } = props;
if (isFileType(component)) {
- return this.setState({ components: [], paging: null });
+ this.setState({ components: [], paging: null });
+ return;
}
const { x, y, size, colors } = getBubbleMetrics(domain, metrics);
const metricsKey = [x.key, y.key, size.key];
@@ -89,7 +91,7 @@ export default class MeasureOverview extends React.PureComponent {
metricsKey.push(colors.map(metric => metric.key));
}
const options = {
- branch,
+ ...getBranchLikeQuery(branchLike),
s: 'metric',
metricSort: size.key,
asc: false,
@@ -114,11 +116,11 @@ export default class MeasureOverview extends React.PureComponent {
};
renderContent() {
- const { branch, component } = this.props;
+ const { branchLike, component } = this.props;
if (isFileType(component)) {
return (
<div className="measure-details-viewer">
- <SourceViewer branch={branch} component={component.key} />
+ <SourceViewer branchLike={branchLike} component={component.key} />
</div>
);
}
@@ -135,7 +137,7 @@ export default class MeasureOverview extends React.PureComponent {
}
render() {
- const { branch, component, currentUser, leakPeriod, rootComponent } = this.props;
+ const { branchLike, component, currentUser, leakPeriod, rootComponent } = this.props;
const isLoggedIn = currentUser && currentUser.isLoggedIn;
const isFile = isFileType(component);
return (
@@ -145,7 +147,7 @@ export default class MeasureOverview extends React.PureComponent {
<div className="layout-page-main-inner">
<Breadcrumbs
backToFirst={true}
- branch={branch}
+ branchLike={branchLike}
className="measure-breadcrumbs spacer-right text-ellipsis"
component={component}
handleSelect={this.props.updateSelected}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.js
index 7268b08f308..b69a9a8378f 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.js
@@ -23,12 +23,13 @@ import MeasureOverview from './MeasureOverview';
import { getComponentShow } from '../../../api/components';
import { getProjectUrl } from '../../../helpers/urls';
import { isViewType } from '../utils';
+import { getBranchLikeQuery } from '../../../helpers/branches';
/*:: import type { Component, Period, Query } from '../types'; */
/*:: import type { RawQuery } from '../../../helpers/query'; */
/*:: import type { Metric } from '../../../store/metrics/actions'; */
/*:: type Props = {|
- branch?: string,
+ branchLike?: { id?: string; name: string },
className?: string,
rootComponent: Component,
currentUser: { isLoggedIn: boolean },
@@ -81,14 +82,14 @@ export default class MeasureOverviewContainer extends React.PureComponent {
this.mounted = false;
}
- fetchComponent = ({ branch, rootComponent, selected } /*: Props */) => {
+ fetchComponent = ({ branchLike, rootComponent, selected } /*: Props */) => {
if (!selected || rootComponent.key === selected) {
this.setState({ component: rootComponent });
this.updateLoading({ component: false });
return;
}
this.updateLoading({ component: true });
- getComponentShow(selected, branch).then(
+ getComponentShow({ component: selected, ...getBranchLikeQuery(branchLike) }).then(
({ component }) => {
if (this.mounted) {
this.setState({ component });
@@ -122,7 +123,7 @@ export default class MeasureOverviewContainer extends React.PureComponent {
return (
<MeasureOverview
- branch={this.props.branch}
+ branchLike={this.props.branchLike}
className={this.props.className}
component={this.state.component}
currentUser={this.props.currentUser}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.js b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.js
index dfa9a644f24..5b02e3d87fb 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.js
@@ -77,7 +77,10 @@ it('should render correctly for leak', () => {
});
it('should render with branch', () => {
- expect(shallow(<MeasureHeader branch="feature" {...PROPS} />).find('Link')).toMatchSnapshot();
+ const shortBranch = { isMain: false, name: 'feature', mergeBranch: '', type: 'SHORT' };
+ expect(
+ shallow(<MeasureHeader branchLike={shortBranch} {...PROPS} />).find('Link')
+ ).toMatchSnapshot();
});
it('should display secondary measure too', () => {
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.js.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.js.snap
index d6c9bceb55a..8da497107c3 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.js.snap
@@ -86,7 +86,6 @@ exports[`should render correctly 1`] = `
Object {
"pathname": "/project/activity",
"query": Object {
- "branch": undefined,
"custom_metrics": "reliability_rating",
"graph": "custom",
"id": "foo",
diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.js b/server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.js
index 5270c221842..1c0747851f1 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.js
@@ -25,7 +25,7 @@ import SourceViewer from '../../../components/SourceViewer/SourceViewer';
/*:: import type { Metric } from '../../../store/metrics/actions'; */
/*:: type Props = {|
- branch?: string,
+ branchLike?: { id?: string; name: string },
component: ComponentEnhanced,
components: Array<ComponentEnhanced>,
leakPeriod?: Period,
@@ -81,7 +81,7 @@ export default class CodeView extends React.PureComponent {
};
render() {
- const { branch, component } = this.props;
- return <SourceViewer branch={branch} component={component.key} />;
+ const { branchLike, component } = this.props;
+ return <SourceViewer branchLike={branchLike} component={component.key} />;
}
}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.js b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.js
index 033a8bcf66b..248b6469e2b 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.js
@@ -23,11 +23,11 @@ import { Link } from 'react-router';
import LinkIcon from '../../../components/icons-components/LinkIcon';
import QualifierIcon from '../../../components/icons-components/QualifierIcon';
import { splitPath } from '../../../helpers/path';
-import { getPathUrlAsString, getProjectUrl } from '../../../helpers/urls';
+import { getPathUrlAsString, getBranchLikeUrl } from '../../../helpers/urls';
/*:: import type { ComponentEnhanced } from '../types'; */
/*:: type Props = {
- branch?: string,
+ branchLike?: { id?: string; name: string },
component: ComponentEnhanced,
onClick: string => void
}; */
@@ -65,15 +65,16 @@ export default class ComponentCell extends React.PureComponent {
}
render() {
- const { branch, component } = this.props;
+ const { branchLike, component } = this.props;
return (
<td className="measure-details-component-cell">
<div className="text-ellipsis">
+ {/* TODO make this <a> link a react-router <Link /> */}
{component.refKey == null ? (
<a
id={'component-measures-component-link-' + component.key}
className="link-no-underline"
- href={getPathUrlAsString(getProjectUrl(component.key, branch))}
+ href={getPathUrlAsString(getBranchLikeUrl(component.key, branchLike))}
onClick={this.handleClick}>
{this.renderInner()}
</a>
@@ -81,7 +82,7 @@ export default class ComponentCell extends React.PureComponent {
<Link
className="link-no-underline"
id={'component-measures-component-link-' + component.key}
- to={getProjectUrl(component.refKey, branch)}>
+ to={getBranchLikeUrl(component.refKey, branchLike)}>
<span className="big-spacer-right">
<LinkIcon />
</span>
diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.js b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.js
index f2b45b73878..0e29a704f0c 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.js
@@ -27,7 +27,7 @@ import { getLocalizedMetricName } from '../../../helpers/l10n';
/*:: import type { Metric } from '../../../store/metrics/actions'; */
/*:: type Props = {|
- branch?: string,
+ branchLike?: { id?: string; name: string },
components: Array<ComponentEnhanced>,
onClick: string => void,
metric: Metric,
@@ -36,7 +36,7 @@ import { getLocalizedMetricName } from '../../../helpers/l10n';
|}; */
export default function ComponentsList(
- { branch, components, onClick, metrics, metric, selectedComponent } /*: Props */
+ { branchLike, components, onClick, metrics, metric, selectedComponent } /*: Props */
) {
if (!components.length) {
return <EmptyResult />;
@@ -65,7 +65,7 @@ export default function ComponentsList(
{components.map(component => (
<ComponentsListRow
key={component.id}
- branch={branch}
+ branchLike={branchLike}
component={component}
otherMetrics={otherMetrics}
isSelected={component.key === selectedComponent}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsListRow.js b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsListRow.js
index e87b224967f..47a5d2bf29a 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsListRow.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsListRow.js
@@ -26,7 +26,7 @@ import MeasureCell from './MeasureCell';
/*:: import type { Metric } from '../../../store/metrics/actions'; */
/*:: type Props = {|
- branch?: string,
+ branchLike?: { id?: string; name: string },
component: ComponentEnhanced,
isSelected: boolean,
onClick: string => void,
@@ -35,7 +35,7 @@ import MeasureCell from './MeasureCell';
|}; */
export default function ComponentsListRow(props /*: Props */) {
- const { branch, component } = props;
+ const { branchLike, component } = props;
const otherMeasures = props.otherMetrics.map(metric => {
const measure = component.measures.find(measure => measure.metric.key === metric.key);
return { ...measure, metric };
@@ -45,7 +45,7 @@ export default function ComponentsListRow(props /*: Props */) {
});
return (
<tr className={rowClass}>
- <ComponentCell branch={branch} component={component} onClick={props.onClick} />
+ <ComponentCell branchLike={branchLike} component={component} onClick={props.onClick} />
<MeasureCell component={component} metric={props.metric} />
diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.js b/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.js
index 9a0ffde8c05..2fd8d7feb27 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.js
@@ -28,7 +28,7 @@ import { scrollToElement } from '../../../helpers/scrolling';
/*:: import type { Metric } from '../../../store/metrics/actions'; */
/*:: type Props = {|
- branch?: string,
+ branchLike?: { id?: string; name: string },
components: Array<ComponentEnhanced>,
fetchMore: () => void,
handleSelect: string => void,
@@ -123,7 +123,7 @@ export default class ListView extends React.PureComponent {
return (
<div ref={elem => (this.listContainer = elem)}>
<ComponentsList
- branch={this.props.branch}
+ branchLike={this.props.branchLike}
components={this.props.components}
metrics={this.props.metrics}
metric={this.props.metric}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.js b/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.js
index 3892abf2689..1dd3fd68f26 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.js
@@ -29,13 +29,13 @@ import QualifierIcon from '../../../components/icons-components/QualifierIcon';
import TreeMap from '../../../components/charts/TreeMap';
import { translate, translateWithParameters, getLocalizedMetricName } from '../../../helpers/l10n';
import { formatMeasure, isDiffMetric } from '../../../helpers/measures';
-import { getProjectUrl } from '../../../helpers/urls';
+import { getBranchLikeUrl } from '../../../helpers/urls';
/*:: import type { Metric } from '../../../store/metrics/actions'; */
/*:: import type { ComponentEnhanced } from '../types'; */
/*:: import type { TreeMapItem } from '../../../components/charts/TreeMap'; */
/*:: type Props = {|
- branch?: string,
+ branchLike?: { id?: string; name: string },
components: Array<ComponentEnhanced>,
handleSelect: string => void,
metric: Metric
@@ -64,7 +64,7 @@ export default class TreeMapView extends React.PureComponent {
}
}
- getTreemapComponents = ({ branch, components, metric } /*: Props */) => {
+ getTreemapComponents = ({ branchLike, components, metric } /*: Props */) => {
const colorScale = this.getColorScale(metric);
return components
.map(component => {
@@ -95,7 +95,7 @@ export default class TreeMapView extends React.PureComponent {
sizeValue
),
label: component.name,
- link: getProjectUrl(component.refKey || component.key, branch)
+ link: getBranchLikeUrl(component.refKey || component.key, branchLike)
};
})
.filter(Boolean);
diff --git a/server/sonar-web/src/main/js/apps/component/components/App.tsx b/server/sonar-web/src/main/js/apps/component/components/App.tsx
index 96ff380b906..71fc859ea2f 100644
--- a/server/sonar-web/src/main/js/apps/component/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/component/components/App.tsx
@@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { PullRequest, BranchType, ShortLivingBranch } from '../../../app/types';
import SourceViewer from '../../../components/SourceViewer/SourceViewer';
interface Props {
@@ -26,6 +27,7 @@ interface Props {
branch?: string;
id: string;
line?: string;
+ pullRequest?: string;
};
};
}
@@ -45,15 +47,30 @@ export default class App extends React.PureComponent<Props> {
};
render() {
- const { branch, id, line } = this.props.location.query;
+ const { branch, id, line, pullRequest } = this.props.location.query;
const finalLine = line ? Number(line) : undefined;
+ // TODO find a way to avoid creating this fakeBranchLike
+ // probably the best way would be to drop this page completely
+ // and redirect to the Code page
+ let fakeBranchLike: ShortLivingBranch | PullRequest | undefined = undefined;
+ if (branch) {
+ fakeBranchLike = {
+ isMain: false,
+ mergeBranch: '',
+ name: branch,
+ type: BranchType.SHORT
+ } as ShortLivingBranch;
+ } else if (pullRequest) {
+ fakeBranchLike = { base: '', branch: '', key: pullRequest, title: '' } as PullRequest;
+ }
+
return (
<div className="page page-limited">
<SourceViewer
aroundLine={finalLine}
- branch={branch}
+ branchLike={fakeBranchLike}
component={id}
highlightedLine={finalLine}
onLoaded={this.scrollToLine}
diff --git a/server/sonar-web/src/main/js/apps/component/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/component/components/__tests__/__snapshots__/App-test.tsx.snap
index 1cb5c653533..f5dd7e1a6ce 100644
--- a/server/sonar-web/src/main/js/apps/component/components/__tests__/__snapshots__/App-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/component/components/__tests__/__snapshots__/App-test.tsx.snap
@@ -6,7 +6,14 @@ exports[`renders 1`] = `
>
<Connect(SourceViewerBase)
aroundLine={7}
- branch="b"
+ branchLike={
+ Object {
+ "isMain": false,
+ "mergeBranch": "",
+ "name": "b",
+ "type": "SHORT",
+ }
+ }
component="foo"
highlightedLine={7}
onLoaded={[Function]}
diff --git a/server/sonar-web/src/main/js/apps/issues/components/App.d.ts b/server/sonar-web/src/main/js/apps/issues/components/App.d.ts
index 9bafb845242..211061e58bd 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/App.d.ts
+++ b/server/sonar-web/src/main/js/apps/issues/components/App.d.ts
@@ -18,11 +18,11 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { Component, CurrentUser } from '../../../app/types';
+import { Component, CurrentUser, BranchLike } from '../../../app/types';
import { RawQuery } from '../../../helpers/query';
interface Props {
- branch?: { name: string };
+ branchLike?: BranchLike;
component?: Component;
currentUser: CurrentUser;
fetchIssues: (query: RawQuery, requestOrganizations?: boolean) => Promise<any>;
diff --git a/server/sonar-web/src/main/js/apps/issues/components/App.js b/server/sonar-web/src/main/js/apps/issues/components/App.js
index bc6cf9826ff..85bf0fa458e 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/App.js
+++ b/server/sonar-web/src/main/js/apps/issues/components/App.js
@@ -59,7 +59,12 @@ import ListFooter from '../../../components/controls/ListFooter';
import EmptySearch from '../../../components/common/EmptySearch';
import FiltersHeader from '../../../components/common/FiltersHeader';
import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
-import { getBranchName, isShortLivingBranch } from '../../../helpers/branches';
+import {
+ isShortLivingBranch,
+ isSameBranchLike,
+ getBranchLikeQuery,
+ isPullRequest
+} from '../../../helpers/branches';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { scrollToElement } from '../../../helpers/scrolling';
import Checkbox from '../../../components/controls/Checkbox';
@@ -69,7 +74,7 @@ import '../styles.css';
/*::
export type Props = {
- branch?: { name: string },
+ branchLike?: { id?: string; name: string },
component?: Component,
currentUser: CurrentUser,
fetchIssues: (query: RawQuery, requestOrganizations?: boolean) => Promise<*>,
@@ -193,7 +198,7 @@ export default class App extends React.PureComponent {
const { query: prevQuery } = prevProps.location;
if (
prevProps.component !== this.props.component ||
- prevProps.branch !== this.props.branch ||
+ !isSameBranchLike(prevProps.branchLike, this.props.branchLike) ||
!areQueriesEqual(prevQuery, query) ||
areMyIssuesSelected(prevQuery) !== areMyIssuesSelected(query)
) {
@@ -337,7 +342,7 @@ export default class App extends React.PureComponent {
pathname: this.props.location.pathname,
query: {
...serializeQuery(this.state.query),
- branch: getBranchName(this.props.branch),
+ ...getBranchLikeQuery(this.props.branchLike),
id: this.props.component && this.props.component.key,
myIssues: this.state.myIssues ? 'true' : undefined,
open: issue
@@ -356,7 +361,7 @@ export default class App extends React.PureComponent {
pathname: this.props.location.pathname,
query: {
...serializeQuery(this.state.query),
- branch: getBranchName(this.props.branch),
+ ...getBranchLikeQuery(this.props.branchLike),
id: this.props.component && this.props.component.key,
myIssues: this.state.myIssues ? 'true' : undefined,
open: undefined
@@ -399,7 +404,7 @@ export default class App extends React.PureComponent {
: undefined;
const parameters = {
- branch: getBranchName(this.props.branch),
+ ...getBranchLikeQuery(this.props.branchLike),
componentKeys: component && component.key,
s: 'FILE_LINE',
...serializeQuery(query),
@@ -594,7 +599,7 @@ export default class App extends React.PureComponent {
pathname: this.props.location.pathname,
query: {
...serializeQuery({ ...this.state.query, ...changes }),
- branch: getBranchName(this.props.branch),
+ ...getBranchLikeQuery(this.props.branchLike),
id: this.props.component && this.props.component.key,
myIssues: this.state.myIssues ? 'true' : undefined
}
@@ -610,7 +615,7 @@ export default class App extends React.PureComponent {
pathname: this.props.location.pathname,
query: {
...serializeQuery({ ...this.state.query, assigned: true, assignees: [] }),
- branch: getBranchName(this.props.branch),
+ ...getBranchLikeQuery(this.props.branchLike),
id: this.props.component && this.props.component.key,
myIssues: myIssues ? 'true' : undefined
}
@@ -637,7 +642,7 @@ export default class App extends React.PureComponent {
pathname: this.props.location.pathname,
query: {
...DEFAULT_QUERY,
- branch: getBranchName(this.props.branch),
+ ...getBranchLikeQuery(this.props.branchLike),
id: this.props.component && this.props.component.key,
myIssues: this.state.myIssues ? 'true' : undefined
}
@@ -724,7 +729,7 @@ export default class App extends React.PureComponent {
handleReload = () => {
this.fetchFirstIssues();
- if (isShortLivingBranch(this.props.branch)) {
+ if (isShortLivingBranch(this.props.branchLike) || isPullRequest(this.props.branchLike)) {
this.props.onBranchesChange();
}
};
@@ -892,7 +897,7 @@ export default class App extends React.PureComponent {
}
renderList() {
- const { branch, component, currentUser, organization } = this.props;
+ const { branchLike, component, currentUser, organization } = this.props;
const { issues, openIssue, paging } = this.state;
const selectedIndex = this.getSelectedIndex();
const selectedIssue = selectedIndex != null ? issues[selectedIndex] : null;
@@ -905,7 +910,7 @@ export default class App extends React.PureComponent {
<div>
{paging.total > 0 && (
<IssuesList
- branch={getBranchName(branch)}
+ branchLike={branchLike}
checked={this.state.checked}
component={component}
issues={issues}
@@ -971,7 +976,7 @@ export default class App extends React.PureComponent {
{openIssue != null ? (
<div className="pull-left width-60">
<ComponentBreadcrumbs
- branch={getBranchName(this.props.branch)}
+ branchLike={this.props.branchLike}
component={component}
issue={openIssue}
organization={this.props.organization}
@@ -1000,7 +1005,7 @@ export default class App extends React.PureComponent {
<div>
{openIssue ? (
<IssuesSourceViewer
- branch={getBranchName(this.props.branch)}
+ branchLike={this.props.branchLike}
component={component}
openIssue={openIssue}
loadIssues={this.fetchIssuesForComponent}
diff --git a/server/sonar-web/src/main/js/apps/issues/components/ComponentBreadcrumbs.tsx b/server/sonar-web/src/main/js/apps/issues/components/ComponentBreadcrumbs.tsx
index 9da37298c41..c86ec19b057 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/ComponentBreadcrumbs.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/ComponentBreadcrumbs.tsx
@@ -21,11 +21,11 @@ import * as React from 'react';
import { Link } from 'react-router';
import Organization from '../../../components/shared/Organization';
import { collapsePath, limitComponentName } from '../../../helpers/path';
-import { getProjectUrl } from '../../../helpers/urls';
-import { Component } from '../../../app/types';
+import { getBranchLikeUrl, getCodeUrl } from '../../../helpers/urls';
+import { Component, BranchLike } from '../../../app/types';
interface Props {
- branch?: string;
+ branchLike?: BranchLike;
component?: Component;
issue: {
component: string;
@@ -39,7 +39,12 @@ interface Props {
organization?: { key: string };
}
-export default function ComponentBreadcrumbs({ branch, component, issue, organization }: Props) {
+export default function ComponentBreadcrumbs({
+ branchLike,
+ component,
+ issue,
+ organization
+}: Props) {
const displayOrganization =
!organization && (component == null || ['VW', 'SVW'].includes(component.qualifier));
const displayProject = component == null || !['TRK', 'BRC', 'DIR'].includes(component.qualifier);
@@ -53,7 +58,7 @@ export default function ComponentBreadcrumbs({ branch, component, issue, organiz
{displayProject && (
<span title={issue.projectName}>
- <Link to={getProjectUrl(issue.project, branch)} className="link-no-underline">
+ <Link to={getBranchLikeUrl(issue.project, branchLike)} className="link-no-underline">
{limitComponentName(issue.projectName)}
</Link>
<span className="slash-separator" />
@@ -64,14 +69,16 @@ export default function ComponentBreadcrumbs({ branch, component, issue, organiz
issue.subProject !== undefined &&
issue.subProjectName !== undefined && (
<span title={issue.subProjectName}>
- <Link to={getProjectUrl(issue.subProject, branch)} className="link-no-underline">
+ <Link to={getBranchLikeUrl(issue.subProject, branchLike)} className="link-no-underline">
{limitComponentName(issue.subProjectName)}
</Link>
<span className="slash-separator" />
</span>
)}
- <Link to={getProjectUrl(issue.component, branch)} className="link-no-underline">
+ <Link
+ to={getCodeUrl(issue.project, branchLike, issue.component)}
+ className="link-no-underline">
<span title={issue.componentLongName}>{collapsePath(issue.componentLongName)}</span>
</Link>
</div>
diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesList.js b/server/sonar-web/src/main/js/apps/issues/components/IssuesList.js
index 30506292162..c7d6bc2e650 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/IssuesList.js
+++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesList.js
@@ -25,7 +25,7 @@ import ListItem from './ListItem';
/*::
type Props = {|
- branch?: string,
+ branchLike?: { id?: string; name: string },
checked: Array<string>,
component?: Component,
issues: Array<Issue>,
@@ -44,13 +44,13 @@ export default class IssuesList extends React.PureComponent {
/*:: props: Props; */
render() {
- const { branch, checked, component, issues, openPopup, selectedIssue } = this.props;
+ const { branchLike, checked, component, issues, openPopup, selectedIssue } = this.props;
return (
<div>
{issues.map((issue, index) => (
<ListItem
- branch={branch}
+ branchLike={branchLike}
checked={checked.includes(issue.key)}
component={component}
key={issue.key}
diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.js b/server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.js
index 6f7422c2290..fb72e2eca3b 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.js
+++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.js
@@ -26,7 +26,7 @@ import { scrollToElement } from '../../../helpers/scrolling';
/*::
type Props = {|
- branch?: string,
+ branchLike?: { id?: string; name: string },
component: Component,
loadIssues: (string, number, number) => Promise<*>,
onIssueChange: Issue => void,
@@ -107,7 +107,7 @@ export default class IssuesSourceViewer extends React.PureComponent {
<div ref={node => (this.node = node)}>
<SourceViewer
aroundLine={aroundLine}
- branch={this.props.branch}
+ branchLike={this.props.branchLike}
component={openIssue.component}
displayAllIssues={true}
displayIssueLocationsCount={false}
diff --git a/server/sonar-web/src/main/js/apps/issues/components/ListItem.js b/server/sonar-web/src/main/js/apps/issues/components/ListItem.js
index 28c6ae69760..e4802fc64a3 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/ListItem.js
+++ b/server/sonar-web/src/main/js/apps/issues/components/ListItem.js
@@ -26,7 +26,7 @@ import Issue from '../../../components/issue/Issue';
/*::
type Props = {|
- branch?: string,
+ branchLike?: { id?: string; name: string },
checked: boolean,
component?: Component,
issue: IssueType,
@@ -89,7 +89,7 @@ export default class ListItem extends React.PureComponent {
};
render() {
- const { branch, component, issue, previousIssue } = this.props;
+ const { branchLike, component, issue, previousIssue } = this.props;
const displayComponent = previousIssue == null || previousIssue.component !== issue.component;
@@ -98,7 +98,7 @@ export default class ListItem extends React.PureComponent {
{displayComponent && (
<div className="issues-workspace-list-component">
<ComponentBreadcrumbs
- branch={branch}
+ branchLike={branchLike}
component={component}
issue={this.props.issue}
organization={this.props.organization}
@@ -106,7 +106,7 @@ export default class ListItem extends React.PureComponent {
</div>
)}
<Issue
- branch={branch}
+ branchLike={branchLike}
checked={this.props.checked}
displayLocationsLink={false}
issue={issue}
diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/ComponentBreadcrumbs-test.tsx b/server/sonar-web/src/main/js/apps/issues/components/__tests__/ComponentBreadcrumbs-test.tsx
index 3bee431c81c..00c83d10dcd 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/ComponentBreadcrumbs-test.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/ComponentBreadcrumbs-test.tsx
@@ -20,6 +20,7 @@
import * as React from 'react';
import { shallow } from 'enzyme';
import ComponentBreadcrumbs from '../ComponentBreadcrumbs';
+import { ShortLivingBranch, BranchType } from '../../../../app/types';
const baseIssue = {
component: 'comp',
@@ -40,5 +41,13 @@ it('renders with sub-project', () => {
it('renders with branch', () => {
const issue = { ...baseIssue, subProject: 'sub-proj', subProjectName: 'sub-proj-name' };
- expect(shallow(<ComponentBreadcrumbs branch="feature" issue={issue} />)).toMatchSnapshot();
+ const shortBranch: ShortLivingBranch = {
+ isMain: false,
+ mergeBranch: '',
+ name: 'feature',
+ type: BranchType.SHORT
+ };
+ expect(
+ shallow(<ComponentBreadcrumbs branchLike={shortBranch} issue={issue} />)
+ ).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/ComponentBreadcrumbs-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/ComponentBreadcrumbs-test.tsx.snap
index 3861f4a0780..ac510c72824 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/ComponentBreadcrumbs-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/ComponentBreadcrumbs-test.tsx.snap
@@ -19,7 +19,6 @@ exports[`renders 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "proj",
},
}
@@ -37,10 +36,10 @@ exports[`renders 1`] = `
style={Object {}}
to={
Object {
- "pathname": "/dashboard",
+ "pathname": "/code",
"query": Object {
- "branch": undefined,
- "id": "comp",
+ "id": "proj",
+ "selected": "comp",
},
}
}
@@ -71,10 +70,11 @@ exports[`renders with branch 1`] = `
style={Object {}}
to={
Object {
- "pathname": "/dashboard",
+ "pathname": "/project/issues",
"query": Object {
"branch": "feature",
"id": "proj",
+ "resolved": "false",
},
}
}
@@ -94,10 +94,11 @@ exports[`renders with branch 1`] = `
style={Object {}}
to={
Object {
- "pathname": "/dashboard",
+ "pathname": "/project/issues",
"query": Object {
"branch": "feature",
"id": "sub-proj",
+ "resolved": "false",
},
}
}
@@ -114,10 +115,11 @@ exports[`renders with branch 1`] = `
style={Object {}}
to={
Object {
- "pathname": "/dashboard",
+ "pathname": "/code",
"query": Object {
"branch": "feature",
- "id": "comp",
+ "id": "proj",
+ "selected": "comp",
},
}
}
@@ -150,7 +152,6 @@ exports[`renders with sub-project 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "proj",
},
}
@@ -173,7 +174,6 @@ exports[`renders with sub-project 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "sub-proj",
},
}
@@ -191,10 +191,10 @@ exports[`renders with sub-project 1`] = `
style={Object {}}
to={
Object {
- "pathname": "/dashboard",
+ "pathname": "/code",
"query": Object {
- "branch": undefined,
- "id": "comp",
+ "id": "proj",
+ "selected": "comp",
},
}
}
diff --git a/server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx b/server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx
index 3592c4daea3..ebea71b75a6 100644
--- a/server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx
@@ -21,15 +21,16 @@ import * as React from 'react';
import BadgeButton from './BadgeButton';
import BadgeParams from './BadgeParams';
import { BadgeType, BadgeOptions, getBadgeUrl } from './utils';
-import { Metric } from '../../../app/types';
+import { Metric, BranchLike } from '../../../app/types';
import CodeSnippet from '../../../components/common/CodeSnippet';
import Modal from '../../../components/controls/Modal';
+import { getBranchLikeQuery } from '../../../helpers/branches';
import { translate } from '../../../helpers/l10n';
import './styles.css';
import { Button, ResetButtonLink } from '../../../components/ui/buttons';
interface Props {
- branch?: string;
+ branchLike?: BranchLike;
metrics: { [key: string]: Metric };
project: string;
}
@@ -64,10 +65,10 @@ export default class BadgesModal extends React.PureComponent<Props, State> {
};
render() {
- const { branch, project } = this.props;
+ const { branchLike, project } = this.props;
const { selectedType, badgeOptions } = this.state;
const header = translate('overview.badges.title');
- const fullBadgeOptions = { branch, project, ...badgeOptions };
+ const fullBadgeOptions = { project, ...badgeOptions, ...getBranchLikeQuery(branchLike) };
return (
<div className="overview-meta-card">
<Button className="js-project-badges" onClick={this.handleOpen}>
diff --git a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx
index cea857f2e90..d77c077d6d9 100644
--- a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx
@@ -21,13 +21,20 @@ import * as React from 'react';
import { shallow } from 'enzyme';
import BadgesModal from '../BadgesModal';
import { click } from '../../../../helpers/testUtils';
+import { ShortLivingBranch, BranchType } from '../../../../app/types';
jest.mock('../../../../helpers/urls', () => ({
getHostUrl: () => 'host'
}));
it('should display the modal after click', () => {
- const wrapper = shallow(<BadgesModal branch="branch-6.6" metrics={{}} project="foo" />);
+ const shortBranch: ShortLivingBranch = {
+ isMain: false,
+ mergeBranch: '',
+ name: 'branch-6.6',
+ type: BranchType.SHORT
+ };
+ const wrapper = shallow(<BadgesModal branchLike={shortBranch} metrics={{}} project="foo" />);
expect(wrapper).toMatchSnapshot();
click(wrapper.find('Button'));
expect(wrapper.find('Modal')).toMatchSnapshot();
diff --git a/server/sonar-web/src/main/js/apps/overview/badges/utils.ts b/server/sonar-web/src/main/js/apps/overview/badges/utils.ts
index 39113f88945..5f300830c1b 100644
--- a/server/sonar-web/src/main/js/apps/overview/badges/utils.ts
+++ b/server/sonar-web/src/main/js/apps/overview/badges/utils.ts
@@ -28,6 +28,7 @@ export interface BadgeOptions {
color?: BadgeColors;
project?: string;
metric?: string;
+ pullRequest?: string;
}
export enum BadgeType {
@@ -38,19 +39,19 @@ export enum BadgeType {
export function getBadgeUrl(
type: BadgeType,
- { branch, project, color = 'white', metric = 'alert_status' }: BadgeOptions
+ { branch, project, color = 'white', metric = 'alert_status', pullRequest }: BadgeOptions
) {
switch (type) {
case BadgeType.marketing:
return `${getHostUrl()}/images/project_badges/sonarcloud-${color}.svg`;
case BadgeType.qualityGate:
return `${getHostUrl()}/api/project_badges/quality_gate?${stringify(
- omitNil({ branch, project })
+ omitNil({ branch, project, pullRequest })
)}`;
case BadgeType.measure:
default:
return `${getHostUrl()}/api/project_badges/measure?${stringify(
- omitNil({ branch, project, metric })
+ omitNil({ branch, project, metric, pullRequest })
)}`;
}
}
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 03d898d4e9e..0d7caeb5d3e 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
@@ -21,12 +21,12 @@ import * as React from 'react';
import * as PropTypes from 'prop-types';
import OverviewApp from './OverviewApp';
import EmptyOverview from './EmptyOverview';
-import { getBranchName, isShortLivingBranch } from '../../../helpers/branches';
-import { getProjectBranchUrl, getCodeUrl } from '../../../helpers/urls';
-import { Branch, Component } from '../../../app/types';
+import { Component, BranchLike } from '../../../app/types';
+import { isShortLivingBranch } from '../../../helpers/branches';
+import { getShortLivingBranchUrl, getCodeUrl } from '../../../helpers/urls';
interface Props {
- branch?: Branch;
+ branchLike?: BranchLike;
component: Component;
isInProgress?: boolean;
isPending?: boolean;
@@ -39,7 +39,7 @@ export default class App extends React.PureComponent<Props> {
};
componentDidMount() {
- const { branch, component } = this.props;
+ const { branchLike, component } = this.props;
if (this.isPortfolio()) {
this.context.router.replace({
@@ -48,10 +48,10 @@ export default class App extends React.PureComponent<Props> {
});
} else if (this.isFile()) {
this.context.router.replace(
- getCodeUrl(component.breadcrumbs[0].key, getBranchName(branch), component.key)
+ getCodeUrl(component.breadcrumbs[0].key, branchLike, component.key)
);
- } else if (isShortLivingBranch(branch)) {
- this.context.router.replace(getProjectBranchUrl(component.key, branch));
+ } else if (isShortLivingBranch(branchLike)) {
+ this.context.router.replace(getShortLivingBranchUrl(component.key, branchLike.name));
}
}
@@ -60,9 +60,9 @@ export default class App extends React.PureComponent<Props> {
isFile = () => ['FIL', 'UTS'].includes(this.props.component.qualifier);
render() {
- const { branch, component } = this.props;
+ const { branchLike, component } = this.props;
- if (this.isPortfolio() || this.isFile() || isShortLivingBranch(branch)) {
+ if (this.isPortfolio() || this.isFile() || isShortLivingBranch(branchLike)) {
return null;
}
@@ -77,7 +77,7 @@ export default class App extends React.PureComponent<Props> {
return (
<OverviewApp
- branch={branch}
+ branchLike={branchLike}
component={component}
onComponentChange={this.props.onComponentChange}
/>
diff --git a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx
index 5bc3679f9df..091826a44a6 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx
@@ -36,14 +36,14 @@ import { getLeakPeriod, Period } from '../../../helpers/periods';
import { getCustomGraph, getGraph } from '../../../helpers/storage';
import { METRICS, HISTORY_METRICS_LIST } from '../utils';
import { DEFAULT_GRAPH, getDisplayedHistoryMetrics } from '../../projectActivity/utils';
-import { getBranchName } from '../../../helpers/branches';
+import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches';
import { fetchMetrics } from '../../../store/rootActions';
import { getMetrics } from '../../../store/rootReducer';
-import { Branch, Component, Metric } from '../../../app/types';
+import { BranchLike, Component, Metric } from '../../../app/types';
import '../styles.css';
interface OwnProps {
- branch?: Branch;
+ branchLike?: BranchLike;
component: Component;
onComponentChange: (changes: {}) => void;
}
@@ -79,7 +79,7 @@ export class OverviewApp extends React.PureComponent<Props, State> {
componentDidUpdate(prevProps: Props) {
if (
this.props.component.key !== prevProps.component.key ||
- this.props.branch !== prevProps.branch
+ !isSameBranchLike(this.props.branchLike, prevProps.branchLike)
) {
this.loadMeasures().then(this.loadHistory, () => {});
}
@@ -90,12 +90,12 @@ export class OverviewApp extends React.PureComponent<Props, State> {
}
loadMeasures() {
- const { branch, component } = this.props;
+ const { branchLike, component } = this.props;
this.setState({ loading: true });
return getMeasuresAndMeta(component.key, METRICS, {
additionalFields: 'metrics,periods',
- branch: getBranchName(branch)
+ ...getBranchLikeQuery(branchLike)
}).then(
r => {
if (this.mounted && r.metrics) {
@@ -116,7 +116,7 @@ export class OverviewApp extends React.PureComponent<Props, State> {
}
loadHistory = () => {
- const { branch, component } = this.props;
+ const { branchLike, component } = this.props;
let graphMetrics = getDisplayedHistoryMetrics(getGraph(), getCustomGraph());
if (!graphMetrics || graphMetrics.length <= 0) {
@@ -124,22 +124,24 @@ export class OverviewApp extends React.PureComponent<Props, State> {
}
const metrics = uniq(HISTORY_METRICS_LIST.concat(graphMetrics));
- return getAllTimeMachineData(component.key, metrics, { branch: getBranchName(branch) }).then(
- r => {
- if (this.mounted) {
- const history: History = {};
- r.measures.forEach(measure => {
- const measureHistory = measure.history.map(analysis => ({
- date: parseDate(analysis.date),
- value: analysis.value
- }));
- history[measure.metric] = measureHistory;
- });
- const historyStartDate = history[HISTORY_METRICS_LIST[0]][0].date;
- this.setState({ history, historyStartDate });
- }
+ return getAllTimeMachineData({
+ ...getBranchLikeQuery(branchLike),
+ component: component.key,
+ metrics: metrics.join()
+ }).then(r => {
+ if (this.mounted) {
+ const history: History = {};
+ r.measures.forEach(measure => {
+ const measureHistory = measure.history.map(analysis => ({
+ date: parseDate(analysis.date),
+ value: analysis.value
+ }));
+ history[measure.metric] = measureHistory;
+ });
+ const historyStartDate = history[HISTORY_METRICS_LIST[0]][0].date;
+ this.setState({ history, historyStartDate });
}
- );
+ });
};
getApplicationLeakPeriod = () =>
@@ -156,7 +158,7 @@ export class OverviewApp extends React.PureComponent<Props, State> {
}
render() {
- const { branch, component } = this.props;
+ const { branchLike, component } = this.props;
const { loading, measures, periods, history, historyStartDate } = this.state;
if (loading) {
@@ -165,9 +167,8 @@ export class OverviewApp extends React.PureComponent<Props, State> {
const leakPeriod =
component.qualifier === 'APP' ? this.getApplicationLeakPeriod() : getLeakPeriod(periods);
- const branchName = getBranchName(branch);
const domainProps = {
- branch: branchName,
+ branchLike,
component,
measures,
leakPeriod,
@@ -182,7 +183,7 @@ export class OverviewApp extends React.PureComponent<Props, State> {
{component.qualifier === 'APP' ? (
<ApplicationQualityGate component={component} />
) : (
- <QualityGate branch={branchName} component={component} measures={measures} />
+ <QualityGate branchLike={branchLike} component={component} measures={measures} />
)}
<div className="overview-domains-list">
@@ -195,7 +196,7 @@ export class OverviewApp extends React.PureComponent<Props, State> {
<div className="overview-sidebar page-sidebar-fixed">
<Meta
- branch={branchName}
+ branchLike={branchLike}
component={component}
history={history}
measures={measures}
diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx
index 4edd63ffba8..f3eb8d43cba 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx
@@ -54,7 +54,7 @@ it('redirects on Code page for files', () => {
qualifier: 'FIL'
};
const replace = jest.fn();
- mount(<App branch={branch} component={newComponent} onComponentChange={jest.fn()} />, {
+ mount(<App branchLike={branch} component={newComponent} onComponentChange={jest.fn()} />, {
context: { router: { replace } }
});
expect(replace).toBeCalledWith({
diff --git a/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.tsx b/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.tsx
index 8380a53b0a5..d405662ace4 100644
--- a/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.tsx
@@ -23,11 +23,13 @@ import Analysis from './Analysis';
import { getProjectActivity, Analysis as IAnalysis } from '../../../api/projectActivity';
import PreviewGraph from '../../../components/preview-graph/PreviewGraph';
import { translate } from '../../../helpers/l10n';
-import { Metric, Component } from '../../../app/types';
+import { Metric, Component, BranchLike } from '../../../app/types';
import { History } from '../../../api/time-machine';
+import { getBranchLikeQuery } from '../../../helpers/branches';
+import { getActivityUrl } from '../../../helpers/urls';
interface Props {
- branch?: string;
+ branchLike?: BranchLike;
component: Component;
history?: History;
metrics: { [key: string]: Metric };
@@ -76,7 +78,7 @@ export default class AnalysesList extends React.PureComponent<Props, State> {
this.setState({ loading: true });
getProjectActivity({
- branch: this.props.branch,
+ ...getBranchLikeQuery(this.props.branchLike),
project: this.getTopLevelComponent(),
ps: PAGE_SIZE
}).then(
@@ -101,7 +103,7 @@ export default class AnalysesList extends React.PureComponent<Props, State> {
return (
<ul className="spacer-top">
{analyses.map(analysis => (
- <Analysis key={analysis.key} analysis={analysis} qualifier={this.props.qualifier} />
+ <Analysis analysis={analysis} key={analysis.key} qualifier={this.props.qualifier} />
))}
</ul>
);
@@ -121,20 +123,16 @@ export default class AnalysesList extends React.PureComponent<Props, State> {
</h4>
<PreviewGraph
- branch={this.props.branch}
+ branchLike={this.props.branchLike}
history={this.props.history}
- project={this.props.component.key}
metrics={this.props.metrics}
+ project={this.props.component.key}
/>
{this.renderList(analyses)}
<div className="spacer-top small">
- <Link
- to={{
- pathname: '/project/activity',
- query: { id: this.props.component.key, branch: this.props.branch }
- }}>
+ <Link to={getActivityUrl(this.props.component.key, this.props.branchLike)}>
{translate('show_more')}
</Link>
</div>
diff --git a/server/sonar-web/src/main/js/apps/overview/events/__tests__/AnalysesList-test.tsx b/server/sonar-web/src/main/js/apps/overview/events/__tests__/AnalysesList-test.tsx
new file mode 100644
index 00000000000..710f2608a55
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/overview/events/__tests__/AnalysesList-test.tsx
@@ -0,0 +1,38 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { shallow } from 'enzyme';
+import AnalysesList from '../AnalysesList';
+
+it('should render show more link', () => {
+ const branchLike = { analysisDate: '2018-03-08T09:49:22+0100', isMain: true, name: 'master' };
+ const component = {
+ breadcrumbs: [{ key: 'foo', name: 'foo', qualifier: 'TRK' }],
+ key: 'foo',
+ name: 'foo',
+ organization: 'org',
+ qualifier: 'TRK'
+ };
+ const wrapper = shallow(
+ <AnalysesList branchLike={branchLike} component={component} metrics={{}} qualifier="TRK" />
+ );
+ wrapper.setState({ loading: false });
+ expect(wrapper.find('Link')).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/AnalysesList-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/AnalysesList-test.tsx.snap
new file mode 100644
index 00000000000..2320d0d0c4f
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/AnalysesList-test.tsx.snap
@@ -0,0 +1,18 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render show more link 1`] = `
+<Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/activity",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+>
+ show_more
+</Link>
+`;
diff --git a/server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.tsx b/server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.tsx
index d6433a10e08..52fd2d5253b 100644
--- a/server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.tsx
@@ -31,7 +31,7 @@ import { translate } from '../../../helpers/l10n';
export class BugsAndVulnerabilities extends React.PureComponent<ComposedProps> {
renderHeader() {
- const { branch, component } = this.props;
+ const { branchLike, component } = this.props;
return (
<div className="overview-card-header">
@@ -39,13 +39,13 @@ export class BugsAndVulnerabilities extends React.PureComponent<ComposedProps> {
<span>{translate('metric.bugs.name')}</span>
<Link
className="button button-small spacer-left text-text-bottom"
- to={getComponentDrilldownUrl(component.key, 'Reliability', branch)}>
+ to={getComponentDrilldownUrl(component.key, 'Reliability', branchLike)}>
<BubblesIcon size={14} />
</Link>
<span className="big-spacer-left">{translate('metric.vulnerabilities.name')}</span>
<Link
className="button button-small spacer-left text-text-bottom"
- to={getComponentDrilldownUrl(component.key, 'Security', branch)}>
+ to={getComponentDrilldownUrl(component.key, 'Security', branchLike)}>
<BubblesIcon size={14} />
</Link>
</div>
diff --git a/server/sonar-web/src/main/js/apps/overview/main/CodeSmells.tsx b/server/sonar-web/src/main/js/apps/overview/main/CodeSmells.tsx
index ac7fcd0c94c..bc632fae96a 100644
--- a/server/sonar-web/src/main/js/apps/overview/main/CodeSmells.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/main/CodeSmells.tsx
@@ -26,6 +26,7 @@ import { translate, translateWithParameters } from '../../../helpers/l10n';
import { formatMeasure, isDiffMetric } from '../../../helpers/measures';
import { getComponentIssuesUrl } from '../../../helpers/urls';
import CodeSmellIcon from '../../../components/icons-components/CodeSmellIcon';
+import { getBranchLikeQuery } from '../../../helpers/branches';
export class CodeSmells extends React.PureComponent<ComposedProps> {
renderHeader() {
@@ -33,10 +34,15 @@ export class CodeSmells extends React.PureComponent<ComposedProps> {
}
renderDebt(metric: string, type: string) {
- const { branch, measures, component } = this.props;
+ const { branchLike, measures, component } = this.props;
const measure = measures.find(measure => measure.metric.key === metric);
const value = measure ? this.props.getValue(measure) : undefined;
- const params = { branch, resolved: 'false', facetMode: 'effort', types: type };
+ const params = {
+ ...getBranchLikeQuery(branchLike),
+ resolved: 'false',
+ facetMode: 'effort',
+ types: type
+ };
if (isDiffMetric(metric)) {
Object.assign(params, { sinceLeakPeriod: 'true' });
diff --git a/server/sonar-web/src/main/js/apps/overview/main/Coverage.tsx b/server/sonar-web/src/main/js/apps/overview/main/Coverage.tsx
index cd1abc807ee..28e5a141ef7 100644
--- a/server/sonar-web/src/main/js/apps/overview/main/Coverage.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/main/Coverage.tsx
@@ -44,7 +44,7 @@ export class Coverage extends React.PureComponent<ComposedProps> {
}
renderCoverage() {
- const { branch, component } = this.props;
+ const { branchLike, component } = this.props;
const metric = 'coverage';
const coverage = this.getCoverage();
@@ -56,7 +56,7 @@ export class Coverage extends React.PureComponent<ComposedProps> {
<div className="display-inline-block text-middle">
<div className="overview-domain-measure-value">
- <DrilldownLink branch={branch} component={component.key} metric={metric}>
+ <DrilldownLink branchLike={branchLike} component={component.key} metric={metric}>
<span className="js-overview-main-coverage">
{formatMeasure(coverage, 'PERCENT')}
</span>
@@ -73,7 +73,7 @@ export class Coverage extends React.PureComponent<ComposedProps> {
}
renderNewCoverage() {
- const { branch, component, leakPeriod, measures } = this.props;
+ const { branchLike, component, leakPeriod, measures } = this.props;
if (!leakPeriod) {
return null;
}
@@ -85,7 +85,7 @@ export class Coverage extends React.PureComponent<ComposedProps> {
newCoverageMeasure && newCoverageValue !== undefined ? (
<div>
<DrilldownLink
- branch={branch}
+ branchLike={branchLike}
component={component.key}
metric={newCoverageMeasure.metric.key}>
<span className="js-overview-main-new-coverage">
@@ -106,7 +106,7 @@ export class Coverage extends React.PureComponent<ComposedProps> {
{translate('overview.coverage_on')}
<br />
<DrilldownLink
- branch={branch}
+ branchLike={branchLike}
className="spacer-right overview-domain-secondary-measure-value"
component={component.key}
metric={newLinesToCover.metric.key}>
diff --git a/server/sonar-web/src/main/js/apps/overview/main/Duplications.tsx b/server/sonar-web/src/main/js/apps/overview/main/Duplications.tsx
index d8a09caee40..49db5f74c36 100644
--- a/server/sonar-web/src/main/js/apps/overview/main/Duplications.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/main/Duplications.tsx
@@ -39,8 +39,7 @@ export class Duplications extends React.PureComponent<ComposedProps> {
}
renderDuplications() {
- const { branch, component, measures } = this.props;
-
+ const { branchLike, component, measures } = this.props;
const measure = measures.find(measure => measure.metric.key === 'duplicated_lines_density');
if (!measure) {
return null;
@@ -57,7 +56,7 @@ export class Duplications extends React.PureComponent<ComposedProps> {
<div className="display-inline-block text-middle">
<div className="overview-domain-measure-value">
<DrilldownLink
- branch={branch}
+ branchLike={branchLike}
component={component.key}
metric="duplicated_lines_density">
{formatMeasure(duplications, 'PERCENT')}
@@ -74,11 +73,10 @@ export class Duplications extends React.PureComponent<ComposedProps> {
}
renderNewDuplications() {
- const { branch, component, measures, leakPeriod } = this.props;
+ const { branchLike, component, measures, leakPeriod } = this.props;
if (!leakPeriod) {
return null;
}
-
const newDuplicationsMeasure = measures.find(
measure => measure.metric.key === 'new_duplicated_lines_density'
);
@@ -88,7 +86,7 @@ export class Duplications extends React.PureComponent<ComposedProps> {
newDuplicationsMeasure && newDuplicationsValue ? (
<div>
<DrilldownLink
- branch={branch}
+ branchLike={branchLike}
component={component.key}
metric={newDuplicationsMeasure.metric.key}>
<span className="js-overview-main-new-duplications">
@@ -108,7 +106,7 @@ export class Duplications extends React.PureComponent<ComposedProps> {
{translate('overview.duplications_on')}
<br />
<DrilldownLink
- branch={branch}
+ branchLike={branchLike}
className="spacer-right overview-domain-secondary-measure-value"
component={component.key}
metric={newLinesMeasure.metric.key}>
diff --git a/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx b/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx
index 2b07c3c9239..2251612e2e3 100644
--- a/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx
@@ -40,11 +40,12 @@ import {
getComponentIssuesUrl,
getMeasureHistoryUrl
} from '../../../helpers/urls';
-import { Component } from '../../../app/types';
+import { Component, BranchLike } from '../../../app/types';
import { History } from '../../../api/time-machine';
+import { getBranchLikeQuery } from '../../../helpers/branches';
export interface EnhanceProps {
- branch?: string;
+ branchLike?: BranchLike;
component: Component;
measures: MeasureEnhanced[];
leakPeriod?: { index: number; date?: string };
@@ -77,14 +78,14 @@ export default function enhance(ComposedComponent: React.ComponentType<ComposedP
};
renderHeader = (domain: string, label: string) => {
- const { branch, component } = this.props;
+ const { branchLike, component } = this.props;
return (
<div className="overview-card-header">
<div className="overview-title">
<span>{label}</span>
<Link
className="button button-small spacer-left text-text-bottom"
- to={getComponentDrilldownUrl(component.key, domain, branch)}>
+ to={getComponentDrilldownUrl(component.key, domain, branchLike)}>
<BubblesIcon size={14} />
</Link>
</div>
@@ -93,7 +94,7 @@ export default function enhance(ComposedComponent: React.ComponentType<ComposedP
};
renderMeasure = (metricKey: string) => {
- const { branch, measures, component } = this.props;
+ const { branchLike, measures, component } = this.props;
const measure = measures.find(measure => measure.metric.key === metricKey);
if (!measure) {
return null;
@@ -102,7 +103,7 @@ export default function enhance(ComposedComponent: React.ComponentType<ComposedP
return (
<div className="overview-domain-measure">
<div className="overview-domain-measure-value">
- <DrilldownLink branch={branch} component={component.key} metric={metricKey}>
+ <DrilldownLink branchLike={branchLike} component={component.key} metric={metricKey}>
<span className="js-overview-main-tests">
{formatMeasure(measure.value, getShortType(measure.metric.type))}
</span>
@@ -118,7 +119,7 @@ export default function enhance(ComposedComponent: React.ComponentType<ComposedP
};
renderRating = (metricKey: string) => {
- const { branch, component, measures } = this.props;
+ const { branchLike, component, measures } = this.props;
const measure = measures.find(measure => measure.metric.key === metricKey);
if (!measure) {
return null;
@@ -130,7 +131,7 @@ export default function enhance(ComposedComponent: React.ComponentType<ComposedP
<Tooltip overlay={title} placement="top">
<div className="overview-domain-measure-sup">
<DrilldownLink
- branch={branch}
+ branchLike={branchLike}
className="link-no-underline"
component={component.key}
metric={metricKey}>
@@ -142,14 +143,14 @@ export default function enhance(ComposedComponent: React.ComponentType<ComposedP
};
renderIssues = (metric: string, type: string) => {
- const { branch, measures, component } = this.props;
+ const { branchLike, measures, component } = this.props;
const measure = measures.find(measure => measure.metric.key === metric);
if (!measure) {
return null;
}
const value = this.getValue(measure);
- const params = { branch, resolved: 'false', types: type };
+ const params = { ...getBranchLikeQuery(branchLike), resolved: 'false', types: type };
if (isDiffMetric(metric)) {
Object.assign(params, { sinceLeakPeriod: 'true' });
}
@@ -166,7 +167,7 @@ export default function enhance(ComposedComponent: React.ComponentType<ComposedP
return (
<Link
className={linkClass}
- to={getMeasureHistoryUrl(this.props.component.key, metricKey, this.props.branch)}>
+ to={getMeasureHistoryUrl(this.props.component.key, metricKey, this.props.branchLike)}>
<HistoryIcon />
</Link>
);
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx b/server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx
index 4a55b5e29a4..edca9b4c4cb 100644
--- a/server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx
@@ -28,13 +28,13 @@ import MetaSize from './MetaSize';
import MetaTags from './MetaTags';
import BadgesModal from '../badges/BadgesModal';
import AnalysesList from '../events/AnalysesList';
-import { Visibility, Component, Metric } from '../../../app/types';
+import { Visibility, Component, Metric, BranchLike } from '../../../app/types';
import { History } from '../../../api/time-machine';
import { translate } from '../../../helpers/l10n';
import { MeasureEnhanced } from '../../../helpers/measures';
interface Props {
- branch?: string;
+ branchLike?: BranchLike;
component: Component;
history?: History;
measures: MeasureEnhanced[];
@@ -50,7 +50,7 @@ export default class Meta extends React.PureComponent<Props> {
render() {
const { onSonarCloud, organizationsEnabled } = this.context;
- const { branch, component, metrics } = this.props;
+ const { branchLike, component, metrics } = this.props;
const { qualifier, description, qualityProfiles, qualityGate, visibility } = component;
const isProject = qualifier === 'TRK';
@@ -66,11 +66,11 @@ export default class Meta extends React.PureComponent<Props> {
{isProject && (
<MetaTags component={component} onComponentChange={this.props.onComponentChange} />
)}
- <MetaSize branch={branch} component={component} measures={this.props.measures} />
+ <MetaSize branchLike={branchLike} component={component} measures={this.props.measures} />
</div>
<AnalysesList
- branch={branch}
+ branchLike={branchLike}
component={component}
history={this.props.history}
metrics={metrics}
@@ -106,7 +106,9 @@ export default class Meta extends React.PureComponent<Props> {
{onSonarCloud &&
isProject &&
- !isPrivate && <BadgesModal branch={branch} metrics={metrics} project={component.key} />}
+ !isPrivate && (
+ <BadgesModal branchLike={branchLike} metrics={metrics} project={component.key} />
+ )}
</div>
);
}
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaSize.tsx b/server/sonar-web/src/main/js/apps/overview/meta/MetaSize.tsx
index 335d0b48795..0fe760979f5 100644
--- a/server/sonar-web/src/main/js/apps/overview/meta/MetaSize.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaSize.tsx
@@ -25,10 +25,10 @@ import SizeRating from '../../../components/ui/SizeRating';
import { formatMeasure, MeasureEnhanced } from '../../../helpers/measures';
import { getMetricName } from '../helpers/metrics';
import { translate } from '../../../helpers/l10n';
-import { LightComponent } from '../../../app/types';
+import { LightComponent, BranchLike } from '../../../app/types';
interface Props {
- branch?: string;
+ branchLike?: BranchLike;
component: LightComponent;
measures: MeasureEnhanced[];
}
@@ -43,7 +43,10 @@ export default class MetaSize extends React.PureComponent<Props> {
<span className="spacer-right">
<SizeRating value={Number(ncloc.value)} />
</span>
- <DrilldownLink branch={this.props.branch} component={this.props.component.key} metric="ncloc">
+ <DrilldownLink
+ branchLike={this.props.branchLike}
+ component={this.props.component.key}
+ metric="ncloc">
{formatMeasure(ncloc.value, 'SHORT_INT')}
</DrilldownLink>
<div className="spacer-top text-muted">{getMetricName('ncloc')}</div>
@@ -71,7 +74,7 @@ export default class MetaSize extends React.PureComponent<Props> {
return projects ? (
<div id="overview-projects" className="overview-meta-size-ncloc is-half-width">
<DrilldownLink
- branch={this.props.branch}
+ branchLike={this.props.branchLike}
component={this.props.component.key}
metric="projects">
{formatMeasure(projects.value, 'SHORT_INT')}
diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGate.js b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGate.js
index 5c3125aedee..20ab7885146 100644
--- a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGate.js
+++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGate.js
@@ -38,13 +38,13 @@ function isProject(component /*: Component */) {
/*::
type Props = {
- branch?: string,
+ branchLike?: {id?: string; name: string },
component: Component,
measures: MeasuresList
};
*/
-export default function QualityGate({ branch, component, measures } /*: Props */) {
+export default function QualityGate({ branchLike, component, measures } /*: Props */) {
const statusMeasure = measures.find(measure => measure.metric.key === 'alert_status');
const detailsMeasure = measures.find(measure => measure.metric.key === 'quality_gate_details');
@@ -81,7 +81,11 @@ export default function QualityGate({ branch, component, measures } /*: Props */
)}
{conditions.length > 0 && (
- <QualityGateConditions branch={branch} component={component} conditions={conditions} />
+ <QualityGateConditions
+ branchLike={branchLike}
+ component={component}
+ conditions={conditions}
+ />
)}
</div>
);
diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js
index 3301c33ef6f..ba6d12fa35d 100644
--- a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js
+++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js
@@ -27,12 +27,13 @@ import IssueTypeIcon from '../../../components/ui/IssueTypeIcon';
import { getPeriodValue, isDiffMetric, formatMeasure } from '../../../helpers/measures';
import { translate } from '../../../helpers/l10n';
import { getComponentIssuesUrl } from '../../../helpers/urls';
+import { getBranchLikeQuery } from '../../../helpers/branches';
/*:: import type { Component } from '../types'; */
/*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */
export default class QualityGateCondition extends React.PureComponent {
/*:: props: {
- branch?: string,
+ branchLike?: { id?: string; name: string },
component: Component,
condition: {
level: string,
@@ -54,7 +55,11 @@ export default class QualityGateCondition extends React.PureComponent {
}
getIssuesUrl = (sinceLeakPeriod /*: boolean */, customQuery /*: {} */) => {
- const query /*: Object */ = { resolved: 'false', branch: this.props.branch, ...customQuery };
+ const query /*: Object */ = {
+ resolved: 'false',
+ ...getBranchLikeQuery(this.props.branchLike),
+ ...customQuery
+ };
if (sinceLeakPeriod) {
Object.assign(query, { sinceLeakPeriod: 'true' });
}
@@ -89,7 +94,7 @@ export default class QualityGateCondition extends React.PureComponent {
}
wrapWithLink(children /*: React.Element<*> */) {
- const { branch, component, condition } = this.props;
+ const { branchLike, component, condition } = this.props;
const className = classNames(
'overview-quality-gate-condition',
@@ -114,7 +119,7 @@ export default class QualityGateCondition extends React.PureComponent {
</Link>
) : (
<DrilldownLink
- branch={branch}
+ branchLike={branchLike}
className={className}
component={component.key}
metric={condition.measure.metric.key}
diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateConditions.js b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateConditions.js
index 8abd92619a1..57dc3002b4d 100644
--- a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateConditions.js
+++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateConditions.js
@@ -19,10 +19,12 @@
*/
import React from 'react';
import { sortBy } from 'lodash';
+import PropTypes from 'prop-types';
import QualityGateCondition from './QualityGateCondition';
import { ComponentType, ConditionsListType } from '../propTypes';
import { getMeasuresAndMeta } from '../../../api/measures';
import { enhanceMeasuresWithMetrics } from '../../../helpers/measures';
+import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches';
const LEVEL_ORDER = ['ERROR', 'WARN'];
@@ -35,7 +37,7 @@ function enhanceConditions(conditions, measures) {
export default class QualityGateConditions extends React.PureComponent {
static propTypes = {
- // branch
+ branchLike: PropTypes.object,
component: ComponentType.isRequired,
conditions: ConditionsListType.isRequired
};
@@ -51,7 +53,7 @@ export default class QualityGateConditions extends React.PureComponent {
componentDidUpdate(prevProps) {
if (
- prevProps.branch !== this.props.branch ||
+ !isSameBranchLike(prevProps.branchLike, this.props.branchLike) ||
prevProps.conditions !== this.props.conditions ||
prevProps.component !== this.props.component
) {
@@ -64,13 +66,13 @@ export default class QualityGateConditions extends React.PureComponent {
}
loadFailedMeasures() {
- const { branch, component, conditions } = this.props;
+ const { branchLike, component, conditions } = this.props;
const failedConditions = conditions.filter(c => c.level !== 'OK');
if (failedConditions.length > 0) {
const metrics = failedConditions.map(condition => condition.metric);
getMeasuresAndMeta(component.key, metrics, {
additionalFields: 'metrics',
- branch
+ ...getBranchLikeQuery(branchLike)
}).then(r => {
if (this.mounted) {
const measures = enhanceMeasuresWithMetrics(r.component.measures, r.metrics);
diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGateProject-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGateProject-test.tsx.snap
index 64d2bf86cfb..4c5e4ee1a87 100644
--- a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGateProject-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGateProject-test.tsx.snap
@@ -9,7 +9,6 @@ exports[`renders 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGateCondition-test.js.snap b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGateCondition-test.js.snap
index e286b3da99d..900cc34dce2 100644
--- a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGateCondition-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGateCondition-test.js.snap
@@ -9,7 +9,6 @@ exports[`new_maintainability_rating 1`] = `
Object {
"pathname": "/project/issues",
"query": Object {
- "branch": undefined,
"id": "abcd-key",
"resolved": "false",
"sinceLeakPeriod": "true",
@@ -104,7 +103,6 @@ exports[`new_reliability_rating 1`] = `
Object {
"pathname": "/project/issues",
"query": Object {
- "branch": undefined,
"id": "abcd-key",
"resolved": "false",
"severities": "BLOCKER,CRITICAL,MAJOR,MINOR",
@@ -158,7 +156,6 @@ exports[`new_security_rating 1`] = `
Object {
"pathname": "/project/issues",
"query": Object {
- "branch": undefined,
"id": "abcd-key",
"resolved": "false",
"severities": "BLOCKER,CRITICAL,MAJOR,MINOR",
@@ -254,7 +251,6 @@ exports[`reliability_rating 1`] = `
Object {
"pathname": "/project/issues",
"query": Object {
- "branch": undefined,
"id": "abcd-key",
"resolved": "false",
"severities": "BLOCKER,CRITICAL,MAJOR,MINOR",
@@ -307,7 +303,6 @@ exports[`security_rating 1`] = `
Object {
"pathname": "/project/issues",
"query": Object {
- "branch": undefined,
"id": "abcd-key",
"resolved": "false",
"severities": "BLOCKER,CRITICAL,MAJOR,MINOR",
@@ -360,7 +355,6 @@ exports[`should work with branch 1`] = `
Object {
"pathname": "/project/issues",
"query": Object {
- "branch": "feature",
"id": "abcd-key",
"resolved": "false",
"sinceLeakPeriod": "true",
@@ -413,7 +407,6 @@ exports[`sqale_rating 1`] = `
Object {
"pathname": "/project/issues",
"query": Object {
- "branch": undefined,
"id": "abcd-key",
"resolved": "false",
"types": "CODE_SMELL",
diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/Activity.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/Activity.tsx
index ddba951e313..01d14bb1807 100644
--- a/server/sonar-web/src/main/js/apps/portfolio/components/Activity.tsx
+++ b/server/sonar-web/src/main/js/apps/portfolio/components/Activity.tsx
@@ -60,13 +60,13 @@ export default class Activity extends React.PureComponent<Props> {
fetchHistory = () => {
const { component } = this.props;
- let graphMetrics = getDisplayedHistoryMetrics(getGraph(), getCustomGraph());
+ let graphMetrics: string[] = getDisplayedHistoryMetrics(getGraph(), getCustomGraph());
if (!graphMetrics || graphMetrics.length <= 0) {
graphMetrics = getDisplayedHistoryMetrics(DEFAULT_GRAPH, []);
}
this.setState({ loading: true });
- return getAllTimeMachineData(component, graphMetrics).then(
+ return getAllTimeMachineData({ component, metrics: graphMetrics.join() }).then(
timeMachine => {
if (this.mounted) {
const history: History = {};
diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx
index 27d4e2d957b..aa0fd5f0a16 100644
--- a/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/portfolio/components/App.tsx
@@ -81,7 +81,7 @@ export class App extends React.PureComponent<Props, State> {
fetchData() {
this.setState({ loading: true });
Promise.all([
- getMeasures(this.props.component.key, PORTFOLIO_METRICS),
+ getMeasures({ componentKey: this.props.component.key, metricKeys: PORTFOLIO_METRICS.join() }),
getChildren(this.props.component.key, SUB_COMPONENTS_METRICS, { ps: 20, s: 'qualifier' })
]).then(
([measures, subComponents]) => {
diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Activity-test.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Activity-test.tsx
index c8c2e81f295..0b5fb1f1584 100644
--- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Activity-test.tsx
+++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/Activity-test.tsx
@@ -67,5 +67,5 @@ it('renders', () => {
it('fetches history', () => {
mount(<Activity component="foo" metrics={{}} />);
- expect(getAllTimeMachineData).toBeCalledWith('foo', ['coverage']);
+ expect(getAllTimeMachineData).toBeCalledWith({ component: 'foo', metrics: 'coverage' });
});
diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/App-test.tsx
index 6cfe8d6890b..4abdc4e056b 100644
--- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/App-test.tsx
+++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/App-test.tsx
@@ -77,23 +77,11 @@ it('fetches measures and children components', () => {
getMeasures.mockClear();
getChildren.mockClear();
mount(<App component={component} fetchMetrics={jest.fn()} metrics={{}} />);
- expect(getMeasures).toBeCalledWith('foo', [
- 'projects',
- 'ncloc',
- 'ncloc_language_distribution',
- 'releasability_rating',
- 'releasability_effort',
- 'sqale_rating',
- 'maintainability_rating_effort',
- 'reliability_rating',
- 'reliability_rating_effort',
- 'security_rating',
- 'security_rating_effort',
- 'last_change_on_releasability_rating',
- 'last_change_on_maintainability_rating',
- 'last_change_on_security_rating',
- 'last_change_on_reliability_rating'
- ]);
+ expect(getMeasures).toBeCalledWith({
+ componentKey: 'foo',
+ metricKeys:
+ 'projects,ncloc,ncloc_language_distribution,releasability_rating,releasability_effort,sqale_rating,maintainability_rating_effort,reliability_rating,reliability_rating_effort,security_rating,security_rating_effort,last_change_on_releasability_rating,last_change_on_maintainability_rating,last_change_on_security_rating,last_change_on_reliability_rating'
+ });
expect(getChildren).toBeCalledWith(
'foo',
[
diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Effort-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Effort-test.tsx.snap
index 8918c9e4b99..b278210c01b 100644
--- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Effort-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Effort-test.tsx.snap
@@ -16,7 +16,6 @@ exports[`renders 1`] = `
Object {
"pathname": "/component_measures",
"query": Object {
- "branch": undefined,
"id": "foo",
"metric": "security_rating",
},
diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/HistoryButtonLink-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/HistoryButtonLink-test.tsx.snap
index e62b2d558c6..8f25ec024e1 100644
--- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/HistoryButtonLink-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/HistoryButtonLink-test.tsx.snap
@@ -9,7 +9,6 @@ exports[`renders 1`] = `
Object {
"pathname": "/project/activity",
"query": Object {
- "branch": undefined,
"custom_metrics": "security_rating",
"graph": "custom",
"id": "foo",
diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MainRating-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MainRating-test.tsx.snap
index d8cc0a6fd99..b90b5afa313 100644
--- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MainRating-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MainRating-test.tsx.snap
@@ -9,7 +9,6 @@ exports[`renders 1`] = `
Object {
"pathname": "/component_measures",
"query": Object {
- "branch": undefined,
"id": "foo",
"metric": "security_rating",
"view": "treemap",
diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MeasuresButtonLink-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MeasuresButtonLink-test.tsx.snap
index a97849bb45e..b4afa69ade1 100644
--- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MeasuresButtonLink-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/MeasuresButtonLink-test.tsx.snap
@@ -9,7 +9,6 @@ exports[`renders 1`] = `
Object {
"pathname": "/component_measures",
"query": Object {
- "branch": undefined,
"id": "foo",
"metric": "security_rating",
},
diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/ReleasabilityBox-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/ReleasabilityBox-test.tsx.snap
index c2120360b56..2401a4dc09b 100644
--- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/ReleasabilityBox-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/ReleasabilityBox-test.tsx.snap
@@ -17,7 +17,6 @@ exports[`renders 1`] = `
Object {
"pathname": "/component_measures",
"query": Object {
- "branch": undefined,
"id": "foo",
"metric": "alert_status",
},
@@ -41,7 +40,6 @@ exports[`renders 1`] = `
Object {
"pathname": "/component_measures",
"query": Object {
- "branch": undefined,
"id": "foo",
"metric": "alert_status",
},
diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Summary-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Summary-test.tsx.snap
index 149c818b286..5c828719017 100644
--- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Summary-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Summary-test.tsx.snap
@@ -24,7 +24,6 @@ exports[`renders 1`] = `
Object {
"pathname": "/component_measures",
"query": Object {
- "branch": undefined,
"id": "foo",
"metric": "projects",
},
@@ -55,7 +54,6 @@ exports[`renders 1`] = `
Object {
"pathname": "/component_measures",
"query": Object {
- "branch": undefined,
"id": "foo",
"metric": "ncloc",
},
diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/WorstProjects-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/WorstProjects-test.tsx.snap
index 736ab248060..ceb66108e3c 100644
--- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/WorstProjects-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/WorstProjects-test.tsx.snap
@@ -53,7 +53,6 @@ exports[`renders 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "foo",
},
}
@@ -141,7 +140,6 @@ exports[`renders 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "barbar",
},
}
@@ -229,7 +227,6 @@ exports[`renders 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "bazbaz",
},
}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js
index 51d28646604..54aa9929ecc 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js
@@ -26,7 +26,7 @@ import { getAllTimeMachineData } from '../../../api/time-machine';
import { getAllMetrics } from '../../../api/metrics';
import * as api from '../../../api/projectActivity';
import * as actions from '../actions';
-import { getBranchName } from '../../../helpers/branches';
+import { getBranchLikeQuery } from '../../../helpers/branches';
import { parseDate } from '../../../helpers/dates';
import { getCustomGraph, getGraph } from '../../../helpers/storage';
import {
@@ -51,7 +51,7 @@ type Component = {
};
type Props = {
- branch?: {},
+ branchLike?: { id?: string; name: string },
location: { pathname: string, query: RawQuery },
component: Component
};
@@ -103,7 +103,7 @@ export default class ProjectActivityAppContainer extends React.PureComponent {
pathname: this.props.location.pathname,
query: {
...serializeUrlQuery(newQuery),
- branch: getBranchName(this.props.branch)
+ ...getBranchLikeQuery(this.props.branchLike)
}
});
} else {
@@ -169,12 +169,7 @@ export default class ProjectActivityAppContainer extends React.PureComponent {
[string]: string
} */
) => {
- const parameters = {
- project,
- p,
- ps,
- branch: getBranchName(this.props.branch)
- };
+ const parameters = { project, p, ps, ...getBranchLikeQuery(this.props.branchLike) };
return api
.getProjectActivity({ ...additional, ...parameters })
.then(({ analyses, paging }) => ({
@@ -187,8 +182,10 @@ export default class ProjectActivityAppContainer extends React.PureComponent {
if (metrics.length <= 0) {
return Promise.resolve([]);
}
- return getAllTimeMachineData(this.props.component.key, metrics, {
- branch: getBranchName(this.props.branch)
+ return getAllTimeMachineData({
+ component: this.props.component.key,
+ metrics: metrics.join(),
+ ...getBranchLikeQuery(this.props.branchLike)
}).then(
({ measures }) =>
measures.map(measure => ({
@@ -300,7 +297,7 @@ export default class ProjectActivityAppContainer extends React.PureComponent {
pathname: this.props.location.pathname,
query: {
...query,
- branch: getBranchName(this.props.branch),
+ ...getBranchLikeQuery(this.props.branchLike),
id: this.props.component.key
}
});
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/App.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/App.tsx
index e4c886668fd..24dac728e91 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/projectBranches/components/App.tsx
@@ -22,14 +22,19 @@ import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router';
import BranchRow from './BranchRow';
import LongBranchesPattern from './LongBranchesPattern';
-import { Branch } from '../../../app/types';
-import { sortBranchesAsTree } from '../../../helpers/branches';
+import { BranchLike } from '../../../app/types';
+import {
+ sortBranchesAsTree,
+ getBranchLikeKey,
+ isShortLivingBranch,
+ isPullRequest
+} from '../../../helpers/branches';
import { translate } from '../../../helpers/l10n';
import { getValues } from '../../../api/settings';
import { formatMeasure } from '../../../helpers/measures';
interface Props {
- branches: Branch[];
+ branchLikes: BranchLike[];
canAdmin?: boolean;
component: { key: string };
onBranchesChange: () => void;
@@ -57,7 +62,7 @@ export default class App extends React.PureComponent<Props, State> {
fetchPurgeSetting() {
this.setState({ loading: true });
- getValues(BRANCH_LIFETIME_SETTING).then(
+ getValues({ keys: BRANCH_LIFETIME_SETTING }).then(
settings => {
if (this.mounted) {
this.setState({
@@ -78,26 +83,29 @@ export default class App extends React.PureComponent<Props, State> {
return null;
}
- const messageKey = this.props.canAdmin
- ? 'project_branches.page.life_time.admin'
- : 'project_branches.page.life_time';
-
return (
<p className="page-description">
<FormattedMessage
- defaultMessage={translate(messageKey)}
- id={messageKey}
- values={{
- days: formatMeasure(this.state.branchLifeTime, 'INT'),
- settings: <Link to="/admin/settings">{translate('settings.page')}</Link>
- }}
+ defaultMessage={translate('project_branches.page.life_time')}
+ id="project_branches.page.life_time"
+ values={{ days: formatMeasure(this.state.branchLifeTime, 'INT') }}
/>
+ {this.props.canAdmin && (
+ <>
+ <br />
+ <FormattedMessage
+ defaultMessage={translate('project_branches.page.life_time.admin')}
+ id="project_branches.page.life_time.admin"
+ values={{ settings: <Link to="/admin/settings">{translate('settings.page')}</Link> }}
+ />
+ </>
+ )}
</p>
);
}
render() {
- const { branches, component, onBranchesChange } = this.props;
+ const { branchLikes, component, onBranchesChange } = this.props;
if (this.state.loading) {
return (
@@ -132,11 +140,15 @@ export default class App extends React.PureComponent<Props, State> {
</tr>
</thead>
<tbody>
- {sortBranchesAsTree(branches).map(branch => (
+ {sortBranchesAsTree(branchLikes).map(branchLike => (
<BranchRow
- branch={branch}
+ branchLike={branchLike}
component={component.key}
- key={branch.name}
+ isOrphan={
+ (isShortLivingBranch(branchLike) || isPullRequest(branchLike)) &&
+ branchLike.isOrphan
+ }
+ key={getBranchLikeKey(branchLike)}
onChange={onBranchesChange}
/>
))}
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/BranchRow.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/BranchRow.tsx
index 723998c7049..0d5e440a372 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/components/BranchRow.tsx
+++ b/server/sonar-web/src/main/js/apps/projectBranches/components/BranchRow.tsx
@@ -22,10 +22,16 @@ import * as classNames from 'classnames';
import DeleteBranchModal from './DeleteBranchModal';
import LeakPeriodForm from './LeakPeriodForm';
import RenameBranchModal from './RenameBranchModal';
-import { Branch } from '../../../app/types';
+import { BranchLike } from '../../../app/types';
import BranchStatus from '../../../components/common/BranchStatus';
import BranchIcon from '../../../components/icons-components/BranchIcon';
-import { isShortLivingBranch, isLongLivingBranch } from '../../../helpers/branches';
+import {
+ isShortLivingBranch,
+ isLongLivingBranch,
+ isMainBranch,
+ getBranchLikeDisplayName,
+ isPullRequest
+} from '../../../helpers/branches';
import { translate } from '../../../helpers/l10n';
import DateFromNow from '../../../components/intl/DateFromNow';
import ActionsDropdown, {
@@ -34,8 +40,9 @@ import ActionsDropdown, {
} from '../../../components/controls/ActionsDropdown';
interface Props {
- branch: Branch;
+ branchLike: BranchLike;
component: string;
+ isOrphan?: boolean;
onChange: () => void;
}
@@ -91,19 +98,19 @@ export default class BranchRow extends React.PureComponent<Props, State> {
};
renderActions() {
- const { branch, component } = this.props;
+ const { branchLike, component } = this.props;
return (
<td className="thin nowrap text-right">
<ActionsDropdown className="ig-spacer-left">
- {isLongLivingBranch(branch) && (
+ {isLongLivingBranch(branchLike) && (
<ActionsDropdownItem
className="js-change-leak-period"
onClick={this.handleChangeLeakClick}>
{translate('branches.set_leak_period')}
</ActionsDropdownItem>
)}
- {isLongLivingBranch(branch) && !branch.isMain && <ActionsDropdownDivider />}
- {branch.isMain ? (
+ {isLongLivingBranch(branchLike) && <ActionsDropdownDivider />}
+ {isMainBranch(branchLike) ? (
<ActionsDropdownItem className="js-rename" onClick={this.handleRenameClick}>
{translate('branches.rename')}
</ActionsDropdownItem>
@@ -112,62 +119,65 @@ export default class BranchRow extends React.PureComponent<Props, State> {
className="js-delete"
destructive={true}
onClick={this.handleDeleteClick}>
- {translate('branches.delete')}
+ {translate(
+ isPullRequest(branchLike) ? 'branches.pull_request.delete' : 'branches.delete'
+ )}
</ActionsDropdownItem>
)}
</ActionsDropdown>
{this.state.deleting && (
<DeleteBranchModal
- branch={branch}
+ branchLike={branchLike}
component={component}
onClose={this.handleDeletingStop}
onDelete={this.handleChange}
/>
)}
- {this.state.renaming && (
- <RenameBranchModal
- branch={branch}
- component={component}
- onClose={this.handleRenamingStop}
- onRename={this.handleChange}
- />
- )}
+ {this.state.renaming &&
+ isMainBranch(branchLike) && (
+ <RenameBranchModal
+ branch={branchLike}
+ component={component}
+ onClose={this.handleRenamingStop}
+ onRename={this.handleChange}
+ />
+ )}
- {this.state.changingLeak && (
- <LeakPeriodForm
- branch={branch.name}
- onClose={this.handleChangingLeakStop}
- project={component}
- />
- )}
+ {this.state.changingLeak &&
+ isLongLivingBranch(branchLike) && (
+ <LeakPeriodForm
+ branch={branchLike.name}
+ onClose={this.handleChangingLeakStop}
+ project={component}
+ />
+ )}
</td>
);
}
render() {
- const { branch } = this.props;
+ const { branchLike, isOrphan } = this.props;
+ const indented = (isShortLivingBranch(branchLike) || isPullRequest(branchLike)) && !isOrphan;
return (
<tr>
<td>
<BranchIcon
- branch={branch}
- className={classNames('little-spacer-right', {
- 'big-spacer-left': isShortLivingBranch(branch) && !branch.isOrphan
- })}
+ branchLike={branchLike}
+ className={classNames('little-spacer-right', { 'big-spacer-left': indented })}
/>
- {branch.name}
- {branch.isMain && (
+ {getBranchLikeDisplayName(branchLike)}
+ {isMainBranch(branchLike) && (
<div className="outline-badge spacer-left">{translate('branches.main_branch')}</div>
)}
</td>
<td className="thin nowrap text-right">
- <BranchStatus branch={branch} />
+ <BranchStatus branchLike={branchLike} />
</td>
<td className="thin nowrap text-right">
- {branch.analysisDate && <DateFromNow date={branch.analysisDate} />}
+ {branchLike.analysisDate && <DateFromNow date={branchLike.analysisDate} />}
</td>
{this.renderActions()}
</tr>
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/DeleteBranchModal.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/DeleteBranchModal.tsx
index 127b3781189..c98bb190f65 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/components/DeleteBranchModal.tsx
+++ b/server/sonar-web/src/main/js/apps/projectBranches/components/DeleteBranchModal.tsx
@@ -18,14 +18,15 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { deleteBranch } from '../../../api/branches';
-import { Branch } from '../../../app/types';
+import { deleteBranch, deletePullRequest } from '../../../api/branches';
+import { BranchLike } from '../../../app/types';
import Modal from '../../../components/controls/Modal';
import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons';
import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { isPullRequest, getBranchLikeDisplayName } from '../../../helpers/branches';
interface Props {
- branch: Branch;
+ branchLike: BranchLike;
component: string;
onClose: () => void;
onDelete: () => void;
@@ -50,7 +51,16 @@ export default class DeleteBranchModal extends React.PureComponent<Props, State>
handleSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
event.preventDefault();
this.setState({ loading: true });
- deleteBranch(this.props.component, this.props.branch.name).then(
+ const request = isPullRequest(this.props.branchLike)
+ ? deletePullRequest({
+ project: this.props.component,
+ pullRequest: this.props.branchLike.key
+ })
+ : deleteBranch({
+ branch: this.props.branchLike.name,
+ project: this.props.component
+ });
+ request.then(
() => {
if (this.mounted) {
this.setState({ loading: false });
@@ -66,8 +76,10 @@ export default class DeleteBranchModal extends React.PureComponent<Props, State>
};
render() {
- const { branch } = this.props;
- const header = translate('branches.delete');
+ const { branchLike } = this.props;
+ const header = translate(
+ isPullRequest(branchLike) ? 'branches.pull_request.delete' : 'branches.delete'
+ );
return (
<Modal contentLabel={header} onRequestClose={this.props.onClose}>
@@ -76,7 +88,12 @@ export default class DeleteBranchModal extends React.PureComponent<Props, State>
</header>
<form onSubmit={this.handleSubmit}>
<div className="modal-body">
- {translateWithParameters('branches.delete.are_you_sure', branch.name)}
+ {translateWithParameters(
+ isPullRequest(branchLike)
+ ? 'branches.pull_request.delete.are_you_sure'
+ : 'branches.delete.are_you_sure',
+ getBranchLikeDisplayName(branchLike)
+ )}
</div>
<footer className="modal-foot">
{this.state.loading && <i className="spinner spacer-right" />}
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/LeakPeriodForm.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/LeakPeriodForm.tsx
index 71d4deaa53f..3bb22c48aed 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/components/LeakPeriodForm.tsx
+++ b/server/sonar-web/src/main/js/apps/projectBranches/components/LeakPeriodForm.tsx
@@ -53,7 +53,7 @@ export default class LeakPeriodForm extends React.PureComponent<Props, State> {
fetchSetting() {
this.setState({ loading: true });
- getValues(LEAK_PERIOD, this.props.project, this.props.branch).then(
+ getValues({ keys: LEAK_PERIOD, component: this.props.project, branch: this.props.branch }).then(
settings => {
if (this.mounted) {
this.setState({ loading: false, setting: settings[0] });
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/LongBranchesPattern.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/LongBranchesPattern.tsx
index e6d8b31d0f2..61c9e133c4c 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/components/LongBranchesPattern.tsx
+++ b/server/sonar-web/src/main/js/apps/projectBranches/components/LongBranchesPattern.tsx
@@ -48,7 +48,7 @@ export default class LongBranchesPattern extends React.PureComponent<Props, Stat
}
fetchSetting() {
- return getValues(LONG_BRANCH_PATTERN, this.props.project).then(
+ return getValues({ keys: LONG_BRANCH_PATTERN, component: this.props.project }).then(
settings => {
if (this.mounted) {
this.setState({ setting: settings[0] });
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/RenameBranchModal.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/RenameBranchModal.tsx
index bc78f0395cb..2e8ee656d44 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/components/RenameBranchModal.tsx
+++ b/server/sonar-web/src/main/js/apps/projectBranches/components/RenameBranchModal.tsx
@@ -19,13 +19,13 @@
*/
import * as React from 'react';
import { renameBranch } from '../../../api/branches';
-import { Branch } from '../../../app/types';
+import { MainBranch } from '../../../app/types';
import Modal from '../../../components/controls/Modal';
import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons';
import { translate } from '../../../helpers/l10n';
interface Props {
- branch: Branch;
+ branch: MainBranch;
component: string;
onClose: () => void;
onRename: () => void;
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/SettingForm.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/SettingForm.tsx
index 6c3b737dd4c..45533ab1da6 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/components/SettingForm.tsx
+++ b/server/sonar-web/src/main/js/apps/projectBranches/components/SettingForm.tsx
@@ -51,6 +51,12 @@ export default class SettingForm extends React.PureComponent<Props, State> {
this.mounted = false;
}
+ stopLoading = () => {
+ if (this.mounted) {
+ this.setState({ submitting: false });
+ }
+ };
+
handleSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
event.preventDefault();
@@ -65,11 +71,7 @@ export default class SettingForm extends React.PureComponent<Props, State> {
component: this.props.project,
key: this.props.setting.key,
value
- }).then(this.props.onChange, () => {
- if (this.mounted) {
- this.setState({ submitting: false });
- }
- });
+ }).then(this.props.onChange, this.stopLoading);
};
handleValueChange = (event: React.SyntheticEvent<HTMLInputElement>) => {
@@ -78,14 +80,11 @@ export default class SettingForm extends React.PureComponent<Props, State> {
handleResetClick = () => {
this.setState({ submitting: true });
- resetSettingValue(this.props.setting.key, this.props.project, this.props.branch).then(
- this.props.onChange,
- () => {
- if (this.mounted) {
- this.setState({ submitting: false });
- }
- }
- );
+ resetSettingValue({
+ keys: this.props.setting.key,
+ component: this.props.project,
+ branch: this.props.branch
+ }).then(this.props.onChange, this.stopLoading);
};
render() {
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/App-test.tsx
index e22b327bbc1..ee90b112e98 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/App-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/App-test.tsx
@@ -25,7 +25,13 @@ jest.mock('../../../../api/settings', () => ({
import * as React from 'react';
import { mount, shallow } from 'enzyme';
import App from '../App';
-import { BranchType, MainBranch, LongLivingBranch, ShortLivingBranch } from '../../../../app/types';
+import {
+ BranchType,
+ LongLivingBranch,
+ ShortLivingBranch,
+ MainBranch,
+ PullRequest
+} from '../../../../app/types';
const getValues = require('../../../../api/settings').getValues as jest.Mock<any>;
@@ -34,19 +40,27 @@ beforeEach(() => {
});
it('renders sorted list of branches', () => {
- const branches: [MainBranch, LongLivingBranch, ShortLivingBranch] = [
+ const branchLikes: [MainBranch, LongLivingBranch, ShortLivingBranch, PullRequest] = [
{ isMain: true, name: 'master' },
{ isMain: false, name: 'branch-1.0', type: BranchType.LONG },
- { isMain: false, name: 'branch-1.0', mergeBranch: 'master', type: BranchType.SHORT }
+ { isMain: false, mergeBranch: 'master', name: 'feature', type: BranchType.SHORT },
+ { base: 'master', branch: 'feature', key: '1234', title: 'Feature PR' }
];
const wrapper = shallow(
- <App branches={branches} component={{ key: 'foo' }} onBranchesChange={jest.fn()} />
+ <App
+ branchLikes={branchLikes}
+ canAdmin={true}
+ component={{ key: 'foo' }}
+ onBranchesChange={jest.fn()}
+ />
);
wrapper.setState({ branchLifeTime: '100', loading: false });
expect(wrapper).toMatchSnapshot();
});
it('fetches branch life time setting on mount', () => {
- mount(<App branches={[]} component={{ key: 'foo' }} onBranchesChange={jest.fn()} />);
- expect(getValues).toBeCalledWith('sonar.dbcleaner.daysBeforeDeletingInactiveShortLivingBranches');
+ mount(<App branchLikes={[]} component={{ key: 'foo' }} onBranchesChange={jest.fn()} />);
+ expect(getValues).toBeCalledWith({
+ keys: 'sonar.dbcleaner.daysBeforeDeletingInactiveShortLivingBranches'
+ });
});
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/BranchRow-test.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/BranchRow-test.tsx
index 8d7532033cc..eac799c7f6f 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/BranchRow-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/BranchRow-test.tsx
@@ -20,7 +20,13 @@
import * as React from 'react';
import { shallow } from 'enzyme';
import BranchRow from '../BranchRow';
-import { MainBranch, ShortLivingBranch, BranchType } from '../../../../app/types';
+import {
+ MainBranch,
+ ShortLivingBranch,
+ BranchType,
+ PullRequest,
+ BranchLike
+} from '../../../../app/types';
import { click } from '../../../../helpers/testUtils';
const mainBranch: MainBranch = { isMain: true, name: 'master' };
@@ -33,6 +39,13 @@ const shortBranch: ShortLivingBranch = {
type: BranchType.SHORT
};
+const pullRequest: PullRequest = {
+ base: 'master',
+ branch: 'feature',
+ key: '1234',
+ title: 'Feature PR'
+};
+
it('renders main branch', () => {
expect(shallowRender(mainBranch)).toMatchSnapshot();
});
@@ -41,6 +54,10 @@ it('renders short-living branch', () => {
expect(shallowRender(shortBranch)).toMatchSnapshot();
});
+it('renders pull request', () => {
+ expect(shallowRender(pullRequest)).toMatchSnapshot();
+});
+
it('renames main branch', () => {
const onChange = jest.fn();
const wrapper = shallowRender(mainBranch, onChange);
@@ -59,8 +76,19 @@ it('deletes short-living branch', () => {
expect(onChange).toBeCalled();
});
-function shallowRender(branch: MainBranch | ShortLivingBranch, onChange: () => void = jest.fn()) {
- const wrapper = shallow(<BranchRow branch={branch} component="foo" onChange={onChange} />);
+it('deletes pull request', () => {
+ const onChange = jest.fn();
+ const wrapper = shallowRender(pullRequest, onChange);
+
+ click(wrapper.find('.js-delete'));
+ (wrapper.find('DeleteBranchModal').prop('onDelete') as Function)();
+ expect(onChange).toBeCalled();
+});
+
+function shallowRender(branchLike: BranchLike, onChange: () => void = jest.fn()) {
+ const wrapper = shallow(
+ <BranchRow branchLike={branchLike} component="foo" isOrphan={false} onChange={onChange} />
+ );
(wrapper.instance() as any).mounted = true;
return wrapper;
}
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx
index 88fd8dffca7..16d7dfaf5cf 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx
@@ -18,42 +18,72 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/* eslint-disable import/first */
-jest.mock('../../../../api/branches', () => ({ deleteBranch: jest.fn() }));
+jest.mock('../../../../api/branches', () => ({
+ deleteBranch: jest.fn(),
+ deletePullRequest: jest.fn()
+}));
import * as React from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
import DeleteBranchModal from '../DeleteBranchModal';
-import { ShortLivingBranch, BranchType } from '../../../../app/types';
+import { ShortLivingBranch, BranchType, BranchLike, PullRequest } from '../../../../app/types';
import { submit, doAsync, click, waitAndUpdate } from '../../../../helpers/testUtils';
-import { deleteBranch } from '../../../../api/branches';
+import { deleteBranch, deletePullRequest } from '../../../../api/branches';
+
+const branch: ShortLivingBranch = {
+ isMain: false,
+ name: 'feature',
+ mergeBranch: 'master',
+ type: BranchType.SHORT
+};
beforeEach(() => {
- (deleteBranch as jest.Mock<any>).mockClear();
+ (deleteBranch as jest.Mock).mockClear();
+ (deletePullRequest as jest.Mock).mockClear();
});
it('renders', () => {
- const wrapper = shallowRender();
+ const wrapper = shallowRender(branch);
expect(wrapper).toMatchSnapshot();
wrapper.setState({ loading: true });
expect(wrapper).toMatchSnapshot();
});
it('deletes branch', async () => {
- (deleteBranch as jest.Mock<any>).mockImplementation(() => Promise.resolve());
+ (deleteBranch as jest.Mock).mockImplementationOnce(() => Promise.resolve());
+ const onDelete = jest.fn();
+ const wrapper = shallowRender(branch, onDelete);
+
+ submitForm(wrapper);
+
+ await waitAndUpdate(wrapper);
+ expect(wrapper.state().loading).toBe(false);
+ expect(onDelete).toBeCalled();
+ expect(deleteBranch).toBeCalledWith({ branch: 'feature', project: 'foo' });
+});
+
+it('deletes pull request', async () => {
+ (deletePullRequest as jest.Mock).mockImplementationOnce(() => Promise.resolve());
+ const pullRequest: PullRequest = {
+ base: 'master',
+ branch: 'feature',
+ key: '1234',
+ title: 'Feature PR'
+ };
const onDelete = jest.fn();
- const wrapper = shallowRender(onDelete);
+ const wrapper = shallowRender(pullRequest, onDelete);
submitForm(wrapper);
await waitAndUpdate(wrapper);
expect(wrapper.state().loading).toBe(false);
expect(onDelete).toBeCalled();
- expect(deleteBranch).toBeCalledWith('foo', 'feature');
+ expect(deletePullRequest).toBeCalledWith({ project: 'foo', pullRequest: '1234' });
});
it('cancels', () => {
const onClose = jest.fn();
- const wrapper = shallowRender(jest.fn(), onClose);
+ const wrapper = shallowRender(branch, jest.fn(), onClose);
click(wrapper.find('ResetButtonLink'));
@@ -63,27 +93,30 @@ it('cancels', () => {
});
it('stops loading on WS error', async () => {
- (deleteBranch as jest.Mock<any>).mockImplementation(() => Promise.reject(null));
+ (deleteBranch as jest.Mock).mockImplementationOnce(() => Promise.reject(null));
const onDelete = jest.fn();
- const wrapper = shallowRender(onDelete);
+ const wrapper = shallowRender(branch, onDelete);
submitForm(wrapper);
await waitAndUpdate(wrapper);
expect(wrapper.state().loading).toBe(false);
expect(onDelete).not.toBeCalled();
- expect(deleteBranch).toBeCalledWith('foo', 'feature');
+ expect(deleteBranch).toBeCalledWith({ branch: 'feature', project: 'foo' });
});
-function shallowRender(onDelete: () => void = jest.fn(), onClose: () => void = jest.fn()) {
- const branch: ShortLivingBranch = {
- isMain: false,
- name: 'feature',
- mergeBranch: 'master',
- type: BranchType.SHORT
- };
+function shallowRender(
+ branchLike: BranchLike,
+ onDelete: () => void = jest.fn(),
+ onClose: () => void = jest.fn()
+) {
const wrapper = shallow(
- <DeleteBranchModal branch={branch} component="foo" onClose={onClose} onDelete={onDelete} />
+ <DeleteBranchModal
+ branchLike={branchLike}
+ component="foo"
+ onClose={onClose}
+ onDelete={onDelete}
+ />
);
(wrapper.instance() as any).mounted = true;
return wrapper;
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/LongBranchesPattern-test.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/LongBranchesPattern-test.tsx
index 0e2a0bf65a7..5c19f9f989f 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/LongBranchesPattern-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/LongBranchesPattern-test.tsx
@@ -53,7 +53,10 @@ it('opens form', () => {
it('fetches setting value on mount', () => {
shallow(<LongBranchesPattern project="project" />);
- expect(getValues).lastCalledWith('sonar.branch.longLivedBranches.regex', 'project');
+ expect(getValues).lastCalledWith({
+ keys: 'sonar.branch.longLivedBranches.regex',
+ component: 'project'
+ });
});
it('fetches new setting value after change', () => {
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/SettingForm-test.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/SettingForm-test.tsx
index 6a107645fc0..4989daa203d 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/SettingForm-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/SettingForm-test.tsx
@@ -76,7 +76,11 @@ it('resets value', async () => {
expect(wrapper).toMatchSnapshot();
click(wrapper.find('Button'));
- expect(resetSettingValue).toBeCalledWith('foo', 'project', undefined);
+ expect(resetSettingValue).toBeCalledWith({
+ keys: 'foo',
+ component: 'project',
+ branch: undefined
+ });
await new Promise(setImmediate);
expect(onChange).toBeCalled();
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/App-test.tsx.snap
index 1c14e27cdbe..a9d71179c0c 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/App-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/App-test.tsx.snap
@@ -29,16 +29,27 @@ exports[`renders sorted list of branches 1`] = `
values={
Object {
"days": "100",
- "settings": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/admin/settings"
- >
- settings.page
- </Link>,
}
}
/>
+ <React.Fragment>
+ <br />
+ <FormattedMessage
+ defaultMessage="project_branches.page.life_time.admin"
+ id="project_branches.page.life_time.admin"
+ values={
+ Object {
+ "settings": <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/admin/settings"
+ >
+ settings.page
+ </Link>,
+ }
+ }
+ />
+ </React.Fragment>
</p>
</header>
<div
@@ -71,31 +82,45 @@ exports[`renders sorted list of branches 1`] = `
</thead>
<tbody>
<BranchRow
- branch={
+ branchLike={
Object {
"isMain": true,
"name": "master",
}
}
component="foo"
- key="master"
+ isOrphan={false}
+ key="branch-master"
+ onChange={[MockFunction]}
+ />
+ <BranchRow
+ branchLike={
+ Object {
+ "base": "master",
+ "branch": "feature",
+ "key": "1234",
+ "title": "Feature PR",
+ }
+ }
+ component="foo"
+ key="pull-request-1234"
onChange={[MockFunction]}
/>
<BranchRow
- branch={
+ branchLike={
Object {
"isMain": false,
"mergeBranch": "master",
- "name": "branch-1.0",
+ "name": "feature",
"type": "SHORT",
}
}
component="foo"
- key="branch-1.0"
+ key="branch-feature"
onChange={[MockFunction]}
/>
<BranchRow
- branch={
+ branchLike={
Object {
"isMain": false,
"name": "branch-1.0",
@@ -103,7 +128,8 @@ exports[`renders sorted list of branches 1`] = `
}
}
component="foo"
- key="branch-1.0"
+ isOrphan={false}
+ key="branch-branch-1.0"
onChange={[MockFunction]}
/>
</tbody>
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchRow-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchRow-test.tsx.snap
index 5a28f352855..d7e94ca3b2e 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchRow-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchRow-test.tsx.snap
@@ -4,7 +4,7 @@ exports[`renders main branch 1`] = `
<tr>
<td>
<BranchIcon
- branch={
+ branchLike={
Object {
"isMain": true,
"name": "master",
@@ -23,7 +23,7 @@ exports[`renders main branch 1`] = `
className="thin nowrap text-right"
>
<BranchStatus
- branch={
+ branchLike={
Object {
"isMain": true,
"name": "master",
@@ -51,11 +51,62 @@ exports[`renders main branch 1`] = `
</tr>
`;
+exports[`renders pull request 1`] = `
+<tr>
+ <td>
+ <BranchIcon
+ branchLike={
+ Object {
+ "base": "master",
+ "branch": "feature",
+ "key": "1234",
+ "title": "Feature PR",
+ }
+ }
+ className="little-spacer-right big-spacer-left"
+ />
+ 1234 – Feature PR
+ </td>
+ <td
+ className="thin nowrap text-right"
+ >
+ <BranchStatus
+ branchLike={
+ Object {
+ "base": "master",
+ "branch": "feature",
+ "key": "1234",
+ "title": "Feature PR",
+ }
+ }
+ />
+ </td>
+ <td
+ className="thin nowrap text-right"
+ />
+ <td
+ className="thin nowrap text-right"
+ >
+ <ActionsDropdown
+ className="ig-spacer-left"
+ >
+ <ActionsDropdownItem
+ className="js-delete"
+ destructive={true}
+ onClick={[Function]}
+ >
+ branches.pull_request.delete
+ </ActionsDropdownItem>
+ </ActionsDropdown>
+ </td>
+</tr>
+`;
+
exports[`renders short-living branch 1`] = `
<tr>
<td>
<BranchIcon
- branch={
+ branchLike={
Object {
"analysisDate": "2017-09-27T00:05:19+0000",
"isMain": false,
@@ -72,7 +123,7 @@ exports[`renders short-living branch 1`] = `
className="thin nowrap text-right"
>
<BranchStatus
- branch={
+ branchLike={
Object {
"analysisDate": "2017-09-27T00:05:19+0000",
"isMain": false,
diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Risk-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Risk-test.tsx.snap
index 505f5276139..733d95af43f 100644
--- a/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Risk-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Risk-test.tsx.snap
@@ -18,7 +18,6 @@ exports[`renders 1`] = `
"link": Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "foo",
},
},
diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/SimpleBubbleChart-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/SimpleBubbleChart-test.tsx.snap
index afb4e501021..b6e9c87e444 100644
--- a/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/SimpleBubbleChart-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/SimpleBubbleChart-test.tsx.snap
@@ -18,7 +18,6 @@ exports[`renders 1`] = `
"link": Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "foo",
},
},
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx
index b43fd2d96fa..2c58557ac4f 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx
@@ -57,8 +57,8 @@ export default class ProjectRowActions extends React.PureComponent<Props, State>
// call `getComponentNavigation` to check if user has the "Administer" permission
// call `getComponentShow` to check if user has the "Browse" permission
Promise.all([
- getComponentNavigation(this.props.project.key),
- getComponentShow(this.props.project.key)
+ getComponentNavigation({ componentKey: this.props.project.key }),
+ getComponentShow({ component: this.props.project.key })
]).then(
([navResponse]) => {
if (this.mounted) {
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 deec1e2e7fd..b0b662adaae 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
@@ -385,7 +385,6 @@ exports[`creates project 4`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "name",
},
}
diff --git a/server/sonar-web/src/main/js/apps/settings/store/actions.js b/server/sonar-web/src/main/js/apps/settings/store/actions.js
index 7865fd56313..dcbf19f63dd 100644
--- a/server/sonar-web/src/main/js/apps/settings/store/actions.js
+++ b/server/sonar-web/src/main/js/apps/settings/store/actions.js
@@ -50,10 +50,10 @@ export const fetchSettings = componentKey => dispatch => {
);
};
-export const fetchValues = (keys, componentKey) => dispatch =>
- getValues(keys, componentKey).then(
+export const fetchValues = (keys, component) => dispatch =>
+ getValues({ keys, component }).then(
settings => {
- dispatch(receiveValues(settings, componentKey));
+ dispatch(receiveValues(settings, component));
dispatch(closeAllGlobalMessages());
},
() => {}
@@ -73,7 +73,7 @@ export const saveValue = (key, componentKey) => (dispatch, getState) => {
}
return setSettingValue(definition, value, componentKey)
- .then(() => getValues(key, componentKey))
+ .then(() => getValues({ keys: key, component: componentKey }))
.then(values => {
dispatch(receiveValues(values, componentKey));
dispatch(cancelChange(key));
@@ -90,8 +90,8 @@ export const saveValue = (key, componentKey) => (dispatch, getState) => {
export const resetValue = (key, componentKey) => dispatch => {
dispatch(startLoading(key));
- return resetSettingValue(key, componentKey)
- .then(() => getValues(key, componentKey))
+ return resetSettingValue({ keys: key, component: componentKey })
+ .then(() => getValues({ keys: key, component: componentKey }))
.then(values => {
if (values.length > 0) {
dispatch(receiveValues(values, componentKey));
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx
index 6e487e2558a..101bc211a1a 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx
@@ -38,14 +38,16 @@ import {
getSources
} from '../../api/components';
import {
+ BranchLike,
+ DuplicatedFile,
Duplication,
FlowLocation,
Issue,
LinearIssueLocation,
SourceLine,
- SourceViewerFile,
- DuplicatedFile
+ SourceViewerFile
} from '../../app/types';
+import { isSameBranchLike, getBranchLikeQuery } from '../../helpers/branches';
import { parseDate } from '../../helpers/dates';
import { translate } from '../../helpers/l10n';
import './styles.css';
@@ -54,7 +56,7 @@ import './styles.css';
interface Props {
aroundLine?: number;
- branch: string | undefined;
+ branchLike: BranchLike | undefined;
component: string;
displayAllIssues?: boolean;
displayIssueLocationsCount?: boolean;
@@ -63,18 +65,21 @@ interface Props {
highlightedLine?: number;
highlightedLocations?: FlowLocation[];
highlightedLocationMessage?: { index: number; text: string };
- loadComponent?: (component: string, branch: string | undefined) => Promise<SourceViewerFile>;
+ loadComponent?: (
+ component: string,
+ branchLike: BranchLike | undefined
+ ) => Promise<SourceViewerFile>;
loadIssues?: (
component: string,
from: number,
to: number,
- branch: string | undefined
+ branchLike: BranchLike | undefined
) => Promise<Issue[]>;
loadSources?: (
component: string,
from: number,
to: number,
- branch: string | undefined
+ branchLike: BranchLike | undefined
) => Promise<SourceLine[]>;
onLoaded?: (component: SourceViewerFile, sources: SourceLine[], issues: Issue[]) => void;
onLocationSelect?: (index: number) => void;
@@ -162,7 +167,10 @@ export default class SourceViewerBase extends React.PureComponent<Props, State>
}
componentDidUpdate(prevProps: Props) {
- if (prevProps.component !== this.props.component || prevProps.branch !== this.props.branch) {
+ if (
+ prevProps.component !== this.props.component ||
+ !isSameBranchLike(prevProps.branchLike, this.props.branchLike)
+ ) {
this.fetchComponent();
} else if (
this.props.aroundLine !== undefined &&
@@ -220,7 +228,7 @@ export default class SourceViewerBase extends React.PureComponent<Props, State>
fetchComponent() {
this.setState({ loading: true });
const loadIssues = (component: SourceViewerFile, sources: SourceLine[]) => {
- this.safeLoadIssues(this.props.component, 1, LINES, this.props.branch).then(
+ this.safeLoadIssues(this.props.component, 1, LINES, this.props.branchLike).then(
issues => {
if (this.mounted) {
const finalSources = sources.slice(0, LINES);
@@ -284,7 +292,7 @@ export default class SourceViewerBase extends React.PureComponent<Props, State>
);
};
- this.safeLoadComponent(this.props.component, this.props.branch).then(
+ this.safeLoadComponent(this.props.component, this.props.branchLike).then(
onResolve,
onFailLoadComponent
);
@@ -324,7 +332,7 @@ export default class SourceViewerBase extends React.PureComponent<Props, State>
this.props.component,
firstSourceLine && firstSourceLine.line,
lastSourceLine && lastSourceLine.line,
- this.props.branch
+ this.props.branchLike
).then(
issues => {
if (this.mounted) {
@@ -364,7 +372,7 @@ export default class SourceViewerBase extends React.PureComponent<Props, State>
// request one additional line to define `hasSourcesAfter`
to++;
- return this.safeLoadSources(this.props.component, from, to, this.props.branch).then(
+ return this.safeLoadSources(this.props.component, from, to, this.props.branchLike).then(
sources => resolve(sources),
onFailLoadSources
);
@@ -382,14 +390,14 @@ export default class SourceViewerBase extends React.PureComponent<Props, State>
this.props.component,
from,
firstSourceLine.line - 1,
- this.props.branch
+ this.props.branchLike
).then(
sources => {
this.safeLoadIssues(
this.props.component,
from,
firstSourceLine.line - 1,
- this.props.branch
+ this.props.branchLike
).then(
issues => {
if (this.mounted) {
@@ -429,9 +437,9 @@ export default class SourceViewerBase extends React.PureComponent<Props, State>
const fromLine = lastSourceLine.line + 1;
// request one additional line to define `hasSourcesAfter`
const toLine = lastSourceLine.line + LINES + 1;
- this.safeLoadSources(this.props.component, fromLine, toLine, this.props.branch).then(
+ this.safeLoadSources(this.props.component, fromLine, toLine, this.props.branchLike).then(
sources => {
- this.safeLoadIssues(this.props.component, fromLine, toLine, this.props.branch).then(
+ this.safeLoadIssues(this.props.component, fromLine, toLine, this.props.branchLike).then(
issues => {
if (this.mounted) {
this.setState(prevState => {
@@ -469,7 +477,10 @@ export default class SourceViewerBase extends React.PureComponent<Props, State>
};
loadDuplications = (line: SourceLine) => {
- getDuplications(this.props.component, this.props.branch).then(
+ getDuplications({
+ key: this.props.component,
+ ...getBranchLikeQuery(this.props.branchLike)
+ }).then(
r => {
if (this.mounted) {
this.setState(() => {
@@ -615,7 +626,7 @@ export default class SourceViewerBase extends React.PureComponent<Props, State>
return (
<DuplicationPopup
blocks={blocks}
- branch={this.props.branch}
+ branchLike={this.props.branchLike}
duplicatedFiles={duplicatedFiles}
inRemovedComponent={inRemovedComponent}
onClose={this.closeLinePopup}
@@ -628,7 +639,7 @@ export default class SourceViewerBase extends React.PureComponent<Props, State>
const hasSourcesBefore = sources.length > 0 && sources[0].line > 1;
return (
<SourceViewerCode
- branch={this.props.branch}
+ branchLike={this.props.branchLike}
componentKey={this.props.component}
displayAllIssues={this.props.displayAllIssues}
displayIssueLocationsCount={this.props.displayIssueLocationsCount}
@@ -706,7 +717,10 @@ export default class SourceViewerBase extends React.PureComponent<Props, State>
return (
<div className={className} ref={node => (this.node = node)}>
{this.state.component && (
- <SourceViewerHeader branch={this.props.branch} sourceViewerFile={this.state.component} />
+ <SourceViewerHeader
+ branchLike={this.props.branchLike}
+ sourceViewerFile={this.state.component}
+ />
)}
{sourceRemoved && (
<div className="alert alert-warning spacer-top">
@@ -719,10 +733,10 @@ export default class SourceViewerBase extends React.PureComponent<Props, State>
}
}
-function defaultLoadComponent(key: string, branch: string | undefined) {
+function defaultLoadComponent(key: string, branchLike: BranchLike | undefined) {
return Promise.all([
- getComponentForSourceViewer(key, branch),
- getComponentData(key, branch)
+ getComponentForSourceViewer({ component: key, ...getBranchLikeQuery(branchLike) }),
+ getComponentData({ component: key, ...getBranchLikeQuery(branchLike) })
]).then(([component, data]) => ({
...component,
leakPeriodDate: data.leakPeriodDate && parseDate(data.leakPeriodDate)
@@ -733,7 +747,7 @@ function defaultLoadSources(
key: string,
from: number | undefined,
to: number | undefined,
- branch: string | undefined
+ branchLike: BranchLike | undefined
) {
- return getSources(key, from, to, branch);
+ return getSources({ key, from, to, ...getBranchLikeQuery(branchLike) });
}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.tsx b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.tsx
index 15db6dd0961..96bd3512e39 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.tsx
@@ -21,7 +21,14 @@ import * as React from 'react';
import { intersection } from 'lodash';
import Line from './components/Line';
import { getLinearLocations } from './helpers/issueLocations';
-import { Duplication, FlowLocation, Issue, LinearIssueLocation, SourceLine } from '../../app/types';
+import {
+ BranchLike,
+ Duplication,
+ FlowLocation,
+ Issue,
+ LinearIssueLocation,
+ SourceLine
+} from '../../app/types';
import { translate } from '../../helpers/l10n';
import { Button } from '../ui/buttons';
@@ -34,7 +41,7 @@ const ZERO_LINE = {
};
interface Props {
- branch: string | undefined;
+ branchLike: BranchLike | undefined;
componentKey: string;
displayAllIssues?: boolean;
displayIssueLocationsCount?: boolean;
@@ -153,7 +160,7 @@ export default class SourceViewerCode extends React.PureComponent<Props> {
return (
<Line
- branch={this.props.branch}
+ branchLike={this.props.branchLike}
componentKey={this.props.componentKey}
displayAllIssues={this.props.displayAllIssues}
displayCoverage={displayCoverage}
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 a29348b9312..0ba3cd6ec40 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx
@@ -17,24 +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 { stringify } from 'querystring';
import * as React from 'react';
import { Link } from 'react-router';
import MeasuresOverlay from './components/MeasuresOverlay';
-import { SourceViewerFile } from '../../app/types';
+import { SourceViewerFile, BranchLike } from '../../app/types';
import QualifierIcon from '../shared/QualifierIcon';
import FavoriteContainer from '../controls/FavoriteContainer';
import {
getPathUrlAsString,
- getProjectUrl,
+ getBranchLikeUrl,
getComponentIssuesUrl,
getBaseUrl
} from '../../helpers/urls';
import { collapsedDirFromPath, fileFromPath } from '../../helpers/path';
import { translate } from '../../helpers/l10n';
+import { getBranchLikeQuery } from '../../helpers/branches';
import { formatMeasure } from '../../helpers/measures';
+import { omitNil } from '../../helpers/request';
interface Props {
- branch: string | undefined;
+ branchLike: BranchLike | undefined;
sourceViewerFile: SourceViewerFile;
}
@@ -58,7 +61,7 @@ export default class SourceViewerHeader extends React.PureComponent<Props, State
event.preventDefault();
const { key } = this.props.sourceViewerFile;
const Workspace = require('../workspace/main').default;
- Workspace.openComponent({ key, branch: this.props.branch });
+ Workspace.openComponent({ key, branchLike: this.props.branchLike });
};
render() {
@@ -75,11 +78,10 @@ export default class SourceViewerHeader extends React.PureComponent<Props, State
} = this.props.sourceViewerFile;
const isUnitTest = q === 'UTS';
const workspace = false;
- let rawSourcesLink =
- getBaseUrl() + `/api/sources/raw?key=${encodeURIComponent(this.props.sourceViewerFile.key)}`;
- if (this.props.branch) {
- rawSourcesLink += `&branch=${encodeURIComponent(this.props.branch)}`;
- }
+ const rawSourcesLink =
+ getBaseUrl() +
+ '/api/sources/raw?' +
+ stringify(omitNil({ key, ...getBranchLikeQuery(this.props.branchLike) }));
// TODO favorite
return (
@@ -89,7 +91,7 @@ export default class SourceViewerHeader extends React.PureComponent<Props, State
<div className="component-name-parent">
<a
className="link-with-icon"
- href={getPathUrlAsString(getProjectUrl(project, this.props.branch))}>
+ href={getPathUrlAsString(getBranchLikeUrl(project, this.props.branchLike))}>
<QualifierIcon qualifier="TRK" /> <span>{projectName}</span>
</a>
</div>
@@ -98,7 +100,7 @@ export default class SourceViewerHeader extends React.PureComponent<Props, State
<div className="component-name-parent">
<a
className="link-with-icon"
- href={getPathUrlAsString(getProjectUrl(subProject, this.props.branch))}>
+ href={getPathUrlAsString(getBranchLikeUrl(subProject, this.props.branchLike))}>
<QualifierIcon qualifier="BRC" /> <span>{subProjectName}</span>
</a>
</div>
@@ -127,7 +129,7 @@ export default class SourceViewerHeader extends React.PureComponent<Props, State
</a>
{this.state.measuresOverlay && (
<MeasuresOverlay
- branch={this.props.branch}
+ branchLike={this.props.branchLike}
onClose={this.handleMeasuresOverlayClose}
sourceViewerFile={this.props.sourceViewerFile}
/>
@@ -138,7 +140,7 @@ export default class SourceViewerHeader extends React.PureComponent<Props, State
className="js-new-window"
href={getPathUrlAsString({
pathname: '/component',
- query: { branch: this.props.branch, id: this.props.sourceViewerFile.key }
+ query: { id: key, ...getBranchLikeQuery(this.props.branchLike) }
})}
target="_blank">
{translate('component_viewer.new_window')}
@@ -188,7 +190,7 @@ export default class SourceViewerHeader extends React.PureComponent<Props, State
to={getComponentIssuesUrl(project, {
resolved: 'false',
fileUuids: uuid,
- branch: this.props.branch
+ ...getBranchLikeQuery(this.props.branchLike)
})}>
{measures.issues != null ? formatMeasure(measures.issues, 'SHORT_INT') : 0}
</Link>
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/CoveragePopup.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/CoveragePopup.tsx
index 2edff725967..ef06788347e 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/CoveragePopup.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/CoveragePopup.tsx
@@ -20,14 +20,15 @@
import * as React from 'react';
import { groupBy } from 'lodash';
import { getTests } from '../../../api/components';
-import { SourceLine, TestCase } from '../../../app/types';
+import { BranchLike, SourceLine, TestCase } from '../../../app/types';
import BubblePopup from '../../common/BubblePopup';
import TestStatusIcon from '../../shared/TestStatusIcon';
+import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches';
import { translate } from '../../../helpers/l10n';
import { collapsePath } from '../../../helpers/path';
interface Props {
- branch: string | undefined;
+ branchLike: BranchLike | undefined;
componentKey: string;
line: SourceLine;
onClose: () => void;
@@ -49,9 +50,8 @@ export default class CoveragePopup extends React.PureComponent<Props, State> {
}
componentDidUpdate(prevProps: Props) {
- // TODO use branchLike
if (
- prevProps.branch !== this.props.branch ||
+ !isSameBranchLike(prevProps.branchLike, this.props.branchLike) ||
prevProps.componentKey !== this.props.componentKey ||
prevProps.line.line !== this.props.line.line
) {
@@ -65,7 +65,11 @@ export default class CoveragePopup extends React.PureComponent<Props, State> {
fetchTests = () => {
this.setState({ loading: true });
- getTests(this.props.componentKey, this.props.line.line, this.props.branch).then(
+ getTests({
+ sourceFileKey: this.props.componentKey,
+ sourceFileLineNumber: this.props.line.line,
+ ...getBranchLikeQuery(this.props.branchLike)
+ }).then(
testCases => {
if (this.mounted) {
this.setState({ loading: false, testCases });
@@ -84,7 +88,7 @@ export default class CoveragePopup extends React.PureComponent<Props, State> {
event.currentTarget.blur();
const { key } = event.currentTarget.dataset;
const Workspace = require('../../workspace/main').default;
- Workspace.openComponent({ key, branch: this.props.branch });
+ Workspace.openComponent({ key, branchLike: this.props.branchLike });
this.props.onClose();
};
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 9c58a3e01ad..d4b75a93b28 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
@@ -20,7 +20,7 @@
import * as React from 'react';
import { Link } from 'react-router';
import { groupBy, sortBy } from 'lodash';
-import { SourceViewerFile, DuplicationBlock, DuplicatedFile } from '../../../app/types';
+import { BranchLike, DuplicatedFile, DuplicationBlock, SourceViewerFile } from '../../../app/types';
import BubblePopup from '../../common/BubblePopup';
import QualifierIcon from '../../shared/QualifierIcon';
import { translate } from '../../../helpers/l10n';
@@ -29,8 +29,7 @@ import { getProjectUrl } from '../../../helpers/urls';
interface Props {
blocks: DuplicationBlock[];
- // TODO use branchLike
- branch: string | undefined;
+ branchLike: BranchLike | undefined;
duplicatedFiles?: { [ref: string]: DuplicatedFile };
inRemovedComponent: boolean;
onClose: () => void;
@@ -51,7 +50,7 @@ export default class DuplicationPopup extends React.PureComponent<Props> {
event.currentTarget.blur();
const Workspace = require('../../workspace/main').default;
const { key, line } = event.currentTarget.dataset;
- Workspace.openComponent({ key, line, branch: this.props.branch });
+ Workspace.openComponent({ key, line, branchLike: this.props.branchLike });
this.props.onClose();
};
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx
index a29255557dd..c6fef8178b3 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx
@@ -27,10 +27,10 @@ import LineDuplications from './LineDuplications';
import LineDuplicationBlock from './LineDuplicationBlock';
import LineIssuesIndicator from './LineIssuesIndicator';
import LineCode from './LineCode';
-import { Issue, LinearIssueLocation, SourceLine } from '../../../app/types';
+import { BranchLike, Issue, LinearIssueLocation, SourceLine } from '../../../app/types';
interface Props {
- branch: string | undefined;
+ branchLike: BranchLike | undefined;
componentKey: string;
displayAllIssues?: boolean;
displayCoverage: boolean;
@@ -121,7 +121,7 @@ export default class Line extends React.PureComponent<Props> {
return (
<tr className={className} data-line-number={line.line}>
<LineNumber
- branch={this.props.branch}
+ branchLike={this.props.branchLike}
componentKey={this.props.componentKey}
line={line}
onPopupToggle={this.props.onLinePopupToggle}
@@ -137,7 +137,7 @@ export default class Line extends React.PureComponent<Props> {
{this.props.displayCoverage && (
<LineCoverage
- branch={this.props.branch}
+ branchLike={this.props.branchLike}
componentKey={this.props.componentKey}
line={line}
onPopupToggle={this.props.onLinePopupToggle}
@@ -171,7 +171,7 @@ export default class Line extends React.PureComponent<Props> {
)}
<LineCode
- branch={this.props.branch}
+ branchLike={this.props.branchLike}
displayIssueLocationsCount={this.props.displayIssueLocationsCount}
displayIssueLocationsLink={this.props.displayIssueLocationsLink}
displayLocationMarkers={this.props.displayLocationMarkers}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx
index c91f58c43c2..9becbdeb876 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx
@@ -20,7 +20,7 @@
import * as React from 'react';
import * as classNames from 'classnames';
import LineIssuesList from './LineIssuesList';
-import { Issue, LinearIssueLocation, SourceLine } from '../../../app/types';
+import { BranchLike, Issue, LinearIssueLocation, SourceLine } from '../../../app/types';
import LocationIndex from '../../common/LocationIndex';
import LocationMessage from '../../common/LocationMessage';
import {
@@ -31,7 +31,7 @@ import {
} from '../helpers/highlight';
interface Props {
- branch: string | undefined;
+ branchLike: BranchLike | undefined;
displayIssueLocationsCount?: boolean;
displayIssueLocationsLink?: boolean;
displayLocationMarkers?: boolean;
@@ -231,7 +231,7 @@ export default class LineCode extends React.PureComponent<Props, State> {
{showIssues &&
issues.length > 0 && (
<LineIssuesList
- branch={this.props.branch}
+ branchLike={this.props.branchLike}
displayIssueLocationsCount={this.props.displayIssueLocationsCount}
displayIssueLocationsLink={this.props.displayIssueLocationsLink}
issuePopup={this.props.issuePopup}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineCoverage.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineCoverage.tsx
index c6b9efb895d..17d98a03eca 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineCoverage.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineCoverage.tsx
@@ -19,13 +19,13 @@
*/
import * as React from 'react';
import CoveragePopup from './CoveragePopup';
-import { SourceLine } from '../../../app/types';
+import { BranchLike, SourceLine } from '../../../app/types';
import Tooltip from '../../controls/Tooltip';
import { translate } from '../../../helpers/l10n';
import BubblePopupHelper from '../../common/BubblePopupHelper';
interface Props {
- branch: string | undefined;
+ branchLike: BranchLike | undefined;
componentKey: string;
line: SourceLine;
onPopupToggle: (x: { index?: number; line: number; name: string; open?: boolean }) => void;
@@ -49,7 +49,7 @@ export default class LineCoverage extends React.PureComponent<Props> {
};
render() {
- const { branch, componentKey, line, popupOpen } = this.props;
+ const { branchLike, componentKey, line, popupOpen } = this.props;
const className =
'source-meta source-line-coverage' +
@@ -80,7 +80,7 @@ export default class LineCoverage extends React.PureComponent<Props> {
isOpen={popupOpen}
popup={
<CoveragePopup
- branch={branch}
+ branchLike={branchLike}
componentKey={componentKey}
line={line}
onClose={this.closePopup}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesList.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesList.tsx
index 231cc639171..6e8cfca7732 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesList.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesList.tsx
@@ -18,11 +18,11 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { Issue as IssueType } from '../../../app/types';
+import { BranchLike, Issue as IssueType } from '../../../app/types';
import Issue from '../../issue/Issue';
interface Props {
- branch: string | undefined;
+ branchLike: BranchLike | undefined;
displayIssueLocationsCount?: boolean;
displayIssueLocationsLink?: boolean;
issuePopup: { issue: string; name: string } | undefined;
@@ -40,7 +40,7 @@ export default function LineIssuesList(props: Props) {
<div className="issue-list">
{props.issues.map(issue => (
<Issue
- branch={props.branch}
+ branchLike={props.branchLike}
displayLocationsCount={props.displayIssueLocationsCount}
displayLocationsLink={props.displayIssueLocationsLink}
issue={issue}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineNumber.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineNumber.tsx
index f6126f5e7ec..6d1f4940141 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineNumber.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineNumber.tsx
@@ -19,12 +19,11 @@
*/
import * as React from 'react';
import LineOptionsPopup from './LineOptionsPopup';
-import { SourceLine } from '../../../app/types';
+import { BranchLike, SourceLine } from '../../../app/types';
import BubblePopupHelper from '../../common/BubblePopupHelper';
interface Props {
- // TODO use branchLike
- branch: string | undefined;
+ branchLike: BranchLike | undefined;
componentKey: string;
line: SourceLine;
onPopupToggle: (x: { index?: number; line: number; name: string; open?: boolean }) => void;
@@ -44,7 +43,7 @@ export default class LineNumber extends React.PureComponent<Props> {
};
render() {
- const { branch, componentKey, line, popupOpen } = this.props;
+ const { branchLike, componentKey, line, popupOpen } = this.props;
const { line: lineNumber } = line;
const hasLineNumber = !!lineNumber;
return hasLineNumber ? (
@@ -57,7 +56,9 @@ export default class LineNumber extends React.PureComponent<Props> {
tabIndex={0}>
<BubblePopupHelper
isOpen={popupOpen}
- popup={<LineOptionsPopup branch={branch} componentKey={componentKey} line={line} />}
+ popup={
+ <LineOptionsPopup branchLike={branchLike} componentKey={componentKey} line={line} />
+ }
position="bottomright"
togglePopup={this.handleTogglePopup}
/>
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineOptionsPopup.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineOptionsPopup.tsx
index db4634b2c28..340304bb280 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineOptionsPopup.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineOptionsPopup.tsx
@@ -19,22 +19,22 @@
*/
import * as React from 'react';
import { Link } from 'react-router';
-import { SourceLine } from '../../../app/types';
+import { BranchLike, SourceLine } from '../../../app/types';
import BubblePopup from '../../common/BubblePopup';
import { translate } from '../../../helpers/l10n';
+import { getBranchLikeQuery } from '../../../helpers/branches';
interface Props {
- // TODO use branchLike
- branch: string | undefined;
+ branchLike: BranchLike | undefined;
componentKey: string;
line: SourceLine;
popupPosition?: any;
}
-export default function LineOptionsPopup({ branch, componentKey, line, popupPosition }: Props) {
+export default function LineOptionsPopup({ branchLike, componentKey, line, popupPosition }: Props) {
const permalink = {
pathname: '/component',
- query: { branch, id: componentKey, line: line.line }
+ query: { id: componentKey, line: line.line, ...getBranchLikeQuery(branchLike) }
};
return (
<BubblePopup customClass="source-viewer-bubble-popup" position={popupPosition}>
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 532e14ff758..11807298299 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
@@ -26,7 +26,7 @@ import { Button } from '../../../components/ui/buttons';
import { getFacets } from '../../../api/issues';
import { getMeasures } from '../../../api/measures';
import { getAllMetrics } from '../../../api/metrics';
-import { FacetValue, SourceViewerFile } from '../../../app/types';
+import { FacetValue, SourceViewerFile, BranchLike } from '../../../app/types';
import Modal from '../../controls/Modal';
import Measure from '../../measure/Measure';
import QualifierIcon from '../../shared/QualifierIcon';
@@ -42,10 +42,11 @@ import {
getDisplayMetrics,
enhanceMeasuresWithMetrics
} from '../../../helpers/measures';
-import { getProjectUrl } from '../../../helpers/urls';
+import { getBranchLikeUrl } from '../../../helpers/urls';
+import { getBranchLikeQuery } from '../../../helpers/branches';
interface Props {
- branch: string | undefined;
+ branchLike: BranchLike | undefined;
onClose: () => void;
sourceViewerFile: SourceViewerFile;
}
@@ -96,23 +97,25 @@ export default class MeasuresOverlay extends React.PureComponent<Props, State> {
const metricKeys = getDisplayMetrics(metrics).map(metric => metric.key);
// eslint-disable-next-line promise/no-nesting
- return getMeasures(this.props.sourceViewerFile.key, metricKeys, this.props.branch).then(
- measures => {
- const withMetrics = enhanceMeasuresWithMetrics(measures, metrics).filter(
- measure => measure.metric
- );
- return keyBy(withMetrics, measure => measure.metric.key);
- }
- );
+ return getMeasures({
+ componentKey: this.props.sourceViewerFile.key,
+ metricKeys: metricKeys.join(),
+ ...getBranchLikeQuery(this.props.branchLike)
+ }).then(measures => {
+ const withMetrics = enhanceMeasuresWithMetrics(measures, metrics).filter(
+ measure => measure.metric
+ );
+ return keyBy(withMetrics, measure => measure.metric.key);
+ });
});
};
fetchIssues = () => {
return getFacets(
{
- branch: this.props.branch,
componentKeys: this.props.sourceViewerFile.key,
- resolved: 'false'
+ resolved: 'false',
+ ...getBranchLikeQuery(this.props.branchLike)
},
['types', 'severities', 'tags']
).then(({ facets }) => {
@@ -383,7 +386,7 @@ export default class MeasuresOverlay extends React.PureComponent<Props, State> {
};
render() {
- const { branch, sourceViewerFile } = this.props;
+ const { branchLike, sourceViewerFile } = this.props;
const { loading } = this.state;
return (
@@ -392,14 +395,14 @@ export default class MeasuresOverlay extends React.PureComponent<Props, State> {
<div className="source-viewer-header-component source-viewer-measures-component">
<div className="source-viewer-header-component-project">
<QualifierIcon className="little-spacer-right" qualifier="TRK" />
- <Link to={getProjectUrl(sourceViewerFile.project, branch)}>
+ <Link to={getBranchLikeUrl(sourceViewerFile.project, branchLike)}>
{sourceViewerFile.projectName}
</Link>
{sourceViewerFile.subProject && (
<>
<QualifierIcon className="big-spacer-left little-spacer-right" qualifier="BRC" />
- <Link to={getProjectUrl(sourceViewerFile.subProject, branch)}>
+ <Link to={getBranchLikeUrl(sourceViewerFile.subProject, branchLike)}>
{sourceViewerFile.subProjectName}
</Link>
</>
@@ -419,7 +422,10 @@ export default class MeasuresOverlay extends React.PureComponent<Props, State> {
{sourceViewerFile.q === 'UTS' ? (
<>
{this.renderTests()}
- <MeasuresOverlayTestCases branch={branch} componentKey={sourceViewerFile.key} />
+ <MeasuresOverlayTestCases
+ branchLike={branchLike}
+ componentKey={sourceViewerFile.key}
+ />
</>
) : (
<div className="source-viewer-measures">
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlayTestCases.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlayTestCases.tsx
index 64dca78addc..8b2f880be51 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlayTestCases.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlayTestCases.tsx
@@ -23,11 +23,12 @@ import { orderBy } from 'lodash';
import MeasuresOverlayCoveredFiles from './MeasuresOverlayCoveredFiles';
import MeasuresOverlayTestCase from './MeasuresOverlayTestCase';
import { getTests } from '../../../api/tests';
-import { TestCase } from '../../../app/types';
+import { TestCase, BranchLike } from '../../../app/types';
import { translate } from '../../../helpers/l10n';
+import { getBranchLikeQuery } from '../../../helpers/branches';
interface Props {
- branch: string | undefined;
+ branchLike: BranchLike | undefined;
componentKey: string;
}
@@ -50,7 +51,7 @@ export default class MeasuresOverlayTestCases extends React.PureComponent<Props,
componentDidUpdate(prevProps: Props) {
if (
- prevProps.branch !== this.props.branch ||
+ prevProps.branchLike !== this.props.branchLike ||
prevProps.componentKey !== this.props.componentKey
) {
this.fetchTests();
@@ -64,7 +65,11 @@ export default class MeasuresOverlayTestCases extends React.PureComponent<Props,
fetchTests = () => {
// TODO implement pagination one day...
this.setState({ loading: true });
- getTests({ branch: this.props.branch, ps: 500, testFileKey: this.props.componentKey }).then(
+ getTests({
+ ps: 500,
+ testFileKey: this.props.componentKey,
+ ...getBranchLikeQuery(this.props.branchLike)
+ }).then(
({ tests: testCases }) => {
if (this.mounted) {
this.setState({ loading: false, testCases });
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.tsx
index a9127eae0ce..c9dc513d817 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.tsx
@@ -20,7 +20,7 @@
import * as React from 'react';
import { shallow } from 'enzyme';
import LineCode from '../LineCode';
-import { Issue } from '../../../../app/types';
+import { BranchType, Issue, ShortLivingBranch } from '../../../../app/types';
const issueBase: Issue = {
component: '',
@@ -50,9 +50,15 @@ it('render code', () => {
code: '<span class="k">class</span> <span class="sym sym-1">Foo</span> {'
};
const issueLocations = [{ from: 0, to: 5, line: 3 }];
+ const branch: ShortLivingBranch = {
+ isMain: false,
+ mergeBranch: 'master',
+ name: 'feature',
+ type: BranchType.SHORT
+ };
const wrapper = shallow(
<LineCode
- branch="feature"
+ branchLike={branch}
highlightedLocationMessage={undefined}
highlightedSymbols={['sym1']}
issueLocations={issueLocations}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCoverage-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCoverage-test.tsx
index a737504b3bd..efbe7ecfe67 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCoverage-test.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCoverage-test.tsx
@@ -27,7 +27,7 @@ it('render covered line', () => {
const line: SourceLine = { line: 3, coverageStatus: 'covered' };
const wrapper = shallow(
<LineCoverage
- branch={undefined}
+ branchLike={undefined}
componentKey="foo"
line={line}
onPopupToggle={jest.fn()}
@@ -42,7 +42,7 @@ it('render uncovered line', () => {
const line: SourceLine = { line: 3, coverageStatus: 'uncovered' };
const wrapper = shallow(
<LineCoverage
- branch={undefined}
+ branchLike={undefined}
componentKey="foo"
line={line}
onPopupToggle={jest.fn()}
@@ -56,7 +56,7 @@ it('render line with unknown coverage', () => {
const line: SourceLine = { line: 3 };
const wrapper = shallow(
<LineCoverage
- branch={undefined}
+ branchLike={undefined}
componentKey="foo"
line={line}
onPopupToggle={jest.fn()}
@@ -71,7 +71,7 @@ it('should open coverage popup', () => {
const onPopupToggle = jest.fn();
const wrapper = shallow(
<LineCoverage
- branch={undefined}
+ branchLike={undefined}
componentKey="foo"
line={line}
onPopupToggle={onPopupToggle}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesList-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesList-test.tsx
index 8ab04fcda21..7e9c9ec5bbe 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesList-test.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesList-test.tsx
@@ -49,7 +49,7 @@ it('render issues list', () => {
const onIssueClick = jest.fn();
const wrapper = shallow(
<LineIssuesList
- branch={undefined}
+ branchLike={undefined}
issuePopup={undefined}
issues={issues}
onIssueChange={jest.fn()}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineNumber-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineNumber-test.tsx
index bf40ca900c8..d027d938756 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineNumber-test.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineNumber-test.tsx
@@ -26,7 +26,7 @@ it('render line 3', () => {
const line = { line: 3 };
const wrapper = shallow(
<LineNumber
- branch={undefined}
+ branchLike={undefined}
componentKey="foo"
line={line}
onPopupToggle={jest.fn()}
@@ -41,7 +41,7 @@ it('render line 0', () => {
const line = { line: 0 };
const wrapper = shallow(
<LineNumber
- branch={undefined}
+ branchLike={undefined}
componentKey="foo"
line={line}
onPopupToggle={jest.fn()}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineOptionsPopup-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineOptionsPopup-test.tsx
index a5de2ec635e..5ada553fbd6 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineOptionsPopup-test.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineOptionsPopup-test.tsx
@@ -20,9 +20,16 @@
import * as React from 'react';
import { shallow } from 'enzyme';
import LineOptionsPopup from '../LineOptionsPopup';
+import { BranchType, ShortLivingBranch } from '../../../../app/types';
it('should render', () => {
const line = { line: 3 };
- const wrapper = shallow(<LineOptionsPopup branch="feature" componentKey="foo" line={line} />);
+ const branch: ShortLivingBranch = {
+ isMain: false,
+ mergeBranch: 'master',
+ name: 'feature',
+ type: BranchType.SHORT
+ };
+ const wrapper = shallow(<LineOptionsPopup branchLike={branch} componentKey="foo" line={line} />);
expect(wrapper).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/MeasuresOverlay-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/MeasuresOverlay-test.tsx
index 1f94f1375d3..3c4a20dd0e2 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/MeasuresOverlay-test.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/MeasuresOverlay-test.tsx
@@ -20,7 +20,7 @@
import * as React from 'react';
import { shallow } from 'enzyme';
import MeasuresOverlay from '../MeasuresOverlay';
-import { SourceViewerFile } from '../../../../app/types';
+import { SourceViewerFile, ShortLivingBranch, BranchType } from '../../../../app/types';
import { waitAndUpdate, click } from '../../../../helpers/testUtils';
jest.mock('../../../../api/issues', () => ({
@@ -148,9 +148,20 @@ const sourceViewerFile: SourceViewerFile = {
uuid: 'abcd123'
};
+const branchLike: ShortLivingBranch = {
+ isMain: false,
+ mergeBranch: 'master',
+ name: 'feature',
+ type: BranchType.SHORT
+};
+
it('should render source file', async () => {
const wrapper = shallow(
- <MeasuresOverlay branch="branch" onClose={jest.fn()} sourceViewerFile={sourceViewerFile} />
+ <MeasuresOverlay
+ branchLike={branchLike}
+ onClose={jest.fn()}
+ sourceViewerFile={sourceViewerFile}
+ />
);
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
@@ -162,7 +173,7 @@ it('should render source file', async () => {
it('should render test file', async () => {
const wrapper = shallow(
<MeasuresOverlay
- branch="branch"
+ branchLike={branchLike}
onClose={jest.fn()}
sourceViewerFile={{ ...sourceViewerFile, q: 'UTS' }}
/>
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/MeasuresOverlayTestCases-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/MeasuresOverlayTestCases-test.tsx
index 72226708c6f..1eacb341dc0 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/MeasuresOverlayTestCases-test.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/MeasuresOverlayTestCases-test.tsx
@@ -21,6 +21,7 @@ import * as React from 'react';
import { shallow } from 'enzyme';
import MeasuresOverlayTestCases from '../MeasuresOverlayTestCases';
import { waitAndUpdate, click } from '../../../../helpers/testUtils';
+import { ShortLivingBranch, BranchType } from '../../../../app/types';
jest.mock('../../../../api/tests', () => ({
getTests: () =>
@@ -60,9 +61,16 @@ jest.mock('../../../../api/tests', () => ({
})
}));
+const branchLike: ShortLivingBranch = {
+ isMain: false,
+ mergeBranch: 'master',
+ name: 'feature',
+ type: BranchType.SHORT
+};
+
it('should render', async () => {
const wrapper = shallow(
- <MeasuresOverlayTestCases branch="branch" componentKey="component-key" />
+ <MeasuresOverlayTestCases branchLike={branchLike} componentKey="component-key" />
);
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap
index f5e24cd2736..dc0f5c92200 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap
@@ -36,7 +36,14 @@ exports[`render code 1`] = `
</pre>
</div>
<LineIssuesList
- branch="feature"
+ branchLike={
+ Object {
+ "isMain": false,
+ "mergeBranch": "master",
+ "name": "feature",
+ "type": "SHORT",
+ }
+ }
issues={
Array [
Object {
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCoverage-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCoverage-test.tsx.snap
index 664a60ddf64..4e01802b363 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCoverage-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCoverage-test.tsx.snap
@@ -20,7 +20,7 @@ exports[`render covered line 1`] = `
isOpen={false}
popup={
<CoveragePopup
- branch={undefined}
+ branchLike={undefined}
componentKey="foo"
line={
Object {
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineNumber-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineNumber-test.tsx.snap
index 477d94d1c3f..2633afb6d2c 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineNumber-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineNumber-test.tsx.snap
@@ -18,7 +18,7 @@ exports[`render line 3 1`] = `
isOpen={false}
popup={
<LineOptionsPopup
- branch={undefined}
+ branchLike={undefined}
componentKey="foo"
line={
Object {
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 7378602ea6b..50bdfbabfcd 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
@@ -24,10 +24,11 @@ exports[`should render source file 1`] = `
style={Object {}}
to={
Object {
- "pathname": "/dashboard",
+ "pathname": "/project/issues",
"query": Object {
- "branch": "branch",
+ "branch": "feature",
"id": "project-key",
+ "resolved": "false",
},
}
}
@@ -44,10 +45,11 @@ exports[`should render source file 1`] = `
style={Object {}}
to={
Object {
- "pathname": "/dashboard",
+ "pathname": "/project/issues",
"query": Object {
- "branch": "branch",
+ "branch": "feature",
"id": "sub-project-key",
+ "resolved": "false",
},
}
}
@@ -401,10 +403,11 @@ exports[`should render source file 2`] = `
style={Object {}}
to={
Object {
- "pathname": "/dashboard",
+ "pathname": "/project/issues",
"query": Object {
- "branch": "branch",
+ "branch": "feature",
"id": "project-key",
+ "resolved": "false",
},
}
}
@@ -421,10 +424,11 @@ exports[`should render source file 2`] = `
style={Object {}}
to={
Object {
- "pathname": "/dashboard",
+ "pathname": "/project/issues",
"query": Object {
- "branch": "branch",
+ "branch": "feature",
"id": "sub-project-key",
+ "resolved": "false",
},
}
}
@@ -1384,10 +1388,11 @@ exports[`should render test file 1`] = `
style={Object {}}
to={
Object {
- "pathname": "/dashboard",
+ "pathname": "/project/issues",
"query": Object {
- "branch": "branch",
+ "branch": "feature",
"id": "project-key",
+ "resolved": "false",
},
}
}
@@ -1404,10 +1409,11 @@ exports[`should render test file 1`] = `
style={Object {}}
to={
Object {
- "pathname": "/dashboard",
+ "pathname": "/project/issues",
"query": Object {
- "branch": "branch",
+ "branch": "feature",
"id": "sub-project-key",
+ "resolved": "false",
},
}
}
@@ -1501,7 +1507,14 @@ exports[`should render test file 1`] = `
</div>
</div>
<MeasuresOverlayTestCases
- branch="branch"
+ branchLike={
+ Object {
+ "isMain": false,
+ "mergeBranch": "master",
+ "name": "feature",
+ "type": "SHORT",
+ }
+ }
componentKey="component-key"
/>
</React.Fragment>
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlayCoveredFiles-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlayCoveredFiles-test.tsx.snap
index c578435451a..43ac2de8f8e 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlayCoveredFiles-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlayCoveredFiles-test.tsx.snap
@@ -55,7 +55,6 @@ exports[`should render OK test 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
- "branch": undefined,
"id": "project:src/file.js",
},
}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/helpers/loadIssues.tsx b/server/sonar-web/src/main/js/components/SourceViewer/helpers/loadIssues.tsx
index 8354099e6f3..e23ce75a528 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/helpers/loadIssues.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/helpers/loadIssues.tsx
@@ -18,20 +18,21 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { searchIssues } from '../../../api/issues';
-import { Issue } from '../../../app/types';
+import { BranchLike, Issue } from '../../../app/types';
+import { getBranchLikeQuery } from '../../../helpers/branches';
import { parseIssueFromResponse } from '../../../helpers/issues';
import { RawQuery } from '../../../helpers/query';
// maximum possible value
const PAGE_SIZE = 500;
-function buildQuery(component: string, branch: string | undefined) {
+function buildQuery(component: string, branchLike: BranchLike | undefined) {
return {
additionalFields: '_all',
resolved: 'false',
componentKeys: component,
- branch,
- s: 'FILE_LINE'
+ s: 'FILE_LINE',
+ ...getBranchLikeQuery(branchLike)
};
}
@@ -75,9 +76,9 @@ export default function loadIssues(
component: string,
_fromLine: number,
toLine: number,
- branch: string | undefined
+ branchLike: BranchLike | undefined
): Promise<Issue[]> {
- const query = buildQuery(component, branch);
+ const query = buildQuery(component, branchLike);
return new Promise(resolve => {
loadPageAndNext(query, toLine, 1).then(issues => {
resolve(issues);
diff --git a/server/sonar-web/src/main/js/components/common/BranchStatus.css b/server/sonar-web/src/main/js/components/common/BranchStatus.css
index dcdb4bfb32d..492b5b861b6 100644
--- a/server/sonar-web/src/main/js/components/common/BranchStatus.css
+++ b/server/sonar-web/src/main/js/components/common/BranchStatus.css
@@ -18,7 +18,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
.branch-status {
- display: flex;
+ display: inline-flex;
+ justify-content: flex-end;
align-items: center;
min-width: 64px;
line-height: calc(2 * var(--gridSize));
diff --git a/server/sonar-web/src/main/js/components/common/BranchStatus.tsx b/server/sonar-web/src/main/js/components/common/BranchStatus.tsx
index 45b271f6179..d2e3f992973 100644
--- a/server/sonar-web/src/main/js/components/common/BranchStatus.tsx
+++ b/server/sonar-web/src/main/js/components/common/BranchStatus.tsx
@@ -19,27 +19,27 @@
*/
import * as React from 'react';
import StatusIndicator from './StatusIndicator';
-import { Branch } from '../../app/types';
import Level from '../ui/Level';
import BugIcon from '../icons-components/BugIcon';
import CodeSmellIcon from '../icons-components/CodeSmellIcon';
import VulnerabilityIcon from '../icons-components/VulnerabilityIcon';
-import { isShortLivingBranch } from '../../helpers/branches';
+import { BranchLike } from '../../app/types';
+import { isShortLivingBranch, isPullRequest, isLongLivingBranch } from '../../helpers/branches';
import './BranchStatus.css';
interface Props {
- branch: Branch;
+ branchLike: BranchLike;
concise?: boolean;
}
-export default function BranchStatus({ branch, concise = false }: Props) {
- if (isShortLivingBranch(branch)) {
- if (!branch.status) {
+export default function BranchStatus({ branchLike, concise = false }: Props) {
+ if (isShortLivingBranch(branchLike) || isPullRequest(branchLike)) {
+ if (!branchLike.status) {
return null;
}
const totalIssues =
- branch.status.bugs + branch.status.vulnerabilities + branch.status.codeSmells;
+ branchLike.status.bugs + branchLike.status.vulnerabilities + branchLike.status.codeSmells;
const indicatorColor = totalIssues > 0 ? 'red' : 'green';
@@ -56,24 +56,26 @@ export default function BranchStatus({ branch, concise = false }: Props) {
<StatusIndicator color={indicatorColor} size="small" />
</li>
<li className="spacer-left">
- {branch.status.bugs}
+ {branchLike.status.bugs}
<BugIcon />
</li>
<li className="spacer-left">
- {branch.status.vulnerabilities}
+ {branchLike.status.vulnerabilities}
<VulnerabilityIcon />
</li>
<li className="spacer-left">
- {branch.status.codeSmells}
+ {branchLike.status.codeSmells}
<CodeSmellIcon />
</li>
</ul>
);
- } else {
- if (!branch.status) {
+ } else if (isLongLivingBranch(branchLike)) {
+ if (!branchLike.status) {
return null;
}
- return <Level level={branch.status.qualityGateStatus} small={true} />;
+ return <Level level={branchLike.status.qualityGateStatus} small={true} />;
+ } else {
+ return null;
}
}
diff --git a/server/sonar-web/src/main/js/components/common/__tests__/BranchStatus-test.tsx b/server/sonar-web/src/main/js/components/common/__tests__/BranchStatus-test.tsx
index 3db595c0a2d..12aef459919 100644
--- a/server/sonar-web/src/main/js/components/common/__tests__/BranchStatus-test.tsx
+++ b/server/sonar-web/src/main/js/components/common/__tests__/BranchStatus-test.tsx
@@ -28,14 +28,14 @@ it('renders status of short-living branches', () => {
checkShort(7, 3, 6);
function checkShort(bugs: number, codeSmells: number, vulnerabilities: number) {
- const branch: ShortLivingBranch = {
+ const shortBranch: ShortLivingBranch = {
isMain: false,
mergeBranch: 'master',
name: 'foo',
status: { bugs, codeSmells, vulnerabilities },
type: BranchType.SHORT
};
- expect(shallow(<BranchStatus branch={branch} />)).toMatchSnapshot();
+ expect(shallow(<BranchStatus branchLike={shortBranch} />)).toMatchSnapshot();
}
});
@@ -53,6 +53,6 @@ it('renders status of long-living branches', () => {
if (qualityGateStatus) {
branch.status = { qualityGateStatus };
}
- return shallow(<BranchStatus branch={branch} />);
+ return shallow(<BranchStatus branchLike={branch} />);
}
});
diff --git a/server/sonar-web/src/main/js/components/icons-components/BranchIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/BranchIcon.tsx
index 915b6a0fbf5..9ff03913a0d 100644
--- a/server/sonar-web/src/main/js/components/icons-components/BranchIcon.tsx
+++ b/server/sonar-web/src/main/js/components/icons-components/BranchIcon.tsx
@@ -20,19 +20,21 @@
import * as React from 'react';
import ShortLivingBranchIcon from './ShortLivingBranchIcon';
import LongLivingBranchIcon from './LongLivingBranchIcon';
+import PullRequestIcon from './PullRequestIcon';
import { IconProps } from './types';
-// import PullRequestIcon from './PullRequestIcon';
-import { Branch } from '../../app/types';
-import { isShortLivingBranch } from '../../helpers/branches';
+import { BranchLike } from '../../app/types';
+import { isShortLivingBranch, isPullRequest } from '../../helpers/branches';
interface Props extends IconProps {
- branch: Branch;
+ branchLike: BranchLike;
}
-export default function BranchIcon({ branch, ...props }: Props) {
- return isShortLivingBranch(branch) ? (
- <ShortLivingBranchIcon {...props} />
- ) : (
- <LongLivingBranchIcon {...props} />
- );
+export default function BranchIcon({ branchLike, ...props }: Props) {
+ if (isPullRequest(branchLike)) {
+ return <PullRequestIcon {...props} />;
+ } else if (isShortLivingBranch(branchLike)) {
+ return <ShortLivingBranchIcon {...props} />;
+ } else {
+ return <LongLivingBranchIcon {...props} />;
+ }
}
diff --git a/server/sonar-web/src/main/js/components/icons-components/PullRequestIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/PullRequestIcon.tsx
index f0e94ac2fbf..0e7571a54dc 100644
--- a/server/sonar-web/src/main/js/components/icons-components/PullRequestIcon.tsx
+++ b/server/sonar-web/src/main/js/components/icons-components/PullRequestIcon.tsx
@@ -30,10 +30,11 @@ export default function PullRequestIcon({ className, fill = theme.blue, size = 1
viewBox="0 0 16 16"
version="1.1"
xmlnsXlink="http://www.w3.org/1999/xlink"
- xmlSpace="preserve">
+ xmlSpace="preserve"
+ style={{ fillRule: 'evenodd' }}>
<path
style={{ fill }}
- d="M3 11.9V4.1c.9-.2 1.5-1 1.5-1.9 0-1.1-.9-2-2-2s-2 .9-2 2c0 .9.6 1.7 1.5 1.9v7.8c-.9.2-1.5 1-1.5 1.9 0 1.1.9 2 2 2s2-.9 2-2c0-.9-.6-1.6-1.5-1.9zM1.5 2.2c0-.6.4-1 1-1s1 .4 1 1-.4 1-1 1-1-.4-1-1zm1 12.7c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1zM14 11.9V5.5c0-.1-.2-3.1-5.1-3.5L10.1.8 9.5.1 6.9 2.6l2.6 2.5.7-.7L8.8 3c4 .2 4.2 2.4 4.2 2.5v6.4c-.9.2-1.5 1-1.5 1.9 0 1.1.9 2 2 2s2-.9 2-2c0-.9-.6-1.6-1.5-1.9zm-.5 3c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1z"
+ d="M13,11.9L13,5.5C13,5.4 13.232,1.996 7.9,2L9.1,0.8L8.5,0.1L5.9,2.6L8.5,5.1L9.2,4.4L7.905,3.008C12.256,2.99 12,5.4 12,5.5L12,11.9C11.1,12.1 10.5,12.9 10.5,13.8C10.5,14.9 11.4,15.8 12.5,15.8C13.6,15.8 14.5,14.9 14.5,13.8C14.5,12.9 13.9,12.2 13,11.9ZM4,11.9C4.9,12.2 5.5,12.9 5.5,13.8C5.5,14.9 4.6,15.8 3.5,15.8C2.4,15.8 1.5,14.9 1.5,13.8C1.5,12.9 2.1,12.1 3,11.9L3,4.1C2.1,3.9 1.5,3.1 1.5,2.2C1.5,1.1 2.4,0.2 3.5,0.2C4.6,0.2 5.5,1.1 5.5,2.2C5.5,3.1 4.9,3.9 4,4.1L4,11.9ZM12.5,14.9C11.9,14.9 11.5,14.5 11.5,13.9C11.5,13.3 11.9,12.9 12.5,12.9C13.1,12.9 13.5,13.3 13.5,13.9C13.5,14.5 13.1,14.9 12.5,14.9ZM3.5,14.9C2.9,14.9 2.5,14.5 2.5,13.9C2.5,13.3 2.9,12.9 3.5,12.9C4.1,12.9 4.5,13.3 4.5,13.9C4.5,14.5 4.1,14.9 3.5,14.9ZM2.5,2.2C2.5,1.6 2.9,1.2 3.5,1.2C4.1,1.2 4.5,1.6 4.5,2.2C4.5,2.8 4.1,3.2 3.5,3.2C2.9,3.2 2.5,2.8 2.5,2.2Z"
/>
</svg>
);
diff --git a/server/sonar-web/src/main/js/components/issue/Issue.d.ts b/server/sonar-web/src/main/js/components/issue/Issue.d.ts
index 6930914f170..389a01a09a2 100644
--- a/server/sonar-web/src/main/js/components/issue/Issue.d.ts
+++ b/server/sonar-web/src/main/js/components/issue/Issue.d.ts
@@ -18,10 +18,10 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { Issue as IssueType } from '../../app/types';
+import { BranchLike, Issue as IssueType } from '../../app/types';
interface IssueProps {
- branch?: string;
+ branchLike?: BranchLike;
checked?: boolean;
displayLocationsCount?: boolean;
displayLocationsLink?: boolean;
diff --git a/server/sonar-web/src/main/js/components/issue/Issue.js b/server/sonar-web/src/main/js/components/issue/Issue.js
index 47bf09dab76..715d4a2d725 100644
--- a/server/sonar-web/src/main/js/components/issue/Issue.js
+++ b/server/sonar-web/src/main/js/components/issue/Issue.js
@@ -29,7 +29,7 @@ import { setIssueAssignee } from '../../api/issues';
/*::
type Props = {|
- branch?: string,
+ branchLike?: { id?: string; name: string },
checked?: boolean,
displayLocationsCount?: boolean;
displayLocationsLink?: boolean;
@@ -150,7 +150,7 @@ export default class Issue extends React.PureComponent {
render() {
return (
<IssueView
- branch={this.props.branch}
+ branchLike={this.props.branchLike}
checked={this.props.checked}
currentPopup={this.props.openPopup}
displayLocationsCount={this.props.displayLocationsCount}
diff --git a/server/sonar-web/src/main/js/components/issue/IssueView.js b/server/sonar-web/src/main/js/components/issue/IssueView.js
index b7a658e77ba..5f814a5347e 100644
--- a/server/sonar-web/src/main/js/components/issue/IssueView.js
+++ b/server/sonar-web/src/main/js/components/issue/IssueView.js
@@ -29,7 +29,7 @@ import { deleteIssueComment, editIssueComment } from '../../api/issues';
/*::
type Props = {|
- branch?: string,
+ branchLike?: { id?: string; name: string },
checked?: boolean,
currentPopup: ?string,
displayLocationsCount?: boolean;
@@ -90,7 +90,7 @@ export default class IssueView extends React.PureComponent {
role="listitem"
tabIndex={0}>
<IssueTitleBar
- branch={this.props.branch}
+ branchLike={this.props.branchLike}
currentPopup={this.props.currentPopup}
displayLocationsCount={this.props.displayLocationsCount}
displayLocationsLink={this.props.displayLocationsLink}
diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.js b/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.js
index 7a1cf1d5d67..13de79da203 100644
--- a/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.js
+++ b/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.js
@@ -26,6 +26,7 @@ import SimilarIssuesFilter from './SimilarIssuesFilter';
import LinkIcon from '../../../components/icons-components/LinkIcon';
import LocationIndex from '../../common/LocationIndex';
import Tooltip from '../../controls/Tooltip';
+import { getBranchLikeQuery } from '../../../helpers/branches';
import { getComponentIssuesUrl } from '../../../helpers/urls';
import { formatMeasure } from '../../../helpers/measures';
import { translate, translateWithParameters } from '../../../helpers/l10n';
@@ -33,7 +34,7 @@ import { translate, translateWithParameters } from '../../../helpers/l10n';
/*::
type Props = {|
- branch?: string,
+ branchLike?: { id?: string; name: string },
currentPopup: ?string,
displayLocationsCount?: boolean;
displayLocationsLink?: boolean;
@@ -68,7 +69,7 @@ export default function IssueTitleBar(props /*: Props */) {
const displayLocations = props.displayLocationsCount && locationsCount > 0;
const issueUrl = getComponentIssuesUrl(issue.project, {
- branch: props.branch,
+ ...getBranchLikeQuery(props.branchLike),
issues: issue.key,
open: issue.key
});
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTitleBar-test.js b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTitleBar-test.js
index f3efe9d2904..9134a0c1f79 100644
--- a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTitleBar-test.js
+++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTitleBar-test.js
@@ -48,7 +48,7 @@ const issueWithLocations = {
it('should render the titlebar correctly', () => {
const element = shallow(
<IssueTitleBar
- branch="feature-1.0"
+ branchLike={{ isMain: false, name: 'feature-1.0', type: 'SHORT' }}
issue={issue}
currentPopup={null}
onFail={jest.fn()}
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.js.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.js.snap
index d9f6c77f6b9..30fc9e84c33 100644
--- a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.js.snap
+++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.js.snap
@@ -160,7 +160,6 @@ exports[`should render the titlebar with the filter 1`] = `
Object {
"pathname": "/project/issues",
"query": Object {
- "branch": undefined,
"id": "myproject",
"issues": "AVsae-CQS-9G3txfbFN2",
"open": "AVsae-CQS-9G3txfbFN2",
diff --git a/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.d.ts b/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.d.ts
index 61de9d4f695..a903ad15af9 100644
--- a/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.d.ts
+++ b/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.d.ts
@@ -19,10 +19,10 @@
*/
import * as React from 'react';
import { History } from '../../api/time-machine';
-import { Metric } from '../../app/types';
+import { Metric, BranchLike } from '../../app/types';
interface Props {
- branch?: string;
+ branchLike?: BranchLike;
history?: History;
metrics: { [key: string]: Metric };
project: string;
diff --git a/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.js b/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.js
index 124b818a267..ff32a9df3c6 100644
--- a/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.js
+++ b/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.js
@@ -34,12 +34,13 @@ import {
} from '../../apps/projectActivity/utils';
import { getCustomGraph, getGraph } from '../../helpers/storage';
import { formatMeasure, getShortType } from '../../helpers/measures';
+import { getBranchLikeQuery } from '../../helpers/branches';
/*:: import type { Serie } from '../charts/AdvancedTimeline'; */
/*:: import type { History, Metric } from '../../apps/overview/types'; */
/*::
type Props = {
- branch?: string,
+ branchLike?: { id?: string; name: string },
history: ?History,
metrics: { [string]: Metric },
project: string,
@@ -144,7 +145,7 @@ export default class PreviewGraph extends React.PureComponent {
handleClick = () => {
this.context.router.push({
pathname: '/project/activity',
- query: { id: this.props.project, branch: this.props.branch }
+ query: { id: this.props.project, ...getBranchLikeQuery(this.props.branchLike) }
});
};
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 1065e91e9ef..ff5587d95b4 100644
--- a/server/sonar-web/src/main/js/components/shared/DrilldownLink.tsx
+++ b/server/sonar-web/src/main/js/components/shared/DrilldownLink.tsx
@@ -20,6 +20,8 @@
import * as React from 'react';
import { Link } from 'react-router';
import { getComponentDrilldownUrl, getComponentIssuesUrl } from '../../helpers/urls';
+import { BranchLike } from '../../app/types';
+import { getBranchLikeQuery } from '../../helpers/branches';
const ISSUE_MEASURES = [
'violations',
@@ -47,7 +49,7 @@ const ISSUE_MEASURES = [
];
interface Props {
- branch?: string;
+ branchLike?: BranchLike;
children?: React.ReactNode;
className?: string;
component: string;
@@ -121,7 +123,7 @@ export default class DrilldownLink extends React.PureComponent<Props> {
renderIssuesLink = () => {
const url = getComponentIssuesUrl(this.props.component, {
...this.propsToIssueParams(),
- branch: this.props.branch
+ ...getBranchLikeQuery(this.props.branchLike)
});
return (
@@ -139,7 +141,7 @@ export default class DrilldownLink extends React.PureComponent<Props> {
const url = getComponentDrilldownUrl(
this.props.component,
this.props.metric,
- this.props.branch
+ this.props.branchLike
);
return (
<Link to={url} className={this.props.className}>
diff --git a/server/sonar-web/src/main/js/components/workspace/views/viewer-view.js b/server/sonar-web/src/main/js/components/workspace/views/viewer-view.js
index 531db332616..eafcac4460e 100644
--- a/server/sonar-web/src/main/js/components/workspace/views/viewer-view.js
+++ b/server/sonar-web/src/main/js/components/workspace/views/viewer-view.js
@@ -49,7 +49,7 @@ export default BaseView.extend({
},
showViewer() {
- const { branch, key, line } = this.model.toJSON();
+ const { branchLike, key, line } = this.model.toJSON();
const el = document.querySelector(this.viewerRegion.el);
@@ -57,7 +57,7 @@ export default BaseView.extend({
<WithStore>
<SourceViewer
aroundLine={line}
- branch={branch}
+ branchLike={branchLike}
component={key}
fromWorkspace={true}
highlightedLine={line}
diff --git a/server/sonar-web/src/main/js/helpers/__tests__/branches-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/branches-test.ts
index 501ff66ed76..abf8bd87438 100644
--- a/server/sonar-web/src/main/js/helpers/__tests__/branches-test.ts
+++ b/server/sonar-web/src/main/js/helpers/__tests__/branches-test.ts
@@ -17,32 +17,39 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { sortBranchesAsTree } from '../branches';
-import { MainBranch, BranchType, ShortLivingBranch, LongLivingBranch } from '../../app/types';
+import { sortBranchesAsTree, isSameBranchLike } from '../branches';
+import {
+ MainBranch,
+ BranchType,
+ ShortLivingBranch,
+ LongLivingBranch,
+ PullRequest
+} from '../../app/types';
describe('#sortBranchesAsTree', () => {
it('sorts main branch and short-living branches', () => {
const main = mainBranch();
- const foo = shortLivingBranch('foo', 'master');
- const bar = shortLivingBranch('bar', 'master');
+ const foo = shortLivingBranch({ name: 'foo' });
+ const bar = shortLivingBranch({ name: 'bar' });
expect(sortBranchesAsTree([main, foo, bar])).toEqual([main, bar, foo]);
});
it('sorts main branch and long-living branches', () => {
const main = mainBranch();
- const foo = longLivingBranch('foo');
- const bar = longLivingBranch('bar');
+ const foo = longLivingBranch({ name: 'foo' });
+ const bar = longLivingBranch({ name: 'bar' });
expect(sortBranchesAsTree([main, foo, bar])).toEqual([main, bar, foo]);
});
it('sorts all types of branches', () => {
const main = mainBranch();
- const shortFoo = shortLivingBranch('shortFoo', 'master');
- const shortBar = shortLivingBranch('shortBar', 'longBaz');
- const shortPre = shortLivingBranch('shortPre', 'shortFoo');
- const longBaz = longLivingBranch('longBaz');
- const longQux = longLivingBranch('longQux');
- const longQwe = longLivingBranch('longQwe');
+ const shortFoo = shortLivingBranch({ name: 'shortFoo', mergeBranch: 'master' });
+ const shortBar = shortLivingBranch({ name: 'shortBar', mergeBranch: 'longBaz' });
+ const shortPre = shortLivingBranch({ name: 'shortPre', mergeBranch: 'shortFoo' });
+ const longBaz = longLivingBranch({ name: 'longBaz' });
+ const longQux = longLivingBranch({ name: 'longQux' });
+ const longQwe = longLivingBranch({ name: 'longQwe' });
+ const pr = pullRequest({ base: 'master' });
// - main - main
// - shortFoo - shortFoo
// - shortPre - shortPre
@@ -51,8 +58,43 @@ describe('#sortBranchesAsTree', () => {
// - longQwe - longQwe
// - longQux - longQux
expect(
- sortBranchesAsTree([main, shortFoo, shortBar, shortPre, longBaz, longQux, longQwe])
- ).toEqual([main, shortFoo, shortPre, longBaz, shortBar, longQux, longQwe]);
+ sortBranchesAsTree([main, shortFoo, shortBar, shortPre, longBaz, longQux, longQwe, pr])
+ ).toEqual([main, pr, shortFoo, shortPre, longBaz, shortBar, longQux, longQwe]);
+ });
+});
+
+describe('#isSameBranchLike', () => {
+ it('compares different kinds', () => {
+ const main = mainBranch();
+ const short = shortLivingBranch({ name: 'foo' });
+ const long = longLivingBranch({ name: 'foo' });
+ const pr = pullRequest();
+ expect(isSameBranchLike(main, pr)).toBeFalsy();
+ expect(isSameBranchLike(main, short)).toBeFalsy();
+ expect(isSameBranchLike(main, long)).toBeFalsy();
+ expect(isSameBranchLike(pr, short)).toBeFalsy();
+ expect(isSameBranchLike(pr, long)).toBeFalsy();
+ expect(isSameBranchLike(short, long)).toBeFalsy();
+ });
+
+ it('compares pull requests', () => {
+ expect(isSameBranchLike(pullRequest({ key: '1234' }), pullRequest({ key: '1234' }))).toBeTruthy();
+ expect(isSameBranchLike(pullRequest({ key: '1234' }), pullRequest({ key: '5678' }))).toBeFalsy();
+ });
+
+ it('compares branches', () => {
+ expect(
+ isSameBranchLike(longLivingBranch({ name: 'foo' }), longLivingBranch({ name: 'foo' }))
+ ).toBeTruthy();
+ expect(
+ isSameBranchLike(shortLivingBranch({ name: 'foo' }), shortLivingBranch({ name: 'foo' }))
+ ).toBeTruthy();
+ expect(
+ isSameBranchLike(longLivingBranch({ name: 'foo' }), longLivingBranch({ name: 'bar' }))
+ ).toBeFalsy();
+ expect(
+ isSameBranchLike(shortLivingBranch({ name: 'foo' }), shortLivingBranch({ name: 'bar' }))
+ ).toBeFalsy();
});
});
@@ -60,12 +102,31 @@ function mainBranch(): MainBranch {
return { isMain: true, name: 'master' };
}
-function shortLivingBranch(name: string, mergeBranch: string): ShortLivingBranch {
+function shortLivingBranch(overrides?: Partial<ShortLivingBranch>): ShortLivingBranch {
const status = { bugs: 0, codeSmells: 0, vulnerabilities: 0 };
- return { isMain: false, mergeBranch, name, status, type: BranchType.SHORT };
+ return {
+ isMain: false,
+ mergeBranch: 'master',
+ name: 'foo',
+ status,
+ type: BranchType.SHORT,
+ ...overrides
+ };
}
-function longLivingBranch(name: string): LongLivingBranch {
+function longLivingBranch(overrides?: Partial<LongLivingBranch>): LongLivingBranch {
const status = { qualityGateStatus: 'OK' };
- return { isMain: false, name, status, type: BranchType.LONG };
+ return { isMain: false, name: 'foo', status, type: BranchType.LONG, ...overrides };
+}
+
+function pullRequest(overrides?: Partial<PullRequest>): PullRequest {
+ const status = { bugs: 0, codeSmells: 0, vulnerabilities: 0 };
+ return {
+ base: 'master',
+ branch: 'feature',
+ key: '1234',
+ status,
+ title: 'Random Name',
+ ...overrides
+ };
}
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 ecd54ab1adb..75382073d32 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
@@ -44,8 +44,8 @@ afterEach(() => {
describe('#getPathUrlAsString', () => {
it('should return component url', () => {
- expect(getPathUrlAsString(getProjectUrl(SIMPLE_COMPONENT_KEY, 'branch:7.0'))).toBe(
- '/dashboard?id=' + SIMPLE_COMPONENT_KEY + '&branch=branch%3A7.0'
+ expect(getPathUrlAsString(getProjectUrl(SIMPLE_COMPONENT_KEY))).toBe(
+ '/dashboard?id=' + SIMPLE_COMPONENT_KEY
);
});
diff --git a/server/sonar-web/src/main/js/helpers/branches.ts b/server/sonar-web/src/main/js/helpers/branches.ts
index 195b5a95a0e..510ed8c3f85 100644
--- a/server/sonar-web/src/main/js/helpers/branches.ts
+++ b/server/sonar-web/src/main/js/helpers/branches.ts
@@ -18,43 +18,116 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { sortBy } from 'lodash';
-import { Branch, BranchType, ShortLivingBranch, LongLivingBranch } from '../app/types';
+import {
+ BranchLike,
+ Branch,
+ BranchType,
+ ShortLivingBranch,
+ LongLivingBranch,
+ PullRequest,
+ MainBranch,
+ BranchParameters
+} from '../app/types';
-export function isShortLivingBranch(branch?: Branch): branch is ShortLivingBranch {
- return branch !== undefined && !branch.isMain && branch.type === BranchType.SHORT;
+export function isBranch(branchLike?: BranchLike): branchLike is Branch {
+ return branchLike !== undefined && (branchLike as Branch).isMain !== undefined;
}
-export function isLongLivingBranch(branch?: Branch): branch is LongLivingBranch {
- return branch !== undefined && !branch.isMain && branch.type === BranchType.LONG;
+export function isShortLivingBranch(branchLike?: BranchLike): branchLike is ShortLivingBranch {
+ return (
+ isBranch(branchLike) &&
+ !branchLike.isMain &&
+ (branchLike as ShortLivingBranch).type === BranchType.SHORT
+ );
}
-export function getBranchName(branch?: Branch): string | undefined {
- return !branch || branch.isMain ? undefined : branch.name;
+export function isLongLivingBranch(branchLike?: BranchLike): branchLike is LongLivingBranch {
+ return (
+ isBranch(branchLike) &&
+ !branchLike.isMain &&
+ (branchLike as LongLivingBranch).type === BranchType.LONG
+ );
}
-export function sortBranchesAsTree(branches: Branch[]): Branch[] {
- const result: Branch[] = [];
+export function isMainBranch(branchLike?: BranchLike): branchLike is MainBranch {
+ return isBranch(branchLike) && branchLike.isMain;
+}
+
+export function isPullRequest(branchLike?: BranchLike): branchLike is PullRequest {
+ return branchLike !== undefined && (branchLike as PullRequest).key !== undefined;
+}
- const shortLivingBranches = branches.filter(isShortLivingBranch);
+export function getPullRequestDisplayName(pullRequest: PullRequest) {
+ return `${pullRequest.key} – ${pullRequest.title}`;
+}
+
+export function getBranchLikeDisplayName(branchLike: BranchLike) {
+ return isPullRequest(branchLike) ? getPullRequestDisplayName(branchLike) : branchLike.name;
+}
+
+export function getBranchLikeKey(branchLike: BranchLike) {
+ return isPullRequest(branchLike) ? `pull-request-${branchLike.key}` : `branch-${branchLike.name}`;
+}
+
+export function isSameBranchLike(a: BranchLike | undefined, b: BranchLike | undefined) {
+ // main branches are always equal
+ if (isMainBranch(a) && isMainBranch(b)) {
+ return true;
+ }
+
+ // short- and long-living branches are compared by type and name
+ if (
+ (isLongLivingBranch(a) && isLongLivingBranch(b)) ||
+ (isShortLivingBranch(a) && isShortLivingBranch(b))
+ ) {
+ return a.type === b.type && a.name === b.name;
+ }
+
+ // pull requests are compared by id
+ if (isPullRequest(a) && isPullRequest(b)) {
+ return a.key === b.key;
+ }
+
+ // finally if both parameters are `undefined`, consider them equal
+ return a === b;
+}
+
+export function sortBranchesAsTree(branchLikes: BranchLike[]) {
+ const result: BranchLike[] = [];
+
+ const mainBranch = branchLikes.find(isMainBranch);
+ const longLivingBranches = branchLikes.filter(isLongLivingBranch);
+ const shortLivingBranches = branchLikes.filter(isShortLivingBranch);
+ const pullRequests = branchLikes.filter(isPullRequest);
// main branch is always first
- const mainBranch = branches.find(branch => branch.isMain);
if (mainBranch) {
- result.push(mainBranch, ...getNestedShortLivingBranches(mainBranch.name));
+ result.push(
+ mainBranch,
+ ...getPullRequests(mainBranch.name),
+ ...getNestedShortLivingBranches(mainBranch.name)
+ );
}
- // the all long-living branches
- sortBy(branches.filter(isLongLivingBranch), 'name').forEach(longLivingBranch => {
- result.push(longLivingBranch, ...getNestedShortLivingBranches(longLivingBranch.name));
+ // then all long-living branches
+ sortBy(longLivingBranches, 'name').forEach(longLivingBranch => {
+ result.push(
+ longLivingBranch,
+ ...getPullRequests(longLivingBranch.name),
+ ...getNestedShortLivingBranches(longLivingBranch.name)
+ );
});
- // finally all orhpan branches
- result.push(...shortLivingBranches.filter(branch => branch.isOrphan));
+ // finally all orhpan pull requests and branches
+ result.push(
+ ...pullRequests.filter(pr => pr.isOrphan),
+ ...shortLivingBranches.filter(branch => branch.isOrphan)
+ );
return result;
/** Get all short-living branches (possibly nested) which should be merged to a given branch */
- function getNestedShortLivingBranches(mergeBranch: string): ShortLivingBranch[] {
+ function getNestedShortLivingBranches(mergeBranch: string) {
const found: ShortLivingBranch[] = shortLivingBranches.filter(
branch => branch.mergeBranch === mergeBranch
);
@@ -68,4 +141,18 @@ export function sortBranchesAsTree(branches: Branch[]): Branch[] {
return sortBy(found, 'name');
}
+
+ function getPullRequests(base: string) {
+ return pullRequests.filter(pr => pr.base === base);
+ }
+}
+
+export function getBranchLikeQuery(branchLike?: BranchLike): BranchParameters {
+ if (isShortLivingBranch(branchLike) || isLongLivingBranch(branchLike)) {
+ return { branch: branchLike.name };
+ } else if (isPullRequest(branchLike)) {
+ return { pullRequest: branchLike.key };
+ } else {
+ return {};
+ }
}
diff --git a/server/sonar-web/src/main/js/helpers/urls.ts b/server/sonar-web/src/main/js/helpers/urls.ts
index ae125724761..2f6b3140840 100644
--- a/server/sonar-web/src/main/js/helpers/urls.ts
+++ b/server/sonar-web/src/main/js/helpers/urls.ts
@@ -19,9 +19,14 @@
*/
import { stringify } from 'querystring';
import { omitBy, isNil } from 'lodash';
-import { isShortLivingBranch } from './branches';
+import {
+ isShortLivingBranch,
+ isPullRequest,
+ isLongLivingBranch,
+ getBranchLikeQuery
+} from './branches';
import { getProfilePath } from '../apps/quality-profiles/utils';
-import { Branch, HomePage, HomePageType } from '../app/types';
+import { BranchLike, HomePage, HomePageType } from '../app/types';
interface Query {
[x: string]: string | undefined;
@@ -44,8 +49,8 @@ export function getPathUrlAsString(path: Location): string {
return `${getBaseUrl()}${path.pathname}?${stringify(omitBy(path.query, isNil))}`;
}
-export function getProjectUrl(key: string, branch?: string): Location {
- return { pathname: '/dashboard', query: { id: key, branch } };
+export function getProjectUrl(project: string): Location {
+ return { pathname: '/dashboard', query: { id: project } };
}
export function getPortfolioUrl(key: string): Location {
@@ -56,19 +61,30 @@ export function getComponentBackgroundTaskUrl(componentKey: string, status?: str
return { pathname: '/project/background_tasks', query: { id: componentKey, status } };
}
-export function getProjectBranchUrl(key: string, branch: Branch): Location {
- if (isShortLivingBranch(branch)) {
- return {
- pathname: '/project/issues',
- query: { branch: branch.name, id: key, resolved: 'false' }
- };
- } else if (!branch.isMain) {
- return { pathname: '/dashboard', query: { branch: branch.name, id: key } };
+export function getBranchLikeUrl(project: string, branchLike?: BranchLike): Location {
+ if (isPullRequest(branchLike)) {
+ return getPullRequestUrl(project, branchLike.key);
+ } else if (isShortLivingBranch(branchLike)) {
+ return getShortLivingBranchUrl(project, branchLike.name);
+ } else if (isLongLivingBranch(branchLike)) {
+ return getLongLivingBranchUrl(project, branchLike.name);
} else {
- return { pathname: '/dashboard', query: { id: key } };
+ return getProjectUrl(project);
}
}
+export function getLongLivingBranchUrl(project: string, branch: string): Location {
+ return { pathname: '/dashboard', query: { branch, id: project } };
+}
+
+export function getShortLivingBranchUrl(project: string, branch: string): Location {
+ return { pathname: '/project/issues', query: { branch, id: project, resolved: 'false' } };
+}
+
+export function getPullRequestUrl(project: string, pullRequest: string): Location {
+ return { pathname: '/project/issues', query: { id: project, pullRequest, resolved: 'false' } };
+}
+
/**
* Generate URL for a global issues page
*/
@@ -90,25 +106,40 @@ export function getComponentIssuesUrl(componentKey: string, query?: Query): Loca
export function getComponentDrilldownUrl(
componentKey: string,
metric: string,
- branch?: string
+ branchLike?: BranchLike
): Location {
- return { pathname: '/component_measures', query: { id: componentKey, metric, branch } };
+ return {
+ pathname: '/component_measures',
+ query: { id: componentKey, metric, ...getBranchLikeQuery(branchLike) }
+ };
}
-export function getMeasureTreemapUrl(component: string, metric: string, branch?: string) {
+export function getMeasureTreemapUrl(component: string, metric: string) {
return {
pathname: '/component_measures',
- query: { id: component, metric, branch, view: 'treemap' }
+ query: { id: component, metric, view: 'treemap' }
+ };
+}
+
+export function getActivityUrl(component: string, branchLike?: BranchLike) {
+ return {
+ pathname: '/project/activity',
+ query: { id: component, ...getBranchLikeQuery(branchLike) }
};
}
/**
* Generate URL for a component's measure history
*/
-export function getMeasureHistoryUrl(component: string, metric: string, branch?: string) {
+export function getMeasureHistoryUrl(component: string, metric: string, branchLike?: BranchLike) {
return {
pathname: '/project/activity',
- query: { id: component, graph: 'custom', custom_metrics: metric, branch }
+ query: {
+ id: component,
+ graph: 'custom',
+ custom_metrics: metric,
+ ...getBranchLikeQuery(branchLike)
+ }
};
}
@@ -172,8 +203,8 @@ export function getMarkdownHelpUrl(): string {
return getBaseUrl() + '/markdown/help';
}
-export function getCodeUrl(project: string, branch?: string, selected?: string) {
- return { pathname: '/code', query: { id: project, branch, selected } };
+export function getCodeUrl(project: string, branchLike?: BranchLike, selected?: string) {
+ return { pathname: '/code', query: { id: project, ...getBranchLikeQuery(branchLike), selected } };
}
export function getOrganizationUrl(organization: string) {
@@ -185,7 +216,9 @@ export function getHomePageUrl(homepage: HomePage) {
case HomePageType.Application:
return getProjectUrl(homepage.component);
case HomePageType.Project:
- return getProjectUrl(homepage.component, homepage.branch);
+ return homepage.branch
+ ? getLongLivingBranchUrl(homepage.component, homepage.branch)
+ : getProjectUrl(homepage.component);
case HomePageType.Organization:
return getOrganizationUrl(homepage.organization);
case HomePageType.Portfolio:
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index 43d19d28015..f5b5fb14f02 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -489,10 +489,10 @@ deletion.page=Deletion
project_deletion.page.description=Delete this project. The operation cannot be undone.
portfolio_deletion.page.description=Delete this portfolio. Component projects and Local Reference Portfolios will not be deleted, but component Standard Portfolios will be deleted. This operation cannot be undone.
application_deletion.page.description=Delete this application. Application projects will not be deleted. This operation cannot be undone.
-project_branches.page=Branches
-project_branches.page.description=Use this page to manage project branches.
-project_branches.page.life_time=Short-lived branches are permanently deleted after {days} days without analysis.
-project_branches.page.life_time.admin=Short-lived branches are permanently deleted after {days} days without analysis. You can adjust this value globally in {settings}.
+project_branches.page=Branches & Pull Requests
+project_branches.page.description=Use this page to manage project branches and pull requests.
+project_branches.page.life_time=Short-lived branches and pull requests are permanently deleted after {days} days without analysis.
+project_branches.page.life_time.admin=You can adjust this value globally in {settings}.
#------------------------------------------------------------------------------
#
@@ -2632,11 +2632,13 @@ branches.learn_how_to_analyze=Learn how to analyze branches in SonarQube
branches.learn_how_to_analyze.text=Quickly setup branch analysis and get separate insights for each of your branches and pull requests.
branches.delete=Delete Branch
branches.delete.are_you_sure=Are you sure you want to delete branch "{0}"?
+branches.pull_request.delete=Delete Pull Request
+branches.pull_request.delete.are_you_sure=Are you sure you want to delete pull request "{0}"?
branches.rename=Rename Branch
branches.manage=Manage branches
branches.orphan_branch=Orphan Branch
-branches.orphan_branches=Orphan Branches
-branches.orphan_branches.tooltip=When a target branch of a short-living branch was deleted, this short-living branch becomes orphan.
+branches.orphan_branches=Orphan Branches & Pull Requests
+branches.orphan_branches.tooltip=When a target branch of a short-living branch or a base of a pull request was deleted, this short-living branch or pull request becomes orphan.
branches.main_branch=Main Branch
branches.branch_settings=Branch Settings
branches.long_living_branches_pattern=Long living branches pattern
@@ -2647,6 +2649,10 @@ branches.last_analysis_date=Last Analysis Date
branches.no_support.header=Get the most out of SonarQube with branches analysis
branches.no_support.header.text=Analyze each branch of your project separately with the Developer Edition.
branches.search_for_branches=Search for branches...
+branches.pull_requests=Pull Requests
+branches.short_lived_branches=Short-lived branches
+branches.pull_request.for_merge_into_x_from_y=for merge into {base} from {branch}
+branches.see_the_pr=See the PR
#------------------------------------------------------------------------------