aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps
diff options
context:
space:
mode:
authorStas Vilchik <stas-vilchik@users.noreply.github.com>2017-03-28 15:25:50 +0200
committerGitHub <noreply@github.com>2017-03-28 15:25:50 +0200
commitc9846f5d48118eca3273de2cf065bd007f5dff45 (patch)
tree70a9009eabfd4a01ecdd97f44441871bb3e65c6f /server/sonar-web/src/main/js/apps
parentaac1e32b731df7358e2ecda8968a5db802c1be46 (diff)
downloadsonarqube-c9846f5d48118eca3273de2cf065bd007f5dff45.tar.gz
sonarqube-c9846f5d48118eca3273de2cf065bd007f5dff45.zip
MMF-721 apply feedback (#1858)
* add real links to the bubble chart * do not display org names if feature is not enabled * display bubbles with zero measure * display color legend * add charts descriptions
Diffstat (limited to 'server/sonar-web/src/main/js/apps')
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/AllProjects.js6
-rw-r--r--server/sonar-web/src/main/js/apps/projects/styles.css7
-rw-r--r--server/sonar-web/src/main/js/apps/projects/visualizations/QualityModel.js30
-rw-r--r--server/sonar-web/src/main/js/apps/projects/visualizations/SimpleBubbleChart.js42
-rw-r--r--server/sonar-web/src/main/js/apps/projects/visualizations/Visualizations.js25
-rw-r--r--server/sonar-web/src/main/js/apps/projects/visualizations/VisualizationsContainer.js9
6 files changed, 70 insertions, 49 deletions
diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js
index 8c0a54dca6c..ec5299b31a8 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js
+++ b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js
@@ -24,7 +24,6 @@ import ProjectsListFooterContainer from './ProjectsListFooterContainer';
import PageSidebar from './PageSidebar';
import VisualizationsContainer from '../visualizations/VisualizationsContainer';
import { parseUrlQuery } from '../store/utils';
-import { getProjectUrl } from '../../../helpers/urls';
import '../styles.css';
export default class AllProjects extends React.Component {
@@ -86,10 +85,6 @@ export default class AllProjects extends React.Component {
});
};
- handleProjectOpen = projectKey => {
- this.props.router.push(getProjectUrl(projectKey));
- };
-
render() {
const { query } = this.state;
const isFiltered = Object.keys(query).some(key => query[key] != null);
@@ -126,7 +121,6 @@ export default class AllProjects extends React.Component {
/>}
{view === 'visualizations' &&
<VisualizationsContainer
- onProjectOpen={this.handleProjectOpen}
onVisualizationChange={this.handleVisualizationChange}
sort={query.sort}
visualization={visualization}
diff --git a/server/sonar-web/src/main/js/apps/projects/styles.css b/server/sonar-web/src/main/js/apps/projects/styles.css
index 87101df7544..1f9a2fc8477 100644
--- a/server/sonar-web/src/main/js/apps/projects/styles.css
+++ b/server/sonar-web/src/main/js/apps/projects/styles.css
@@ -198,12 +198,17 @@
}
.projects-visualizations-footer {
+ margin-top: 8px;
padding: 15px 0;
- color: #777;
+ border-top: 1px solid #e6e6e6;
font-size: 12px;
text-align: center;
}
+.projects-visualizations-footer .note {
+ font-style: italic;
+}
+
.measure-details-bubble-chart-axis {
position: absolute;
color: #777;
diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/QualityModel.js b/server/sonar-web/src/main/js/apps/projects/visualizations/QualityModel.js
index a9c9392940b..0f6e7ac43d1 100644
--- a/server/sonar-web/src/main/js/apps/projects/visualizations/QualityModel.js
+++ b/server/sonar-web/src/main/js/apps/projects/visualizations/QualityModel.js
@@ -23,6 +23,7 @@ import BubbleChart from '../../../components/charts/BubbleChart';
import { formatMeasure } from '../../../helpers/measures';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { RATING_COLORS } from '../../../helpers/constants';
+import { getProjectUrl } from '../../../helpers/urls';
type Project = {
key: string,
@@ -43,17 +44,25 @@ const COLOR_METRIC_TYPE = 'RATING';
export default class QualityModel extends React.PureComponent {
props: {
- onProjectOpen: (?string) => void,
+ displayOrganizations: boolean,
projects: Array<Project>
};
- getMetricTooltip(metric: { key: string, type: string }, value: number) {
+ getMetricTooltip(metric: { key: string, type: string }, value: ?number) {
const name = translate('metric', metric.key, 'name');
- return `<div>${name}: ${formatMeasure(value, metric.type)}</div>`;
+ const formattedValue = value != null ? formatMeasure(value, metric.type) : '–';
+ return `<div>${name}: ${formattedValue}</div>`;
}
- getTooltip(project: Project, x: number, y: number, size: number, color1: number, color2: number) {
- const fullProjectName = project.organization
+ getTooltip(
+ project: Project,
+ x: number,
+ y: ?number,
+ size: number,
+ color1: number,
+ color2: number
+ ) {
+ const fullProjectName = this.props.displayOrganizations && project.organization
? `<div class="little-spacer-bottom">${project.organization.name} / <strong>${project.name}</strong></div>`
: `<div class="little-spacer-bottom"><strong>${project.name}</strong></div>`;
const inner = [
@@ -64,7 +73,6 @@ export default class QualityModel extends React.PureComponent {
this.getMetricTooltip({ key: X_METRIC, type: X_METRIC_TYPE }, x),
this.getMetricTooltip({ key: SIZE_METRIC, type: SIZE_METRIC_TYPE }, size)
].join('');
-
return `<div class="text-left">${inner}</div>`;
}
@@ -73,31 +81,28 @@ export default class QualityModel extends React.PureComponent {
.filter(
({ measures }) =>
measures[X_METRIC] != null &&
- measures[Y_METRIC] != null &&
measures[SIZE_METRIC] != null &&
measures[COLOR_METRIC_1] != null &&
measures[COLOR_METRIC_2] != null
)
.map(project => {
const x = Number(project.measures[X_METRIC]);
- const y = Number(project.measures[Y_METRIC]);
+ const y = project.measures[Y_METRIC] != null ? Number(project.measures[Y_METRIC]) : null;
const size = Number(project.measures[SIZE_METRIC]);
const color1 = Number(project.measures[COLOR_METRIC_1]);
const color2 = Number(project.measures[COLOR_METRIC_2]);
return {
x,
- y,
+ y: y || 0,
size,
color: RATING_COLORS[Math.max(color1, color2) - 1],
key: project.key,
tooltip: this.getTooltip(project, x, y, size, color1, color2),
- link: project.key
+ link: getProjectUrl(project.key)
};
});
-
const formatXTick = tick => formatMeasure(tick, X_METRIC_TYPE);
const formatYTick = tick => formatMeasure(tick, Y_METRIC_TYPE);
-
return (
<div>
<BubbleChart
@@ -106,7 +111,6 @@ export default class QualityModel extends React.PureComponent {
height={600}
items={items}
padding={[40, 20, 60, 100]}
- onBubbleClick={this.props.onProjectOpen}
yDomain={[100, 0]}
/>
<div className="measure-details-bubble-chart-axis x">
diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/SimpleBubbleChart.js b/server/sonar-web/src/main/js/apps/projects/visualizations/SimpleBubbleChart.js
index a44fbf96d7a..76e704ae57a 100644
--- a/server/sonar-web/src/main/js/apps/projects/visualizations/SimpleBubbleChart.js
+++ b/server/sonar-web/src/main/js/apps/projects/visualizations/SimpleBubbleChart.js
@@ -23,6 +23,7 @@ import BubbleChart from '../../../components/charts/BubbleChart';
import { formatMeasure } from '../../../helpers/measures';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { RATING_COLORS } from '../../../helpers/constants';
+import { getProjectUrl } from '../../../helpers/urls';
type Metric = { key: string, type: string };
@@ -35,7 +36,7 @@ type Project = {
export default class SimpleBubbleChart extends React.PureComponent {
props: {
- onProjectOpen: (?string) => void,
+ displayOrganizations: boolean,
projects: Array<Project>,
sizeMetric: Metric,
xMetric: Metric,
@@ -44,13 +45,14 @@ export default class SimpleBubbleChart extends React.PureComponent {
colorMetric?: string
};
- getMetricTooltip(metric: Metric, value: number) {
+ getMetricTooltip(metric: Metric, value: ?number) {
const name = translate('metric', metric.key, 'name');
- return `<div>${name}: ${formatMeasure(value, metric.type)}</div>`;
+ const formattedValue = value != null ? formatMeasure(value, metric.type) : '–';
+ return `<div>${name}: ${formattedValue}</div>`;
}
- getTooltip(project: Project, x: number, y: number, size: number, color?: number) {
- const fullProjectName = project.organization
+ getTooltip(project: Project, x: ?number, y: ?number, size: ?number, color?: number) {
+ const fullProjectName = this.props.displayOrganizations && project.organization
? `<div class="little-spacer-bottom">${project.organization.name} / <strong>${project.name}</strong></div>`
: `<div class="little-spacer-bottom"><strong>${project.name}</strong></div>`;
@@ -73,23 +75,26 @@ export default class SimpleBubbleChart extends React.PureComponent {
const { xMetric, yMetric, sizeMetric, colorMetric } = this.props;
const items = this.props.projects
- .filter(project => project.measures[xMetric.key] != null)
- .filter(project => project.measures[yMetric.key] != null)
- .filter(project => project.measures[sizeMetric.key] != null)
.filter(project => colorMetric == null || project.measures[colorMetric] !== null)
.map(project => {
- const x = Number(project.measures[xMetric.key]);
- const y = Number(project.measures[yMetric.key]);
- const size = Number(project.measures[sizeMetric.key]);
+ const x = project.measures[xMetric.key] != null
+ ? Number(project.measures[xMetric.key])
+ : null;
+ const y = project.measures[yMetric.key] != null
+ ? Number(project.measures[yMetric.key])
+ : null;
+ const size = project.measures[sizeMetric.key] != null
+ ? Number(project.measures[sizeMetric.key])
+ : null;
const color = colorMetric ? Number(project.measures[colorMetric]) : undefined;
return {
- x,
- y,
- size,
+ x: x || 0,
+ y: y || 0,
+ size: size || 0,
color: color ? RATING_COLORS[color - 1] : undefined,
key: project.key,
tooltip: this.getTooltip(project, x, y, size, color),
- link: project.key
+ link: getProjectUrl(project.key)
};
});
@@ -103,7 +108,6 @@ export default class SimpleBubbleChart extends React.PureComponent {
formatYTick={formatYTick}
height={600}
items={items}
- onBubbleClick={this.props.onProjectOpen}
padding={[40, 20, 60, 100]}
yDomain={this.props.yDomain}
/>
@@ -114,6 +118,12 @@ export default class SimpleBubbleChart extends React.PureComponent {
{translate('metric', yMetric.key, 'name')}
</div>
<div className="measure-details-bubble-chart-axis size">
+ {colorMetric != null && <span className="spacer-right">
+ {translateWithParameters(
+ 'component_measures.legend.color_x',
+ translate('metric', colorMetric, 'name')
+ )}
+ </span>}
{translateWithParameters(
'component_measures.legend.size_x',
translate('metric', sizeMetric.key, 'name')
diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/Visualizations.js b/server/sonar-web/src/main/js/apps/projects/visualizations/Visualizations.js
index 5803e5bc13b..633aadb6ab9 100644
--- a/server/sonar-web/src/main/js/apps/projects/visualizations/Visualizations.js
+++ b/server/sonar-web/src/main/js/apps/projects/visualizations/Visualizations.js
@@ -27,11 +27,11 @@ import CodeSmells from './CodeSmells';
import UncoveredLines from './UncoveredLines';
import DuplicatedBlocks from './DuplicatedBlocks';
import { localizeSorting } from '../utils';
-import { translateWithParameters } from '../../../helpers/l10n';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
export default class Visualizations extends React.PureComponent {
props: {
- onProjectOpen: (string) => void,
+ displayOrganizations: boolean,
onVisualizationChange: (string) => void,
projects?: Array<*>,
sort?: string,
@@ -51,24 +51,27 @@ export default class Visualizations extends React.PureComponent {
const Component = visualizationToComponent[this.props.visualization];
return Component
- ? <Component onProjectOpen={this.props.onProjectOpen} projects={projects} />
+ ? <Component displayOrganizations={this.props.displayOrganizations} projects={projects} />
: null;
}
renderFooter() {
const { projects, total, sort } = this.props;
- if (projects == null || total == null || projects.length >= total) {
- return null;
- }
+ const limitReached = projects != null && total != null && projects.length < total;
return (
<footer className="projects-visualizations-footer">
- {translateWithParameters(
- 'projects.limited_set_of_projects',
- projects.length,
- localizeSorting(sort)
- )}
+ <p>{translate('projects.visualization', this.props.visualization, 'description')}</p>
+ {limitReached &&
+ <p className="note spacer-top">
+ {translateWithParameters(
+ 'projects.limited_set_of_projects',
+ // $FlowFixMe
+ projects.length,
+ localizeSorting(sort)
+ )}
+ </p>}
</footer>
);
}
diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/VisualizationsContainer.js b/server/sonar-web/src/main/js/apps/projects/visualizations/VisualizationsContainer.js
index 5ee27e1f90a..2de9e7bebb7 100644
--- a/server/sonar-web/src/main/js/apps/projects/visualizations/VisualizationsContainer.js
+++ b/server/sonar-web/src/main/js/apps/projects/visualizations/VisualizationsContainer.js
@@ -24,7 +24,8 @@ import {
getComponent,
getComponentMeasures,
getOrganizationByKey,
- getProjectsAppState
+ getProjectsAppState,
+ areThereCustomOrganizations
} from '../../../store/rootReducer';
const mapStateToProps = state => {
@@ -38,7 +39,11 @@ const mapStateToProps = state => {
};
});
const appState = getProjectsAppState(state);
- return { projects, total: appState.total };
+ return {
+ projects,
+ total: appState.total,
+ displayOrganizations: areThereCustomOrganizations(state)
+ };
};
export default connect(mapStateToProps)(Visualizations);