From bdd14b31bcd4ff599dd2dffd133d80f43b8970c3 Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Thu, 24 Mar 2016 11:20:18 +0100 Subject: [PATCH] SONAR-7402 improve display of measures home page --- .../it/measure/ProjectMeasuresPageTest.java | 1 - .../should_display_measures_page.html | 12 +- .../should_drilldown_on_list_view.html | 2 +- .../should_drilldown_on_tree_view.html | 2 +- server/sonar-web/.eslintrc | 13 +- .../js/apps/code/components/SourceViewer.js | 1 - .../main/js/apps/component-measures/app.js | 14 +- .../js/apps/component-measures/app/actions.js | 5 + .../js/apps/component-measures/app/reducer.js | 7 +- .../component-measures/components/Measure.js | 49 ++++ .../bubbleChart/BubbleChart.js | 41 +-- .../MeasureBubbleChartContainer.js | 3 +- .../apps/component-measures/config/bubbles.js | 22 +- .../apps/component-measures/config/domains.js | 235 +++++++++++------- .../details/MeasureDetails.js | 27 +- .../details/MeasureDetailsContainer.js | 1 + .../details/MeasureDetailsHeader.js | 4 +- .../details/drilldown/ListView.js | 15 +- .../details/drilldown/MeasureDrilldown.js | 10 +- .../details/drilldown/TreeView.js | 15 +- .../component-measures/home/AllMeasures.js | 42 ++-- .../home/AllMeasuresContainer.js | 12 +- .../home/AllMeasuresDomain.js | 97 ++++---- .../component-measures/home/DomainMeasures.js | 84 +++++++ .../home/DomainMeasuresContainer.js | 42 ++++ .../js/apps/component-measures/home/Home.js | 78 ++++++ .../component-measures/home/HomeContainer.js | 44 ++++ .../component-measures/home/MainMeasures.js | 50 ++++ .../component-measures/home/MeasuresList.js | 58 +++++ .../apps/component-measures/home/reducer.js | 29 ++- .../main/js/apps/component-measures/hooks.js | 13 +- .../js/apps/component-measures/styles.css | 45 +++- .../main/js/apps/component-measures/utils.js | 4 +- .../src/main/js/components/shared/Level.js | 34 +++ server/sonar-web/src/main/js/helpers/urls.js | 2 +- .../src/main/less/components/ui.less | 58 +++++ .../app/controllers/drilldown_controller.rb | 2 +- server/sonar-web/tests/helpers/urls-test.js | 4 +- .../resources/org/sonar/l10n/core.properties | 4 +- 39 files changed, 891 insertions(+), 290 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/component-measures/components/Measure.js rename server/sonar-web/src/main/js/apps/component-measures/{details => components}/bubbleChart/BubbleChart.js (88%) rename server/sonar-web/src/main/js/apps/component-measures/{details => components}/bubbleChart/MeasureBubbleChartContainer.js (94%) create mode 100644 server/sonar-web/src/main/js/apps/component-measures/home/DomainMeasures.js create mode 100644 server/sonar-web/src/main/js/apps/component-measures/home/DomainMeasuresContainer.js create mode 100644 server/sonar-web/src/main/js/apps/component-measures/home/Home.js create mode 100644 server/sonar-web/src/main/js/apps/component-measures/home/HomeContainer.js create mode 100644 server/sonar-web/src/main/js/apps/component-measures/home/MainMeasures.js create mode 100644 server/sonar-web/src/main/js/apps/component-measures/home/MeasuresList.js create mode 100644 server/sonar-web/src/main/js/components/shared/Level.js diff --git a/it/it-tests/src/test/java/it/measure/ProjectMeasuresPageTest.java b/it/it-tests/src/test/java/it/measure/ProjectMeasuresPageTest.java index a84550085ec..461ed496c7c 100644 --- a/it/it-tests/src/test/java/it/measure/ProjectMeasuresPageTest.java +++ b/it/it-tests/src/test/java/it/measure/ProjectMeasuresPageTest.java @@ -20,7 +20,6 @@ package it.measure; import com.sonar.orchestrator.Orchestrator; -import com.sonar.orchestrator.build.SonarRunner; import com.sonar.orchestrator.build.SonarScanner; import com.sonar.orchestrator.selenium.Selenese; import it.Category1Suite; diff --git a/it/it-tests/src/test/resources/measure/ProjectMeasuresPageTest/should_display_measures_page.html b/it/it-tests/src/test/resources/measure/ProjectMeasuresPageTest/should_display_measures_page.html index 40992b13252..52c94cb562f 100644 --- a/it/it-tests/src/test/resources/measure/ProjectMeasuresPageTest/should_display_measures_page.html +++ b/it/it-tests/src/test/resources/measure/ProjectMeasuresPageTest/should_display_measures_page.html @@ -15,28 +15,28 @@ waitForElementPresent - id=measure-ncloc + id=measure-code_smells assertText - id=measure-ncloc - *13* + id=measure-code_smells-value + *0* assertText - id=measure-ncloc-leak + id=measure-code_smells-leak *+0* click - css=#measure-ncloc > a + css=#measure-code_smells > a waitForText content - *Lines of code*13* + *Code Smells*0* waitForText diff --git a/it/it-tests/src/test/resources/measure/ProjectMeasuresPageTest/should_drilldown_on_list_view.html b/it/it-tests/src/test/resources/measure/ProjectMeasuresPageTest/should_drilldown_on_list_view.html index ee439b1286c..d7d6085f671 100644 --- a/it/it-tests/src/test/resources/measure/ProjectMeasuresPageTest/should_drilldown_on_list_view.html +++ b/it/it-tests/src/test/resources/measure/ProjectMeasuresPageTest/should_drilldown_on_list_view.html @@ -10,7 +10,7 @@ open - /component_measures/ncloc/list?id=project-measures-page-test-project + /component_measures/metric/ncloc/list?id=project-measures-page-test-project diff --git a/it/it-tests/src/test/resources/measure/ProjectMeasuresPageTest/should_drilldown_on_tree_view.html b/it/it-tests/src/test/resources/measure/ProjectMeasuresPageTest/should_drilldown_on_tree_view.html index 399b22cf4d3..a654f8e5c0e 100644 --- a/it/it-tests/src/test/resources/measure/ProjectMeasuresPageTest/should_drilldown_on_tree_view.html +++ b/it/it-tests/src/test/resources/measure/ProjectMeasuresPageTest/should_drilldown_on_tree_view.html @@ -10,7 +10,7 @@ open - /component_measures/ncloc/tree?id=project-measures-page-test-project + /component_measures/metric/ncloc/tree?id=project-measures-page-test-project diff --git a/server/sonar-web/.eslintrc b/server/sonar-web/.eslintrc index 21fa38c27a1..b22ddbb9776 100644 --- a/server/sonar-web/.eslintrc +++ b/server/sonar-web/.eslintrc @@ -1,5 +1,9 @@ { - "extends": "eslint:recommended", + "extends": [ + "eslint:recommended", + "plugin:import/errors", + "plugin:import/warnings" + ], "ecmaFeatures": { "jsx": true, @@ -71,11 +75,6 @@ "react/prop-types": 0, "react/react-in-jsx-scope": 2, "react/self-closing-comp": 2, - "react/sort-comp": 1, - - "import/no-unresolved": 2, - "import/export": 2, - "import/no-duplicates": 2, - "import/imports-first": 2 + "react/sort-comp": 1 } } diff --git a/server/sonar-web/src/main/js/apps/code/components/SourceViewer.js b/server/sonar-web/src/main/js/apps/code/components/SourceViewer.js index 0f0431a9e3d..d79983625c6 100644 --- a/server/sonar-web/src/main/js/apps/code/components/SourceViewer.js +++ b/server/sonar-web/src/main/js/apps/code/components/SourceViewer.js @@ -60,7 +60,6 @@ export default class SourceViewer extends Component { if (period) { const periodDate = getPeriodDate(period); const periodLabel = getPeriodLabel(period); - console.log(periodDate, periodLabel); this.sourceViewer.filterLinesByDate(periodDate, periodLabel); } } diff --git a/server/sonar-web/src/main/js/apps/component-measures/app.js b/server/sonar-web/src/main/js/apps/component-measures/app.js index f3340803c41..264b835b95e 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/app.js +++ b/server/sonar-web/src/main/js/apps/component-measures/app.js @@ -24,17 +24,18 @@ import { createHistory } from 'history'; import { Provider } from 'react-redux'; import AppContainer from './app/AppContainer'; +import HomeContainer from './home/HomeContainer'; import AllMeasuresContainer from './home/AllMeasuresContainer'; +import DomainMeasuresContainer from './home/DomainMeasuresContainer'; import MeasureDetailsContainer from './details/MeasureDetailsContainer'; import ListViewContainer from './details/drilldown/ListViewContainer'; import TreeViewContainer from './details/drilldown/TreeViewContainer'; import MeasureHistoryContainer from './details/history/MeasureHistoryContainer'; -import MeasureBubbleChartContainer from './details/bubbleChart/MeasureBubbleChartContainer'; import MeasureTreemapContainer from './details/treemap/MeasureTreemapContainer'; import configureStore from './store/configureStore'; -import { checkHistoryExistence, checkBubbleChartExistence } from './hooks'; +import { checkHistoryExistence } from './hooks'; import './styles.css'; @@ -59,13 +60,16 @@ window.sonarqube.appStarted.then(options => { - - + + + + + + - diff --git a/server/sonar-web/src/main/js/apps/component-measures/app/actions.js b/server/sonar-web/src/main/js/apps/component-measures/app/actions.js index e38934a9bee..137b9592f72 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/app/actions.js +++ b/server/sonar-web/src/main/js/apps/component-measures/app/actions.js @@ -24,6 +24,7 @@ import { getMetrics } from '../../../api/metrics'; */ export const DISPLAY_HOME = 'app/DISPLAY_HOME'; +export const DISPLAY_DOMAIN = 'app/DISPLAY_DOMAIN'; export const RECEIVE_METRICS = 'app/RECEIVE_METRICS'; @@ -35,6 +36,10 @@ export function displayHome () { return { type: DISPLAY_HOME }; } +export function displayDomain (domainName) { + return { type: DISPLAY_DOMAIN, domainName }; +} + function receiveMetrics (metrics) { return { type: RECEIVE_METRICS, metrics }; } diff --git a/server/sonar-web/src/main/js/apps/component-measures/app/reducer.js b/server/sonar-web/src/main/js/apps/component-measures/app/reducer.js index 2484e2d0563..f481ff479a7 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/app/reducer.js +++ b/server/sonar-web/src/main/js/apps/component-measures/app/reducer.js @@ -17,14 +17,17 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { RECEIVE_METRICS } from './actions'; +import { DISPLAY_DOMAIN, RECEIVE_METRICS } from './actions'; const initialState = { - metrics: undefined + metrics: undefined, + lastDisplayedDomain: undefined }; export default function appReducer (state = initialState, action = {}) { switch (action.type) { + case DISPLAY_DOMAIN: + return { ...state, lastDisplayedDomain: action.domainName }; case RECEIVE_METRICS: return { ...state, metrics: action.metrics }; default: diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/Measure.js b/server/sonar-web/src/main/js/apps/component-measures/components/Measure.js new file mode 100644 index 00000000000..755d5eb20d9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/component-measures/components/Measure.js @@ -0,0 +1,49 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import React from 'react'; + +import { Rating } from '../../../components/shared/rating'; +import Level from '../../../components/shared/Level'; +import { formatMeasure } from '../../../helpers/measures'; +import { formatLeak, isDiffMetric } from '../utils'; + +const Measure = ({ measure, metric }) => { + const finalMetric = metric || measure.metric; + + if (finalMetric.type === 'RATING') { + return ; + } + + if (finalMetric.type === 'LEVEL') { + return ; + } + + const formattedValue = isDiffMetric(finalMetric) ? + formatLeak(measure.leak, finalMetric) : + formatMeasure(measure.value, finalMetric.type); + + return ( + + {formattedValue} + + ); +}; + +export default Measure; diff --git a/server/sonar-web/src/main/js/apps/component-measures/details/bubbleChart/BubbleChart.js b/server/sonar-web/src/main/js/apps/component-measures/components/bubbleChart/BubbleChart.js similarity index 88% rename from server/sonar-web/src/main/js/apps/component-measures/details/bubbleChart/BubbleChart.js rename to server/sonar-web/src/main/js/apps/component-measures/components/bubbleChart/BubbleChart.js index a43e93f53e7..2fcfb83778b 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/details/bubbleChart/BubbleChart.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/bubbleChart/BubbleChart.js @@ -19,7 +19,7 @@ */ import React from 'react'; -import Spinner from './../../components/Spinner'; +import Spinner from './../Spinner'; import { BubbleChart as OriginalBubbleChart } from '../../../../components/charts/bubble-chart'; import bubbles from '../../config/bubbles'; import { getComponentLeaves } from '../../../../api/components'; @@ -35,17 +35,12 @@ function getMeasure (component, metric) { export default class BubbleChart extends React.Component { state = { - fetching: true, + fetching: 0, files: [] }; componentWillMount () { - const { metric, metrics } = this.props; - const conf = bubbles[metric.key]; - - this.xMetric = metrics.find(m => m.key === conf.x); - this.yMetric = metrics.find(m => m.key === conf.y); - this.sizeMetric = metrics.find(m => m.key === conf.size); + this.updateMetrics(this.props); } componentDidMount () { @@ -53,8 +48,12 @@ export default class BubbleChart extends React.Component { this.fetchFiles(); } + componentWillUpdate (nextProps) { + this.updateMetrics(nextProps); + } + componentDidUpdate (nextProps) { - if (nextProps.metric !== this.props.metric) { + if (nextProps.domainName !== this.props.domainName) { this.fetchFiles(); } } @@ -63,6 +62,14 @@ export default class BubbleChart extends React.Component { this.mounted = false; } + updateMetrics (props) { + const { metrics, domainName } = props; + const conf = bubbles[domainName]; + this.xMetric = metrics.find(m => m.key === conf.x); + this.yMetric = metrics.find(m => m.key === conf.y); + this.sizeMetric = metrics.find(m => m.key === conf.size); + } + fetchFiles () { const { component } = this.props; const metrics = [this.xMetric.key, this.yMetric.key, this.sizeMetric.key]; @@ -73,6 +80,10 @@ export default class BubbleChart extends React.Component { ps: BUBBLES_LIMIT }; + if (this.mounted) { + this.setState({ fetching: this.state.fetching + 1 }); + } + getComponentLeaves(component.key, metrics, options).then(r => { const files = r.components.map(file => { const measures = {}; @@ -83,11 +94,13 @@ export default class BubbleChart extends React.Component { return { ...file, measures }; }); - this.setState({ - files, - fetching: false, - total: files.length - }); + if (this.mounted) { + this.setState({ + files, + fetching: this.state.fetching - 1, + total: files.length + }); + } }); } diff --git a/server/sonar-web/src/main/js/apps/component-measures/details/bubbleChart/MeasureBubbleChartContainer.js b/server/sonar-web/src/main/js/apps/component-measures/components/bubbleChart/MeasureBubbleChartContainer.js similarity index 94% rename from server/sonar-web/src/main/js/apps/component-measures/details/bubbleChart/MeasureBubbleChartContainer.js rename to server/sonar-web/src/main/js/apps/component-measures/components/bubbleChart/MeasureBubbleChartContainer.js index 0dba5dcada8..f219bfe01cc 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/details/bubbleChart/MeasureBubbleChartContainer.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/bubbleChart/MeasureBubbleChartContainer.js @@ -24,8 +24,7 @@ import MeasureBubbleChart from './BubbleChart'; const mapStateToProps = state => { return { component: state.app.component, - metrics: state.app.metrics, - metric: state.details.metric + metrics: state.app.metrics }; }; diff --git a/server/sonar-web/src/main/js/apps/component-measures/config/bubbles.js b/server/sonar-web/src/main/js/apps/component-measures/config/bubbles.js index bfbaed418b7..a9b0702c0a3 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/config/bubbles.js +++ b/server/sonar-web/src/main/js/apps/component-measures/config/bubbles.js @@ -18,23 +18,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ const bubblesConfig = { - 'code_smells': { x: 'ncloc', y: 'sqale_index', size: 'code_smells' }, - 'sqale_index': { x: 'ncloc', y: 'code_smells', size: 'sqale_index' }, - - 'coverage': { x: 'complexity', y: 'coverage', size: 'uncovered_lines' }, - 'it_coverage': { x: 'complexity', y: 'it_coverage', size: 'it_uncovered_lines' }, - 'overall_coverage': { x: 'complexity', y: 'overall_coverage', size: 'overall_uncovered_lines' }, - - 'uncovered_lines': { x: 'complexity', y: 'coverage', size: 'uncovered_lines' }, - 'it_uncovered_lines': { x: 'complexity', y: 'it_coverage', size: 'it_uncovered_lines' }, - 'overall_uncovered_lines': { x: 'complexity', y: 'overall_coverage', size: 'overall_uncovered_lines' }, - - 'uncovered_conditions': { x: 'complexity', y: 'coverage', size: 'uncovered_conditions' }, - 'it_uncovered_conditions': { x: 'complexity', y: 'it_coverage', size: 'it_uncovered_conditions' }, - 'overall_uncovered_conditions': { x: 'complexity', y: 'overall_coverage', size: 'overall_uncovered_conditions' }, - - 'duplicated_lines': { x: 'ncloc', y: 'duplicated_lines', size: 'duplicated_blocks' }, - 'duplicated_blocks': { x: 'ncloc', y: 'duplicated_lines', size: 'duplicated_blocks' } + 'Reliability': { x: 'ncloc', y: 'reliability_remediation_effort', size: 'bugs' }, + 'Security': { x: 'ncloc', y: 'security_remediation_effort', size: 'vulnerabilities' }, + 'Maintainability': { x: 'ncloc', y: 'sqale_index', size: 'code_smells' }, + 'Tests': { x: 'complexity', y: 'coverage', size: 'uncovered_lines' }, + 'Duplication': { x: 'ncloc', y: 'duplicated_lines', size: 'duplicated_blocks' } }; export default bubblesConfig; diff --git a/server/sonar-web/src/main/js/apps/component-measures/config/domains.js b/server/sonar-web/src/main/js/apps/component-measures/config/domains.js index 3e91106645a..931dbcdba57 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/config/domains.js +++ b/server/sonar-web/src/main/js/apps/component-measures/config/domains.js @@ -17,105 +17,156 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -export default { - 'Issues': [ - 'violations', - 'new_violations', - 'blocker_violations', - 'new_blocker_violations', - 'critical_violations', - 'new_critical_violations', - 'major_violations', - 'new_major_violations', - 'minor_violations', - 'new_minor_violations', - 'info_violations', - 'new_info_violations', - 'open_issues', - 'reopened_issues', - 'confirmed_issues', - 'false_positive_issues' - ], +export const domains = { + 'Reliability': { + main: [ + 'bugs', + 'new_bugs', + 'reliability_rating' + ], + order: [ + 'bugs', + 'new_bugs', + 'reliability_rating', + 'reliability_remediation_effort', + 'new_reliability_remediation_effort', + 'effort_to_reach_reliability_rating_a' + ] + }, - 'Maintainability': [ - 'code_smells', - 'new_code_smells', - 'sqale_index', - 'new_technical_debt', - 'sqale_rating', - 'sqale_debt_ratio', - 'new_sqale_debt_ratio', - 'effort_to_reach_maintainability_rating_a' - ], + 'Security': { + main: [ + 'vulnerabilities', + 'new_vulnerabilities', + 'security_rating' + ], + order: [ + 'vulnerabilities', + 'new_vulnerabilities', + 'security_remediation_effort', + 'new_security_remediation_effort', + 'effort_to_reach_security_rating_a' + ] + }, - 'Reliability': [ - 'bugs', - 'new_bugs', - 'reliability_rating', - 'reliability_remediation_effort', - 'new_reliability_remediation_effort', - 'effort_to_reach_reliability_rating_a' - ], + 'Maintainability': { + main: [ + 'code_smells', + 'new_code_smells', + 'sqale_index', + 'new_technical_debt', + 'sqale_rating' + ], + order: [ + 'code_smells', + 'new_code_smells', + 'sqale_index', + 'new_technical_debt', + 'sqale_debt_ratio', + 'new_sqale_debt_ratio', + 'effort_to_reach_maintainability_rating_a' + ] + }, - 'Security': [ - 'vulnerabilities', - 'new_vulnerabilities', - 'security_rating', - 'security_remediation_effort', - 'new_security_remediation_effort', - 'effort_to_reach_security_rating_a' - ], + 'Tests': { + main: [ + 'overall_coverage', + 'new_overall_coverage', + 'coverage', + 'new_coverage', + 'it_coverage', + 'new_it_coverage', + 'tests' + ], + order: [ + 'overall_coverage', + 'new_overall_coverage', + 'overall_line_coverage', + 'new_overall_line_coverage', + 'overall_branch_coverage', + 'new_overall_branch_coverage', + 'overall_uncovered_lines', + 'new_overall_uncovered_lines', + 'overall_uncovered_conditions', + 'new_overall_uncovered_conditions', + 'new_overall_lines_to_cover', - 'Size': [ - 'ncloc', - 'lines' - ], + 'coverage', + 'new_coverage', + 'line_coverage', + 'new_line_coverage', + 'branch_coverage', + 'new_branch_coverage', + 'uncovered_lines', + 'new_uncovered_lines', + 'uncovered_conditions', + 'new_uncovered_conditions', + 'new_lines_to_cover', - 'Tests': [ - 'overall_coverage', - 'new_overall_coverage', - 'overall_line_coverage', - 'new_overall_line_coverage', - 'overall_branch_coverage', - 'new_overall_branch_coverage', - 'overall_uncovered_lines', - 'new_overall_uncovered_lines', - 'overall_uncovered_conditions', - 'new_overall_uncovered_conditions', - 'new_overall_lines_to_cover', + 'it_coverage', + 'new_it_coverage', + 'it_line_coverage', + 'new_it_line_coverage', + 'it_branch_coverage', + 'new_it_branch_coverage', + 'it_uncovered_lines', + 'new_it_uncovered_lines', + 'it_uncovered_conditions', + 'new_it_uncovered_conditions', + 'new_it_lines_to_cover', - 'coverage', - 'new_coverage', - 'line_coverage', - 'new_line_coverage', - 'branch_coverage', - 'new_branch_coverage', - 'uncovered_lines', - 'new_uncovered_lines', - 'uncovered_conditions', - 'new_uncovered_conditions', - 'new_lines_to_cover', + 'lines_to_cover', - 'it_coverage', - 'new_it_coverage', - 'it_line_coverage', - 'new_it_line_coverage', - 'it_branch_coverage', - 'new_it_branch_coverage', - 'it_uncovered_lines', - 'new_it_uncovered_lines', - 'it_uncovered_conditions', - 'new_it_uncovered_conditions', - 'new_it_lines_to_cover', + 'tests', + 'test_success', + 'test_errors', + 'test_failures', + 'skipped_tests', + 'test_success_density', + 'test_execution_time' + ] + }, - 'lines_to_cover', + 'Duplication': { + main: [ + 'duplicated_lines_density', + 'duplicated_blocks' + ], + order: [ + 'duplicated_lines_density', + 'duplicated_blocks', + 'duplicated_lines', + 'duplicated_files' + ] + }, - 'tests', - 'test_success', - 'test_errors', - 'test_failures', - 'skipped_tests', - 'test_success_density', - 'test_execution_time' - ] + 'Size': { + main: [ + 'ncloc', + 'files' + ], + order: [] + }, + + 'Issues': { + main: [], + order: [ + 'violations', + 'new_violations', + 'blocker_violations', + 'new_blocker_violations', + 'critical_violations', + 'new_critical_violations', + 'major_violations', + 'new_major_violations', + 'minor_violations', + 'new_minor_violations', + 'info_violations', + 'new_info_violations', + 'open_issues', + 'reopened_issues', + 'confirmed_issues', + 'false_positive_issues' + ] + } }; diff --git a/server/sonar-web/src/main/js/apps/component-measures/details/MeasureDetails.js b/server/sonar-web/src/main/js/apps/component-measures/details/MeasureDetails.js index 8cd30894cdd..93ccd1e32e0 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/details/MeasureDetails.js +++ b/server/sonar-web/src/main/js/apps/component-measures/details/MeasureDetails.js @@ -18,14 +18,13 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import React from 'react'; -import { IndexLink } from 'react-router'; +import { Link, IndexLink } from 'react-router'; import Spinner from './../components/Spinner'; import MeasureDetailsHeader from './MeasureDetailsHeader'; import MeasureDrilldown from './drilldown/MeasureDrilldown'; - import { getLeakPeriod, getPeriodDate, getPeriodLabel } from '../../../helpers/periods'; -import { translate } from '../../../helpers/l10n'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; export default class MeasureDetails extends React.Component { componentWillMount () { @@ -55,7 +54,7 @@ export default class MeasureDetails extends React.Component { } render () { - const { component, metric, secondaryMeasure, measure, periods, children } = this.props; + const { component, metric, secondaryMeasure, measure, periods, lastDisplayedDomain, children } = this.props; if (measure == null) { return ; @@ -68,12 +67,20 @@ export default class MeasureDetails extends React.Component { return (
- - {translate('component_measures.back_to_all_measures')} - + {lastDisplayedDomain ? ( + + {translateWithParameters('component_measures.back_to_domain_measures', lastDisplayedDomain)} + + ) : ( + + {translate('component_measures.back_to_all_measures')} + + )} { return { component: state.app.component, + lastDisplayedDomain: state.app.lastDisplayedDomain, metrics: state.app.metrics, metric: state.details.metric, measure: state.details.measure, diff --git a/server/sonar-web/src/main/js/apps/component-measures/details/MeasureDetailsHeader.js b/server/sonar-web/src/main/js/apps/component-measures/details/MeasureDetailsHeader.js index 13e3080a352..3020d94a017 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/details/MeasureDetailsHeader.js +++ b/server/sonar-web/src/main/js/apps/component-measures/details/MeasureDetailsHeader.js @@ -19,10 +19,10 @@ */ import React from 'react'; +import Measure from './../components/Measure'; import LanguageDistribution from './../components/LanguageDistribution'; import { ComplexityDistribution } from '../../overview/components/complexity-distribution'; import { formatLeak } from '../utils'; -import { formatMeasure } from '../../../helpers/measures'; import { translateWithParameters } from '../../../helpers/l10n'; import { TooltipsContainer } from '../../../components/mixins/tooltips-mixin'; @@ -41,7 +41,7 @@ export default function MeasureDetailsHeader ({ measure, metric, secondaryMeasur
{measure.value != null && (
- {formatMeasure(measure.value, metric.type)} +
)} diff --git a/server/sonar-web/src/main/js/apps/component-measures/details/drilldown/ListView.js b/server/sonar-web/src/main/js/apps/component-measures/details/drilldown/ListView.js index 97c25a1b723..1ade9e39ba4 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/details/drilldown/ListView.js +++ b/server/sonar-web/src/main/js/apps/component-measures/details/drilldown/ListView.js @@ -22,6 +22,7 @@ import classNames from 'classnames'; import ComponentsList from './ComponentsList'; import ListHeader from './ListHeader'; +import Spinner from '../../components/Spinner'; import SourceViewer from '../../../code/components/SourceViewer'; import ListFooter from '../../../../components/shared/list-footer'; @@ -104,11 +105,15 @@ export default class ListView extends React.Component { {!selected && (
- + {(!fetching || components.length !== 0) ? ( + + ) : ( + + )} + to={{ pathname: `metric/${metric.key}/list`, query: { id: component.key } }}> {translate('component_measures.tab.list')} @@ -50,7 +50,7 @@ export default class MeasureDrilldown extends React.Component {
  • + to={{ pathname: `metric/${metric.key}/tree`, query: { id: component.key } }}> {translate('component_measures.tab.tree')} @@ -60,7 +60,7 @@ export default class MeasureDrilldown extends React.Component {
  • + to={{ pathname: `metric/${metric.key}/bubbles`, query: { id: component.key } }}> {translate('component_measures.tab.bubbles')} @@ -71,7 +71,7 @@ export default class MeasureDrilldown extends React.Component {
  • + to={{ pathname: `metric/${metric.key}/treemap`, query: { id: component.key } }}> {translate('component_measures.tab.treemap')} @@ -82,7 +82,7 @@ export default class MeasureDrilldown extends React.Component {
  • + to={{ pathname: `metric/${metric.key}/history`, query: { id: component.key } }}> {translate('component_measures.tab.history')} diff --git a/server/sonar-web/src/main/js/apps/component-measures/details/drilldown/TreeView.js b/server/sonar-web/src/main/js/apps/component-measures/details/drilldown/TreeView.js index 1d10919bd2f..7a4048589e1 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/details/drilldown/TreeView.js +++ b/server/sonar-web/src/main/js/apps/component-measures/details/drilldown/TreeView.js @@ -21,6 +21,7 @@ import React from 'react'; import ComponentsList from './ComponentsList'; import ListHeader from './ListHeader'; +import Spinner from '../../components/Spinner'; import SourceViewer from '../../../code/components/SourceViewer'; import ListFooter from '../../../../components/shared/list-footer'; @@ -102,11 +103,15 @@ export default class TreeView extends React.Component { {!selected && (
    - + {(!fetching || components.length !== 0) ? ( + + ) : ( + + )} ; - } - - const domains = _.sortBy(_.pairs(_.groupBy(measures, measure => measure.metric.domain)).map(r => { - const [name, measures] = r; - const sortedMeasures = _.sortBy(measures, measure => measure.metric.name); - - return { name, measures: sortedMeasures }; - }), 'name'); + componentDidUpdate () { + this.props.onDisplay(); + } + render () { + const { component, domains, periods } = this.props; const leakPeriodLabel = getLeakPeriodLabel(periods); return ( -
    -
      - {domains.map((domain, index) => ( - - ))} -
    -
    +
      + {domains.map(domain => ( + + ))} +
    ); } } diff --git a/server/sonar-web/src/main/js/apps/component-measures/home/AllMeasuresContainer.js b/server/sonar-web/src/main/js/apps/component-measures/home/AllMeasuresContainer.js index c12fac1ac13..8cdcb3d40b7 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/home/AllMeasuresContainer.js +++ b/server/sonar-web/src/main/js/apps/component-measures/home/AllMeasuresContainer.js @@ -20,23 +20,19 @@ import { connect } from 'react-redux'; import AllMeasures from './AllMeasures'; -import { fetchMeasures } from './actions'; -import { displayHome } from '../app/actions'; +import { displayDomain } from '../app/actions'; const mapStateToProps = state => { return { component: state.app.component, - metrics: state.app.metrics, - measures: state.home.measures, - periods: state.home.periods, - fetching: state.status.fetching + domains: state.home.domains, + periods: state.home.periods }; }; const mapDispatchToProps = dispatch => { return { - onDisplay: () => dispatch(displayHome()), - fetchMeasures: () => dispatch(fetchMeasures()) + onDisplay: () => dispatch(displayDomain(undefined)) }; }; diff --git a/server/sonar-web/src/main/js/apps/component-measures/home/AllMeasuresDomain.js b/server/sonar-web/src/main/js/apps/component-measures/home/AllMeasuresDomain.js index 67fe53d31d7..5bf2d5585b1 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/home/AllMeasuresDomain.js +++ b/server/sonar-web/src/main/js/apps/component-measures/home/AllMeasuresDomain.js @@ -20,64 +20,55 @@ import sortBy from '../../../../../../node_modules/lodash/sortBy'; import partition from '../../../../../../node_modules/lodash/partition'; import React from 'react'; -import { Link } from 'react-router'; -import domains from '../config/domains'; -import { formatLeak } from '../utils'; -import { formatMeasure } from '../../../helpers/measures'; -import { translateWithParameters } from '../../../helpers/l10n'; +import MeasuresList from './MeasuresList'; +import { domains } from '../config/domains'; -export default function AllMeasuresDomain ({ domain, component, displayLeakHeader, leakPeriodLabel }) { - const hasLeak = !!leakPeriodLabel; - const { measures } = domain; - const knownMetrics = domains[domain.name] || []; +const sortMeasures = (measures, order) => { + const [known, unknown] = partition(measures, measure => order.includes(measure.metric.key)); + return [ + ...sortBy(known, measure => order.indexOf(measure.metric.key)), + ...sortBy(unknown, measure => measure.metric.name) + ]; +}; - const [knownMeasures, otherMeasures] = - partition(measures, measure => knownMetrics.indexOf(measure.metric.key) !== -1); +export default class AllMeasuresDomain extends React.Component { + render () { + const { domain, component, leakPeriodLabel, displayHeader } = this.props; - const finalMeasures = [ - ...sortBy(knownMeasures, measure => knownMetrics.indexOf(measure.metric.key)), - ...sortBy(otherMeasures, measure => measure.metric.name) - ]; + const hasLeak = !!leakPeriodLabel; + const { measures } = domain; + const domainConfig = domains[domain.name] || { main: [], order: [] }; + const mainMetrics = domainConfig.main; + const orderedMeasures = domainConfig.order; + const [mainMeasures, otherMeasures] = partition(measures, + measure => mainMetrics.indexOf(measure.metric.key) !== -1); + const sortedMainMeasures = sortMeasures(mainMeasures, orderedMeasures); + const sortedOtherMeasures = sortMeasures(otherMeasures, orderedMeasures); + const finalMeasures = [...sortedMainMeasures, ...sortedOtherMeasures]; - return ( -
  • -
    -

    {domain.name}

    - {displayLeakHeader && hasLeak && ( -
    - {translateWithParameters('overview.leak_period_x', leakPeriodLabel)} -
    + return ( +
  • + {displayHeader && ( +
    +

    {domain.name}

    +
    )} -
  • -
      - {finalMeasures.map(measure => ( -
    • - -
      - - {measure.metric.name} - -
      -
      - {measure.value != null && ( - - {formatMeasure(measure.value, measure.metric.type)} - - )} -
      - {hasLeak && measure.leak != null && ( -
      - - {formatLeak(measure.leak, measure.metric)} - -
      - )} - -
    • - ))} -
    -
  • - ); + + + ); + } } + +AllMeasuresDomain.defaultProps = { + displayHeader: true +}; + +AllMeasuresDomain.propTypes = { + displayHeader: React.PropTypes.bool +}; + diff --git a/server/sonar-web/src/main/js/apps/component-measures/home/DomainMeasures.js b/server/sonar-web/src/main/js/apps/component-measures/home/DomainMeasures.js new file mode 100644 index 00000000000..8dcd15583ee --- /dev/null +++ b/server/sonar-web/src/main/js/apps/component-measures/home/DomainMeasures.js @@ -0,0 +1,84 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 sortBy from '../../../../../../node_modules/lodash/sortBy'; +import partition from '../../../../../../node_modules/lodash/partition'; +import React from 'react'; + +import MainMeasures from './MainMeasures'; +import MeasuresList from './MeasuresList'; +import MeasureBubbleChartContainer from '../components/bubbleChart/MeasureBubbleChartContainer'; +import { getLeakPeriodLabel } from '../../../helpers/periods'; +import { hasBubbleChart } from '../utils'; +import { domains as domainsConf } from '../config/domains'; + +const sortMeasures = (measures, order) => { + const [known, unknown] = partition(measures, measure => order.includes(measure.metric.key)); + return [ + ...sortBy(known, measure => order.indexOf(measure.metric.key)), + ...sortBy(unknown, measure => measure.metric.name) + ]; +}; + +export default class DomainMeasures extends React.Component { + componentDidMount () { + this.props.onDisplay(this.props.params.domainName); + } + + componentDidUpdate () { + this.props.onDisplay(this.props.params.domainName); + } + + render () { + const { component, domains, periods } = this.props; + const { domainName } = this.props.params; + const domain = domains.find(d => d.name === domainName); + const { measures } = domain; + const leakPeriodLabel = getLeakPeriodLabel(periods); + + const conf = domainsConf[domainName]; + const mainMetrics = conf ? conf.main : []; + const order = conf ? conf.order : []; + const [mainMeasures, otherMeasures] = partition(measures, m => mainMetrics.includes(m.metric.key)); + const sortedMainMeasures = sortMeasures(mainMeasures, order); + const sortedOtherMeasures = sortMeasures(otherMeasures, order); + + return ( +
    + {mainMeasures.length > 0 && ( + + )} + + {otherMeasures.length > 0 && ( + + )} + + {hasBubbleChart(domainName) && ( + + )} +
    + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/component-measures/home/DomainMeasuresContainer.js b/server/sonar-web/src/main/js/apps/component-measures/home/DomainMeasuresContainer.js new file mode 100644 index 00000000000..0ff458548b3 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/component-measures/home/DomainMeasuresContainer.js @@ -0,0 +1,42 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { connect } from 'react-redux'; + +import DomainMeasures from './DomainMeasures'; +import { displayDomain } from '../app/actions'; + +const mapStateToProps = state => { + return { + component: state.app.component, + domains: state.home.domains, + periods: state.home.periods + }; +}; + +const mapDispatchToProps = dispatch => { + return { + onDisplay: domainName => dispatch(displayDomain(domainName)) + }; +}; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(DomainMeasures); diff --git a/server/sonar-web/src/main/js/apps/component-measures/home/Home.js b/server/sonar-web/src/main/js/apps/component-measures/home/Home.js new file mode 100644 index 00000000000..a9e4dd58e9f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/component-measures/home/Home.js @@ -0,0 +1,78 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import React from 'react'; +import { Link, IndexLink } from 'react-router'; + +import { getLeakPeriodLabel } from '../../../helpers/periods'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; + +export default class Home extends React.Component { + componentDidMount () { + this.props.onDisplay(); + this.props.fetchMeasures(); + } + + render () { + const { component, domains, periods } = this.props; + + if (domains == null) { + return null; + } + + const leakPeriodLabel = getLeakPeriodLabel(periods); + + return ( +
    +
    + + + {leakPeriodLabel != null && ( +
    + {translateWithParameters('overview.leak_period_x', leakPeriodLabel)} +
    + )} +
    + +
    + {this.props.children} +
    +
    + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/component-measures/home/HomeContainer.js b/server/sonar-web/src/main/js/apps/component-measures/home/HomeContainer.js new file mode 100644 index 00000000000..a5473ec3dba --- /dev/null +++ b/server/sonar-web/src/main/js/apps/component-measures/home/HomeContainer.js @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { connect } from 'react-redux'; + +import Home from './Home'; +import { fetchMeasures } from './actions'; +import { displayHome } from '../app/actions'; + +const mapStateToProps = state => { + return { + component: state.app.component, + domains: state.home.domains, + periods: state.home.periods + }; +}; + +const mapDispatchToProps = dispatch => { + return { + onDisplay: () => dispatch(displayHome()), + fetchMeasures: () => dispatch(fetchMeasures()) + }; +}; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(Home); diff --git a/server/sonar-web/src/main/js/apps/component-measures/home/MainMeasures.js b/server/sonar-web/src/main/js/apps/component-measures/home/MainMeasures.js new file mode 100644 index 00000000000..172fb67896b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/component-measures/home/MainMeasures.js @@ -0,0 +1,50 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import React from 'react'; +import { Link } from 'react-router'; +import classNames from 'classnames'; + +import Measure from '../components/Measure'; +import { isDiffMetric } from '../utils'; + +const MainMeasures = ({ measures, component }) => { + return ( +
      + {measures.map(measure => ( +
    • +
      + + + +
      + +
      + {measure.metric.name} +
      +
    • + ))} +
    + ); +}; + +export default MainMeasures; diff --git a/server/sonar-web/src/main/js/apps/component-measures/home/MeasuresList.js b/server/sonar-web/src/main/js/apps/component-measures/home/MeasuresList.js new file mode 100644 index 00000000000..4bf7833968d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/component-measures/home/MeasuresList.js @@ -0,0 +1,58 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import React from 'react'; +import { Link } from 'react-router'; + +import Measure from '../components/Measure'; +import { formatLeak } from '../utils'; + +const MeasuresList = ({ measures, hasLeak, component }) => { + return ( +
      + {measures.map(measure => ( +
    • + +
      + + {measure.metric.name} + +
      +
      + {measure.value != null && ( + + + + )} +
      + {hasLeak && measure.leak != null && ( +
      + + {formatLeak(measure.leak, measure.metric)} + +
      + )} + +
    • + ))} +
    + ); +}; + +export default MeasuresList; diff --git a/server/sonar-web/src/main/js/apps/component-measures/home/reducer.js b/server/sonar-web/src/main/js/apps/component-measures/home/reducer.js index e80c7b1129b..9ba2b5f86ec 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/home/reducer.js +++ b/server/sonar-web/src/main/js/apps/component-measures/home/reducer.js @@ -17,17 +17,44 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import _ from 'underscore'; +import sortBy from '../../../../../../node_modules/lodash/sortBy'; +import partition from '../../../../../../node_modules/lodash/partition'; + import { RECEIVE_MEASURES } from './actions'; const initialState = { measures: undefined, + domains: undefined, periods: undefined }; +function groupByDomains (measures) { + const KNOWN_DOMAINS = ['Reliability', 'Security', 'Maintainability', 'Tests', 'Duplication', 'Size', 'Complexity']; + + const domains = _.sortBy(_.pairs(_.groupBy(measures, measure => measure.metric.domain)).map(r => { + const [name, measures] = r; + const sortedMeasures = _.sortBy(measures, measure => measure.metric.name); + + return { name, measures: sortedMeasures }; + }), 'name'); + const [knownDomains, unknownDomains] = + partition(domains, domain => KNOWN_DOMAINS.includes(domain.name)); + return [ + ...sortBy(knownDomains, domain => KNOWN_DOMAINS.indexOf(domain.name)), + ...sortBy(unknownDomains, domain => domain.name) + ]; +} + export default function (state = initialState, action = {}) { switch (action.type) { case RECEIVE_MEASURES: - return { ...state, measures: action.measures, periods: action.periods }; + return { + ...state, + measures: action.measures, + domains: groupByDomains(action.measures), + periods: action.periods + }; default: return state; } diff --git a/server/sonar-web/src/main/js/apps/component-measures/hooks.js b/server/sonar-web/src/main/js/apps/component-measures/hooks.js index 03653fa3067..d9d58658fc4 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/hooks.js +++ b/server/sonar-web/src/main/js/apps/component-measures/hooks.js @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { hasHistory, hasBubbleChart } from './utils'; +import { hasHistory } from './utils'; export function checkHistoryExistence (nextState, replace) { const { metricKey } = nextState.params; @@ -29,14 +29,3 @@ export function checkHistoryExistence (nextState, replace) { }); } } - -export function checkBubbleChartExistence (nextState, replace) { - const { metricKey } = nextState.params; - - if (!hasBubbleChart(metricKey)) { - replace({ - pathname: metricKey, - query: nextState.location.query - }); - } -} diff --git a/server/sonar-web/src/main/js/apps/component-measures/styles.css b/server/sonar-web/src/main/js/apps/component-measures/styles.css index 198f0aeea91..6864b465258 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/styles.css +++ b/server/sonar-web/src/main/js/apps/component-measures/styles.css @@ -1,17 +1,23 @@ +.home-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 30px; +} + .measures-domains { } .measures-domains > li { - padding-bottom: 40px; - break-inside: avoid; + padding-bottom: 20px; } .measures-domains-leak-header { - float: right; line-height: 22px; padding: 0 10px; border: 1px solid #eae3c7; background-color: #fbf3d5; + white-space: nowrap; } .domain-measures { @@ -28,6 +34,10 @@ background-color: #f8f8f8; } +.domain-measures > li > a:hover { + background-color: #ecf6fe !important; +} + .domain-measures-name, .domain-measures-value { padding: 7px 10px; @@ -57,6 +67,32 @@ background-color: #f5eed0; } +.domain-main-measures { + display: flex; + flex-wrap: wrap; + padding: 0 10px; +} + +.domain-main-measures > li { + margin-right: 40px; + margin-bottom: 30px; +} + +.domain-main-measures-value { + line-height: 1; + font-size: 24px; + font-weight: 300; +} + +.domain-main-measures-leak { + +} + +.domain-main-measures-label { + margin-top: 4px; + white-space: nowrap; +} + .measure-details { margin-top: 10px; } @@ -232,7 +268,7 @@ .measure-details-bubble-chart { position: relative; margin: 10px 0; - padding: 30px 0 30px 50px; + padding: 30px 0 30px 60px; } .measure-details-bubble-chart-axis { @@ -265,7 +301,6 @@ margin-bottom: 10px; } - .component-measures-breadcrumbs { display: flex; flex-wrap: wrap; diff --git a/server/sonar-web/src/main/js/apps/component-measures/utils.js b/server/sonar-web/src/main/js/apps/component-measures/utils.js index 5349f97864a..3e54ffe49da 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/utils.js +++ b/server/sonar-web/src/main/js/apps/component-measures/utils.js @@ -91,8 +91,8 @@ export function hasHistory (metricKey) { return metricKey.indexOf('new_') !== 0; } -export function hasBubbleChart (metricKey) { - return !!bubbles[metricKey]; +export function hasBubbleChart (domainName) { + return !!bubbles[domainName]; } export function hasTreemap (metric) { diff --git a/server/sonar-web/src/main/js/components/shared/Level.js b/server/sonar-web/src/main/js/components/shared/Level.js new file mode 100644 index 00000000000..cd92ed80d0e --- /dev/null +++ b/server/sonar-web/src/main/js/components/shared/Level.js @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import React from 'react'; + +import { formatMeasure } from '../../helpers/measures'; + +const Level = ({ level }) => { + const formatted = formatMeasure(level, 'LEVEL'); + const className = 'level level-' + level; + return ( + + {formatted} + + ); +}; + +export default Level; diff --git a/server/sonar-web/src/main/js/helpers/urls.js b/server/sonar-web/src/main/js/helpers/urls.js index be2f332895b..5279154290a 100644 --- a/server/sonar-web/src/main/js/helpers/urls.js +++ b/server/sonar-web/src/main/js/helpers/urls.js @@ -48,7 +48,7 @@ export function getComponentIssuesUrl (componentKey, query) { * @returns {string} */ export function getComponentDrilldownUrl (componentKey, metric) { - return `/component_measures/${metric}?id=${encodeURIComponent(componentKey)}`; + return `/component_measures/metric/${metric}?id=${encodeURIComponent(componentKey)}`; } diff --git a/server/sonar-web/src/main/less/components/ui.less b/server/sonar-web/src/main/less/components/ui.less index 84c6235101a..15f4bb163b5 100644 --- a/server/sonar-web/src/main/less/components/ui.less +++ b/server/sonar-web/src/main/less/components/ui.less @@ -69,6 +69,36 @@ a & { border-bottom-color: #EE0000; } } +.level { + display: inline-block; + height: 1.3em; + line-height: 1.3; + padding: 0 0.3em; + color: #fff; + font-weight: 300; + text-align: center; + + a > & { + margin-bottom: -1px; + border-bottom: 1px solid; + transition: all 0.2s ease; + + &:hover { opacity: 0.8; } + } +} + +.level-OK { + background-color: @green; +} + +.level-WARN { + background-color: @orange; +} + +.level-ERROR { + background-color: @red; +} + .processes-container { position: fixed; @@ -231,6 +261,34 @@ } } +.nav-pills { + & > ul { + display: flex; + flex-wrap: wrap; + + & > li > a { + display: inline-block; + vertical-align: middle; + padding: 3px 10px; + border: 1px solid transparent; + border-radius: 24px; + background-color: #fff; + color: @darkBlue; + transition: none; + + &:hover { + border-color: @darkBlue; + } + } + + & > li.active > a, + & > li > a.active { + background-color: @darkBlue; + color: #fff; + } + } +} + .flash { background-color: transparent; diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/drilldown_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/drilldown_controller.rb index 3ef48fcabb2..7d6c243c921 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/drilldown_controller.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/drilldown_controller.rb @@ -24,7 +24,7 @@ class DrilldownController < ApplicationController def measures metric = params[:metric] || 'ncloc' - return redirect_to("/component_measures/#{metric}?id=#{url_encode(@resource.key)}") + return redirect_to("/component_measures/metric/#{metric}?id=#{url_encode(@resource.key)}") end def issues diff --git a/server/sonar-web/tests/helpers/urls-test.js b/server/sonar-web/tests/helpers/urls-test.js index b99f948c4b2..1cb46cd7952 100644 --- a/server/sonar-web/tests/helpers/urls-test.js +++ b/server/sonar-web/tests/helpers/urls-test.js @@ -56,12 +56,12 @@ describe('URLs', function () { describe('#getComponentDrilldownUrl', function () { it('should return component drilldown url', function () { expect(getComponentDrilldownUrl(SIMPLE_COMPONENT_KEY, METRIC)).to.equal( - '/component_measures/' + METRIC + '?id=' + SIMPLE_COMPONENT_KEY); + '/component_measures/metric/' + METRIC + '?id=' + SIMPLE_COMPONENT_KEY); }); it('should encode component key', function () { expect(getComponentDrilldownUrl(COMPLEX_COMPONENT_KEY, METRIC)).to.equal( - '/component_measures/' + METRIC + '?id=' + COMPLEX_COMPONENT_KEY_ENCODED); + '/component_measures/metric/' + METRIC + '?id=' + COMPLEX_COMPONENT_KEY_ENCODED); }); }); }); diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 931389b118e..5571498c912 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -256,7 +256,8 @@ select_a_metric=Select a metric set_as_default=Set as Default unset_as_default=Unset as Default shared_by=Shared by -show_more=Show more +show_less=Show Less +show_more=Show More since_x=since {0} since_x.short={0} since_previous_analysis=since previous analysis @@ -3233,6 +3234,7 @@ code.open_component_page=Open Component's Page # #------------------------------------------------------------------------------ component_measures.back_to_all_measures=Back to All Measures +component_measures.back_to_domain_measures=Back to {0} Measures component_measures.back_to_list=Back to List component_measures.tab.tree=Tree component_measures.tab.list=List -- 2.39.5