Explorar el Código

SONAR-10374 Support pull request in the web app

tags/7.5
Teryk Bellahsene hace 6 años
padre
commit
913c82c877
Se han modificado 100 ficheros con 1256 adiciones y 817 borrados
  1. 16
    4
      server/sonar-web/src/main/js/api/branches.ts
  2. 19
    29
      server/sonar-web/src/main/js/api/components.ts
  3. 3
    7
      server/sonar-web/src/main/js/api/measures.ts
  4. 5
    2
      server/sonar-web/src/main/js/api/nav.ts
  5. 4
    8
      server/sonar-web/src/main/js/api/projectActivity.ts
  6. 15
    22
      server/sonar-web/src/main/js/api/settings.ts
  7. 11
    10
      server/sonar-web/src/main/js/api/tests.ts
  8. 19
    20
      server/sonar-web/src/main/js/api/time-machine.ts
  9. 40
    32
      server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
  10. 3
    3
      server/sonar-web/src/main/js/app/components/ProjectAdminContainer.tsx
  11. 23
    10
      server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
  12. 4
    0
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css
  13. 10
    7
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx
  14. 56
    35
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx
  15. 68
    41
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenu.tsx
  16. 25
    17
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenuItem.tsx
  17. 6
    6
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNavHeader.tsx
  18. 29
    75
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx
  19. 46
    30
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx
  20. 8
    1
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNav-test.tsx
  21. 42
    16
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx
  22. 27
    10
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenu-test.tsx
  23. 4
    3
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenuItem-test.tsx
  24. 14
    3
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavHeader-test.tsx
  25. 5
    5
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMenu-test.tsx
  26. 31
    3
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMeta-test.tsx
  27. 1
    1
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap
  28. 61
    4
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranch-test.tsx.snap
  29. 58
    21
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenu-test.tsx.snap
  30. 9
    9
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenuItem-test.tsx.snap
  31. 0
    2
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavHeader-test.tsx.snap
  32. 0
    39
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap
  33. 46
    1
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMeta-test.tsx.snap
  34. 0
    8
      server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.js.snap
  35. 28
    16
      server/sonar-web/src/main/js/app/types.ts
  36. 1
    1
      server/sonar-web/src/main/js/apps/about/actions.js
  37. 0
    1
      server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/ProjectNotifications-test.js.snap
  38. 1
    1
      server/sonar-web/src/main/js/apps/account/organizations/actions.ts
  39. 32
    2
      server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.tsx
  40. 0
    2
      server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/TaskComponent-test.tsx.snap
  41. 2
    0
      server/sonar-web/src/main/js/apps/background-tasks/types.ts
  42. 21
    16
      server/sonar-web/src/main/js/apps/code/components/App.tsx
  43. 4
    3
      server/sonar-web/src/main/js/apps/code/components/Breadcrumbs.tsx
  44. 6
    5
      server/sonar-web/src/main/js/apps/code/components/Component.tsx
  45. 5
    4
      server/sonar-web/src/main/js/apps/code/components/ComponentLink.tsx
  46. 6
    4
      server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx
  47. 6
    5
      server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx
  48. 5
    4
      server/sonar-web/src/main/js/apps/code/components/Components.tsx
  49. 16
    9
      server/sonar-web/src/main/js/apps/code/components/Search.tsx
  50. 46
    24
      server/sonar-web/src/main/js/apps/code/utils.ts
  51. 10
    10
      server/sonar-web/src/main/js/apps/component-measures/components/App.js
  52. 3
    2
      server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.js
  53. 9
    6
      server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.js
  54. 10
    9
      server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js
  55. 5
    5
      server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.js
  56. 1
    1
      server/sonar-web/src/main/js/apps/component-measures/components/MeasureFavoriteContainer.js
  57. 3
    3
      server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js
  58. 10
    8
      server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.js
  59. 5
    4
      server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.js
  60. 4
    1
      server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.js
  61. 0
    1
      server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.js.snap
  62. 3
    3
      server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.js
  63. 6
    5
      server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.js
  64. 3
    3
      server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.js
  65. 3
    3
      server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsListRow.js
  66. 2
    2
      server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.js
  67. 4
    4
      server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.js
  68. 19
    2
      server/sonar-web/src/main/js/apps/component/components/App.tsx
  69. 8
    1
      server/sonar-web/src/main/js/apps/component/components/__tests__/__snapshots__/App-test.tsx.snap
  70. 2
    2
      server/sonar-web/src/main/js/apps/issues/components/App.d.ts
  71. 19
    14
      server/sonar-web/src/main/js/apps/issues/components/App.js
  72. 14
    7
      server/sonar-web/src/main/js/apps/issues/components/ComponentBreadcrumbs.tsx
  73. 3
    3
      server/sonar-web/src/main/js/apps/issues/components/IssuesList.js
  74. 2
    2
      server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.js
  75. 4
    4
      server/sonar-web/src/main/js/apps/issues/components/ListItem.js
  76. 10
    1
      server/sonar-web/src/main/js/apps/issues/components/__tests__/ComponentBreadcrumbs-test.tsx
  77. 13
    13
      server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/ComponentBreadcrumbs-test.tsx.snap
  78. 5
    4
      server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx
  79. 8
    1
      server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx
  80. 4
    3
      server/sonar-web/src/main/js/apps/overview/badges/utils.ts
  81. 11
    11
      server/sonar-web/src/main/js/apps/overview/components/App.tsx
  82. 28
    27
      server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx
  83. 1
    1
      server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx
  84. 9
    11
      server/sonar-web/src/main/js/apps/overview/events/AnalysesList.tsx
  85. 38
    0
      server/sonar-web/src/main/js/apps/overview/events/__tests__/AnalysesList-test.tsx
  86. 18
    0
      server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/AnalysesList-test.tsx.snap
  87. 3
    3
      server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.tsx
  88. 8
    2
      server/sonar-web/src/main/js/apps/overview/main/CodeSmells.tsx
  89. 5
    5
      server/sonar-web/src/main/js/apps/overview/main/Coverage.tsx
  90. 5
    7
      server/sonar-web/src/main/js/apps/overview/main/Duplications.tsx
  91. 12
    11
      server/sonar-web/src/main/js/apps/overview/main/enhance.tsx
  92. 8
    6
      server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx
  93. 7
    4
      server/sonar-web/src/main/js/apps/overview/meta/MetaSize.tsx
  94. 7
    3
      server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGate.js
  95. 9
    4
      server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js
  96. 6
    4
      server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateConditions.js
  97. 0
    1
      server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGateProject-test.tsx.snap
  98. 0
    7
      server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGateCondition-test.js.snap
  99. 2
    2
      server/sonar-web/src/main/js/apps/portfolio/components/Activity.tsx
  100. 0
    0
      server/sonar-web/src/main/js/apps/portfolio/components/App.tsx

