aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'server/sonar-web/src/main')
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/details/MeasureDetailsHeader.js4
-rw-r--r--server/sonar-web/src/main/js/apps/overview/app.js30
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/App.js (renamed from server/sonar-web/src/main/js/apps/overview/gate/gate-conditions.js)38
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.js6
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.js (renamed from server/sonar-web/src/main/js/apps/overview/components/OverviewMain.js)35
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/OverviewApp.js156
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/Timeline.js (renamed from server/sonar-web/src/main/js/apps/overview/main/timeline.js)48
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/coverage-selection-mixin.js30
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/language-distribution.js85
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/legend.js30
-rw-r--r--server/sonar-web/src/main/js/apps/overview/events/Event.js (renamed from server/sonar-web/src/main/js/apps/overview/components/event.js)55
-rw-r--r--server/sonar-web/src/main/js/apps/overview/events/EventsList.js (renamed from server/sonar-web/src/main/js/apps/overview/components/EventsList.js)9
-rw-r--r--server/sonar-web/src/main/js/apps/overview/events/EventsListFilter.js (renamed from server/sonar-web/src/main/js/apps/overview/components/events-list-filter.js)48
-rw-r--r--server/sonar-web/src/main/js/apps/overview/gate/gate-condition.js74
-rw-r--r--server/sonar-web/src/main/js/apps/overview/gate/gate.js62
-rw-r--r--server/sonar-web/src/main/js/apps/overview/helpers/periods.js41
-rw-r--r--server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.js131
-rw-r--r--server/sonar-web/src/main/js/apps/overview/main/CodeSmells.js157
-rw-r--r--server/sonar-web/src/main/js/apps/overview/main/Coverage.js193
-rw-r--r--server/sonar-web/src/main/js/apps/overview/main/Duplications.js132
-rw-r--r--server/sonar-web/src/main/js/apps/overview/main/Size.js108
-rw-r--r--server/sonar-web/src/main/js/apps/overview/main/code-smells.js149
-rw-r--r--server/sonar-web/src/main/js/apps/overview/main/components.js161
-rw-r--r--server/sonar-web/src/main/js/apps/overview/main/coverage.js150
-rw-r--r--server/sonar-web/src/main/js/apps/overview/main/duplications.js118
-rw-r--r--server/sonar-web/src/main/js/apps/overview/main/enhance.js213
-rw-r--r--server/sonar-web/src/main/js/apps/overview/main/main.js155
-rw-r--r--server/sonar-web/src/main/js/apps/overview/main/risk.js165
-rw-r--r--server/sonar-web/src/main/js/apps/overview/main/structure.js99
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/Meta.js (renamed from server/sonar-web/src/main/js/apps/overview/components/Meta.js)8
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/MetaKey.js (renamed from server/sonar-web/src/main/js/apps/overview/components/MetaKey.js)6
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/MetaLinks.js (renamed from server/sonar-web/src/main/js/apps/overview/components/MetaLinks.js)6
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/MetaQualityGate.js (renamed from server/sonar-web/src/main/js/apps/overview/components/MetaQualityGate.js)6
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.js (renamed from server/sonar-web/src/main/js/apps/overview/components/MetaQualityProfiles.js)6
-rw-r--r--server/sonar-web/src/main/js/apps/overview/propTypes.js69
-rw-r--r--server/sonar-web/src/main/js/apps/overview/qualityGate/EmptyQualityGate.js (renamed from server/sonar-web/src/main/js/apps/overview/gate/gate-empty.js)26
-rw-r--r--server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGate.js78
-rw-r--r--server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js84
-rw-r--r--server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateConditions.js116
-rw-r--r--server/sonar-web/src/main/js/apps/overview/styles.css292
-rw-r--r--server/sonar-web/src/main/js/components/charts/LanguageDistribution.js (renamed from server/sonar-web/src/main/js/apps/component-measures/components/LanguageDistribution.js)58
-rw-r--r--server/sonar-web/src/main/js/components/shared/complexity-distribution.js (renamed from server/sonar-web/src/main/js/apps/overview/components/complexity-distribution.js)17
-rw-r--r--server/sonar-web/src/main/js/helpers/measures.js33
-rw-r--r--server/sonar-web/src/main/js/widgets/complexity/index.js2
-rw-r--r--server/sonar-web/src/main/less/pages/overview.less596
-rw-r--r--server/sonar-web/src/main/less/sonar.less1
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/views/overview/index.html.erb52
47 files changed, 1996 insertions, 2142 deletions
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 67063af2737..07c90a9b306 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
@@ -20,9 +20,9 @@
import React from 'react';
import Measure from './../components/Measure';
-import LanguageDistribution from './../components/LanguageDistribution';
+import LanguageDistribution from '../../../components/charts/LanguageDistribution';
import LeakPeriodLegend from '../components/LeakPeriodLegend';
-import { ComplexityDistribution } from '../../overview/components/complexity-distribution';
+import { ComplexityDistribution } from '../../../components/shared/complexity-distribution';
import { isDiffMetric, formatLeak } from '../utils';
import { TooltipsContainer } from '../../../components/mixins/tooltips-mixin';
import { getLocalizedMetricName } from '../../../helpers/l10n';
diff --git a/server/sonar-web/src/main/js/apps/overview/app.js b/server/sonar-web/src/main/js/apps/overview/app.js
index 6c9b8872b54..5d8ad1b4a17 100644
--- a/server/sonar-web/src/main/js/apps/overview/app.js
+++ b/server/sonar-web/src/main/js/apps/overview/app.js
@@ -18,26 +18,14 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
-import ReactDOM from 'react-dom';
+import { render } from 'react-dom';
-import OverviewApp from './components/OverviewApp';
-import EmptyOverview from './components/EmptyOverview';
+import App from './components/App';
-const LEAK_PERIOD = '1';
-
-class App {
- start (options) {
- const opts = { ...options, ...window.sonarqube.overview };
- Object.assign(opts.component, options.component);
-
- const el = document.querySelector(opts.el);
-
- if (opts.component.hasSnapshot) {
- ReactDOM.render(<OverviewApp {...opts} leakPeriodIndex={LEAK_PERIOD}/>, el);
- } else {
- ReactDOM.render(<EmptyOverview {...opts}/>, el);
- }
- }
-}
-
-window.sonarqube.appStarted.then(options => new App().start(options));
+window.sonarqube.appStarted.then(options => {
+ const el = document.querySelector(options.el);
+ const component = { ...options.component, ...window.sonarqube.overview.component };
+ render((
+ <App component={component}/>
+ ), el);
+});
diff --git a/server/sonar-web/src/main/js/apps/overview/gate/gate-conditions.js b/server/sonar-web/src/main/js/apps/overview/components/App.js
index 99dd41b43f6..d791a021882 100644
--- a/server/sonar-web/src/main/js/apps/overview/gate/gate-conditions.js
+++ b/server/sonar-web/src/main/js/apps/overview/components/App.js
@@ -18,18 +18,32 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
-import GateCondition from './gate-condition';
+import shallowCompare from 'react-addons-shallow-compare';
-export default React.createClass({
- propTypes: {
- gate: React.PropTypes.object.isRequired,
- component: React.PropTypes.object.isRequired
- },
+import OverviewApp from './OverviewApp';
+import EmptyOverview from './EmptyOverview';
+import { ComponentType } from '../propTypes';
- render() {
- const conditions = this.props.gate.conditions
- .filter(c => c.level !== 'OK')
- .map(c => <GateCondition key={c.metric.name} condition={c} component={this.props.component}/>);
- return <ul className="overview-gate-conditions-list">{conditions}</ul>;
+export default class App extends React.Component {
+ static propTypes = {
+ component: ComponentType.isRequired
+ };
+
+ shouldComponentUpdate (nextProps, nextState) {
+ return shallowCompare(this, nextProps, nextState);
+ }
+
+ render () {
+ const { component } = this.props;
+
+ if (!component.snapshotDate) {
+ return <EmptyOverview {...this.props}/>;
+ }
+
+ return (
+ <OverviewApp
+ {...this.props}
+ leakPeriodIndex="1"/>
+ );
}
-});
+}
diff --git a/server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.js b/server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.js
index b39005a49e5..5d88e24b8d0 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.js
+++ b/server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.js
@@ -21,7 +21,7 @@ import React from 'react';
import { translate } from '../../../helpers/l10n';
-export default function EmptyOverview ({ component }) {
+const EmptyOverview = ({ component }) => {
return (
<div className="page page-limited">
<div className="alert alert-warning">
@@ -33,4 +33,6 @@ export default function EmptyOverview ({ component }) {
</div>
</div>
);
-}
+};
+
+export default EmptyOverview;
diff --git a/server/sonar-web/src/main/js/apps/overview/components/OverviewMain.js b/server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.js
index 015c3cde255..a2129c56b13 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/OverviewMain.js
+++ b/server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.js
@@ -18,21 +18,30 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
+import moment from 'moment';
-import Gate from '../gate/gate';
-import GeneralMain from './../main/main';
-import Meta from './Meta';
+import { getPeriodDate, getPeriodLabel } from '../../../helpers/periods';
+import { translateWithParameters } from '../../../helpers/l10n';
+
+const LeakPeriodLegend = ({ period }) => {
+ const leakPeriodLabel = getPeriodLabel(period);
+ const leakPeriodDate = getPeriodDate(period);
+
+ const momentDate = moment(leakPeriodDate);
+ const fromNow = momentDate.fromNow();
+ const tooltip = translateWithParameters(
+ 'overview.started_on_x',
+ momentDate.format('LL'));
-export default function OverviewMain (props) {
return (
- <div className="page page-limited">
- <div className="overview">
- <div className="overview-main">
- <Gate component={props.component} gate={props.gate}/>
- <GeneralMain {...props}/>
- </div>
- <Meta component={props.component}/>
- </div>
+ <div className="overview-legend" title={tooltip} data-toggle="tooltip">
+ {translateWithParameters('overview.leak_period_x', leakPeriodLabel)}
+ <br/>
+ <span className="note">
+ {translateWithParameters('overview.started_x', fromNow)}
+ </span>
</div>
);
-}
+};
+
+export default LeakPeriodLegend;
diff --git a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.js b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.js
index 1bf027ac518..fdd667d396d 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.js
+++ b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.js
@@ -18,28 +18,137 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
+import moment from 'moment';
+import shallowCompare from 'react-addons-shallow-compare';
-import OverviewMain from './OverviewMain';
-import { getMetrics } from '../../../api/metrics';
+import QualityGate from '../qualityGate/QualityGate';
+import BugsAndVulnerabilities from '../main/BugsAndVulnerabilities';
+import CodeSmells from '../main/CodeSmells';
+import Coverage from '../main/Coverage';
+import Duplications from '../main/Duplications';
+import Size from '../main/Size';
+import Meta from './../meta/Meta';
+import { getMeasuresAndMeta } from '../../../api/measures';
+import { getTimeMachineData } from '../../../api/time-machine';
+import { enhanceMeasuresWithMetrics } from '../../../helpers/measures';
+import { getLeakPeriod } from '../../../helpers/periods';
+import { ComponentType } from '../propTypes';
+
+import '../styles.css';
+
+const METRICS = [
+ // quality gate
+ 'alert_status',
+ 'quality_gate_details',
+
+ // bugs
+ 'bugs',
+ 'new_bugs',
+ 'reliability_rating',
+
+ // vulnerabilities
+ 'vulnerabilities',
+ 'new_vulnerabilities',
+ 'security_rating',
+
+ // code smells
+ 'code_smells',
+ 'new_code_smells',
+ 'sqale_rating',
+ 'sqale_index',
+ 'new_technical_debt',
+
+ // coverage
+ 'overall_coverage',
+ 'new_overall_coverage',
+ 'coverage',
+ 'new_coverage',
+ 'it_coverage',
+ 'new_it_coverage',
+ 'tests',
+
+ // duplications
+ 'duplicated_lines_density',
+ 'duplicated_blocks',
+
+ // size
+ 'ncloc',
+ 'ncloc_language_distribution'
+];
+
+const HISTORY_METRICS_LIST = [
+ 'sqale_index',
+ 'duplicated_lines_density',
+ 'ncloc',
+ 'overall_coverage',
+ 'it_coverage',
+ 'coverage'
+];
export default class OverviewApp extends React.Component {
- state = {};
+ static propTypes = {
+ component: ComponentType.isRequired
+ };
+
+ state = {
+ loading: true
+ };
componentDidMount () {
this.mounted = true;
document.querySelector('html').classList.add('dashboard-page');
- this.requestMetrics();
+ this.loadMeasures(this.props.component)
+ .then(() => this.loadHistory(this.props.component));
+ }
+
+ shouldComponentUpdate (nextProps, nextState) {
+ return shallowCompare(this, nextProps, nextState);
+ }
+
+ componentDidUpdate (nextProps) {
+ if (this.props.component !== nextProps.component) {
+ this.loadMeasures(nextProps.component)
+ .then(() => this.loadHistory(nextProps.component));
+ }
}
componentWillUnmount () {
this.mounted = false;
- document.querySelector('html').classList.delete('dashboard-page');
+ document.querySelector('html').classList.remove('dashboard-page');
+ }
+
+ loadMeasures (component) {
+ this.setState({ loading: true });
+
+ return getMeasuresAndMeta(
+ component.key,
+ METRICS,
+ { additionalFields: 'metrics,periods' }
+ ).then(r => {
+ if (this.mounted) {
+ this.setState({
+ loading: false,
+ measures: enhanceMeasuresWithMetrics(r.component.measures, r.metrics),
+ periods: r.periods
+ });
+ }
+ });
}
- requestMetrics () {
- return getMetrics().then(metrics => {
+ loadHistory (component) {
+ const metrics = HISTORY_METRICS_LIST.join(',');
+ return getTimeMachineData(component.key, metrics).then(r => {
if (this.mounted) {
- this.setState({ metrics });
+ const history = {};
+ r[0].cols.forEach((col, index) => {
+ history[col.metric] = r[0].cells.map(cell => {
+ const date = moment(cell.d).toDate();
+ const value = cell.v[index] || 0;
+ return { date, value };
+ });
+ });
+ const historyStartDate = history[HISTORY_METRICS_LIST[0]][0].date;
+ this.setState({ history, historyStartDate });
}
});
}
@@ -53,10 +162,37 @@ export default class OverviewApp extends React.Component {
}
render () {
- if (!this.state.metrics) {
+ const { component } = this.props;
+ const { loading, measures, periods, history, historyStartDate } = this.state;
+
+ if (loading) {
return this.renderLoading();
}
- return <OverviewMain {...this.props} metrics={this.state.metrics}/>;
+ const leakPeriod = getLeakPeriod(periods);
+ const domainProps = { component, measures, leakPeriod, history, historyStartDate };
+
+ return (
+ <div className="page page-limited">
+ <div className="overview">
+ <div className="overview-main">
+ <QualityGate
+ component={component}
+ measures={measures}
+ periods={periods}/>
+
+ <div className="overview-domains-list">
+ <BugsAndVulnerabilities {...domainProps}/>
+ <CodeSmells {...domainProps}/>
+ <Coverage {...domainProps}/>
+ <Duplications {...domainProps}/>
+ <Size {...domainProps}/>
+ </div>
+ </div>
+
+ <Meta component={component}/>
+ </div>
+ </div>
+ );
}
}
diff --git a/server/sonar-web/src/main/js/apps/overview/main/timeline.js b/server/sonar-web/src/main/js/apps/overview/components/Timeline.js
index 81f76ceb266..90a9728f4ca 100644
--- a/server/sonar-web/src/main/js/apps/overview/main/timeline.js
+++ b/server/sonar-web/src/main/js/apps/overview/components/Timeline.js
@@ -19,16 +19,31 @@
*/
import d3 from 'd3';
import React from 'react';
+import shallowCompare from 'react-addons-shallow-compare';
import { LineChart } from '../../../components/charts/line-chart';
const HEIGHT = 80;
-export class Timeline extends React.Component {
+export default class Timeline extends React.Component {
+ static propTypes = {
+ history: React.PropTypes.arrayOf(
+ React.PropTypes.object
+ ).isRequired,
+ before: React.PropTypes.object,
+ after: React.PropTypes.object
+ };
+
+ shouldComponentUpdate (nextProps, nextState) {
+ return shallowCompare(this, nextProps, nextState);
+ }
+
filterSnapshots () {
- return this.props.history.filter(s => {
- const matchBefore = !this.props.before || s.date <= this.props.before;
- const matchAfter = !this.props.after || s.date >= this.props.after;
+ const { history, before, after } = this.props;
+
+ return history.filter(s => {
+ const matchBefore = !before || s.date <= before;
+ const matchAfter = !after || s.date >= after;
return matchBefore && matchAfter;
});
}
@@ -46,19 +61,16 @@ export class Timeline extends React.Component {
const domain = [0, d3.max(this.props.history, d => d.value)];
- return <LineChart data={data}
- domain={domain}
- interpolate="basis"
- displayBackdrop={true}
- displayPoints={false}
- displayVerticalGrid={false}
- height={HEIGHT}
- padding={[0, 0, 0, 0]}/>;
+ return (
+ <LineChart
+ data={data}
+ domain={domain}
+ interpolate="basis"
+ displayBackdrop={true}
+ displayPoints={false}
+ displayVerticalGrid={false}
+ height={HEIGHT}
+ padding={[0, 0, 0, 0]}/>
+ );
}
}
-
-Timeline.propTypes = {
- history: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
- before: React.PropTypes.object,
- after: React.PropTypes.object
-};
diff --git a/server/sonar-web/src/main/js/apps/overview/components/coverage-selection-mixin.js b/server/sonar-web/src/main/js/apps/overview/components/coverage-selection-mixin.js
deleted file mode 100644
index 1f5fb57f227..00000000000
--- a/server/sonar-web/src/main/js/apps/overview/components/coverage-selection-mixin.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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.
- */
-export const CoverageSelectionMixin = {
- getCoverageMetricPrefix (measures) {
- if (measures['coverage'] != null && measures['it_coverage'] != null && measures['overall_coverage'] != null) {
- return 'overall_';
- } else if (measures['coverage'] != null) {
- return '';
- } else {
- return 'it_';
- }
- }
-};
diff --git a/server/sonar-web/src/main/js/apps/overview/components/language-distribution.js b/server/sonar-web/src/main/js/apps/overview/components/language-distribution.js
deleted file mode 100644
index 73e568a94c6..00000000000
--- a/server/sonar-web/src/main/js/apps/overview/components/language-distribution.js
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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 _ from 'underscore';
-import React from 'react';
-
-import { Histogram } from '../../../components/charts/histogram';
-import { formatMeasure } from '../../../helpers/measures';
-import { getLanguages } from '../../../api/languages';
-import { translate } from '../../../helpers/l10n';
-
-export const LanguageDistribution = React.createClass({
- propTypes: {
- distribution: React.PropTypes.string.isRequired,
- lines: React.PropTypes.number.isRequired
- },
-
- componentDidMount () {
- this.requestLanguages();
- },
-
- requestLanguages () {
- getLanguages().then(languages => this.setState({ languages }));
- },
-
- getLanguageName (langKey) {
- if (this.state && this.state.languages) {
- const lang = _.findWhere(this.state.languages, { key: langKey });
- return lang ? lang.name : translate('unknown');
- } else {
- return langKey;
- }
- },
-
- cutLanguageName (name) {
- return name.length > 10 ? `${name.substr(0, 7)}...` : name;
- },
-
- renderBarChart () {
- let data = this.props.distribution.split(';').map((point, index) => {
- const tokens = point.split('=');
- return { x: parseInt(tokens[1], 10), y: index, value: tokens[0] };
- });
-
- data = _.sortBy(data, d => -d.x);
-
- const yTicks = data.map(point => this.getLanguageName(point.value)).map(this.cutLanguageName);
- const yValues = data.map(point => {
- const percent = point.x / this.props.lines * 100;
- return percent >= 0.1 ? formatMeasure(percent, 'PERCENT') : '<0.1%';
- });
-
- return <Histogram data={data}
- yTicks={yTicks}
- yValues={yValues}
- height={data.length * 25}
- barsWidth={10}
- padding={[0, 60, 0, 80]}/>;
- },
-
- render () {
- const count = this.props.distribution.split(';').length;
- const height = count * 25;
-
- return <div className="overview-bar-chart" style={{ height }}>
- {this.renderBarChart()}
- </div>;
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/overview/components/legend.js b/server/sonar-web/src/main/js/apps/overview/components/legend.js
deleted file mode 100644
index 408b827ac9f..00000000000
--- a/server/sonar-web/src/main/js/apps/overview/components/legend.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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 { DomainLeakTitle } from '../main/components';
-
-export const Legend = React.createClass({
- render() {
- return <div className="overview-legend overview-leak">
- <DomainLeakTitle label={this.props.leakPeriodLabel} date={this.props.leakPeriodDate}/>
- </div>;
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/overview/components/event.js b/server/sonar-web/src/main/js/apps/overview/events/Event.js
index 35832990704..acef6b789cb 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/event.js
+++ b/server/sonar-web/src/main/js/apps/overview/events/Event.js
@@ -20,32 +20,37 @@
import React from 'react';
import moment from 'moment';
-import { TooltipsMixin } from '../../../components/mixins/tooltips-mixin';
+import { EventType } from '../propTypes';
+import { TooltipsContainer } from '../../../components/mixins/tooltips-mixin';
import { translate } from '../../../helpers/l10n';
-export const Event = React.createClass({
- propTypes: {
- event: React.PropTypes.shape({
- id: React.PropTypes.string.isRequired,
- date: React.PropTypes.object.isRequired,
- type: React.PropTypes.string.isRequired,
- name: React.PropTypes.string.isRequired,
- text: React.PropTypes.string
- })
- },
+const Event = ({ event }) => {
+ return (
+ <TooltipsContainer>
+ <li className="spacer-top">
+ <p>
+ <strong className="js-event-type">
+ {translate('event.category', event.type)}
+ </strong>
+ {': '}
+ <span className="js-event-name">{event.name}</span>
+ {event.text && (
+ <i
+ className="spacer-left icon-help"
+ data-toggle="tooltip"
+ title={event.text}/>
+ )}
+ </p>
+ <p className="note little-spacer-top js-event-date">
+ {moment(event.date).format('LL')}
+ </p>
+ </li>
+ </TooltipsContainer>
+ );
+};
- mixins: [TooltipsMixin],
+Event.propTypes = {
+ event: EventType.isRequired
+};
- render () {
- const { event } = this.props;
- return <li className="spacer-top">
- <p>
- <strong className="js-event-type">{translate('event.category', event.type)}</strong>
- :&nbsp;
- <span className="js-event-name">{event.name}</span>
- { event.text && <i className="spacer-left icon-help" data-toggle="tooltip" title={event.text}/> }
- </p>
- <p className="note little-spacer-top js-event-date">{moment(event.date).format('LL')}</p>
- </li>;
- }
-});
+export default Event;
diff --git a/server/sonar-web/src/main/js/apps/overview/components/EventsList.js b/server/sonar-web/src/main/js/apps/overview/events/EventsList.js
index 7ce48f1c416..40ae9b25573 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/EventsList.js
+++ b/server/sonar-web/src/main/js/apps/overview/events/EventsList.js
@@ -19,9 +19,10 @@
*/
import moment from 'moment';
import React from 'react';
+import shallowCompare from 'react-addons-shallow-compare';
-import { Event } from './event';
-import { EventsListFilter } from './events-list-filter';
+import Event from './Event';
+import EventsListFilter from './EventsListFilter';
import { getEvents } from '../../../api/events';
import { translate } from '../../../helpers/l10n';
@@ -39,6 +40,10 @@ export default class EventsList extends React.Component {
this.fetchEvents();
}
+ shouldComponentUpdate (nextProps, nextState) {
+ return shallowCompare(this, nextProps, nextState);
+ }
+
componentDidUpdate (nextProps) {
if (nextProps.component !== this.props.component) {
this.fetchEvents();
diff --git a/server/sonar-web/src/main/js/apps/overview/components/events-list-filter.js b/server/sonar-web/src/main/js/apps/overview/events/EventsListFilter.js
index 2dcb0eaf398..cfd142fd3e8 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/events-list-filter.js
+++ b/server/sonar-web/src/main/js/apps/overview/events/EventsListFilter.js
@@ -23,28 +23,30 @@ import { translate } from '../../../helpers/l10n';
const TYPES = ['All', 'Version', 'Alert', 'Profile', 'Other'];
-export const EventsListFilter = React.createClass({
- propTypes: {
- onFilter: React.PropTypes.func.isRequired,
- currentFilter: React.PropTypes.string.isRequired
- },
+const EventsListFilter = ({ currentFilter, onFilter }) => {
+ const handleChange = selected => onFilter(selected.value);
- handleChange(selected) {
- this.props.onFilter(selected.value);
- },
+ const options = TYPES.map(type => {
+ return {
+ value: type,
+ label: translate('event.category', type)
+ };
+ });
- render () {
- const options = TYPES.map(type => {
- return {
- value: type,
- label: translate('event.category', type)
- };
- });
- return <Select value={this.props.currentFilter}
- options={options}
- clearable={false}
- searchable={false}
- onChange={this.handleChange}
- style={{ width: '125px' }}/>;
- }
-});
+ return (
+ <Select
+ value={currentFilter}
+ options={options}
+ clearable={false}
+ searchable={false}
+ onChange={handleChange}
+ style={{ width: '125px' }}/>
+ );
+};
+
+EventsListFilter.propTypes = {
+ onFilter: React.PropTypes.func.isRequired,
+ currentFilter: React.PropTypes.string.isRequired
+};
+
+export default EventsListFilter;
diff --git a/server/sonar-web/src/main/js/apps/overview/gate/gate-condition.js b/server/sonar-web/src/main/js/apps/overview/gate/gate-condition.js
deleted file mode 100644
index 621102841ba..00000000000
--- a/server/sonar-web/src/main/js/apps/overview/gate/gate-condition.js
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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 { getPeriodLabel, getPeriodDate } from '../helpers/periods';
-import { DrilldownLink } from '../../../components/shared/drilldown-link';
-import { formatMeasure } from '../../../helpers/measures';
-import { translate } from '../../../helpers/l10n';
-
-const Measure = React.createClass({
- render() {
- if (this.props.value == null || isNaN(this.props.value)) {
- return null;
- }
- const formatted = formatMeasure(this.props.value, this.props.type);
- return <span>{formatted}</span>;
- }
-});
-
-export default React.createClass({
- render() {
- const metricName = translate('metric', this.props.condition.metric.name, 'name');
- const threshold = this.props.condition.level === 'ERROR' ?
- this.props.condition.error : this.props.condition.warning;
- const period = this.props.condition.period ?
- getPeriodLabel(this.props.component.periods, this.props.condition.period) : null;
- const periodDate = getPeriodDate(this.props.component.periods, this.props.condition.period);
-
- const classes = 'alert_' + this.props.condition.level.toUpperCase();
-
- return (
- <li className="overview-gate-condition">
- <div className="little-spacer-bottom">{period}</div>
-
- <div style={{ display: 'flex', alignItems: 'center' }}>
- <div className="overview-gate-condition-value">
- <DrilldownLink component={this.props.component.key} metric={this.props.condition.metric.name}
- period={this.props.condition.period} periodDate={periodDate}>
- <span className={classes}>
- <Measure value={this.props.condition.actual} type={this.props.condition.metric.type}/>
- </span>
- </DrilldownLink>&nbsp;
- </div>
-
- <div className="overview-gate-condition-metric">
- <div>{metricName}</div>
- <div>
- {translate('quality_gates.operator', this.props.condition.op, 'short')}
- {' '}
- <Measure value={threshold} type={this.props.condition.metric.type}/>
- </div>
- </div>
- </div>
- </li>
- );
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/overview/gate/gate.js b/server/sonar-web/src/main/js/apps/overview/gate/gate.js
deleted file mode 100644
index 74d2671ee4d..00000000000
--- a/server/sonar-web/src/main/js/apps/overview/gate/gate.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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 GateConditions from './gate-conditions';
-import GateEmpty from './gate-empty';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-
-export default React.createClass({
- renderGateConditions () {
- return <GateConditions gate={this.props.gate} component={this.props.component}/>;
- },
-
- renderGateText () {
- let text = '';
- if (this.props.gate.level === 'ERROR') {
- text = translateWithParameters('overview.gate.view.errors', this.props.gate.text);
- } else if (this.props.gate.level === 'WARN') {
- text = translateWithParameters('overview.gate.view.warnings', this.props.gate.text);
- } else {
- text = translate('overview.gate.view.no_alert');
- }
- return <div className="overview-card">{text}</div>;
- },
-
- render() {
- if (!this.props.gate || !this.props.gate.level) {
- return this.props.component.qualifier === 'TRK' ? <GateEmpty/> : null;
- }
-
- const level = this.props.gate.level.toLowerCase();
- const badgeClassName = 'badge badge-' + level;
- const badgeText = translate('overview.gate', this.props.gate.level);
-
- return (
- <div className="overview-gate">
- <h2 className="overview-title">
- {translate('overview.quality_gate')}
- <span className={badgeClassName}>{badgeText}</span>
- </h2>
- {this.props.gate.conditions ? this.renderGateConditions() : this.renderGateText()}
- </div>
- );
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/overview/helpers/periods.js b/server/sonar-web/src/main/js/apps/overview/helpers/periods.js
deleted file mode 100644
index 20bc9981ff0..00000000000
--- a/server/sonar-web/src/main/js/apps/overview/helpers/periods.js
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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 _ from 'underscore';
-import moment from 'moment';
-
-import { getPeriodLabel as getLabel } from '../../../helpers/periods';
-
-export function getPeriodLabel (periods, periodIndex) {
- const period = _.findWhere(periods, { index: periodIndex });
-
- if (!period) {
- return null;
- }
-
- return getLabel(period);
-}
-
-export function getPeriodDate (periods, periodIndex) {
- const period = _.findWhere(periods, { index: periodIndex });
- if (!period) {
- return null;
- }
- return period.date ? moment(period.date).toDate() : null;
-}
diff --git a/server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.js b/server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.js
new file mode 100644
index 00000000000..c78181b7af0
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.js
@@ -0,0 +1,131 @@
+/*
+ * 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 enhance from './enhance';
+import LeakPeriodLegend from '../components/LeakPeriodLegend';
+import { getMetricName } from '../helpers/metrics';
+import { translate } from '../../../helpers/l10n';
+
+class BugsAndVulnerabilities extends React.Component {
+ renderHeader () {
+ const { component } = this.props;
+ const bugsDomainUrl = window.baseUrl + '/component_measures/domain/Reliability?id=' +
+ encodeURIComponent(component.key);
+ const vulnerabilitiesDomainUrl = window.baseUrl + '/component_measures/domain/Security?id=' +
+ encodeURIComponent(component.key);
+
+ return (
+ <div className="overview-card-header">
+ <div className="overview-title">
+ <a href={bugsDomainUrl}>
+ {translate('metric.bugs.name')}
+ </a>
+ {' & '}
+ <a href={vulnerabilitiesDomainUrl}>
+ {translate('metric.vulnerabilities.name')}
+ </a>
+ </div>
+ </div>
+ );
+ }
+
+ renderLeak () {
+ const { leakPeriod } = this.props;
+
+ if (leakPeriod == null) {
+ return null;
+ }
+
+ return (
+ <div className="overview-domain-leak">
+ <LeakPeriodLegend period={leakPeriod}/>
+
+ <div className="overview-domain-measures">
+ <div className="overview-domain-measure">
+ <div className="overview-domain-measure-value">
+ {this.props.renderIssues('new_bugs', 'BUG')}
+ </div>
+ <div className="overview-domain-measure-label">
+ {getMetricName('new_bugs')}
+ </div>
+ </div>
+
+ <div className="overview-domain-measure">
+ <div className="overview-domain-measure-value">
+ {this.props.renderIssues('new_vulnerabilities', 'VULNERABILITY')}
+ </div>
+ <div className="overview-domain-measure-label">
+ {getMetricName('new_vulnerabilities')}
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ renderNutshell () {
+ return (
+ <div className="overview-domain-nutshell">
+ <div className="overview-domain-measures">
+
+ <div className="overview-domain-measure">
+ <div className="display-inline-block text-middle" style={{ paddingLeft: 56 }}>
+ <div className="overview-domain-measure-value">
+ {this.props.renderIssues('bugs', 'BUG')}
+ {this.props.renderRating('reliability_rating')}
+ </div>
+ <div className="overview-domain-measure-label">
+ {getMetricName('bugs')}
+ </div>
+ </div>
+ </div>
+
+ <div className="overview-domain-measure">
+ <div className="display-inline-block text-middle">
+ <div className="overview-domain-measure-value">
+ {this.props.renderIssues('vulnerabilities', 'VULNERABILITY')}
+ {this.props.renderRating('security_rating')}
+ </div>
+ <div className="overview-domain-measure-label">
+ {getMetricName('vulnerabilities')}
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ render () {
+ return (
+ <div className="overview-card overview-card-special">
+ {this.renderHeader()}
+
+ <div className="overview-domain-panel">
+ {this.renderNutshell()}
+ {this.renderLeak()}
+ </div>
+ </div>
+ );
+ }
+}
+
+export default enhance(BugsAndVulnerabilities);
diff --git a/server/sonar-web/src/main/js/apps/overview/main/CodeSmells.js b/server/sonar-web/src/main/js/apps/overview/main/CodeSmells.js
new file mode 100644
index 00000000000..16dcb18ea73
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/overview/main/CodeSmells.js
@@ -0,0 +1,157 @@
+/*
+ * 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 moment from 'moment';
+import React from 'react';
+
+import enhance from './enhance';
+import { IssuesLink } from '../../../components/shared/issues-link';
+import { getMetricName } from '../helpers/metrics';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { formatMeasure, isDiffMetric } from '../../../helpers/measures';
+
+class CodeSmells extends React.Component {
+ renderHeader () {
+ return this.props.renderHeader(
+ 'Maintainability',
+ translate('metric.code_smells.name'));
+ }
+
+ renderDebt (metric, type) {
+ const { measures, component } = this.props;
+ const measure = measures.find(measure => measure.metric.key === metric);
+ const value = this.props.getValue(measure);
+ const params = { resolved: 'false', facetMode: 'effort', types: type };
+
+ if (isDiffMetric(metric)) {
+ Object.assign(params, { sinceLeakPeriod: 'true' });
+ }
+
+ const formattedSnapshotDate = moment(component.snapshotDate).format('LLL');
+ const tooltip = translateWithParameters('widget.as_calculated_on_x', formattedSnapshotDate);
+
+ return (
+ <IssuesLink component={component.key} params={params}>
+ <span title={tooltip} data-toggle="tooltip">
+ {formatMeasure(value, 'SHORT_WORK_DUR')}
+ </span>
+ </IssuesLink>
+ );
+ }
+
+ renderTimelineStartDate () {
+ const momentDate = moment(this.props.historyStartDate);
+ const fromNow = momentDate.fromNow();
+ return (
+ <span className="overview-domain-timeline-date">
+ {translateWithParameters('overview.started_x', fromNow)}
+ </span>
+ );
+ }
+
+ renderTimeline (range, displayDate) {
+ return this.props.renderTimeline(
+ 'sqale_index',
+ range,
+ displayDate ? this.renderTimelineStartDate() : null);
+ }
+
+ renderLeak () {
+ const { leakPeriod } = this.props;
+
+ if (leakPeriod == null) {
+ return null;
+ }
+
+ return (
+ <div className="overview-domain-leak">
+ <div className="overview-domain-measures">
+ <div className="overview-domain-measure">
+ <div className="overview-domain-measure-value">
+ {this.props.renderIssues('new_code_smells', 'CODE_SMELL')}
+ </div>
+ <div className="overview-domain-measure-label">
+ {getMetricName('new_code_smells')}
+ </div>
+ </div>
+
+ <div className="overview-domain-measure">
+ <div className="overview-domain-measure-value">
+ {this.renderDebt('new_effort', 'CODE_SMELL')}
+ </div>
+ <div className="overview-domain-measure-label">
+ {getMetricName('new_effort')}
+ </div>
+ </div>
+ </div>
+
+ {this.renderTimeline('after')}
+ </div>
+ );
+ }
+
+ renderNutshell () {
+ return (
+ <div className="overview-domain-nutshell">
+ <div className="overview-domain-measures">
+
+ <div className="overview-domain-measure">
+ <div className="display-inline-block text-middle" style={{ paddingLeft: 56 }}>
+ <div className="overview-domain-measure-value">
+ {this.props.renderIssues('code_smells', 'CODE_SMELL')}
+ {this.props.renderRating('sqale_rating')}
+ </div>
+ <div className="overview-domain-measure-label">
+ {getMetricName('code_smells')}
+ </div>
+ </div>
+ </div>
+
+ <div className="overview-domain-measure">
+ <div className="display-inline-block text-middle">
+ <div className="overview-domain-measure-value">
+ {this.renderDebt('sqale_index', 'CODE_SMELL')}
+ </div>
+ <div className="overview-domain-measure-label">
+ {getMetricName('effort')}
+ </div>
+ </div>
+ </div>
+ </div>
+
+ {this.renderTimeline('before', true)}
+ </div>
+ );
+ }
+
+ render () {
+ return (
+ <div className="overview-card">
+ {this.renderHeader()}
+
+ <div className="overview-domain-panel">
+ {this.renderNutshell()}
+ {this.renderLeak()}
+ </div>
+ </div>
+ );
+ }
+}
+
+export default enhance(CodeSmells);
diff --git a/server/sonar-web/src/main/js/apps/overview/main/Coverage.js b/server/sonar-web/src/main/js/apps/overview/main/Coverage.js
new file mode 100644
index 00000000000..d86fb2b88ff
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/overview/main/Coverage.js
@@ -0,0 +1,193 @@
+/*
+ * 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 enhance from './enhance';
+import { DrilldownLink } from '../../../components/shared/drilldown-link';
+import { getMetricName } from '../helpers/metrics';
+import { formatMeasure, getPeriodValue } from '../../../helpers/measures';
+import { translate } from '../../../helpers/l10n';
+
+class Coverage extends React.Component {
+ getCoverageMetricPrefix () {
+ const { measures } = this.props;
+ const hasOverallCoverage = !!measures
+ .find(measure => measure.metric.key === 'overall_coverage');
+ const hasUTCoverage = !!measures
+ .find(measure => measure.metric.key === 'coverage');
+ const hasITCoverage = !!measures
+ .find(measure => measure.metric.key === 'it_coverage');
+
+ if (hasOverallCoverage && hasUTCoverage && hasITCoverage) {
+ return 'overall_';
+ } else if (hasITCoverage) {
+ return 'it_';
+ } else {
+ return '';
+ }
+ }
+
+ getCoverage (prefix) {
+ const { measures } = this.props;
+ const { value } = measures
+ .find(measure => measure.metric.key === `${prefix}coverage`);
+ return Number(value);
+ }
+
+ getNewCoverageMeasure (prefix) {
+ const { measures } = this.props;
+ return measures
+ .find(measure => measure.metric.key === `new_${prefix}coverage`);
+ }
+
+ renderHeader () {
+ return this.props.renderHeader(
+ 'Coverage',
+ translate('metric.coverage.name'));
+ }
+
+ renderTimeline (coverageMetricPrefix, range) {
+ const metricKey = `${coverageMetricPrefix}coverage`;
+ return this.props.renderTimeline(metricKey, range);
+ }
+
+ renderTests () {
+ return this.props.renderMeasure('tests');
+ }
+
+ renderCoverageDonut (coverage) {
+ const data = [
+ { value: coverage, fill: '#85bb43' },
+ { value: 100 - coverage, fill: '#d4333f' }
+ ];
+ return this.props.renderDonut(data);
+ }
+
+ renderCoverage (coverageMetricPrefix) {
+ const { component } = this.props;
+ const metric = `${coverageMetricPrefix}coverage`;
+ const coverage = this.getCoverage(coverageMetricPrefix);
+
+ return (
+ <div className="overview-domain-measure">
+ {this.renderCoverageDonut(coverage)}
+
+ <div className="display-inline-block text-middle">
+ <div className="overview-domain-measure-value">
+ <DrilldownLink component={component.key} metric={metric}>
+ <span className="js-overview-main-coverage">
+ {formatMeasure(coverage, 'PERCENT')}
+ </span>
+ </DrilldownLink>
+ </div>
+
+ <div className="overview-domain-measure-label">
+ {getMetricName('coverage')}
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ renderNewCoverage (coverageMetricPrefix) {
+ const { component, leakPeriod } = this.props;
+ const newCoverageMeasure = this.getNewCoverageMeasure(coverageMetricPrefix);
+
+ const value = newCoverageMeasure ? (
+ <DrilldownLink
+ component={component.key}
+ metric={newCoverageMeasure.metric.key}
+ period={leakPeriod.index}>
+ <span className="js-overview-main-new-coverage">
+ {formatMeasure(getPeriodValue(newCoverageMeasure, leakPeriod.index), 'PERCENT')}
+ </span>
+ </DrilldownLink>
+ ) : (
+ <span>—</span>
+ );
+
+ return (
+ <div className="overview-domain-measure">
+ <div className="overview-domain-measure-value">
+ {value}
+ </div>
+
+ <div className="overview-domain-measure-label">
+ {getMetricName('new_coverage')}
+ </div>
+ </div>
+ );
+ }
+
+ renderNutshell (coverageMetricPrefix) {
+ return (
+ <div className="overview-domain-nutshell">
+ <div className="overview-domain-measures">
+ {this.renderCoverage(coverageMetricPrefix)}
+ {this.renderTests()}
+ </div>
+
+ {this.renderTimeline(coverageMetricPrefix, 'before')}
+ </div>
+ );
+ }
+
+ renderLeak (coverageMetricPrefix) {
+ const { leakPeriod } = this.props;
+
+ if (leakPeriod == null) {
+ return null;
+ }
+
+ return (
+ <div className="overview-domain-leak">
+ <div className="overview-domain-measures">
+ {this.renderNewCoverage(coverageMetricPrefix)}
+ </div>
+
+ {this.renderTimeline(coverageMetricPrefix, 'after')}
+ </div>
+ );
+ }
+
+ render () {
+ const { measures } = this.props;
+ const coverageMetricPrefix = this.getCoverageMetricPrefix();
+ const coverageMeasure =
+ measures.find(measure => measure.metric.key === `${coverageMetricPrefix}coverage`);
+
+ if (coverageMeasure == null) {
+ return null;
+ }
+
+ return (
+ <div className="overview-card">
+ {this.renderHeader()}
+
+ <div className="overview-domain-panel">
+ {this.renderNutshell(coverageMetricPrefix)}
+ {this.renderLeak(coverageMetricPrefix)}
+ </div>
+ </div>
+ );
+ }
+}
+
+export default enhance(Coverage);
diff --git a/server/sonar-web/src/main/js/apps/overview/main/Duplications.js b/server/sonar-web/src/main/js/apps/overview/main/Duplications.js
new file mode 100644
index 00000000000..06d9c113942
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/overview/main/Duplications.js
@@ -0,0 +1,132 @@
+/*
+ * 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 enhance from './enhance';
+import { DrilldownLink } from '../../../components/shared/drilldown-link';
+import { getMetricName } from '../helpers/metrics';
+import { formatMeasure } from '../../../helpers/measures';
+import { translate } from '../../../helpers/l10n';
+
+class Duplications extends React.Component {
+ renderHeader () {
+ return this.props.renderHeader(
+ 'Duplications',
+ translate('overview.domain.duplications'));
+ }
+
+ renderTimeline (range) {
+ return this.props.renderTimeline('duplicated_lines_density', range);
+ }
+
+ renderDuplicatedBlocks () {
+ return this.props.renderMeasure('duplicated_blocks');
+ }
+
+ renderDuplicationsDonut (duplications) {
+ const data = [
+ { value: duplications, fill: '#f3ca8e' },
+ { value: Math.max(0, 20 - duplications), fill: '#e6e6e6' }
+ ];
+ return this.props.renderDonut(data);
+ }
+
+ renderDuplications () {
+ const { component, measures } = this.props;
+ const measure = measures.find(measure => measure.metric.key === 'duplicated_lines_density');
+ const duplications = Number(measure.value);
+
+ return (
+ <div className="overview-domain-measure">
+ {this.renderDuplicationsDonut(duplications)}
+
+ <div className="display-inline-block text-middle">
+ <div className="overview-domain-measure-value">
+ <DrilldownLink component={component.key} metric="duplicated_lines_density">
+ {formatMeasure(duplications, 'PERCENT')}
+ </DrilldownLink>
+ </div>
+
+ <div className="overview-domain-measure-label">
+ {getMetricName('duplications')}
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ renderNutshell () {
+ return (
+ <div className="overview-domain-nutshell">
+ <div className="overview-domain-measures">
+ {this.renderDuplications()}
+ {this.renderDuplicatedBlocks()}
+ </div>
+
+ {this.renderTimeline('before')}
+ </div>
+ );
+ }
+
+ renderLeak () {
+ const { leakPeriod } = this.props;
+
+ if (leakPeriod == null) {
+ return null;
+ }
+
+ const measure = this.props.renderMeasureVariation(
+ 'duplicated_lines_density',
+ getMetricName('duplications'));
+
+ return (
+ <div className="overview-domain-leak">
+ <div className="overview-domain-measures">
+ {measure}
+ </div>
+
+ {this.renderTimeline('after')}
+ </div>
+ );
+ }
+
+ render () {
+ const { measures } = this.props;
+ const duplications =
+ measures.find(measure => measure.metric.key === 'duplicated_lines_density');
+
+ if (duplications == null) {
+ return null;
+ }
+
+ return (
+ <div className="overview-card">
+ {this.renderHeader()}
+
+ <div className="overview-domain-panel">
+ {this.renderNutshell()}
+ {this.renderLeak()}
+ </div>
+ </div>
+ );
+ }
+}
+
+export default enhance(Duplications);
diff --git a/server/sonar-web/src/main/js/apps/overview/main/Size.js b/server/sonar-web/src/main/js/apps/overview/main/Size.js
new file mode 100644
index 00000000000..e8f5f1e433c
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/overview/main/Size.js
@@ -0,0 +1,108 @@
+/*
+ * 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 enhance from './enhance';
+import LanguageDistribution from '../../../components/charts/LanguageDistribution';
+import { translate } from '../../../helpers/l10n';
+
+class Size extends React.Component {
+ renderHeader () {
+ return this.props.renderHeader(
+ 'Size',
+ translate('overview.domain.structure'));
+ }
+
+ renderTimeline (range) {
+ return this.props.renderTimeline('ncloc', range);
+ }
+
+ renderLeak () {
+ const { leakPeriod } = this.props;
+
+ if (leakPeriod == null) {
+ return null;
+ }
+
+ return (
+ <div className="overview-domain-leak">
+ <div className="overview-domain-measures">
+ {this.props.renderMeasureVariation('ncloc')}
+ </div>
+
+ {this.renderTimeline('after')}
+ </div>
+ );
+ }
+
+ renderLanguageDistribution () {
+ const { measures } = this.props;
+ const distribution =
+ measures.find(measure => measure.metric.key === 'ncloc_language_distribution');
+
+ if (!distribution) {
+ return null;
+ }
+
+ return (
+ <div className="overview-domain-measure">
+ <div style={{ width: 200 }}>
+ <LanguageDistribution distribution={distribution.value}/>
+ </div>
+ </div>
+ );
+ }
+
+ renderNutshell () {
+ return (
+ <div className="overview-domain-nutshell">
+ <div className="overview-domain-measures">
+ {this.renderLanguageDistribution()}
+ {this.props.renderMeasure('ncloc')}
+ </div>
+
+ {this.renderTimeline('before')}
+ </div>
+ );
+ }
+
+ render () {
+ const { measures } = this.props;
+ const linesOfCode =
+ measures.find(measure => measure.metric.key === 'ncloc');
+
+ if (!linesOfCode) {
+ return null;
+ }
+
+ return (
+ <div className="overview-card">
+ {this.renderHeader()}
+
+ <div className="overview-domain-panel">
+ {this.renderNutshell()}
+ {this.renderLeak()}
+ </div>
+ </div>
+ );
+ }
+}
+
+export default enhance(Size);
diff --git a/server/sonar-web/src/main/js/apps/overview/main/code-smells.js b/server/sonar-web/src/main/js/apps/overview/main/code-smells.js
deleted file mode 100644
index cc7cba65253..00000000000
--- a/server/sonar-web/src/main/js/apps/overview/main/code-smells.js
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * 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 moment from 'moment';
-import React from 'react';
-
-import {
- Domain,
- DomainPanel,
- DomainNutshell,
- DomainLeak,
- MeasuresList,
- Measure,
- DomainMixin
-} from './components';
-import { Rating } from './../../../components/shared/rating';
-import { IssuesLink } from '../../../components/shared/issues-link';
-import { DrilldownLink } from '../../../components/shared/drilldown-link';
-import { TooltipsMixin } from '../../../components/mixins/tooltips-mixin';
-import { getMetricName } from '../helpers/metrics';
-import { formatMeasure } from '../../../helpers/measures';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-
-export const CodeSmells = React.createClass({
- propTypes: {
- leakPeriodLabel: React.PropTypes.string,
- leakPeriodDate: React.PropTypes.object
- },
-
- mixins: [TooltipsMixin, DomainMixin],
-
- renderLeak () {
- if (!this.hasLeakPeriod()) {
- return null;
- }
-
- const { snapshotDate } = this.props.component;
- const formattedSnapshotDate = moment(snapshotDate).format('LLL');
- const newDebt = this.props.leak['new_technical_debt'] || 0;
- const newCodeSmells = this.props.leak['new_code_smells'] || 0;
-
- return <DomainLeak>
- <MeasuresList>
- <Measure label={getMetricName('new_code_smells')}>
- <IssuesLink
- component={this.props.component.key}
- params={{ resolved: 'false', types: 'CODE_SMELL', sinceLeakPeriod: 'true' }}>
- <span
- title={translateWithParameters('widget.as_calculated_on_x', formattedSnapshotDate)}
- data-toggle="tooltip">
- {formatMeasure(newCodeSmells, 'SHORT_INT')}
- </span>
- </IssuesLink>
- </Measure>
- <Measure label={getMetricName('new_effort')}>
- <IssuesLink
- component={this.props.component.key}
- params={{ resolved: 'false', types: 'CODE_SMELL', facetMode: 'effort', sinceLeakPeriod: 'true' }}>
- <span
- title={translateWithParameters('widget.as_calculated_on_x', formattedSnapshotDate)}
- data-toggle="tooltip">
- {formatMeasure(newDebt, 'SHORT_WORK_DUR')}
- </span>
- </IssuesLink>
- </Measure>
- </MeasuresList>
- {this.renderTimeline('after')}
- </DomainLeak>;
- },
-
- render () {
- const debt = this.props.measures['sqale_index'] || 0;
- const codeSmells = this.props.measures['code_smells'] || 0;
- const { snapshotDate } = this.props.component;
- const formattedSnapshotDate = moment(snapshotDate).format('LLL');
-
- const domainUrl = window.baseUrl + '/component_measures/domain/Maintainability?id=' +
- encodeURIComponent(this.props.component.key);
-
- return <Domain>
- <div className="overview-card-header">
- <div className="overview-title">
- <a href={domainUrl}>
- {translate('metric.code_smells.name')}
- </a>
- </div>
- </div>
-
- <DomainPanel>
- <DomainNutshell>
- <MeasuresList>
-
- <Measure composite={true}>
- <div className="display-inline-block text-middle" style={{ paddingLeft: 56 }}>
- <div className="overview-domain-measure-value">
- <IssuesLink
- component={this.props.component.key}
- params={{ resolved: 'false', types: 'CODE_SMELL' }}>
- <span
- title={translateWithParameters('widget.as_calculated_on_x', formattedSnapshotDate)}
- data-toggle="tooltip">
- {formatMeasure(codeSmells, 'SHORT_INT')}
- </span>
- </IssuesLink>
- <div className="overview-domain-measure-sup">
- <DrilldownLink component={this.props.component.key} metric="sqale_rating">
- <Rating value={this.props.measures['sqale_rating']}/>
- </DrilldownLink>
- </div>
- </div>
- <div className="overview-domain-measure-label">{getMetricName('code_smells')}</div>
- </div>
- </Measure>
-
- <Measure label={getMetricName('effort')}>
- <IssuesLink
- component={this.props.component.key}
- params={{ resolved: 'false', types: 'CODE_SMELL', facetMode: 'effort' }}>
- <span
- title={translateWithParameters('widget.as_calculated_on_x', formattedSnapshotDate)}
- data-toggle="tooltip">
- {formatMeasure(debt, 'SHORT_WORK_DUR')}
- </span>
- </IssuesLink>
- </Measure>
- </MeasuresList>
- {this.renderTimeline('before', true)}
- </DomainNutshell>
- {this.renderLeak()}
- </DomainPanel>
- </Domain>;
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/overview/main/components.js b/server/sonar-web/src/main/js/apps/overview/main/components.js
deleted file mode 100644
index 8fe41b3014a..00000000000
--- a/server/sonar-web/src/main/js/apps/overview/main/components.js
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * 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 moment from 'moment';
-import React from 'react';
-
-import { Timeline } from './timeline';
-import { translateWithParameters } from '../../../helpers/l10n';
-
-export const Domain = React.createClass({
- render () {
- return <div className="overview-card">{this.props.children}</div>;
- }
-});
-
-export const DomainTitle = React.createClass({
- render () {
- return <div className="overview-title">{this.props.children}</div>;
- }
-});
-
-export const DomainLeakTitle = React.createClass({
- renderInline (tooltip, fromNow) {
- return <span className="overview-domain-leak-title" title={tooltip} data-toggle="tooltip">
- <span>{translateWithParameters('overview.leak_period_x', this.props.label)}</span>
- <span className="note spacer-left">{translateWithParameters('overview.started_x', fromNow)}</span>
- </span>;
- },
-
- render() {
- if (!this.props.label || !this.props.date) {
- return null;
- }
- const momentDate = moment(this.props.date);
- const fromNow = momentDate.fromNow();
- const tooltip = 'Started on ' + momentDate.format('LL');
- if (this.props.inline) {
- return this.renderInline(tooltip, fromNow);
- }
- return <span className="overview-domain-leak-title" title={tooltip} data-toggle="tooltip">
- <span>{translateWithParameters('overview.leak_period_x', this.props.label)}</span>
- <br/>
- <span className="note">{translateWithParameters('overview.started_x', fromNow)}</span>
- </span>;
- }
-});
-
-export const DomainHeader = React.createClass({
- render () {
- return <div className="overview-card-header">
- <DomainTitle {...this.props}>{this.props.title}</DomainTitle>
- </div>;
- }
-});
-
-export const DomainPanel = React.createClass({
- propTypes: {
- domain: React.PropTypes.string
- },
-
- render () {
- return <div className="overview-domain-panel">
- {this.props.children}
- </div>;
- }
-});
-
-export const DomainNutshell = React.createClass({
- render () {
- return <div className="overview-domain-nutshell">{this.props.children}</div>;
- }
-});
-
-export const DomainLeak = React.createClass({
- render () {
- return <div className="overview-domain-leak">{this.props.children}</div>;
- }
-});
-
-export const MeasuresList = React.createClass({
- render () {
- return <div className="overview-domain-measures">{this.props.children}</div>;
- }
-});
-
-export const Measure = React.createClass({
- propTypes: {
- label: React.PropTypes.string,
- composite: React.PropTypes.bool
- },
-
- getDefaultProps() {
- return { composite: false };
- },
-
- renderValue () {
- if (this.props.composite) {
- return this.props.children;
- } else {
- return <div className="overview-domain-measure-value">
- {this.props.children}
- </div>;
- }
- },
-
- renderLabel() {
- return this.props.label ?
- <div className="overview-domain-measure-label">{this.props.label}</div> : null;
- },
-
- render () {
- return <div className="overview-domain-measure">
- {this.renderValue()}
- {this.renderLabel()}
- </div>;
- }
-});
-
-export const DomainMixin = {
- renderTimelineStartDate() {
- const momentDate = moment(this.props.historyStartDate);
- const fromNow = momentDate.fromNow();
- return (
- <span className="overview-domain-timeline-date">
- {translateWithParameters('overview.started_x', fromNow)}
- </span>
- );
- },
-
- renderTimeline(range, displayDate) {
- if (!this.props.history) {
- return null;
- }
- const props = { history: this.props.history };
- props[range] = this.props.leakPeriodDate;
- return <div className="overview-domain-timeline">
- <Timeline {...props}/>
- {displayDate ? this.renderTimelineStartDate(range) : null}
- </div>;
- },
-
- hasLeakPeriod () {
- return this.props.leakPeriodDate != null;
- }
-};
diff --git a/server/sonar-web/src/main/js/apps/overview/main/coverage.js b/server/sonar-web/src/main/js/apps/overview/main/coverage.js
deleted file mode 100644
index 81639060a0d..00000000000
--- a/server/sonar-web/src/main/js/apps/overview/main/coverage.js
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * 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 {
- Domain,
- DomainPanel,
- DomainNutshell,
- DomainLeak,
- MeasuresList,
- Measure,
- DomainMixin
-} from './components';
-import { DrilldownLink } from '../../../components/shared/drilldown-link';
-import { TooltipsMixin } from '../../../components/mixins/tooltips-mixin';
-import { DonutChart } from '../../../components/charts/donut-chart';
-import { getMetricName } from '../helpers/metrics';
-import { formatMeasure } from '../../../helpers/measures';
-import { translate } from '../../../helpers/l10n';
-
-export const GeneralCoverage = React.createClass({
- propTypes: {
- measures: React.PropTypes.object.isRequired,
- leakPeriodLabel: React.PropTypes.string,
- leakPeriodDate: React.PropTypes.object,
- coverageMetricPrefix: React.PropTypes.string.isRequired
- },
-
- mixins: [TooltipsMixin, DomainMixin],
-
- getCoverageMetric () {
- return this.props.coverageMetricPrefix + 'coverage';
- },
-
- getNewCoverageMetric () {
- return 'new_' + this.props.coverageMetricPrefix + 'coverage';
- },
-
- renderNewCoverage () {
- const newCoverageMetric = this.getNewCoverageMetric();
-
- if (this.props.leak[newCoverageMetric] != null) {
- return <DrilldownLink component={this.props.component.key} metric={newCoverageMetric}
- period={this.props.leakPeriodIndex}>
- <span className="js-overview-main-new-coverage">
- {formatMeasure(this.props.leak[newCoverageMetric], 'PERCENT')}
- </span>
- </DrilldownLink>;
- } else {
- return <span>—</span>;
- }
- },
-
- renderLeak () {
- if (!this.hasLeakPeriod()) {
- return null;
- }
-
- return <DomainLeak>
- <MeasuresList>
- <Measure label={getMetricName('new_coverage')}>{this.renderNewCoverage()}</Measure>
- </MeasuresList>
- {this.renderTimeline('after')}
- </DomainLeak>;
- },
-
- renderTests() {
- const tests = this.props.measures['tests'];
- if (tests == null) {
- return null;
- }
- return <Measure label={getMetricName('tests')}>
- <DrilldownLink component={this.props.component.key} metric="tests">
- <span className="js-overview-main-tests">{formatMeasure(tests, 'SHORT_INT')}</span>
- </DrilldownLink>
- </Measure>;
- },
-
- render () {
- const coverageMetric = this.getCoverageMetric();
- if (this.props.measures[coverageMetric] == null) {
- return null;
- }
-
- const donutData = [
- { value: this.props.measures[coverageMetric], fill: '#85bb43' },
- { value: 100 - this.props.measures[coverageMetric], fill: '#d4333f' }
- ];
-
- const domainUrl = window.baseUrl + '/component_measures/domain/Coverage?id=' +
- encodeURIComponent(this.props.component.key);
-
- return <Domain>
- <div className="overview-card-header">
- <div className="overview-title">
- <a href={domainUrl}>
- {translate('metric.coverage.name')}
- </a>
- </div>
- </div>
-
- <DomainPanel>
- <DomainNutshell>
- <MeasuresList>
-
- <Measure composite={true}>
- <div className="display-inline-block text-middle big-spacer-right">
- <DonutChart width="40"
- height="40"
- thickness="4"
- data={donutData}/>
- </div>
- <div className="display-inline-block text-middle">
- <div className="overview-domain-measure-value">
- <DrilldownLink component={this.props.component.key} metric={coverageMetric}>
- <span className="js-overview-main-coverage">
- {formatMeasure(this.props.measures[coverageMetric], 'PERCENT')}
- </span>
- </DrilldownLink>
- </div>
- <div className="overview-domain-measure-label">{getMetricName('coverage')}</div>
- </div>
- </Measure>
-
- {this.renderTests()}
- </MeasuresList>
- {this.renderTimeline('before')}
- </DomainNutshell>
- {this.renderLeak()}
- </DomainPanel>
- </Domain>;
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/overview/main/duplications.js b/server/sonar-web/src/main/js/apps/overview/main/duplications.js
deleted file mode 100644
index 96c94d50e61..00000000000
--- a/server/sonar-web/src/main/js/apps/overview/main/duplications.js
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * 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 { Domain,
- DomainPanel,
- DomainNutshell,
- DomainLeak,
- MeasuresList,
- Measure,
- DomainMixin } from './components';
-import { DrilldownLink } from '../../../components/shared/drilldown-link';
-import { TooltipsMixin } from '../../../components/mixins/tooltips-mixin';
-import { DonutChart } from '../../../components/charts/donut-chart';
-import { getMetricName } from '../helpers/metrics';
-import { formatMeasure, formatMeasureVariation } from '../../../helpers/measures';
-import { translate } from '../../../helpers/l10n';
-
-export const GeneralDuplications = React.createClass({
- propTypes: {
- leakPeriodLabel: React.PropTypes.string,
- leakPeriodDate: React.PropTypes.object
- },
-
- mixins: [TooltipsMixin, DomainMixin],
-
- renderLeak () {
- if (!this.hasLeakPeriod()) {
- return null;
- }
- const measure = this.props.leak['duplicated_lines_density'];
- const formatted = measure != null ? formatMeasureVariation(measure, 'PERCENT') : '—';
- return <DomainLeak>
- <MeasuresList>
- <Measure label={getMetricName('duplications')}>
- {formatted}
- </Measure>
- </MeasuresList>
- {this.renderTimeline('after')}
- </DomainLeak>;
- },
-
- renderDuplicatedBlocks () {
- if (this.props.measures['duplicated_blocks'] == null) {
- return null;
- }
- return <Measure label={getMetricName('duplicated_blocks')}>
- <DrilldownLink component={this.props.component.key} metric="duplicated_blocks">
- {formatMeasure(this.props.measures['duplicated_blocks'], 'SHORT_INT')}
- </DrilldownLink>
- </Measure>;
- },
-
- render () {
- const donutData = [
- { value: this.props.measures['duplicated_lines_density'], fill: '#f3ca8e' },
- { value: Math.max(0, 20 - this.props.measures['duplicated_lines_density']), fill: '#e6e6e6' }
- ];
-
- const domainUrl = window.baseUrl + '/component_measures/domain/Duplications?id=' +
- encodeURIComponent(this.props.component.key);
-
- return <Domain>
- <div className="overview-card-header">
- <div className="overview-title">
- <a href={domainUrl}>
- {translate('overview.domain.duplications')}
- </a>
- </div>
- </div>
-
- <DomainPanel>
- <DomainNutshell>
- <MeasuresList>
-
- <Measure composite={true}>
- <div className="display-inline-block text-middle big-spacer-right">
- <DonutChart width="40"
- height="40"
- thickness="4"
- data={donutData}/>
- </div>
- <div className="display-inline-block text-middle">
- <div className="overview-domain-measure-value">
- <DrilldownLink component={this.props.component.key} metric="duplicated_lines_density">
- {formatMeasure(this.props.measures['duplicated_lines_density'], 'PERCENT')}
- </DrilldownLink>
- </div>
- <div className="overview-domain-measure-label">{getMetricName('duplications')}</div>
- </div>
- </Measure>
-
- {this.renderDuplicatedBlocks()}
- </MeasuresList>
- {this.renderTimeline('before')}
- </DomainNutshell>
- {this.renderLeak()}
- </DomainPanel>
- </Domain>;
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/overview/main/enhance.js b/server/sonar-web/src/main/js/apps/overview/main/enhance.js
new file mode 100644
index 00000000000..280a9dc799b
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/overview/main/enhance.js
@@ -0,0 +1,213 @@
+/*
+ * 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 moment from 'moment';
+import shallowCompare from 'react-addons-shallow-compare';
+
+import { DrilldownLink } from '../../../components/shared/drilldown-link';
+import { IssuesLink } from '../../../components/shared/issues-link';
+import { DonutChart } from '../../../components/charts/donut-chart';
+import { Rating } from './../../../components/shared/rating';
+import Timeline from '../components/Timeline';
+import {
+ formatMeasure,
+ formatMeasureVariation,
+ isDiffMetric,
+ getPeriodValue,
+ getShortType
+} from '../../../helpers/measures';
+import { translateWithParameters } from '../../../helpers/l10n';
+import { getPeriodDate } from '../../../helpers/periods';
+
+export default function enhance (ComposedComponent) {
+ return class extends React.Component {
+ static displayName = `enhance(${ComposedComponent.displayName})}`;
+
+ shouldComponentUpdate (nextProps, nextState) {
+ return shallowCompare(this, nextProps, nextState);
+ }
+
+ getValue (measure) {
+ const { leakPeriod } = this.props;
+
+ if (!measure) {
+ return 0;
+ }
+
+ return isDiffMetric(measure.metric.key) ?
+ getPeriodValue(measure, leakPeriod.index) :
+ measure.value;
+ }
+
+ renderHeader (label, domain) {
+ const { component } = this.props;
+ const domainUrl =
+ window.baseUrl +
+ `/component_measures/domain/${domain}` +
+ `id=${encodeURIComponent(component.key)}`;
+
+ return (
+ <div className="overview-card-header">
+ <div className="overview-title">
+ <a href={domainUrl}>{label}</a>
+ </div>
+ </div>
+ );
+ }
+
+ renderMeasure (metricKey) {
+ const { measures, component } = this.props;
+ const measure = measures.find(measure => measure.metric.key === metricKey);
+
+ if (measure == null) {
+ return null;
+ }
+
+ return (
+ <div className="overview-domain-measure">
+ <div className="overview-domain-measure-value">
+ <DrilldownLink component={component.key} metric={metricKey}>
+ <span className="js-overview-main-tests">
+ {formatMeasure(measure.value, getShortType(measure.metric.type))}
+ </span>
+ </DrilldownLink>
+ </div>
+
+ <div className="overview-domain-measure-label">
+ {measure.metric.name}
+ </div>
+ </div>
+ );
+ }
+
+ renderMeasureVariation (metricKey, customLabel) {
+ const NO_VALUE = '—';
+ const { measures, leakPeriod } = this.props;
+
+ const measure = measures.find(measure => measure.metric.key === metricKey);
+ const periodValue = getPeriodValue(measure, leakPeriod.index);
+ const formatted = periodValue != null ?
+ formatMeasureVariation(periodValue, getShortType(measure.metric.type)) :
+ NO_VALUE;
+
+ return (
+ <div className="overview-domain-measure">
+ <div className="overview-domain-measure-value">
+ {formatted}
+ </div>
+
+ <div className="overview-domain-measure-label">
+ {customLabel || measure.metric.name}
+ </div>
+ </div>
+ );
+ }
+
+ renderDonut (data) {
+ return (
+ <div className="display-inline-block text-middle big-spacer-right">
+ <DonutChart
+ data={data}
+ width={40}
+ height={40}
+ thickness={4}/>
+ </div>
+ );
+ }
+
+ renderRating (metricKey) {
+ const { component, measures } = this.props;
+ const measure = measures.find(measure => measure.metric.key === metricKey);
+
+ if (!measure) {
+ return null;
+ }
+
+ return (
+ <div className="overview-domain-measure-sup">
+ <DrilldownLink component={component.key} metric={metricKey}>
+ <Rating value={measure.value}/>
+ </DrilldownLink>
+ </div>
+ );
+ }
+
+ renderIssues (metric, type) {
+ const { measures, component } = this.props;
+ const measure = measures.find(measure => measure.metric.key === metric);
+ const value = this.getValue(measure);
+ const params = { resolved: 'false', types: type };
+
+ if (isDiffMetric(metric)) {
+ Object.assign(params, { sinceLeakPeriod: 'true' });
+ }
+
+ const formattedSnapshotDate = moment(component.snapshotDate).format('LLL');
+ const tooltip = translateWithParameters('widget.as_calculated_on_x', formattedSnapshotDate);
+
+ return (
+ <IssuesLink component={component.key} params={params}>
+ <span title={tooltip} data-toggle="tooltip">
+ {formatMeasure(value, 'SHORT_INT')}
+ </span>
+ </IssuesLink>
+ );
+ }
+
+ renderTimeline (metricKey, range, children) {
+ if (!this.props.history) {
+ return null;
+ }
+
+ const history = this.props.history[metricKey];
+
+ if (!history) {
+ return null;
+ }
+
+ const props = {
+ history,
+ [range]: getPeriodDate(this.props.leakPeriod)
+ };
+
+ return (
+ <div className="overview-domain-timeline">
+ <Timeline {...props}/>
+ {children}
+ </div>
+ );
+ }
+
+ render () {
+ return (
+ <ComposedComponent
+ {...this.props}
+ getValue={this.getValue.bind(this)}
+ renderHeader={this.renderHeader.bind(this)}
+ renderMeasure={this.renderMeasure.bind(this)}
+ renderMeasureVariation={this.renderMeasureVariation.bind(this)}
+ renderDonut={this.renderDonut.bind(this)}
+ renderRating={this.renderRating.bind(this)}
+ renderIssues={this.renderIssues.bind(this)}
+ renderTimeline={this.renderTimeline.bind(this)}/>
+ );
+ }
+ };
+}
diff --git a/server/sonar-web/src/main/js/apps/overview/main/main.js b/server/sonar-web/src/main/js/apps/overview/main/main.js
deleted file mode 100644
index 540ce0b3e2a..00000000000
--- a/server/sonar-web/src/main/js/apps/overview/main/main.js
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * 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 _ from 'underscore';
-import moment from 'moment';
-import React from 'react';
-
-import { Risk } from './risk';
-import { CodeSmells } from './code-smells';
-import { GeneralCoverage } from './coverage';
-import { GeneralDuplications } from './duplications';
-import { GeneralStructure } from './structure';
-import { CoverageSelectionMixin } from '../components/coverage-selection-mixin';
-import { getPeriodLabel, getPeriodDate } from './../helpers/periods';
-import { getMeasures } from '../../../api/measures';
-import { getTimeMachineData } from '../../../api/time-machine';
-
-const METRICS_LIST = [
- 'overall_coverage',
- 'new_overall_coverage',
- 'coverage',
- 'new_coverage',
- 'it_coverage',
- 'new_it_coverage',
- 'tests',
- 'duplicated_lines_density',
- 'duplicated_blocks',
- 'ncloc',
- 'ncloc_language_distribution',
-
- 'sqale_index',
- 'new_technical_debt',
- 'code_smells',
- 'new_code_smells',
- 'sqale_rating',
- 'reliability_rating',
- 'bugs',
- 'new_bugs',
- 'security_rating',
- 'vulnerabilities',
- 'new_vulnerabilities'
-];
-
-const HISTORY_METRICS_LIST = [
- 'sqale_index',
- 'duplicated_lines_density',
- 'ncloc'
-];
-
-export default React.createClass({
- propTypes: {
- leakPeriodIndex: React.PropTypes.string.isRequired
- },
-
- mixins: [CoverageSelectionMixin],
-
- getInitialState() {
- return {
- ready: false,
- history: {},
- leakPeriodLabel: getPeriodLabel(this.props.component.periods, this.props.leakPeriodIndex),
- leakPeriodDate: getPeriodDate(this.props.component.periods, this.props.leakPeriodIndex)
- };
- },
-
- componentDidMount() {
- this.requestMeasures().then(r => {
- const measures = this.getMeasuresValues(r);
-
- let leak;
- if (this.state.leakPeriodDate) {
- leak = this.getMeasuresValues(r, Number(this.props.leakPeriodIndex));
- }
-
- this.setState({
- ready: true,
- measures,
- leak,
- coverageMetricPrefix: this.getCoverageMetricPrefix(measures)
- }, this.requestHistory);
- });
- },
-
- requestMeasures () {
- return getMeasures(this.props.component.key, METRICS_LIST);
- },
-
- getMeasuresValues (measures, period) {
- const values = {};
- measures.forEach(measure => {
- const container = period ? _.findWhere(measure.periods, { index: period }) : measure;
- if (container) {
- values[measure.metric] = container.value;
- }
- });
- return values;
- },
-
- requestHistory () {
- const coverageMetric = this.state.coverageMetricPrefix + 'coverage';
- const metrics = [].concat(HISTORY_METRICS_LIST, coverageMetric).join(',');
- return getTimeMachineData(this.props.component.key, metrics).then(r => {
- const history = {};
- r[0].cols.forEach((col, index) => {
- history[col.metric] = r[0].cells.map(cell => {
- const date = moment(cell.d).toDate();
- const value = cell.v[index] || 0;
- return { date, value };
- });
- });
- const historyStartDate = history[HISTORY_METRICS_LIST[0]][0].date;
- this.setState({ history, historyStartDate });
- });
- },
-
- renderLoading () {
- return <div className="text-center">
- <i className="spinner spinner-margin"/>
- </div>;
- },
-
- render() {
- if (!this.state.ready) {
- return this.renderLoading();
- }
-
- const coverageMetric = this.state.coverageMetricPrefix + 'coverage';
- const props = _.extend({}, this.props, this.state);
-
- return <div className="overview-domains-list">
- <Risk {...props}/>
- <CodeSmells {...props} history={this.state.history['sqale_index']}/>
- <GeneralCoverage {...props} coverageMetricPrefix={this.state.coverageMetricPrefix}
- history={this.state.history[coverageMetric]}/>
- <GeneralDuplications {...props} history={this.state.history['duplicated_lines_density']}/>
- <GeneralStructure {...props} history={this.state.history['ncloc']}/>
- </div>;
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/overview/main/risk.js b/server/sonar-web/src/main/js/apps/overview/main/risk.js
deleted file mode 100644
index d6942d02056..00000000000
--- a/server/sonar-web/src/main/js/apps/overview/main/risk.js
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * 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 moment from 'moment';
-import React from 'react';
-
-import {
- DomainPanel,
- DomainNutshell,
- DomainLeak,
- MeasuresList,
- Measure,
- DomainMixin
-} from './components';
-import { Rating } from './../../../components/shared/rating';
-import { IssuesLink } from '../../../components/shared/issues-link';
-import { DrilldownLink } from '../../../components/shared/drilldown-link';
-import { TooltipsMixin } from '../../../components/mixins/tooltips-mixin';
-import { Legend } from '../components/legend';
-import { getMetricName } from '../helpers/metrics';
-import { formatMeasure } from '../../../helpers/measures';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-
-export const Risk = React.createClass({
- propTypes: {
- leakPeriodLabel: React.PropTypes.string,
- leakPeriodDate: React.PropTypes.object
- },
-
- mixins: [TooltipsMixin, DomainMixin],
-
- renderLeak () {
- if (!this.hasLeakPeriod()) {
- return null;
- }
-
- const { snapshotDate } = this.props.component;
- const formattedSnapshotDate = moment(snapshotDate).format('LLL');
- const newBugs = this.props.leak['new_bugs'] || 0;
- const newVulnerabilities = this.props.leak['new_vulnerabilities'] || 0;
-
- return <DomainLeak>
- <Legend leakPeriodLabel={this.props.leakPeriodLabel} leakPeriodDate={this.props.leakPeriodDate}/>
-
- <MeasuresList>
- <Measure label={getMetricName('new_bugs')}>
- <IssuesLink
- component={this.props.component.key}
- params={{ resolved: 'false', types: 'BUG', sinceLeakPeriod: 'true' }}>
- <span
- title={translateWithParameters('widget.as_calculated_on_x', formattedSnapshotDate)}
- data-toggle="tooltip">
- {formatMeasure(newBugs, 'SHORT_INT')}
- </span>
- </IssuesLink>
- </Measure>
- <Measure label={getMetricName('new_vulnerabilities')}>
- <IssuesLink
- component={this.props.component.key}
- params={{ resolved: 'false', types: 'VULNERABILITY', sinceLeakPeriod: 'true' }}>
- <span
- title={translateWithParameters('widget.as_calculated_on_x', formattedSnapshotDate)}
- data-toggle="tooltip">
- {formatMeasure(newVulnerabilities, 'SHORT_INT')}
- </span>
- </IssuesLink>
- </Measure>
- </MeasuresList>
- </DomainLeak>;
- },
-
- render () {
- const { snapshotDate } = this.props.component;
- const formattedSnapshotDate = moment(snapshotDate).format('LLL');
- const bugs = this.props.measures['bugs'] || 0;
- const vulnerabilities = this.props.measures['vulnerabilities'] || 0;
-
- const bugsDomainUrl = window.baseUrl + '/component_measures/domain/Reliability?id=' +
- encodeURIComponent(this.props.component.key);
- const vulnerabilitiesDomainUrl = window.baseUrl + '/component_measures/domain/Security?id=' +
- encodeURIComponent(this.props.component.key);
-
- return <div className="overview-card overview-card-special">
- <div className="overview-card-header">
- <div className="overview-title">
- <a href={bugsDomainUrl}>
- {translate('metric.bugs.name')}
- </a>
- {' & '}
- <a href={vulnerabilitiesDomainUrl}>
- {translate('metric.vulnerabilities.name')}
- </a>
- </div>
- </div>
-
- <DomainPanel>
- <DomainNutshell>
- <MeasuresList>
-
- <Measure composite={true}>
- <div className="display-inline-block text-middle" style={{ paddingLeft: 56 }}>
- <div className="overview-domain-measure-value">
- <IssuesLink
- component={this.props.component.key}
- params={{ resolved: 'false', types: 'BUG' }}>
- <span
- title={translateWithParameters('widget.as_calculated_on_x', formattedSnapshotDate)}
- data-toggle="tooltip">
- {formatMeasure(bugs, 'SHORT_INT')}
- </span>
- </IssuesLink>
- <div className="overview-domain-measure-sup">
- <DrilldownLink component={this.props.component.key} metric="reliability_rating">
- <Rating value={this.props.measures['reliability_rating']}/>
- </DrilldownLink>
- </div>
- </div>
- <div className="overview-domain-measure-label">{getMetricName('bugs')}</div>
- </div>
- </Measure>
-
- <Measure composite={true}>
- <div className="display-inline-block text-middle">
- <div className="overview-domain-measure-value">
- <IssuesLink
- component={this.props.component.key}
- params={{ resolved: 'false', types: 'VULNERABILITY' }}>
- <span
- title={translateWithParameters('widget.as_calculated_on_x', formattedSnapshotDate)}
- data-toggle="tooltip">
- {formatMeasure(vulnerabilities, 'SHORT_INT')}
- </span>
- </IssuesLink>
- <div className="overview-domain-measure-sup">
- <DrilldownLink component={this.props.component.key} metric="security_rating">
- <Rating value={this.props.measures['security_rating']}/>
- </DrilldownLink>
- </div>
- </div>
- <div className="overview-domain-measure-label">{getMetricName('vulnerabilities')}</div>
- </div>
- </Measure>
- </MeasuresList>
- </DomainNutshell>
- {this.renderLeak()}
- </DomainPanel>
- </div>;
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/overview/main/structure.js b/server/sonar-web/src/main/js/apps/overview/main/structure.js
deleted file mode 100644
index fe0e43bd5e1..00000000000
--- a/server/sonar-web/src/main/js/apps/overview/main/structure.js
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * 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 { Domain,
- DomainPanel,
- DomainNutshell,
- DomainLeak,
- MeasuresList,
- Measure,
- DomainMixin } from './components';
-import { DrilldownLink } from '../../../components/shared/drilldown-link';
-import { TooltipsMixin } from '../../../components/mixins/tooltips-mixin';
-import { getMetricName } from '../helpers/metrics';
-import { formatMeasure, formatMeasureVariation } from '../../../helpers/measures';
-import { LanguageDistribution } from '../components/language-distribution';
-import { translate } from '../../../helpers/l10n';
-
-export const GeneralStructure = React.createClass({
- propTypes: {
- leakPeriodLabel: React.PropTypes.string,
- leakPeriodDate: React.PropTypes.object
- },
-
- mixins: [TooltipsMixin, DomainMixin],
-
- renderLeak () {
- if (!this.hasLeakPeriod()) {
- return null;
- }
- const measure = this.props.leak['ncloc'];
- const formatted = measure != null ? formatMeasureVariation(measure, 'SHORT_INT') : '—';
- return <DomainLeak>
- <MeasuresList>
- <Measure label={getMetricName('ncloc')}>{formatted}</Measure>
- </MeasuresList>
- {this.renderTimeline('after')}
- </DomainLeak>;
- },
-
- renderLanguageDistribution() {
- if (!this.props.measures['ncloc'] || !this.props.measures['ncloc_language_distribution']) {
- return null;
- }
- return <Measure composite={true}>
- <div style={{ width: 200 }}>
- <LanguageDistribution lines={Number(this.props.measures['ncloc'])}
- distribution={this.props.measures['ncloc_language_distribution']}/>
- </div>
- </Measure>;
- },
-
- render () {
- const domainUrl = window.baseUrl + '/component_measures/domain/Size?id=' +
- encodeURIComponent(this.props.component.key);
-
- return <Domain>
- <div className="overview-card-header">
- <div className="overview-title">
- <a href={domainUrl}>
- {translate('overview.domain.structure')}
- </a>
- </div>
- </div>
-
- <DomainPanel>
- <DomainNutshell>
- <MeasuresList>
- {this.renderLanguageDistribution()}
- <Measure label={getMetricName('ncloc')}>
- <DrilldownLink component={this.props.component.key} metric="ncloc">
- {formatMeasure(this.props.measures['ncloc'], 'SHORT_INT')}
- </DrilldownLink>
- </Measure>
- </MeasuresList>
- {this.renderTimeline('before')}
- </DomainNutshell>
- {this.renderLeak()}
- </DomainPanel>
- </Domain>;
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/overview/components/Meta.js b/server/sonar-web/src/main/js/apps/overview/meta/Meta.js
index 77b5d3b6d75..c87e51e3010 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/Meta.js
+++ b/server/sonar-web/src/main/js/apps/overview/meta/Meta.js
@@ -23,9 +23,9 @@ import MetaKey from './MetaKey';
import MetaLinks from './MetaLinks';
import MetaQualityGate from './MetaQualityGate';
import MetaQualityProfiles from './MetaQualityProfiles';
-import EventsList from './EventsList';
+import EventsList from './../events/EventsList';
-export default function Meta ({ component }) {
+const Meta = ({ component }) => {
const { qualifier, description, links, profiles, gate } = component;
const isView = qualifier === 'VW' || qualifier === 'SVW';
@@ -70,4 +70,6 @@ export default function Meta ({ component }) {
<EventsList component={component}/>
</div>
);
-}
+};
+
+export default Meta;
diff --git a/server/sonar-web/src/main/js/apps/overview/components/MetaKey.js b/server/sonar-web/src/main/js/apps/overview/meta/MetaKey.js
index 71d212891cc..9a7e67346c4 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/MetaKey.js
+++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaKey.js
@@ -21,7 +21,7 @@ import React from 'react';
import { translate } from '../../../helpers/l10n';
-export default function MetaKey ({ component }) {
+const MetaKey = ({ component }) => {
return (
<div>
<h4 className="overview-meta-header">
@@ -34,4 +34,6 @@ export default function MetaKey ({ component }) {
readOnly={true}/>
</div>
);
-}
+};
+
+export default MetaKey;
diff --git a/server/sonar-web/src/main/js/apps/overview/components/MetaLinks.js b/server/sonar-web/src/main/js/apps/overview/meta/MetaLinks.js
index 0d44738ff1f..369846ccb28 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/MetaLinks.js
+++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaLinks.js
@@ -19,7 +19,7 @@
*/
import React from 'react';
-export default function MetaLinks ({ links }) {
+const MetaLinks = ({ links }) => {
return (
<ul className="overview-meta-list big-spacer-bottom">
{links.map(link => (
@@ -36,4 +36,6 @@ export default function MetaLinks ({ links }) {
))}
</ul>
);
-}
+};
+
+export default MetaLinks;
diff --git a/server/sonar-web/src/main/js/apps/overview/components/MetaQualityGate.js b/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityGate.js
index 8f5170d04ea..3b26eb80623 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/MetaQualityGate.js
+++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityGate.js
@@ -22,7 +22,7 @@ import React from 'react';
import { QualityGateLink } from '../../../components/shared/quality-gate-link';
import { translate } from '../../../helpers/l10n';
-export default function MetaQualityGate ({ gate }) {
+const MetaQualityGate = ({ gate }) => {
return (
<div className="big-spacer-bottom">
<h4 className="overview-meta-header">
@@ -43,4 +43,6 @@ export default function MetaQualityGate ({ gate }) {
</ul>
</div>
);
-}
+};
+
+export default MetaQualityGate;
diff --git a/server/sonar-web/src/main/js/apps/overview/components/MetaQualityProfiles.js b/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.js
index 1bb15965a5a..d889f815c2f 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/MetaQualityProfiles.js
+++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.js
@@ -22,7 +22,7 @@ import React from 'react';
import { QualityProfileLink } from '../../../components/shared/quality-profile-link';
import { translate } from '../../../helpers/l10n';
-export default function MetaQualityProfiles ({ profiles }) {
+const MetaQualityProfiles = ({ profiles }) => {
return (
<div>
<h4 className="overview-meta-header">
@@ -43,4 +43,6 @@ export default function MetaQualityProfiles ({ profiles }) {
</ul>
</div>
);
-}
+};
+
+export default MetaQualityProfiles;
diff --git a/server/sonar-web/src/main/js/apps/overview/propTypes.js b/server/sonar-web/src/main/js/apps/overview/propTypes.js
new file mode 100644
index 00000000000..50c3cffb978
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/overview/propTypes.js
@@ -0,0 +1,69 @@
+/*
+ * 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 { PropTypes } from 'react';
+
+const { shape, arrayOf, array, string, number, object } = PropTypes;
+
+export const ComponentType = shape({
+ id: string.isRequired
+});
+
+export const MetricType = shape({
+ key: string.isRequired,
+ name: string.isRequired,
+ type: string.isRequired
+});
+
+export const MeasureType = shape({
+ metric: MetricType.isRequired,
+ value: string,
+ periods: array
+});
+
+export const MeasuresListType = arrayOf(MeasureType);
+
+export const ConditionType = shape({
+ metric: string.isRequired
+});
+
+export const EnhancedConditionType = shape({
+ measure: MeasureType.isRequired
+});
+
+export const ConditionsListType = arrayOf(ConditionType);
+
+export const EnhancedConditionsListType = arrayOf(EnhancedConditionType);
+
+export const PeriodType = shape({
+ index: number.isRequired,
+ date: string.isRequired,
+ mode: string.isRequired,
+ parameter: string
+});
+
+export const PeriodsListType = arrayOf(PeriodType);
+
+export const EventType = shape({
+ id: string.isRequired,
+ date: object.isRequired,
+ type: string.isRequired,
+ name: string.isRequired,
+ text: string
+});
diff --git a/server/sonar-web/src/main/js/apps/overview/gate/gate-empty.js b/server/sonar-web/src/main/js/apps/overview/qualityGate/EmptyQualityGate.js
index 75aee6e6144..56e607c05cb 100644
--- a/server/sonar-web/src/main/js/apps/overview/gate/gate-empty.js
+++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/EmptyQualityGate.js
@@ -18,18 +18,20 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
+
import { translate } from '../../../helpers/l10n';
-export default React.createClass({
- render() {
- const qualityGatesUrl = window.baseUrl + '/quality_gates';
+const EmptyQualityGate = () => {
+ return (
+ <div className="overview-quality-gate">
+ <h2 className="overview-title">
+ {translate('overview.quality_gate')}
+ </h2>
+ <p className="overview-quality-gate-warning">
+ {translate('overview.you_should_define_quality_gate')}
+ </p>
+ </div>
+ );
+};
- return (
- <div className="overview-gate">
- <h2 className="overview-title">{translate('overview.quality_gate')}</h2>
- <p className="overview-gate-warning">
- You should <a href={qualityGatesUrl}>define</a> a quality gate on this project.</p>
- </div>
- );
- }
-});
+export default EmptyQualityGate;
diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGate.js b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGate.js
new file mode 100644
index 00000000000..6217905ae83
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGate.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 QualityGateConditions from './QualityGateConditions';
+import EmptyQualityGate from './EmptyQualityGate';
+import { ComponentType, MeasuresListType, PeriodsListType } from '../propTypes';
+import { translate } from '../../../helpers/l10n';
+
+function parseQualityGateDetails (rawDetails) {
+ return JSON.parse(rawDetails);
+}
+
+function isProject (component) {
+ return component.qualifier === 'TRK';
+}
+
+const QualityGate = ({ component, measures, periods }) => {
+ const statusMeasure =
+ measures.find(measure => measure.metric.key === 'alert_status');
+ const detailsMeasure =
+ measures.find(measure => measure.metric.key === 'quality_gate_details');
+
+ if (!statusMeasure) {
+ return isProject(component) ? <EmptyQualityGate/> : null;
+ }
+
+ const level = statusMeasure.value;
+
+ let conditions = [];
+ if (detailsMeasure) {
+ conditions = parseQualityGateDetails(detailsMeasure.value).conditions;
+ }
+
+ return (
+ <div className="overview-quality-gate" id="overview-quality-gate">
+ <h2 className="overview-title">
+ {translate('overview.quality_gate')}
+ <span className={'badge badge-' + level.toLowerCase()}>
+ {translate('overview.gate', level)}
+ </span>
+ </h2>
+
+ {conditions.length > 0 && (
+ <QualityGateConditions
+ component={component}
+ periods={periods}
+ conditions={conditions}/>
+ )}
+ </div>
+ );
+};
+
+QualityGate.propTypes = {
+ component: ComponentType.isRequired,
+ measures: MeasuresListType.isRequired,
+ periods: PeriodsListType.isRequired
+};
+
+export default QualityGate;
+
diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js
new file mode 100644
index 00000000000..7708f2f90a8
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.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 React from 'react';
+
+import { ComponentType, PeriodsListType, EnhancedConditionType } from '../propTypes';
+import { DrilldownLink } from '../../../components/shared/drilldown-link';
+import { formatMeasure, getPeriodValue } from '../../../helpers/measures';
+import { translate } from '../../../helpers/l10n';
+import { getPeriod, getPeriodLabel, getPeriodDate } from '../../../helpers/periods';
+
+const QualityGateCondition = ({ component, periods, condition }) => {
+ const { measure } = condition;
+ const { metric } = measure;
+
+ const threshold = condition.level === 'ERROR' ?
+ condition.error :
+ condition.warning;
+
+ const actual = condition.period ?
+ getPeriodValue(measure, condition.period) :
+ measure.value;
+
+ const period = getPeriod(periods, condition.period);
+ const periodLabel = getPeriodLabel(period);
+ const periodDate = getPeriodDate(period);
+
+ return (
+ <li className="overview-quality-gate-condition">
+ <div className="overview-quality-gate-condition-period">
+ {periodLabel}
+ </div>
+
+ <div className="overview-quality-gate-condition-container">
+ <div className="overview-quality-gate-condition-value">
+ <DrilldownLink
+ component={component.key}
+ metric={metric.name}
+ period={condition.period}
+ periodDate={periodDate}>
+ <span className={'alert_' + condition.level.toUpperCase()}>
+ {formatMeasure(actual, metric.type)}
+ </span>
+ </DrilldownLink>
+ </div>
+
+ <div>
+ <div className="overview-quality-gate-condition-metric">
+ {metric.name}
+ </div>
+ <div className="overview-quality-gate-condition-threshold">
+ {translate('quality_gates.operator', condition.op, 'short')}
+ {' '}
+ {formatMeasure(threshold, metric.type)}
+ </div>
+ </div>
+ </div>
+ </li>
+ );
+};
+
+QualityGateCondition.propTypes = {
+ component: ComponentType.isRequired,
+ periods: PeriodsListType.isRequired,
+ condition: EnhancedConditionType.isRequired
+};
+
+export default QualityGateCondition;
diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateConditions.js b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateConditions.js
new file mode 100644
index 00000000000..d48614ce09d
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateConditions.js
@@ -0,0 +1,116 @@
+/*
+ * 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 shallowCompare from 'react-addons-shallow-compare';
+import sortBy from 'lodash/sortBy';
+
+import QualityGateCondition from './QualityGateCondition';
+import { ComponentType, ConditionsListType } from '../propTypes';
+import { getMeasuresAndMeta } from '../../../api/measures';
+import { enhanceMeasuresWithMetrics } from '../../../helpers/measures';
+
+const LEVEL_ORDER = ['ERROR', 'WARN'];
+
+function enhanceConditions (conditions, measures) {
+ return conditions.map(c => {
+ const measure = measures.find(measure => measure.metric.key === c.metric);
+ return { ...c, measure };
+ });
+}
+
+export default class QualityGateConditions extends React.Component {
+ static propTypes = {
+ component: ComponentType.isRequired,
+ conditions: ConditionsListType.isRequired
+ };
+
+ state = {
+ loading: true
+ };
+
+ componentDidMount () {
+ this.mounted = true;
+ this.loadFailedMeasures(this.props);
+ }
+
+ shouldComponentUpdate (nextProps, nextState) {
+ return shallowCompare(this, nextProps, nextState);
+ }
+
+ componentDidUpdate (nextProps) {
+ if (nextProps.conditions !== this.props.conditions ||
+ nextProps.component !== this.props.component) {
+ this.loadFailedMeasures(nextProps);
+ }
+ }
+
+ componentWillUnmount () {
+ this.mounted = false;
+ }
+
+ loadFailedMeasures (props) {
+ const { component, conditions } = props;
+ const failedConditions = conditions.filter(c => c.level !== 'OK');
+ const metrics = failedConditions.map(condition => condition.metric);
+
+ getMeasuresAndMeta(
+ component.key,
+ metrics,
+ { additionalFields: 'metrics' }
+ ).then(r => {
+ if (this.mounted) {
+ const measures = enhanceMeasuresWithMetrics(r.component.measures, r.metrics);
+ this.setState({
+ conditions: enhanceConditions(failedConditions, measures),
+ loading: false
+ });
+ }
+ });
+ }
+
+ render () {
+ const { component, periods } = this.props;
+ const { loading, conditions } = this.state;
+
+ if (loading) {
+ return null;
+ }
+
+ const sortedConditions = sortBy(
+ conditions,
+ condition => LEVEL_ORDER.indexOf(condition.level),
+ condition => condition.metric.name
+ );
+
+ return (
+ <ul
+ className="overview-quality-gate-conditions-list"
+ id="overview-quality-gate-conditions-list">
+ {sortedConditions.map(condition => (
+ <QualityGateCondition
+ key={condition.measure.metric.key}
+ component={component}
+ periods={periods}
+ condition={condition}/>
+ ))}
+ </ul>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/overview/styles.css b/server/sonar-web/src/main/js/apps/overview/styles.css
new file mode 100644
index 00000000000..7e09cd5241c
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/overview/styles.css
@@ -0,0 +1,292 @@
+.overview {
+ display: flex;
+ animation: fadeIn 0.5s forwards;
+}
+
+.overview-main {
+ flex-grow: 1;
+ box-sizing: border-box;
+ background-color: #f3f3f3;
+ transition: transform 0.5s ease, opacity 0.5s ease;
+}
+
+/*
+ * Title
+ */
+
+.overview-title {
+ font-size: 16px;
+ font-weight: 400;
+}
+
+.overview-title > .badge {
+ position: relative;
+ top: -2px;
+ margin-left: 15px;
+ padding: 6px 12px;
+ font-size: 14px;
+ letter-spacing: 0.05em;
+}
+
+.overview-title > a {
+ border-bottom-color: #d0d0d0;
+ color: #444;
+}
+
+.overview-title > a:hover,
+.overview-title > a:focus {
+ border-bottom-color: #cae3f2;
+ color: #4b9fd5;
+}
+
+/*
+ * Quality Gate
+ */
+
+.overview-quality-gate {
+ padding-bottom: 15px;
+ border-bottom: 1px solid #e6e6e6;
+ background-color: #f3f3f3;
+}
+
+.overview-quality-gate-conditions-list {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: flex-end;
+}
+
+.overview-quality-gate-condition {
+ padding: 10px 40px 10px 0;
+}
+
+.overview-quality-gate-condition-period {
+ margin-bottom: 4px;
+}
+
+.overview-quality-gate-condition-container {
+ display: flex;
+ align-items: center;
+}
+
+.overview-quality-gate-condition-value {
+ margin-right: 8px;
+ font-size: 24px;
+ font-weight: 300;
+}
+
+.overview-quality-gate-condition-metric {
+}
+
+.overview-quality-gate-condition-threshold {
+}
+
+.overview-quality-gate-warning {
+ margin: 15px 0 0;
+}
+
+/*
+ * Domain
+ */
+
+.overview-domains-list {
+ animation: fadeIn 0.5s forwards;
+}
+
+.overview-card {
+ margin: 15px 0;
+}
+
+.overview-card-special {
+ padding-bottom: 26px;
+ border-bottom: 1px solid #e6e6e6;
+}
+
+.overview-card-header {
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ margin-bottom: 10px;
+ line-height: 24px;
+}
+
+.overview-domain-panel {
+ display: flex;
+ margin-top: 10px;
+ border: 1px solid #e6e6e6;
+ background-color: #fff;
+}
+
+.overview-domain-nutshell,
+.overview-domain-leak {
+ position: relative;
+ display: flex;
+ padding: 15px 10px;
+}
+
+.overview-domain-nutshell {
+ flex: 2;
+}
+
+.overview-domain-nutshell .line-chart-backdrop {
+ fill: #e5f1f9;
+}
+
+.overview-domain-leak {
+ flex: 1;
+ background-color: #fbf3d5;
+}
+
+.overview-domain-leak .overview-domain-measures {
+ padding: 0;
+}
+
+.overview-domain-leak .line-chart-backdrop {
+ fill: #efe7b8;
+}
+
+.overview-domain-measures {
+ position: relative;
+ z-index: 2;
+ display: flex;
+ flex: 1;
+ align-items: center;
+ padding: 0 10%;
+}
+
+.overview-domain-measures + .overview-domain-measures {
+ margin-top: 30px;
+}
+
+.overview-domain-measures + .overview-domain-measures .overview-domain-measure-value {
+ font-size: 14px;
+ font-weight: 400;
+}
+
+.overview-domain-measures + .overview-domain-measures .overview-domain-measure-label {
+ margin-top: 4px;
+}
+
+.overview-domain-measure {
+ flex: 1;
+}
+
+.overview-domain-measure + .overview-domain-measure {
+ padding-left: 15%;
+}
+
+.overview-domain-measure-value {
+ line-height: 1;
+ font-size: 36px;
+ font-weight: 300;
+}
+
+.overview-domain-leak .overview-domain-measure-value {
+ text-align: center;
+}
+
+.overview-domain-measure-label {
+ margin-top: 10px;
+}
+
+.overview-domain-leak .overview-domain-measure-label {
+ text-align: center;
+}
+
+.overview-domain-measure-sup {
+ display: inline-block;
+ vertical-align: top;
+ margin-top: -4px;
+ margin-left: 6px;
+ font-size: 16px;
+}
+
+.overview-domain-timeline {
+ position: absolute;
+ z-index: 1;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ animation: fadeIn 0.5s forwards;
+}
+
+.overview-domain-timeline .line-chart-path {
+ fill: none;
+ stroke: none;
+}
+
+.overview-domain-timeline-date {
+ position: absolute;
+ bottom: 2px;
+ left: 5px;
+ color: rgba(119, 119, 119, 0.6);
+ font-size: 11px;
+}
+
+/*
+ * Meta
+ */
+
+.overview-meta {
+ flex-shrink: 0;
+ width: 260px;
+ padding-left: 40px;
+ background-color: #f3f3f3;
+}
+
+.overview-meta-card {
+ min-width: 200px;
+ padding-bottom: 20px;
+ box-sizing: border-box;
+}
+
+.overview-meta-description {
+ line-height: 1.5;
+}
+
+.overview-meta-header {
+ color: #797979;
+}
+
+.overview-meta-list > li {
+ padding-bottom: 4px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+/*
+ * Other
+ */
+
+.overview-legend {
+ position: absolute;
+ bottom: 100%;
+ left: 0;
+ right: -1px;
+ padding: 5px 0 2px;
+ border: 1px solid #e6e6e6;
+ background-color: #fbf3d5;
+ font-size: 14px;
+ text-align: center;
+ transform: translateY(-4px);
+}
+
+.overview-key {
+ width: 100%;
+ padding: 0 !important;
+ border: none !important;
+ background-color: transparent !important;
+}
+
+/*
+ * Animations
+ */
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/LanguageDistribution.js b/server/sonar-web/src/main/js/components/charts/LanguageDistribution.js
index fe8c4d6849f..5105fca04b8 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/LanguageDistribution.js
+++ b/server/sonar-web/src/main/js/components/charts/LanguageDistribution.js
@@ -20,23 +20,43 @@
import find from 'lodash/find';
import sortBy from 'lodash/sortBy';
import React from 'react';
+import shallowCompare from 'react-addons-shallow-compare';
-import { Histogram } from '../../../components/charts/histogram';
-import { formatMeasure } from '../../../helpers/measures';
-import { getLanguages } from '../../../api/languages';
-import { translate } from '../../../helpers/l10n';
+import { Histogram } from './histogram';
+import { formatMeasure } from '../../helpers/measures';
+import { getLanguages } from '../../api/languages';
+import { translate } from '../../helpers/l10n';
export default class LanguageDistribution extends React.Component {
+ static propTypes = {
+ distribution: React.PropTypes.string.isRequired
+ };
+
+ state = {};
+
componentDidMount () {
+ this.mounted = true;
this.requestLanguages();
}
+ shouldComponentUpdate (nextProps, nextState) {
+ return shallowCompare(this, nextProps, nextState);
+ }
+
+ componentWillUnmount () {
+ this.mounted = false;
+ }
+
requestLanguages () {
- getLanguages().then(languages => this.setState({ languages }));
+ getLanguages().then(languages => {
+ if (this.mounted) {
+ this.setState({ languages });
+ }
+ });
}
getLanguageName (langKey) {
- if (this.state && this.state.languages) {
+ if (this.state.languages) {
const lang = find(this.state.languages, { key: langKey });
return lang ? lang.name : translate('unknown');
} else {
@@ -49,23 +69,27 @@ export default class LanguageDistribution extends React.Component {
}
render () {
- let data = this.props.distribution.split(';').map((point, index) => {
- const tokens = point.split('=');
- return { x: parseInt(tokens[1], 10), y: index, value: tokens[0] };
- });
+ let data = this.props.distribution.split(';')
+ .map((point, index) => {
+ const tokens = point.split('=');
+ return { x: parseInt(tokens[1], 10), y: index, value: tokens[0] };
+ });
data = sortBy(data, d => -d.x);
- const yTicks = data.map(point => this.getLanguageName(point.value)).map(this.cutLanguageName);
+ const yTicks = data
+ .map(point => this.getLanguageName(point.value))
+ .map(this.cutLanguageName);
const yValues = data.map(point => formatMeasure(point.x, 'SHORT_INT'));
return (
- <Histogram data={data}
- yTicks={yTicks}
- yValues={yValues}
- barsWidth={10}
- height={data.length * 25}
- padding={[0, 60, 0, 80]}/>
+ <Histogram
+ data={data}
+ yTicks={yTicks}
+ yValues={yValues}
+ barsWidth={10}
+ height={data.length * 25}
+ padding={[0, 60, 0, 80]}/>
);
}
}
diff --git a/server/sonar-web/src/main/js/apps/overview/components/complexity-distribution.js b/server/sonar-web/src/main/js/components/shared/complexity-distribution.js
index e57f7072e6c..4771ae96489 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/complexity-distribution.js
+++ b/server/sonar-web/src/main/js/components/shared/complexity-distribution.js
@@ -19,9 +19,9 @@
*/
import React from 'react';
-import { BarChart } from '../../../components/charts/bar-chart';
-import { formatMeasure } from '../../../helpers/measures';
-import { translateWithParameters } from '../../../helpers/l10n';
+import { BarChart } from '../charts/bar-chart';
+import { formatMeasure } from '../../helpers/measures';
+import { translateWithParameters } from '../../helpers/l10n';
const HEIGHT = 80;
@@ -57,8 +57,13 @@ export const ComplexityDistribution = React.createClass({
},
render () {
- return <div className="overview-bar-chart" style={{ height: HEIGHT }}>
- {this.renderBarChart()}
- </div>;
+ // TODO remove inline styling
+ return (
+ <div
+ className="overview-bar-chart"
+ style={{ height: HEIGHT, paddingTop: 10, paddingBottom: 15 }}>
+ {this.renderBarChart()}
+ </div>
+ );
}
});
diff --git a/server/sonar-web/src/main/js/helpers/measures.js b/server/sonar-web/src/main/js/helpers/measures.js
index 0a66ef903b7..bf6df21284a 100644
--- a/server/sonar-web/src/main/js/helpers/measures.js
+++ b/server/sonar-web/src/main/js/helpers/measures.js
@@ -80,6 +80,39 @@ export function getShortType (type) {
return type;
}
+/**
+ * Map metrics
+ * @param {Array} measures
+ * @param {Array} metrics
+ * @returns {Array}
+ */
+export function enhanceMeasuresWithMetrics (measures, metrics) {
+ return measures.map(measure => {
+ const metric = metrics.find(metric => metric.key === measure.metric);
+ return { ...measure, metric };
+ });
+}
+
+/**
+ * Get period value of a measure
+ * @param measure
+ * @param periodIndex
+ */
+export function getPeriodValue (measure, periodIndex) {
+ const { periods } = measure;
+ const period = periods.find(period => period.index === periodIndex);
+ return period ? period.value : null;
+}
+
+/**
+ * Check if metric is differential
+ * @param {string} metricKey
+ * @returns {boolean}
+ */
+export function isDiffMetric (metricKey) {
+ return metricKey.indexOf('new_') === 0;
+}
+
/*
* Helpers
*/
diff --git a/server/sonar-web/src/main/js/widgets/complexity/index.js b/server/sonar-web/src/main/js/widgets/complexity/index.js
index 246a2ac9245..ffa5a9fd551 100644
--- a/server/sonar-web/src/main/js/widgets/complexity/index.js
+++ b/server/sonar-web/src/main/js/widgets/complexity/index.js
@@ -20,7 +20,7 @@
import React from 'react';
import { render } from 'react-dom';
import { translate } from '../../helpers/l10n';
-import { ComplexityDistribution } from '../../apps/overview/components/complexity-distribution';
+import { ComplexityDistribution } from '../../components/shared/complexity-distribution';
const Widget = ({ value, of }) => {
return (
diff --git a/server/sonar-web/src/main/less/pages/overview.less b/server/sonar-web/src/main/less/pages/overview.less
deleted file mode 100644
index 749e17de3c1..00000000000
--- a/server/sonar-web/src/main/less/pages/overview.less
+++ /dev/null
@@ -1,596 +0,0 @@
-/*
- * 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 (reference) "../variables";
-@import (reference) "../mixins";
-@import (reference) "../init/type";
-@import (reference) "../init/links";
-
-.overview {
- display: flex;
- min-height: ~"calc(100vh - @{navbarGlobalHeight} - @{navbarContextHeight} - @{pageFooterHeight})";
- animation: fadeIn 0.5s forwards;
-}
-
-.overview-main {
- flex-grow: 1;
- box-sizing: border-box;
- background-color: @barBackgroundColor;
- transition: transform 0.5s ease, opacity 0.5s ease;
-}
-
-/*
- * Gate
- */
-
-.overview-gate {
- padding-bottom: 15px;
- border-bottom: 1px solid @barBorderColor;
- background-color: @barBackgroundColor;
-}
-
-.overview-gate-conditions-list {
- display: flex;
- flex-wrap: wrap;
-}
-
-.overview-gate-condition {
- padding: 10px 40px 10px 0;
-}
-
-.overview-gate-condition-value {
- margin-right: 4px;
- font-weight: 300;
- font-size: 24px;
-}
-
-.overview-gate-warning {
- margin: 15px 20px 0;
-}
-
-/*
- * Title
- */
-
-.overview-title {
- font-size: 16px;
- font-weight: 400;
-
- & > .badge {
- position: relative;
- top: -2px;
- margin-left: 15px;
- padding: 6px 12px;
- font-size: 14px;
- letter-spacing: 0.05em;
- }
-
- & > a {
- border-bottom-color: #d0d0d0;
- color: @baseFontColor;
-
- &:hover, &:focus {
- border-bottom-color: @lightBlue;
- color: @blue;
- }
- }
-}
-
-/*
- * Meta
- */
-
-.overview-meta {
- flex-shrink: 0;
- width: 260px;
- padding-left: 40px;
- background-color: @barBackgroundColor;
-}
-
-.overview-meta-card {
- min-width: 200px;
- padding-bottom: 20px;
- box-sizing: border-box;
-}
-
-.overview-meta-description {
- line-height: 1.5;
-}
-
-.overview-meta-header {
- color: #797979;
-}
-
-.overview-meta-list {
- & > li {
- padding-bottom: 4px;
- .text-ellipsis;
- }
-}
-
-/*
- * Domain
- */
-
-.overview-domains-list {
- animation: fadeIn 0.5s forwards;
-}
-
-.overview-cards-list {
- display: flex;
-
- & > .overview-card,
- & > .overview-domain-chart {
- flex: 1;
- }
-}
-
-.overview-card {
- margin: 15px 0;
-}
-
-.overview-card-special {
- padding-bottom: 26px;
- border-bottom: 1px solid #e6e6e6;
-}
-
-.overview-card-fixed-width {
- max-width: 560px;
-}
-
-.overview-card-header {
- display: flex;
- align-items: baseline;
- justify-content: space-between;
- margin-bottom: 10px;
- line-height: @formControlHeight;
-
- .overview-title {
- flex: 1;
- }
-}
-
-.overview-domain-panel {
- display: flex;
- margin-top: 10px;
- border: 1px solid @barBorderColor;
- background-color: #fff;
-
- .overview-bar-chart {
- padding: 0;
- }
-}
-
-.overview-domain-nutshell,
-.overview-domain-leak {
- position: relative;
- display: flex;
- padding: 15px 10px;
-}
-
-.overview-domain-nutshell {
- flex: 2;
-
- .line-chart-backdrop {
- fill: #e5f1f9;
- }
-}
-
-.overview-domain-leak {
- flex: 1;
- background-color: #fbf3d5;
-
- .line-chart-backdrop {
- fill: #efe7b8;
- }
-}
-
-.overview-domain-measures {
- position: relative;
- z-index: 2;
- display: flex;
- flex: 1;
- align-items: center;
- padding: 0 10%;
-}
-
-.overview-domain-measures + .overview-domain-measures {
- margin-top: 30px;
-
- .overview-domain-measure-value {
- font-size: 14px;
- font-weight: 400;
- }
-
- .overview-domain-measure-label {
- margin-top: 4px;
- }
-}
-
-.overview-domain-leak .overview-domain-measures {
- padding: 0;
-}
-
-.overview-domain-measure {
- flex: 1;
-}
-
-.overview-domain-measure + .overview-domain-measure {
- padding-left: 15%;
-}
-
-.overview-domain-measure-value {
- line-height: 1;
- font-size: 36px;
- font-weight: 300;
-
- .overview-domain-leak & { text-align: center; }
-}
-
-.overview-domain-measure-label {
- margin-top: 10px;
-
- .overview-domain-leak & { text-align: center; }
-}
-
-.overview-domain-measure-sup {
- display: inline-block;
- vertical-align: top;
- margin-top: -4px;
- margin-left: 6px;
- font-size: 16px;
-}
-
-.overview-domain-timeline {
- position: absolute;
- z-index: 1;
- bottom: 0;
- left: 0;
- right: 0;
- animation: fadeIn 0.5s forwards;
-
- .line-chart-path {
- fill: none;
- stroke: none;
- }
-}
-
-.overview-domain-timeline-date {
- position: absolute;
- bottom: 2px;
- left: 5px;
- color: fade(@secondFontColor, 60%);
- font-size: 11px;
-}
-
-/*
- * Detailed Pages
- */
-
-.overview-detailed-page {
- flex: 1;
- animation: fadeIn 0.5s forwards;
-
- .overview-domain-leak-title {
- padding: 0 10px;
- border: 1px solid @barBorderColor;
- background: #fbf3d5;
- }
-}
-
-.overview-detailed-measures-list {
- border: 1px solid @barBorderColor;
- background-color: #fff;
- overflow: hidden;
-}
-
-.overview-detailed-measures-list + .overview-detailed-measures-list {
- margin-top: 40px;
-}
-
-.overview-detailed-measures-list-inline {
- display: flex;
- border: none;
- background: none;
-
- .overview-detailed-measure {
- flex: 1;
- flex-direction: column;
- border: 1px solid @barBorderColor;
- }
-
- .overview-detailed-measure-name {
- margin-bottom: 8px;
- flex: 0 1 auto;
- font-weight: 500;
- text-align: center;
- }
-
- .overview-detailed-measure + .overview-detailed-measure {
- margin-left: 10px;
- }
-
- .overview-detailed-measure-nutshell {
- flex-flow: column nowrap;
- justify-content: flex-start;
- align-items: stretch;
- flex: 3 0 auto;
-
- .overview-detailed-measure-value {
- flex: 1 0 auto;
- display: flex;
- justify-content: center;
- align-items: center;
- }
- }
-
- .overview-detailed-measure-leak {
- flex: 0 1 auto;
- }
-}
-
-.overview-detailed-measure {
- display: flex;
- background-color: #fff;
-}
-
-.overview-detailed-measure-rating {
- border: none !important;
- background: none;
-
- .overview-detailed-measure-value { font-size: 36px; }
-}
-
-.overview-detailed-measure-nutshell,
-.overview-detailed-measure-leak,
-.overview-detailed-measure-chart {
- position: relative;
- padding: 7px 10px;
-
- .overview-detailed-measure-nutshell,
- .overview-detailed-measure-leak,
- .overview-detailed-measure-chart {
- padding-right: 0;
- }
-}
-
-.overview-detailed-measure-nutshell {
- display: flex;
- flex-wrap: wrap;
- justify-content: space-between;
- align-items: baseline;
- flex: 3;
-}
-
-.overview-detailed-measure-leak {
- flex: 1;
- background-color: #fbf3d5;
- text-align: center;
-}
-
-.overview-detailed-measure-name {
- flex: 1;
-}
-
-.overview-detailed-measure-value {
- font-size: 16px;
-}
-
-.overview-detailed-layout-size {
- display: flex;
- justify-content: space-between;
- margin: 0 -10px;
-
- .overview-detailed-layout-column {
- flex: 1;
- max-width: 560px;
- }
-}
-
-.overview-detailed-layout-column {
- padding: 0 10px;
-}
-
-.overview-legend {
- position: absolute;
- bottom: 100%;
- left: 0;
- right: -1px;
- padding: 5px 0 2px;
- border: 1px solid @barBorderColor;
- font-size: 14px;
- text-align: center;
- transform: translateY(-4px);
-}
-
-/*
- * Charts
- */
-
-.overview-domain-chart {
- .overview-title {
- display: inline-block;
- margin-right: 20px;
- }
-}
-
-.overview-domain-chart + .overview-domain-chart {
- margin-top: 60px;
-}
-
-.overview-bar-chart {
- width: 100%;
- padding-top: 10px;
- padding-bottom: 15px;
-
- svg {
- position: absolute;
- }
-}
-
-.overview-timeline {
- padding: 10px;
- border: 1px solid @barBorderColor;
- box-sizing: border-box;
- background-color: #fff;
-
- svg {
- position: absolute;
- }
-
- .line-chart-backdrop {
- fill: #fbf3d5;
- }
-}
-
-.overview-timeline-1 {
- .line-chart-path {
- stroke: @purple;
- }
-
- .line-chart-point {
- stroke: darken(@purple, 20%);
- }
-}
-
-.overview-timeline-sample {
- display: inline-block;
- vertical-align: middle;
- width: 16px;
- height: 2px;
- margin-right: 8px;
-}
-
-.overview-timeline-sample-0 {
- background-color: @blue;
-}
-
-.overview-timeline-sample-1 {
- background-color: @purple;
-}
-
-.overview-timeline-chart {
- text-align: center;
-}
-
-.overview-timeline-chart + .overview-timeline-chart {
- margin-top: 40px;
-}
-
-.overview-timeline-select {
- width: 12em;
- height: @formControlHeight;
- line-height: @formControlHeight;
- border: 1px solid #cdcdcd;
- background: none;
-}
-
-.overview-treemap {
- & > div {
- position: absolute;
- }
-}
-
-.overview-chart-placeholder {
- display: flex;
- justify-content: center;
- align-items: center;
-}
-
-.overview-bubble-chart {
- padding: 10px;
- border: 1px solid @barBorderColor;
- background-color: #fff;
-
- svg {
- position: absolute;
- }
-
- .bubble-chart-bubble {
- fill: @blue;
- fill-opacity: 0.2;
- stroke: @blue;
- cursor: pointer;
- transition: all 0.2s ease;
-
- &:hover {
- fill-opacity: 0.8;
- }
- }
-
- .bubble-chart-grid {
- shape-rendering: crispedges;
- stroke: #eee;
- }
-
- .bubble-chart-tick {
- fill: @secondFontColor;
- font-size: 12px;
- text-anchor: middle;
- }
-
- .bubble-chart-tick-y {
- text-anchor: end;
- }
-}
-
-.overview-donut-chart {
- position: relative;
- text-align: center;
-
- .overview-detailed-measure-value {
- position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- }
-}
-
-/*
- * Misc
- */
-
-.overview-nutshell {
- background-color: #fff;
-}
-
-.overview-leak {
- background-color: #fbf3d5;
-}
-
-.overview-key {
- width: 100%;
- padding: 0 !important;
- border: none !important;
- background-color: transparent !important;
-}
-
-/*
- * Animations
- */
-
-@keyframes fadeIn {
- from {
- opacity: 0;
- }
- to {
- opacity: 1;
- }
-}
diff --git a/server/sonar-web/src/main/less/sonar.less b/server/sonar-web/src/main/less/sonar.less
index abba2b8e055..0389a58b796 100644
--- a/server/sonar-web/src/main/less/sonar.less
+++ b/server/sonar-web/src/main/less/sonar.less
@@ -64,7 +64,6 @@
@import "pages/quality-gates";
@import "pages/maintenance";
@import "pages/login";
-@import "pages/overview";
@import 'style';
@import 'deprecated';
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/overview/index.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/overview/index.html.erb
index e5fc799417b..aecf8510253 100644
--- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/overview/index.html.erb
+++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/overview/index.html.erb
@@ -48,23 +48,6 @@
id: '<%= escape_javascript @resource.uuid %>',
key: '<%= escape_javascript @resource.key %>',
description: '<%= escape_javascript @resource.description %>',
- hasSnapshot: <%= @snapshot ? true : false %>,
- periods: [
- <%
- if @snapshot && @snapshot.project_snapshot.periods?
- (1..5).each do |index|
- if @snapshot.period_mode(index)
- %>
- {
- index: '<%= index -%>',
- mode: '<%= escape_javascript @snapshot.period_mode(index) -%>',
- modeParam: '<%= escape_javascript @snapshot.period_param(index) -%>',
- date: '<%= escape_javascript @snapshot.period_datetime(index) ? @snapshot.period_datetime(index).strftime('%FT%T%z') : "" -%>'
- },
- <% end %>
- <% end %>
- <% end %>
- ],
links: [
<% @resource.project_links.sort.each_with_index do |link, index| %>
{
@@ -92,41 +75,8 @@
<% end %>
};
- <% if m %>
- var gate = {
- level: '<%= m.alert_status -%>',
- conditions: [
- <% conditions.sort_by {|condition| [ -condition['level'].length, metric(condition['metric']).short_name] }.each do |condition| %>
- <% metric = metric(condition['metric']) %>
- {
- level: '<%= escape_javascript condition['level'] %>',
- metric: {
- name: '<%= escape_javascript metric.name %>',
- type: '<%= escape_javascript metric.value_type %>'
- },
- op: '<%= escape_javascript condition['op'] %>',
- period: '<%= condition['period'] %>',
- warning: '<%= escape_javascript condition['warning'] %>',
- error: '<%= escape_javascript condition['error'] %>',
- actual: '<%= escape_javascript condition['actual'] %>',
- },
- <% end %>
- ]
- };
- <% else %>
- <% if alert_status && !alert_status.alert_status.blank? %>
- var gate = {
- level: '<%= alert_status.alert_status -%>',
- text: '<%= alert_status.alert_text -%>'
- };
- <% else %>
- var gate = null;
- <% end %>
- <% end %>
-
window.sonarqube.overview = {
- component: component,
- gate: gate
+ component: component
};
})();
</script>