Browse Source

SONAR-12140 Do not return "Warning" in quality gate facet when no project in warning

tags/8.0
Julien Lancelot 4 years ago
parent
commit
2629f81a58

+ 5
- 1
server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java View File

@@ -57,6 +57,7 @@ import org.sonar.server.es.SearchOptions;
import org.sonar.server.measure.index.ProjectMeasuresIndex;
import org.sonar.server.measure.index.ProjectMeasuresQuery;
import org.sonar.server.project.Visibility;
import org.sonar.server.qualitygate.ProjectsInWarning;
import org.sonar.server.user.UserSession;
import org.sonarqube.ws.Common;
import org.sonarqube.ws.Components.Component;
@@ -104,11 +105,13 @@ public class SearchProjectsAction implements ComponentsWsAction {
private final DbClient dbClient;
private final ProjectMeasuresIndex index;
private final UserSession userSession;
private final ProjectsInWarning projectsInWarning;

public SearchProjectsAction(DbClient dbClient, ProjectMeasuresIndex index, UserSession userSession) {
public SearchProjectsAction(DbClient dbClient, ProjectMeasuresIndex index, UserSession userSession, ProjectsInWarning projectsInWarning) {
this.dbClient = dbClient;
this.index = index;
this.userSession = userSession;
this.projectsInWarning = projectsInWarning;
}

@Override
@@ -239,6 +242,7 @@ public class SearchProjectsAction implements ComponentsWsAction {
Set<String> favoriteProjectUuids = loadFavoriteProjectUuids(dbSession);
List<Criterion> criteria = FilterParser.parse(firstNonNull(request.getFilter(), ""));
ProjectMeasuresQuery query = newProjectMeasuresQuery(criteria, hasFavoriteFilter(criteria) ? favoriteProjectUuids : null)
.setIgnoreWarning(projectsInWarning.count() == 0L)
.setSort(request.getSort())
.setAsc(request.getAsc());
Optional.ofNullable(organization)

+ 7
- 3
server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java View File

@@ -50,6 +50,7 @@ import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilde
import org.elasticsearch.search.aggregations.metrics.sum.Sum;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.NestedSortBuilder;
import org.sonar.api.measures.Metric;
import org.sonar.api.server.ServerSide;
import org.sonar.api.utils.System2;
import org.sonar.core.util.stream.MoreCollectors;
@@ -150,7 +151,7 @@ public class ProjectMeasuresIndex {
.put(NEW_RELIABILITY_RATING_KEY, (esSearch, query, facetBuilder) -> addRatingFacet(esSearch, NEW_RELIABILITY_RATING_KEY, facetBuilder))
.put(SECURITY_RATING_KEY, (esSearch, query, facetBuilder) -> addRatingFacet(esSearch, SECURITY_RATING_KEY, facetBuilder))
.put(NEW_SECURITY_RATING_KEY, (esSearch, query, facetBuilder) -> addRatingFacet(esSearch, NEW_SECURITY_RATING_KEY, facetBuilder))
.put(ALERT_STATUS_KEY, (esSearch, query, facetBuilder) -> esSearch.addAggregation(createStickyFacet(ALERT_STATUS_KEY, facetBuilder, createQualityGateFacet())))
.put(ALERT_STATUS_KEY, (esSearch, query, facetBuilder) -> esSearch.addAggregation(createStickyFacet(ALERT_STATUS_KEY, facetBuilder, createQualityGateFacet(query))))
.put(FILTER_LANGUAGES, ProjectMeasuresIndex::addLanguagesFacet)
.put(FIELD_TAGS, ProjectMeasuresIndex::addTagsFacet)
.build();
@@ -338,10 +339,13 @@ public class ProjectMeasuresIndex {
new KeyedFilter("5", termQuery(FIELD_MEASURES_VALUE, 5d)))));
}

private static AbstractAggregationBuilder createQualityGateFacet() {
private static AbstractAggregationBuilder createQualityGateFacet(ProjectMeasuresQuery projectMeasuresQuery) {
return filters(
ALERT_STATUS_KEY,
QUALITY_GATE_STATUS.entrySet().stream()
QUALITY_GATE_STATUS
.entrySet()
.stream()
.filter(qgs -> !(projectMeasuresQuery.isIgnoreWarning() && qgs.getKey().equals(Metric.Level.WARN.name())))
.map(entry -> new KeyedFilter(entry.getKey(), termQuery(FIELD_QUALITY_GATE_STATUS, entry.getValue())))
.toArray(KeyedFilter[]::new));
}

+ 10
- 0
server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresQuery.java View File

@@ -46,6 +46,7 @@ public class ProjectMeasuresQuery {
private boolean asc = true;
private String queryText;
private boolean ignoreAuthorization;
private boolean ignoreWarning;

public ProjectMeasuresQuery addMetricCriterion(MetricCriterion metricCriterion) {
this.metricCriteria.add(metricCriterion);
@@ -137,6 +138,15 @@ public class ProjectMeasuresQuery {
return this;
}

public boolean isIgnoreWarning() {
return ignoreWarning;
}

public ProjectMeasuresQuery setIgnoreWarning(boolean ignoreWarning) {
this.ignoreWarning = ignoreWarning;
return this;
}

public static class MetricCriterion {
private final String metricKey;
private final Operator operator;

+ 70
- 39
server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java View File

@@ -29,10 +29,12 @@ import java.util.List;
import java.util.function.Consumer;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.sonar.api.measures.Metric;
import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.server.ws.WebService.Param;
@@ -52,6 +54,7 @@ import org.sonar.server.measure.index.ProjectMeasuresIndex;
import org.sonar.server.measure.index.ProjectMeasuresIndexer;
import org.sonar.server.permission.index.PermissionIndexerTester;
import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
import org.sonar.server.qualitygate.ProjectsInWarning;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestRequest;
import org.sonar.server.ws.WsActionTester;
@@ -64,6 +67,7 @@ import static java.util.Collections.singletonList;
import static java.util.Optional.ofNullable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY;
import static org.sonar.api.measures.CoreMetrics.DUPLICATED_LINES_DENSITY_KEY;
import static org.sonar.api.measures.CoreMetrics.NCLOC_LANGUAGE_DISTRIBUTION_KEY;
import static org.sonar.api.measures.CoreMetrics.NEW_DUPLICATED_LINES_DENSITY_KEY;
@@ -114,12 +118,12 @@ public class SearchProjectsActionTest {

@DataProvider
public static Object[][] rating_metric_keys() {
return new Object[][]{{SQALE_RATING_KEY}, {RELIABILITY_RATING_KEY}, {SECURITY_RATING_KEY}};
return new Object[][] {{SQALE_RATING_KEY}, {RELIABILITY_RATING_KEY}, {SECURITY_RATING_KEY}};
}

@DataProvider
public static Object[][] new_rating_metric_keys() {
return new Object[][]{{NEW_MAINTAINABILITY_RATING_KEY}, {NEW_RELIABILITY_RATING_KEY}, {NEW_SECURITY_RATING_KEY}};
return new Object[][] {{NEW_MAINTAINABILITY_RATING_KEY}, {NEW_RELIABILITY_RATING_KEY}, {NEW_SECURITY_RATING_KEY}};
}

private DbClient dbClient = db.getDbClient();
@@ -128,12 +132,17 @@ public class SearchProjectsActionTest {
private PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, new ProjectMeasuresIndexer(dbClient, es.client()));
private ProjectMeasuresIndex index = new ProjectMeasuresIndex(es.client(), new WebAuthorizationTypeSupport(userSession), System2.INSTANCE);
private ProjectMeasuresIndexer projectMeasuresIndexer = new ProjectMeasuresIndexer(db.getDbClient(), es.client());
private ProjectsInWarning projectsInWarning = new ProjectsInWarning();

private WsActionTester ws = new WsActionTester(
new SearchProjectsAction(dbClient, index, userSession));
private WsActionTester ws = new WsActionTester(new SearchProjectsAction(dbClient, index, userSession, projectsInWarning));

private RequestBuilder request = SearchProjectsRequest.builder();

@Before
public void setUp() throws Exception {
projectsInWarning.update(0L);
}

@Test
public void verify_definition() {
WebService.Action def = ws.getDef();
@@ -199,18 +208,18 @@ public class SearchProjectsActionTest {

MetricDto coverage = db.measures().insertMetric(c -> c.setKey(COVERAGE).setValueType("PERCENT"));
ComponentDto project1 = insertProject(organization1Dto, c -> c
.setDbKey(KEY_PROJECT_EXAMPLE_001)
.setName("My Project 1")
.setTagsString("finance, java"),
.setDbKey(KEY_PROJECT_EXAMPLE_001)
.setName("My Project 1")
.setTagsString("finance, java"),
new Measure(coverage, c -> c.setValue(80d)));
ComponentDto project2 = insertProject(organization1Dto, c -> c
.setDbKey(KEY_PROJECT_EXAMPLE_002)
.setName("My Project 2"),
.setDbKey(KEY_PROJECT_EXAMPLE_002)
.setName("My Project 2"),
new Measure(coverage, c -> c.setValue(90d)));
ComponentDto project3 = insertProject(organization2Dto, c -> c
.setDbKey(KEY_PROJECT_EXAMPLE_003)
.setName("My Project 3")
.setTagsString("sales, offshore, java"),
.setDbKey(KEY_PROJECT_EXAMPLE_003)
.setName("My Project 3")
.setTagsString("sales, offshore, java"),
new Measure(coverage, c -> c.setValue(20d)));
addFavourite(project1);

@@ -629,6 +638,30 @@ public class SearchProjectsActionTest {
tuple("500000.0-*", 1L));
}

@Test
public void return_new_lines_facet() {
userSession.logIn();
OrganizationDto organizationDto = db.organizations().insert();
MetricDto coverage = db.measures().insertMetric(c -> c.setKey(NEW_LINES_KEY).setValueType(INT.name()));
insertProject(organizationDto, new Measure(coverage, c -> c.setVariation(100d)));
insertProject(organizationDto, new Measure(coverage, c -> c.setVariation(15_000d)));
insertProject(organizationDto, new Measure(coverage, c -> c.setVariation(50_000d)));

SearchProjectsWsResponse result = call(request.setFacets(singletonList(NEW_LINES_KEY)));

Common.Facet facet = result.getFacets().getFacetsList().stream()
.filter(oneFacet -> NEW_LINES_KEY.equals(oneFacet.getProperty()))
.findFirst().orElseThrow(IllegalStateException::new);
assertThat(facet.getValuesList())
.extracting(Common.FacetValue::getVal, Common.FacetValue::getCount)
.containsExactly(
tuple("*-1000.0", 1L),
tuple("1000.0-10000.0", 0L),
tuple("10000.0-100000.0", 2L),
tuple("100000.0-500000.0", 0L),
tuple("500000.0-*", 0L));
}

@Test
public void return_languages_facet() {
userSession.logIn();
@@ -873,51 +906,49 @@ public class SearchProjectsActionTest {
}

@Test
public void return_ncloc_facet() {
public void return_quality_gate_facet() {
userSession.logIn();
OrganizationDto organizationDto = db.organizations().insert();
MetricDto coverage = db.measures().insertMetric(c -> c.setKey(NCLOC).setValueType(INT.name()));
insertProject(organizationDto, new Measure(coverage, c -> c.setValue(100d)));
insertProject(organizationDto, new Measure(coverage, c -> c.setValue(15_000d)));
insertProject(organizationDto, new Measure(coverage, c -> c.setValue(50_000d)));
MetricDto qualityGateStatus = db.measures().insertMetric(c -> c.setKey(ALERT_STATUS_KEY).setValueType(LEVEL.name()));
insertProject(organizationDto, new Measure(qualityGateStatus, c -> c.setData(Metric.Level.ERROR.name()).setValue(null)));
insertProject(organizationDto, new Measure(qualityGateStatus, c -> c.setData(Metric.Level.ERROR.name()).setValue(null)));
insertProject(organizationDto, new Measure(qualityGateStatus, c -> c.setData(Metric.Level.WARN.name()).setValue(null)));
insertProject(organizationDto, new Measure(qualityGateStatus, c -> c.setData(Metric.Level.OK.name()).setValue(null)));
projectsInWarning.update(1L);

SearchProjectsWsResponse result = call(request.setFacets(singletonList(NCLOC)));
SearchProjectsWsResponse result = call(request.setFacets(singletonList(ALERT_STATUS_KEY)));

Common.Facet facet = result.getFacets().getFacetsList().stream()
.filter(oneFacet -> NCLOC.equals(oneFacet.getProperty()))
.filter(oneFacet -> ALERT_STATUS_KEY.equals(oneFacet.getProperty()))
.findFirst().orElseThrow(IllegalStateException::new);
assertThat(facet.getValuesList())
.extracting(Common.FacetValue::getVal, Common.FacetValue::getCount)
.containsExactly(
tuple("*-1000.0", 1L),
tuple("1000.0-10000.0", 0L),
tuple("10000.0-100000.0", 2L),
tuple("100000.0-500000.0", 0L),
tuple("500000.0-*", 0L));
.containsOnly(
tuple("OK", 1L),
tuple("ERROR", 2L),
tuple("WARN", 1L));
}

@Test
public void return_new_lines_facet() {
public void return_quality_gate_facet_without_warning_when_no_projects_in_warning() {
userSession.logIn();
OrganizationDto organizationDto = db.organizations().insert();
MetricDto coverage = db.measures().insertMetric(c -> c.setKey(NEW_LINES_KEY).setValueType(INT.name()));
insertProject(organizationDto, new Measure(coverage, c -> c.setVariation(100d)));
insertProject(organizationDto, new Measure(coverage, c -> c.setVariation(15_000d)));
insertProject(organizationDto, new Measure(coverage, c -> c.setVariation(50_000d)));
MetricDto qualityGateStatus = db.measures().insertMetric(c -> c.setKey(ALERT_STATUS_KEY).setValueType(LEVEL.name()));
insertProject(organizationDto, new Measure(qualityGateStatus, c -> c.setData(Metric.Level.ERROR.name()).setValue(null)));
insertProject(organizationDto, new Measure(qualityGateStatus, c -> c.setData(Metric.Level.ERROR.name()).setValue(null)));
insertProject(organizationDto, new Measure(qualityGateStatus, c -> c.setData(Metric.Level.OK.name()).setValue(null)));
projectsInWarning.update(0L);

SearchProjectsWsResponse result = call(request.setFacets(singletonList(NEW_LINES_KEY)));
SearchProjectsWsResponse result = call(request.setFacets(singletonList(ALERT_STATUS_KEY)));

Common.Facet facet = result.getFacets().getFacetsList().stream()
.filter(oneFacet -> NEW_LINES_KEY.equals(oneFacet.getProperty()))
.filter(oneFacet -> ALERT_STATUS_KEY.equals(oneFacet.getProperty()))
.findFirst().orElseThrow(IllegalStateException::new);
assertThat(facet.getValuesList())
.extracting(Common.FacetValue::getVal, Common.FacetValue::getCount)
.containsExactly(
tuple("*-1000.0", 1L),
tuple("1000.0-10000.0", 0L),
tuple("10000.0-100000.0", 2L),
tuple("100000.0-500000.0", 0L),
tuple("500000.0-*", 0L));
.containsOnly(
tuple("OK", 1L),
tuple("ERROR", 2L));
}

@Test
@@ -1099,7 +1130,7 @@ public class SearchProjectsActionTest {

List<Component> projects = call(request
.setFilter("alert_status = WARN"))
.getComponentsList();
.getComponentsList();

assertThat(projects)
.extracting(Component::getKey)

+ 21
- 0
server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java View File

@@ -1144,6 +1144,27 @@ public class ProjectMeasuresIndexTest {
entry(OK.name(), 2L));
}

@Test
public void facet_quality_gate_does_not_return_deprecated_warning_when_set_ignore_warning_is_true() {
index(
// 2 docs with QG OK
newDoc().setQualityGateStatus(OK.name()),
newDoc().setQualityGateStatus(OK.name()),
// 4 docs with QG ERROR
newDoc().setQualityGateStatus(ERROR.name()),
newDoc().setQualityGateStatus(ERROR.name()),
newDoc().setQualityGateStatus(ERROR.name()),
newDoc().setQualityGateStatus(ERROR.name()));

assertThat(underTest.search(new ProjectMeasuresQuery().setIgnoreWarning(true), new SearchOptions().addFacets(ALERT_STATUS_KEY)).getFacets().get(ALERT_STATUS_KEY)).containsOnly(
entry(ERROR.name(), 4L),
entry(OK.name(), 2L));
assertThat(underTest.search(new ProjectMeasuresQuery().setIgnoreWarning(false), new SearchOptions().addFacets(ALERT_STATUS_KEY)).getFacets().get(ALERT_STATUS_KEY)).containsOnly(
entry(ERROR.name(), 4L),
entry(WARN.name(), 0L),
entry(OK.name(), 2L));
}

@Test
public void facet_languages() {
index(

+ 4
- 1
server/sonar-web/src/main/js/apps/projects/filters/QualityGateFilter.tsx View File

@@ -37,6 +37,9 @@ export interface Props {
}

export default function QualityGateFilter(props: Props) {
const hasWarnStatus = props.facet && props.facet['WARN'] !== undefined;
const options = hasWarnStatus ? ['OK', 'WARN', 'ERROR'] : ['OK', 'ERROR'];

return (
<Filter
facet={props.facet}
@@ -44,7 +47,7 @@ export default function QualityGateFilter(props: Props) {
header={<FilterHeader name={translate('projects.facets.quality_gate')} />}
maxFacetValue={props.maxFacetValue}
onQueryChange={props.onQueryChange}
options={['OK', 'WARN', 'ERROR']}
options={options}
organization={props.organization}
property="gate"
query={props.query}

+ 15
- 3
server/sonar-web/src/main/js/apps/projects/filters/__tests__/QualityGateFilter-test.tsx View File

@@ -19,15 +19,27 @@
*/
import * as React from 'react';
import { shallow } from 'enzyme';
import QualityGateFilter from '../QualityGateFilter';
import QualityGateFilter, { Props } from '../QualityGateFilter';

it('renders', () => {
const wrapper = shallow(<QualityGateFilter onQueryChange={jest.fn()} query={{}} />);
const wrapper = shallowRender();
expect(wrapper).toMatchSnapshot();

const renderOption = wrapper.prop('renderOption');
expect(renderOption(2, false)).toMatchSnapshot();

const getFacetValueForOption = wrapper.prop('getFacetValueForOption');
expect(getFacetValueForOption({ ERROR: 1, WARN: 2, OK: 3 }, 'WARN')).toBe(2);
expect(getFacetValueForOption({ ERROR: 1, OK: 3 }, 'OK')).toBe(3);
});

it('should render with warning facet', () => {
expect(
shallowRender({ facet: { ERROR: 1, WARN: 2, OK: 3 } })
.find('Filter')
.prop('options')
).toEqual(['OK', 'WARN', 'ERROR']);
});

function shallowRender(props: Partial<Props> = {}) {
return shallow(<QualityGateFilter onQueryChange={jest.fn()} query={{}} {...props} />);
}

+ 0
- 1
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/QualityGateFilter-test.tsx.snap View File

@@ -12,7 +12,6 @@ exports[`renders 1`] = `
options={
Array [
"OK",
"WARN",
"ERROR",
]
}

Loading…
Cancel
Save