+ 16
- 4
server/sonar-web/src/main/js/api/branches.ts Ver fichero

@@ -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);
}

+ 19
- 29
server/sonar-web/src/main/js/api/components.ts Ver fichero

@@ -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);
}

+ 3
- 7
server/sonar-web/src/main/js/api/measures.ts Ver fichero

@@ -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 {

+ 5
- 2
server/sonar-web/src/main/js/api/nav.ts Ver fichero

@@ -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> {

+ 4
- 8
server/sonar-web/src/main/js/api/projectActivity.ts Ver fichero

@@ -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);
}


+ 15
- 22
server/sonar-web/src/main/js/api/settings.ts Ver fichero

@@ -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> {

+ 11
- 10
server/sonar-web/src/main/js/api/tests.ts Ver fichero

@@ -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);
}


+ 19
- 20
server/sonar-web/src/main/js/api/time-machine.ts Ver fichero

@@ -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);
});
}

+ 40
- 32
server/sonar-web/src/main/js/app/components/ComponentContainer.tsx Ver fichero

@@ -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,

+ 3
- 3
server/sonar-web/src/main/js/app/components/ProjectAdminContainer.tsx Ver fichero

@@ -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;

+ 23
- 10
server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx Ver fichero

@@ -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 () => {

+ 4
- 0
server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css Ver fichero

@@ -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;

+ 10
- 7
server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx Ver fichero

@@ -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}

+ 56
- 35
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranch.tsx Ver fichero

@@ -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>

+ 68
- 41
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenu.tsx Ver fichero

@@ -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>
);

+ 25
- 17
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBranchesMenuItem.tsx Ver fichero

@@ -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>

+ 6
- 6
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavHeader.tsx Ver fichero

@@ -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}
/>

+ 29
- 75
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx Ver fichero

@@ -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;
}


+ 46
- 30
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx Ver fichero

@@ -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)
});

+ 8
- 1
server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNav-test.tsx Ver fichero

@@ -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();
});

