]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-12140 Do not return "Warning" in quality gate facet when no project in warning
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 25 Jun 2019 11:50:25 +0000 (13:50 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 28 Jun 2019 06:45:59 +0000 (08:45 +0200)
server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java
server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java
server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresQuery.java
server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java
server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java
server/sonar-web/src/main/js/apps/projects/filters/QualityGateFilter.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/QualityGateFilter-test.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/QualityGateFilter-test.tsx.snap

index f9bf6565af11deb90aad1a681f644c80231e07f2..3091a1780c22455a233c409430d45dbadd430bfe 100644 (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)
index 16965c8ad282521acc3c8ef8a2669bc468f71fdf..e60cf06a1db590bb21c62142ac47bf0f81a7a7e6 100644 (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));
   }
index 197f6309874ec9e0f87f1917fec7d775f6295a98..2074358fcae27c2b27bdc9b866aa4cb252781078 100644 (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;
index 8028c359981c4dfb49ab1938255e35ed607f2f46..c1c13f64918e28abccb0f037169807933fffbc1e 100644 (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)
index e9853f9093b38ba9e0f47b9f660d225f5c896a2c..741d823a6e878bad23073c4469d8e79739c7655d 100644 (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(
index 7fa456cadc380e220567a825a5e9dfdf749863b5..b5f4d8f4c54b8944fb1ed568b401c86b099c1174 100644 (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}
index 8c5c6981207762cac0b471e770f022c68a2c8dfc..49eb71d6cf586999bd2b7d57bc867cf296b54170 100644 (file)
  */
 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} />);
+}