Przeglądaj źródła

SONAR-9472 Change the rendering of best values on the Measures page

tags/7.5
Pascal Mugnier 6 lat temu
rodzic
commit
a7d7420a71

+ 46
- 45
server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java Wyświetl plik

@@ -185,6 +185,7 @@ public class ComponentTreeAction implements MeasuresWsAction {
.setHandler(this)
.addPagingParams(100, MAX_SIZE)
.setChangelog(
new Change("7.2", "field 'bestValue' is added to the response"),
new Change("6.3", format("Number of metric keys is limited to %s", MAX_METRIC_KEYS)),
new Change("6.6", "the response field id is deprecated. Use key instead."),
new Change("6.6", "the response field refId is deprecated. Use refKey instead."));
@@ -382,7 +383,7 @@ public class ComponentTreeAction implements MeasuresWsAction {
}

private static Measures.Component.Builder toWsComponent(ComponentDto component, Map<MetricDto, ComponentTreeData.Measure> measures,
Map<String, ComponentDto> referenceComponentsByUuid) {
Map<String, ComponentDto> referenceComponentsByUuid) {
Measures.Component.Builder wsComponent = componentDtoToWsComponent(component);
ComponentDto referenceComponent = referenceComponentsByUuid.get(component.getCopyResourceUuid());
if (referenceComponent != null) {
@@ -406,16 +407,16 @@ public class ComponentTreeAction implements MeasuresWsAction {
Optional<SnapshotDto> baseSnapshot = dbClient.snapshotDao().selectLastAnalysisByRootComponentUuid(dbSession, baseComponent.projectUuid());
if (!baseSnapshot.isPresent()) {
return ComponentTreeData.builder()
.setBaseComponent(baseComponent)
.build();
.setBaseComponent(baseComponent)
.build();
}

ComponentTreeQuery componentTreeQuery = toComponentTreeQuery(wsRequest, baseComponent);
List<ComponentDto> components = searchComponents(dbSession, componentTreeQuery);
List<MetricDto> metrics = searchMetrics(dbSession, wsRequest);
Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric = searchMeasuresByComponentUuidAndMetric(dbSession, baseComponent, componentTreeQuery,
components,
metrics);
components,
metrics);

components = filterComponents(components, measuresByComponentUuidAndMetric, metrics, wsRequest);
components = sortComponents(components, wsRequest, metrics, measuresByComponentUuidAndMetric);
@@ -424,14 +425,14 @@ public class ComponentTreeAction implements MeasuresWsAction {
components = paginateComponents(components, wsRequest);

return ComponentTreeData.builder()
.setBaseComponent(baseComponent)
.setComponentsFromDb(components)
.setComponentCount(componentCount)
.setMeasuresByComponentUuidAndMetric(measuresByComponentUuidAndMetric)
.setMetrics(metrics)
.setPeriods(snapshotToWsPeriods(baseSnapshot.get()))
.setReferenceComponentsByUuid(searchReferenceComponentsById(dbSession, components))
.build();
.setBaseComponent(baseComponent)
.setComponentsFromDb(components)
.setComponentCount(componentCount)
.setMeasuresByComponentUuidAndMetric(measuresByComponentUuidAndMetric)
.setMetrics(metrics)
.setPeriods(snapshotToWsPeriods(baseSnapshot.get()))
.setReferenceComponentsByUuid(searchReferenceComponentsById(dbSession, components))
.build();
}
}

@@ -451,15 +452,15 @@ public class ComponentTreeAction implements MeasuresWsAction {

private Map<String, ComponentDto> searchReferenceComponentsById(DbSession dbSession, List<ComponentDto> components) {
List<String> referenceComponentUUids = components.stream()
.map(ComponentDto::getCopyResourceUuid)
.filter(Objects::nonNull)
.collect(MoreCollectors.toList(components.size()));
.map(ComponentDto::getCopyResourceUuid)
.filter(Objects::nonNull)
.collect(MoreCollectors.toList(components.size()));
if (referenceComponentUUids.isEmpty()) {
return emptyMap();
}

return FluentIterable.from(dbClient.componentDao().selectByUuids(dbSession, referenceComponentUUids))
.uniqueIndex(ComponentDto::uuid);
.uniqueIndex(ComponentDto::uuid);
}

private List<ComponentDto> searchComponents(DbSession dbSession, ComponentTreeQuery componentTreeQuery) {
@@ -476,16 +477,16 @@ public class ComponentTreeAction implements MeasuresWsAction {
if (metrics.size() < metricKeys.size()) {
List<String> foundMetricKeys = Lists.transform(metrics, MetricDto::getKey);
Set<String> missingMetricKeys = Sets.difference(
new LinkedHashSet<>(metricKeys),
new LinkedHashSet<>(foundMetricKeys));
new LinkedHashSet<>(metricKeys),
new LinkedHashSet<>(foundMetricKeys));

throw new NotFoundException(format("The following metric keys are not found: %s", COMMA_JOINER.join(missingMetricKeys)));
}
String forbiddenMetrics = metrics.stream()
.filter(metric -> ComponentTreeAction.FORBIDDEN_METRIC_TYPES.contains(metric.getValueType()))
.map(MetricDto::getKey)
.sorted()
.collect(MoreCollectors.join(COMMA_JOINER));
.filter(metric -> ComponentTreeAction.FORBIDDEN_METRIC_TYPES.contains(metric.getValueType()))
.map(MetricDto::getKey)
.sorted()
.collect(MoreCollectors.join(COMMA_JOINER));
checkArgument(forbiddenMetrics.isEmpty(), "Metrics %s can't be requested in this web service. Please use api/measures/component", forbiddenMetrics);
return metrics;
}
@@ -495,19 +496,19 @@ public class ComponentTreeAction implements MeasuresWsAction {

Map<Integer, MetricDto> metricsById = Maps.uniqueIndex(metrics, MetricDto::getId);
MeasureTreeQuery measureQuery = MeasureTreeQuery.builder()
.setStrategy(MeasureTreeQuery.Strategy.valueOf(componentTreeQuery.getStrategy().name()))
.setNameOrKeyQuery(componentTreeQuery.getNameOrKeyQuery())
.setQualifiers(componentTreeQuery.getQualifiers())
.setMetricIds(new ArrayList<>(metricsById.keySet()))
.build();
.setStrategy(MeasureTreeQuery.Strategy.valueOf(componentTreeQuery.getStrategy().name()))
.setNameOrKeyQuery(componentTreeQuery.getNameOrKeyQuery())
.setQualifiers(componentTreeQuery.getQualifiers())
.setMetricIds(new ArrayList<>(metricsById.keySet()))
.build();

Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric = HashBasedTable.create(components.size(), metrics.size());
dbClient.liveMeasureDao().selectTreeByQuery(dbSession, baseComponent, measureQuery, result -> {
LiveMeasureDto measureDto = result.getResultObject();
measuresByComponentUuidAndMetric.put(
measureDto.getComponentUuid(),
metricsById.get(measureDto.getMetricId()),
ComponentTreeData.Measure.createFromMeasureDto(measureDto));
measureDto.getComponentUuid(),
metricsById.get(measureDto.getMetricId()),
ComponentTreeData.Measure.createFromMeasureDto(measureDto));
});

addBestValuesToMeasures(measuresByComponentUuidAndMetric, components, metrics);
@@ -523,11 +524,11 @@ public class ComponentTreeAction implements MeasuresWsAction {
* </ul>
*/
private static void addBestValuesToMeasures(Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric, List<ComponentDto> components,
List<MetricDto> metrics) {
List<MetricDto> metrics) {
List<MetricDtoWithBestValue> metricDtosWithBestValueMeasure = metrics.stream()
.filter(MetricDtoFunctions.isOptimizedForBestValue())
.map(new MetricDtoToMetricDtoWithBestValue())
.collect(MoreCollectors.toList(metrics.size()));
.filter(MetricDtoFunctions.isOptimizedForBestValue())
.map(new MetricDtoToMetricDtoWithBestValue())
.collect(MoreCollectors.toList(metrics.size()));
if (metricDtosWithBestValueMeasure.isEmpty()) {
return;
}
@@ -537,7 +538,7 @@ public class ComponentTreeAction implements MeasuresWsAction {
for (MetricDtoWithBestValue metricWithBestValue : metricDtosWithBestValueMeasure) {
if (measuresByComponentUuidAndMetric.get(component.uuid(), metricWithBestValue.getMetric()) == null) {
measuresByComponentUuidAndMetric.put(component.uuid(), metricWithBestValue.getMetric(),
ComponentTreeData.Measure.createFromMeasureDto(metricWithBestValue.getBestValue()));
ComponentTreeData.Measure.createFromMeasureDto(metricWithBestValue.getBestValue()));
}
}
});
@@ -554,9 +555,9 @@ public class ComponentTreeAction implements MeasuresWsAction {
checkState(metricToSort.isPresent(), "Metric '%s' not found", metricKeyToSort, wsRequest.getMetricKeys());

return components
.stream()
.filter(new HasMeasure(measuresByComponentUuidAndMetric, metricToSort.get(), wsRequest.getMetricPeriodSort()))
.collect(MoreCollectors.toList(components.size()));
.stream()
.filter(new HasMeasure(measuresByComponentUuidAndMetric, metricToSort.get(), wsRequest.getMetricPeriodSort()))
.collect(MoreCollectors.toList(components.size()));
}

private static boolean componentWithMeasuresOnly(ComponentTreeRequest wsRequest) {
@@ -564,15 +565,15 @@ public class ComponentTreeAction implements MeasuresWsAction {
}

private static List<ComponentDto> sortComponents(List<ComponentDto> components, ComponentTreeRequest wsRequest, List<MetricDto> metrics,
Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) {
Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) {
return ComponentTreeSort.sortComponents(components, wsRequest, metrics, measuresByComponentUuidAndMetric);
}

private static List<ComponentDto> paginateComponents(List<ComponentDto> components, ComponentTreeRequest wsRequest) {
return components.stream()
.skip(offset(wsRequest.getPage(), wsRequest.getPageSize()))
.limit(wsRequest.getPageSize())
.collect(MoreCollectors.toList(wsRequest.getPageSize()));
.skip(offset(wsRequest.getPage(), wsRequest.getPageSize()))
.limit(wsRequest.getPageSize())
.collect(MoreCollectors.toList(wsRequest.getPageSize()));
}

@CheckForNull
@@ -600,8 +601,8 @@ public class ComponentTreeAction implements MeasuresWsAction {
List<String> childrenQualifiers = childrenQualifiers(wsRequest, baseComponent.qualifier());

ComponentTreeQuery.Builder componentTreeQueryBuilder = ComponentTreeQuery.builder()
.setBaseUuid(baseComponent.uuid())
.setStrategy(STRATEGIES.get(wsRequest.getStrategy()));
.setBaseUuid(baseComponent.uuid())
.setStrategy(STRATEGIES.get(wsRequest.getStrategy()));

if (wsRequest.getQuery() != null) {
componentTreeQueryBuilder.setNameOrKeyQuery(wsRequest.getQuery());

+ 8
- 2
server/sonar-server/src/main/java/org/sonar/server/measure/ws/MeasureDtoToWsMeasure.java Wyświetl plik

@@ -20,12 +20,14 @@
package org.sonar.server.measure.ws;

import javax.annotation.Nullable;
import org.sonar.core.util.Protobuf;
import org.sonar.db.measure.LiveMeasureDto;
import org.sonar.db.measure.MeasureDto;
import org.sonar.db.metric.MetricDto;
import org.sonarqube.ws.Measures;
import org.sonarqube.ws.Measures.Measure;

import static org.sonar.core.util.Protobuf.setNullable;
import static org.sonar.server.measure.ws.MeasureValueFormatter.formatMeasureValue;
import static org.sonar.server.measure.ws.MeasureValueFormatter.formatNumericalValue;

@@ -49,18 +51,22 @@ class MeasureDtoToWsMeasure {

static void updateMeasureBuilder(Measure.Builder measureBuilder, MetricDto metric, double doubleValue, @Nullable String stringValue, double variation) {
measureBuilder.setMetric(metric.getKey());
Double bestValue = metric.getBestValue();
// a measure value can be null, new_violations metric for example
if (!Double.isNaN(doubleValue) || stringValue != null) {
measureBuilder.setValue(formatMeasureValue(doubleValue, stringValue, metric));
setNullable(bestValue, v -> measureBuilder.setBestValue(doubleValue == v));
}

Measures.PeriodValue.Builder periodBuilder = Measures.PeriodValue.newBuilder();
if (Double.isNaN(variation)) {
return;
}
measureBuilder.getPeriodsBuilder().addPeriodsValue(periodBuilder
Measures.PeriodValue.Builder builderForValue = periodBuilder
.clear()
.setIndex(1)
.setValue(formatNumericalValue(variation, metric)));
.setValue(formatNumericalValue(variation, metric));
setNullable(bestValue, v -> builderForValue.setBestValue(variation == v));
measureBuilder.getPeriodsBuilder().addPeriodsValue(builderForValue);
}
}

+ 2
- 2
server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentActionTest.java Wyświetl plik

@@ -263,8 +263,8 @@ public class ComponentActionTest {
.executeProtobuf(ComponentWsResponse.class);

assertThat(response.getComponent().getMeasuresList())
.extracting(Measures.Measure::getMetric, Measures.Measure::getValue)
.containsExactly(tuple(metric.getKey(), "7"));
.extracting(Measures.Measure::getMetric, Measures.Measure::getValue, Measures.Measure::getBestValue)
.containsExactly(tuple(metric.getKey(), "7", true));
}

@Test

+ 46
- 2
server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java Wyświetl plik

@@ -51,9 +51,11 @@ import org.sonar.server.ws.WsActionTester;
import org.sonarqube.ws.Common;
import org.sonarqube.ws.Measures;
import org.sonarqube.ws.Measures.ComponentTreeWsResponse;
import org.sonarqube.ws.Measures.PeriodValue;

import static java.lang.Double.parseDouble;
import static java.lang.String.format;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_RATING_KEY;
@@ -251,14 +253,56 @@ public class ComponentTreeActionTest {
assertThat(response.getComponentsList().get(0).getMeasuresList()).extracting("metric").containsOnly("coverage");
// file measures
List<Measures.Measure> fileMeasures = response.getComponentsList().get(1).getMeasuresList();
assertThat(fileMeasures).extracting("metric").containsOnly("ncloc", "coverage", "new_violations");
assertThat(fileMeasures).extracting("value").containsOnly("100", "15.5", "");
assertThat(fileMeasures)
.extracting(Measures.Measure::getMetric, Measures.Measure::getValue, Measures.Measure::getBestValue, Measures.Measure::hasBestValue)
.containsExactlyInAnyOrder(tuple("ncloc", "100", true, true),
tuple("coverage", "15.5", false, false),
tuple("new_violations", "", false, false));

List<Common.Metric> metrics = response.getMetrics().getMetricsList();
assertThat(metrics).extracting("bestValue").contains("100", "");
assertThat(metrics).extracting("worstValue").contains("1000");
}

@Test
public void return_is_best_value_on_leak_measures() {
ComponentDto project = db.components().insertPrivateProject();
db.components().insertSnapshot(project);
userSession.anonymous().addProjectPermission(UserRole.USER, project);
ComponentDto file = newFileDto(project, null);
db.components().insertComponent(file);

MetricDto matchingBestValue = db.measures().insertMetric(m -> m
.setKey("new_lines")
.setValueType(INT.name())
.setBestValue(100d));
MetricDto doesNotMatchBestValue = db.measures().insertMetric(m -> m
.setKey("new_lines_2")
.setValueType(INT.name())
.setBestValue(100d));
MetricDto noBestValue = db.measures().insertMetric(m -> m
.setKey("new_violations")
.setValueType(INT.name())
.setBestValue(null));
db.measures().insertLiveMeasure(file, matchingBestValue, m -> m.setValue(null).setData((String) null).setVariation(100d));
db.measures().insertLiveMeasure(file, doesNotMatchBestValue, m -> m.setValue(null).setData((String) null).setVariation(10d));
db.measures().insertLiveMeasure(file, noBestValue, m -> m.setValue(null).setData((String) null).setVariation(42.0d));

ComponentTreeWsResponse response = ws.newRequest()
.setParam(PARAM_COMPONENT, project.getKey())
.setParam(PARAM_METRIC_KEYS, "new_lines,new_lines_2,new_violations")
.executeProtobuf(ComponentTreeWsResponse.class);

// file measures
List<Measures.Measure> fileMeasures = response.getComponentsList().get(0).getMeasuresList();
assertThat(fileMeasures)
.extracting(Measures.Measure::getMetric, m -> m.getPeriods().getPeriodsValueList())
.containsExactlyInAnyOrder(
tuple(matchingBestValue.getKey(), singletonList(PeriodValue.newBuilder().setIndex(1).setValue("100").setBestValue(true).build())),
tuple(doesNotMatchBestValue.getKey(), singletonList(PeriodValue.newBuilder().setIndex(1).setValue("10").setBestValue(false).build())),
tuple(noBestValue.getKey(), singletonList(PeriodValue.newBuilder().setIndex(1).setValue("42").build())));
}

@Test
public void use_best_value_for_rating() {
ComponentDto project = db.components().insertPrivateProject();

+ 25
- 2
server/sonar-server/src/test/java/org/sonar/server/measure/ws/SearchActionTest.java Wyświetl plik

@@ -44,6 +44,7 @@ import org.sonarqube.ws.Measures.Measure;
import org.sonarqube.ws.Measures.SearchWsResponse;

import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.transform;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
@@ -126,6 +127,29 @@ public class SearchActionTest {
assertThat(measure.getValue()).isEqualTo("15.5");
}

@Test
public void return_best_value() {
ComponentDto project = db.components().insertPrivateProject(db.getDefaultOrganization());
userSession.addProjectPermission(UserRole.USER, project);
MetricDto matchBestValue = db.measures().insertMetric(m -> m.setValueType(FLOAT.name()).setBestValue(15.5d));
db.measures().insertLiveMeasure(project, matchBestValue, m -> m.setValue(15.5d));
MetricDto doesNotMatchBestValue = db.measures().insertMetric(m -> m.setValueType(INT.name()).setBestValue(50d));
db.measures().insertLiveMeasure(project, doesNotMatchBestValue, m -> m.setValue(40d));
MetricDto noBestValue = db.measures().insertMetric(m -> m.setValueType(INT.name()).setBestValue(null));
db.measures().insertLiveMeasure(project, noBestValue, m -> m.setValue(123d));

SearchWsResponse result = call(singletonList(project.getDbKey()),
asList(matchBestValue.getKey(), doesNotMatchBestValue.getKey(), noBestValue.getKey()));

List<Measure> measures = result.getMeasuresList();
assertThat(measures)
.extracting(Measure::getMetric, Measure::getValue, Measure::getBestValue, Measure::hasBestValue)
.containsExactlyInAnyOrder(
tuple(matchBestValue.getKey(), "15.5", true, true),
tuple(doesNotMatchBestValue.getKey(), "40", false, true),
tuple(noBestValue.getKey(), "123", false, false));
}

@Test
public void return_measures_on_leak_period() {
OrganizationDto organization = db.organizations().insert();
@@ -189,7 +213,6 @@ public class SearchActionTest {
assertThat(measure.getValue()).isEqualTo("15.5");
}


@Test
public void return_measures_on_application() {
OrganizationDto organization = db.organizations().insert();
@@ -232,7 +255,7 @@ public class SearchActionTest {
ComponentDto project2 = db.components().insertPrivateProject(db.getDefaultOrganization());
db.measures().insertLiveMeasure(project1, metric, m -> m.setValue(15.5d));
db.measures().insertLiveMeasure(project2, metric, m -> m.setValue(42.0d));
Arrays.stream(new ComponentDto[]{project1}).forEach(p -> userSession.addProjectPermission(UserRole.USER, p));
Arrays.stream(new ComponentDto[] {project1}).forEach(p -> userSession.addProjectPermission(UserRole.USER, p));

SearchWsResponse result = call(asList(project1.getDbKey(), project2.getDbKey()), singletonList(metric.getKey()));


+ 5
- 0
server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js Wyświetl plik

@@ -62,6 +62,7 @@ import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches'
|}; */

/*:: type State = {
bestValue?: string,
components: Array<ComponentEnhanced>,
metric: ?Metric,
paging?: Paging,
@@ -117,6 +118,7 @@ export default class MeasureContent extends React.PureComponent {
const metricKeys = [metric.key];
const opts /*: Object */ = {
...getBranchLikeQuery(this.props.branchLike),
additionalFields: 'metrics',
metricSortFilter: 'withMeasuresOnly'
};
const isDiff = isDiffMetric(metric.key);
@@ -151,6 +153,7 @@ export default class MeasureContent extends React.PureComponent {
if (metric === this.props.metric) {
if (this.mounted) {
this.setState(({ selected } /*: State */) => ({
bestValue: r.metrics[0].bestValue,
components: r.components.map(component =>
enhanceComponent(component, metric, metrics)
),
@@ -185,6 +188,7 @@ export default class MeasureContent extends React.PureComponent {
if (metric === this.props.metric) {
if (this.mounted) {
this.setState(state => ({
bestValue: r.metrics[0].bestValue,
components: [
...state.components,
...r.components.map(component => enhanceComponent(component, metric, metrics))
@@ -245,6 +249,7 @@ export default class MeasureContent extends React.PureComponent {
const selectedIdx = this.getSelectedIndex();
return (
<FilesView
bestValue={this.state.bestValue}
branchLike={this.props.branchLike}
components={this.state.components}
fetchMore={this.fetchMoreComponents}

+ 104
- 48
server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.js Wyświetl plik

@@ -22,11 +22,13 @@ import React from 'react';
import ComponentsListRow from './ComponentsListRow';
import EmptyResult from './EmptyResult';
import { complementary } from '../config/complementary';
import { getLocalizedMetricName } from '../../../helpers/l10n';
import { getLocalizedMetricName, translate, translateWithParameters } from '../../../helpers/l10n';
import { formatMeasure, isDiffMetric, isPeriodBestValue } from '../../../helpers/measures';
/*:: import type { Component, ComponentEnhanced } from '../types'; */
/*:: import type { Metric } from '../../../store/metrics/actions'; */

/*:: type Props = {|
bestValue?: string,
branchLike?: { id?: string; name: string },
components: Array<ComponentEnhanced>,
onClick: string => void,
@@ -36,54 +38,108 @@ import { getLocalizedMetricName } from '../../../helpers/l10n';
selectedComponent?: ?string
|}; */

export default function ComponentsList(
{
branchLike,
components,
onClick,
metrics,
metric,
rootComponent,
selectedComponent
} /*: Props */
) {
if (!components.length) {
return <EmptyResult />;
/*:: type State = {
hideBest: boolean
}; */

export default class ComponentsList extends React.PureComponent {
/*:: props: Props; */
state /*: State */ = {
hideBest: true
};

componentWillReceiveProps(nextProps /*: Props */) {
if (nextProps.metric !== this.props.metric) {
this.setState({ hideBest: true });
}
}

displayAll = (event /*: Event */) => {
event.preventDefault();
this.setState({ hideBest: false });
};

hasBestValue(component /*: Component*/, otherMetrics /*: Array<Metric> */) {
const { metric } = this.props;
const focusedMeasure = component.measures.find(measure => measure.metric.key === metric.key);
if (isDiffMetric(focusedMeasure.metric.key)) {
return isPeriodBestValue(focusedMeasure, 1);
}
return focusedMeasure.bestValue;
}

const otherMetrics = (complementary[metric.key] || []).map(key => metrics[key]);
return (
<table className="data zebra zebra-hover">
{otherMetrics.length > 0 && (
<thead>
<tr>
<th>&nbsp;</th>
<th className="text-right">
<span className="small">{getLocalizedMetricName(metric)}</span>
</th>
{otherMetrics.map(metric => (
<th className="text-right" key={metric.key}>
<span className="small">{getLocalizedMetricName(metric)}</span>
</th>
))}
</tr>
</thead>
)}
renderComponent(component /*: Component*/, otherMetrics /*: Array<Metric> */) {
const { branchLike, metric, selectedComponent, onClick, rootComponent } = this.props;
return (
<ComponentsListRow
branchLike={branchLike}
component={component}
isSelected={component.key === selectedComponent}
key={component.id}
metric={metric}
onClick={onClick}
otherMetrics={otherMetrics}
rootComponent={rootComponent}
/>
);
}

<tbody>
{components.map(component => (
<ComponentsListRow
branchLike={branchLike}
component={component}
isSelected={component.key === selectedComponent}
key={component.id}
metric={metric}
onClick={onClick}
otherMetrics={otherMetrics}
rootComponent={rootComponent}
/>
))}
</tbody>
</table>
);
renderHiddenLink(hiddenCount /*: number*/, colCount /*: number*/) {
return (
<div className="alert alert-info spacer-top">
{translateWithParameters(
'component_measures.hidden_best_score_metrics',
hiddenCount,
formatMeasure(this.props.bestValue, this.props.metric.type)
)}
<a className="spacer-left" href="#" onClick={this.displayAll}>
{translate('show_all')}
</a>
</div>
);
}

render() {
const { components, metric, metrics } = this.props;
if (!components.length) {
return <EmptyResult />;
}

const otherMetrics = (complementary[metric.key] || []).map(key => metrics[key]);
const notBestComponents = components.filter(
component => !this.hasBestValue(component, otherMetrics)
);
const hiddenCount = components.length - notBestComponents.length;
const shouldHideBest = this.state.hideBest && hiddenCount !== components.length;
return (
<React.Fragment>
<table className="data zebra zebra-hover">
{otherMetrics.length > 0 && (
<thead>
<tr>
<th>&nbsp;</th>
<th className="text-right">
<span className="small">{getLocalizedMetricName(metric)}</span>
</th>
{otherMetrics.map(metric => (
<th className="text-right" key={metric.key}>
<span className="small">{getLocalizedMetricName(metric)}</span>
</th>
))}
</tr>
</thead>
)}

<tbody>
{(shouldHideBest ? notBestComponents : components).map(component =>
this.renderComponent(component, otherMetrics)
)}
</tbody>
</table>
{shouldHideBest &&
hiddenCount > 0 &&
this.renderHiddenLink(hiddenCount, otherMetrics.length + 3)}
</React.Fragment>
);
}
}

+ 2
- 0
server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.js Wyświetl plik

@@ -28,6 +28,7 @@ import { scrollToElement } from '../../../helpers/scrolling';
/*:: import type { Metric } from '../../../store/metrics/actions'; */

/*:: type Props = {|
bestValue?: string,
branchLike?: { id?: string; name: string },
components: Array<ComponentEnhanced>,
fetchMore: () => void,
@@ -124,6 +125,7 @@ export default class ListView extends React.PureComponent {
return (
<div ref={elem => (this.listContainer = elem)}>
<ComponentsList
bestValue={this.props.bestValue}
branchLike={this.props.branchLike}
components={this.props.components}
metric={this.props.metric}

+ 10
- 0
server/sonar-web/src/main/js/helpers/measures.ts Wyświetl plik

@@ -25,6 +25,7 @@ const HOURS_IN_DAY = 8;
export interface MeasurePeriod {
index: number;
value: string;
bestValue?: boolean;
}

export interface MeasureIntern {
@@ -90,6 +91,15 @@ export function getPeriodValue(
return period ? period.value : undefined;
}

export function isPeriodBestValue(
measure: Measure | MeasureEnhanced,
periodIndex: number
): boolean {
const { periods } = measure;
const period = periods && periods.find(period => period.index === periodIndex);
return (period && period.bestValue) || false;
}

/** Check if metric is differential */
export function isDiffMetric(metricKey: string): boolean {
return metricKey.indexOf('new_') === 0;

+ 1
- 0
server/sonar-web/src/main/js/store/metrics/actions.js Wyświetl plik

@@ -19,6 +19,7 @@
*/
// @flow
/*:: export type Metric = {
bestValue?: string,
custom?: boolean,
decimalScale?: number,
description?: string,

+ 2
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties Wyświetl plik

@@ -233,6 +233,7 @@ short_number_suffix.g=G
short_number_suffix.k=k
short_number_suffix.m=M
show_more=Show More
show_all=Show All
should_be_unique=Should be unique
since_x=since {0}
since_previous_analysis=since previous analysis
@@ -2441,6 +2442,7 @@ component_measures.not_found=The requested measure was not found.
component_measures.to_select_files=to select files
component_measures.to_navigate=to navigate
component_measures.to_navigate_files=to next/previous file
component_measures.hidden_best_score_metrics=There are {0} hidden components with a score of {1}.

component_measures.overview.project_overview.facet=Project Overview
component_measures.overview.project_overview.title=Risk

+ 2
- 0
sonar-ws/src/main/protobuf/ws-measures.proto Wyświetl plik

@@ -99,6 +99,7 @@ message Measure {
optional string value = 2;
optional PeriodsValue periods = 3;
optional string component = 4;
optional bool bestValue = 5;
}

message PeriodsValue {
@@ -108,4 +109,5 @@ message PeriodsValue {
message PeriodValue {
optional int32 index = 1;
optional string value = 2;
optional bool bestValue = 3;
}

Ładowanie…
Anuluj
Zapisz