+ 42
- 16
server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranch-test.tsx Ver fichero

@@ -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();

+ 27
- 10
server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenu-test.tsx Ver fichero

@@ -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
};
}

+ 4
- 3
server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBranchesMenuItem-test.tsx Ver fichero

@@ -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}

+ 14
- 3
server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavHeader-test.tsx Ver fichero

@@ -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);
});

+ 5
- 5
server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMenu-test.tsx Ver fichero

@@ -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();

+ 31
- 3
server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMeta-test.tsx Ver fichero

@@ -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();
});

+ 1
- 1
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap Ver fichero

@@ -6,7 +6,7 @@ exports[`renders 1`] = `
id="context-navigation"
>
<Connect(ComponentNavHeader)
branches={Array []}
branchLikes={Array []}
component={
Object {
"breadcrumbs": Array [

+ 61
- 4
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranch-test.tsx.snap Ver fichero

@@ -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",

+ 58
- 21
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenu-test.tsx.snap Ver fichero

@@ -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}
/>

+ 9
- 9
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBranchesMenuItem-test.tsx.snap Ver fichero

@@ -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,

+ 0
- 2
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavHeader-test.tsx.snap Ver fichero

@@ -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",
},
}

+ 0
- 39
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap Ver fichero

@@ -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",
},
}

+ 46
- 1
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMeta-test.tsx.snap Ver fichero

@@ -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",

+ 0
- 8
server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.js.snap Ver fichero

@@ -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",
},
}

+ 28
- 16
server/sonar-web/src/main/js/app/types.ts Ver fichero

@@ -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;

+ 1
- 1
server/sonar-web/src/main/js/apps/about/actions.js Ver fichero

@@ -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));
});
};

+ 0
- 1
server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/ProjectNotifications-test.js.snap Ver fichero

@@ -23,7 +23,6 @@ exports[`should match snapshot 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "foo",
},
}

+ 1
- 1
server/sonar-web/src/main/js/apps/account/organizations/actions.ts Ver fichero

@@ -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));
});
};

+ 32
- 2
server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.tsx Ver fichero

@@ -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);
}
}

+ 0
- 2
server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/TaskComponent-test.tsx.snap Ver fichero

@@ -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",
},
}

+ 2
- 0
server/sonar-web/src/main/js/apps/background-tasks/types.ts Ver fichero

@@ -29,6 +29,8 @@ export interface Task {
hasScannerContext?: boolean;
id: string;
organization?: string;
pullRequest?: string;
pullRequestTitle?: string;
startedAt?: string;
status: string;
submittedAt: string;

+ 21
- 16
server/sonar-web/src/main/js/apps/code/components/App.tsx Ver fichero

@@ -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>

+ 4
- 3
server/sonar-web/src/main/js/apps/code/components/Breadcrumbs.tsx Ver fichero

@@ -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}

+ 6
- 5
server/sonar-web/src/main/js/apps/code/components/Component.tsx Ver fichero

@@ -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}

+ 5
- 4
server/sonar-web/src/main/js/apps/code/components/ComponentLink.tsx Ver fichero

@@ -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>
);

+ 6
- 4
server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx Ver fichero

@@ -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 });
}

+ 6
- 5
server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx Ver fichero

@@ -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 (

+ 5
- 4
server/sonar-web/src/main/js/apps/code/components/Components.tsx Ver fichero

@@ -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}

+ 16
- 9
server/sonar-web/src/main/js/apps/code/components/Search.tsx Ver fichero

@@ -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}

+ 46
- 24
server/sonar-web/src/main/js/apps/code/utils.ts Ver fichero

@@ -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);

+ 10
- 10
server/sonar-web/src/main/js/apps/component-measures/components/App.js Ver fichero

@@ -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}

+ 3
- 2
server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.js Ver fichero

@@ -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()))

+ 9
- 6
server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.js Ver fichero

@@ -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() {

+ 10
- 9
server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js Ver fichero

@@ -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}

+ 5
- 5
server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.js Ver fichero

@@ -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}

+ 1
- 1
server/sonar-web/src/main/js/apps/component-measures/components/MeasureFavoriteContainer.js Ver fichero

@@ -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);
});

+ 3
- 3
server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js Ver fichero

@@ -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>

+ 10
- 8
server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.js Ver fichero

@@ -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}

+ 5
- 4
server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.js Ver fichero

@@ -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}

+ 4
- 1
server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.js Ver fichero

@@ -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', () => {

+ 0
- 1
server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.js.snap Ver fichero

@@ -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",

+ 3
- 3
server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.js Ver fichero

@@ -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} />;
}
}

+ 6
- 5
server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.js Ver fichero

@@ -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>

+ 3
- 3
server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.js Ver fichero

@@ -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}

+ 3
- 3
server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsListRow.js Ver fichero

@@ -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} />


+ 2
- 2
server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.js Ver fichero

@@ -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}

+ 4
- 4
server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.js Ver fichero

@@ -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);

+ 19
- 2
server/sonar-web/src/main/js/apps/component/components/App.tsx Ver fichero

@@ -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}

+ 8
- 1
server/sonar-web/src/main/js/apps/component/components/__tests__/__snapshots__/App-test.tsx.snap Ver fichero

@@ -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]}

+ 2
- 2
server/sonar-web/src/main/js/apps/issues/components/App.d.ts Ver fichero

@@ -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>;

+ 19
- 14
server/sonar-web/src/main/js/apps/issues/components/App.js Ver fichero

@@ -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}

+ 14
- 7
server/sonar-web/src/main/js/apps/issues/components/ComponentBreadcrumbs.tsx Ver fichero

@@ -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>

+ 3
- 3
server/sonar-web/src/main/js/apps/issues/components/IssuesList.js Ver fichero

@@ -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}

+ 2
- 2
server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.js Ver fichero

@@ -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}

+ 4
- 4
server/sonar-web/src/main/js/apps/issues/components/ListItem.js Ver fichero

@@ -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}

+ 10
- 1
server/sonar-web/src/main/js/apps/issues/components/__tests__/ComponentBreadcrumbs-test.tsx Ver fichero

@@ -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();
});

+ 13
- 13
server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/ComponentBreadcrumbs-test.tsx.snap Ver fichero

@@ -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",
},
}
}

+ 5
- 4
server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx Ver fichero

@@ -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}>

+ 8
- 1
server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx Ver fichero

@@ -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();

+ 4
- 3
server/sonar-web/src/main/js/apps/overview/badges/utils.ts Ver fichero

@@ -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 })
)}`;
}
}

+ 11
- 11
server/sonar-web/src/main/js/apps/overview/components/App.tsx Ver fichero

@@ -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}
/>

+ 28
- 27
server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx Ver fichero

@@ -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}

+ 1
- 1
server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx Ver fichero

@@ -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({

+ 9
- 11
server/sonar-web/src/main/js/apps/overview/events/AnalysesList.tsx Ver fichero

@@ -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>

+ 38
- 0
server/sonar-web/src/main/js/apps/overview/events/__tests__/AnalysesList-test.tsx Ver fichero

@@ -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();
});

+ 18
- 0
server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/AnalysesList-test.tsx.snap Ver fichero

@@ -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>
`;

+ 3
- 3
server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.tsx Ver fichero

@@ -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>

+ 8
- 2
server/sonar-web/src/main/js/apps/overview/main/CodeSmells.tsx Ver fichero

@@ -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' });

+ 5
- 5
server/sonar-web/src/main/js/apps/overview/main/Coverage.tsx Ver fichero

@@ -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}>

+ 5
- 7
server/sonar-web/src/main/js/apps/overview/main/Duplications.tsx Ver fichero

@@ -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}>

+ 12
- 11
server/sonar-web/src/main/js/apps/overview/main/enhance.tsx Ver fichero

@@ -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>
);

+ 8
- 6
server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx Ver fichero

@@ -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>
);
}

+ 7
- 4
server/sonar-web/src/main/js/apps/overview/meta/MetaSize.tsx Ver fichero

@@ -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')}

+ 7
- 3
server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGate.js Ver fichero

@@ -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>
);

+ 9
- 4
server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js Ver fichero

@@ -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}

+ 6
- 4
server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateConditions.js Ver fichero

@@ -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);

+ 0
- 1
server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGateProject-test.tsx.snap Ver fichero

@@ -9,7 +9,6 @@ exports[`renders 1`] = `
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "foo",
},
}

+ 0
- 7
server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGateCondition-test.js.snap Ver fichero

@@ -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",

+ 2
- 2
server/sonar-web/src/main/js/apps/portfolio/components/Activity.tsx Ver fichero

@@ -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 = {};

+ 0
- 0
server/sonar-web/src/main/js/apps/portfolio/components/App.tsx Ver fichero


Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio

Cargando…
Cancelar
Guardar