You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

IssueIndex.java 75KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2023 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.server.issue.index;
  21. import com.google.common.base.Preconditions;
  22. import java.util.ArrayList;
  23. import java.util.Arrays;
  24. import java.util.Collection;
  25. import java.util.Collections;
  26. import java.util.Date;
  27. import java.util.List;
  28. import java.util.Map;
  29. import java.util.Objects;
  30. import java.util.Optional;
  31. import java.util.OptionalInt;
  32. import java.util.OptionalLong;
  33. import java.util.Set;
  34. import java.util.function.Consumer;
  35. import java.util.function.Function;
  36. import java.util.stream.Collectors;
  37. import java.util.stream.IntStream;
  38. import java.util.stream.Stream;
  39. import javax.annotation.CheckForNull;
  40. import javax.annotation.Nullable;
  41. import org.apache.commons.lang.StringUtils;
  42. import org.apache.lucene.search.join.ScoreMode;
  43. import org.elasticsearch.action.search.SearchRequest;
  44. import org.elasticsearch.action.search.SearchResponse;
  45. import org.elasticsearch.index.query.BoolQueryBuilder;
  46. import org.elasticsearch.index.query.QueryBuilder;
  47. import org.elasticsearch.index.query.QueryBuilders;
  48. import org.elasticsearch.indices.TermsLookup;
  49. import org.elasticsearch.search.aggregations.AggregationBuilder;
  50. import org.elasticsearch.search.aggregations.AggregationBuilders;
  51. import org.elasticsearch.search.aggregations.BucketOrder;
  52. import org.elasticsearch.search.aggregations.HasAggregations;
  53. import org.elasticsearch.search.aggregations.bucket.filter.FilterAggregationBuilder;
  54. import org.elasticsearch.search.aggregations.bucket.filter.FiltersAggregator;
  55. import org.elasticsearch.search.aggregations.bucket.filter.ParsedFilter;
  56. import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval;
  57. import org.elasticsearch.search.aggregations.bucket.histogram.LongBounds;
  58. import org.elasticsearch.search.aggregations.bucket.terms.IncludeExclude;
  59. import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
  60. import org.elasticsearch.search.aggregations.bucket.terms.Terms;
  61. import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
  62. import org.elasticsearch.search.aggregations.metrics.Min;
  63. import org.elasticsearch.search.aggregations.metrics.ParsedMax;
  64. import org.elasticsearch.search.aggregations.metrics.ParsedValueCount;
  65. import org.elasticsearch.search.aggregations.metrics.SumAggregationBuilder;
  66. import org.elasticsearch.search.builder.SearchSourceBuilder;
  67. import org.elasticsearch.search.sort.FieldSortBuilder;
  68. import org.joda.time.Duration;
  69. import org.sonar.api.issue.Issue;
  70. import org.sonar.api.issue.impact.SoftwareQuality;
  71. import org.sonar.api.rule.Severity;
  72. import org.sonar.api.rules.CleanCodeAttributeCategory;
  73. import org.sonar.api.rules.RuleType;
  74. import org.sonar.api.server.rule.RulesDefinition;
  75. import org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version;
  76. import org.sonar.api.server.rule.RulesDefinition.PciDssVersion;
  77. import org.sonar.api.utils.DateUtils;
  78. import org.sonar.api.utils.System2;
  79. import org.sonar.server.es.EsClient;
  80. import org.sonar.server.es.EsUtils;
  81. import org.sonar.server.es.SearchOptions;
  82. import org.sonar.server.es.Sorting;
  83. import org.sonar.server.es.searchrequest.RequestFiltersComputer;
  84. import org.sonar.server.es.searchrequest.RequestFiltersComputer.AllFilters;
  85. import org.sonar.server.es.searchrequest.SimpleFieldTopAggregationDefinition;
  86. import org.sonar.server.es.searchrequest.SubAggregationHelper;
  87. import org.sonar.server.es.searchrequest.TopAggregationDefinition;
  88. import org.sonar.server.es.searchrequest.TopAggregationDefinition.SimpleFieldFilterScope;
  89. import org.sonar.server.es.searchrequest.TopAggregationHelper;
  90. import org.sonar.server.issue.index.IssueQuery.PeriodStart;
  91. import org.sonar.server.permission.index.AuthorizationDoc;
  92. import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
  93. import org.sonar.server.security.SecurityStandards;
  94. import org.sonar.server.security.SecurityStandards.PciDss;
  95. import org.sonar.server.security.SecurityStandards.SQCategory;
  96. import org.sonar.server.user.UserSession;
  97. import org.sonar.server.view.index.ViewIndexDefinition;
  98. import org.springframework.util.CollectionUtils;
  99. import static com.google.common.base.Preconditions.checkState;
  100. import static java.lang.String.format;
  101. import static java.util.Collections.singletonList;
  102. import static java.util.stream.Collectors.toCollection;
  103. import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
  104. import static org.elasticsearch.index.query.QueryBuilders.existsQuery;
  105. import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
  106. import static org.elasticsearch.index.query.QueryBuilders.nestedQuery;
  107. import static org.elasticsearch.index.query.QueryBuilders.prefixQuery;
  108. import static org.elasticsearch.index.query.QueryBuilders.rangeQuery;
  109. import static org.elasticsearch.index.query.QueryBuilders.termQuery;
  110. import static org.elasticsearch.index.query.QueryBuilders.termsQuery;
  111. import static org.elasticsearch.search.aggregations.AggregationBuilders.filters;
  112. import static org.elasticsearch.search.aggregations.AggregationBuilders.reverseNested;
  113. import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
  114. import static org.sonar.api.rules.RuleType.VULNERABILITY;
  115. import static org.sonar.server.es.EsUtils.escapeSpecialRegexChars;
  116. import static org.sonar.server.es.IndexType.FIELD_INDEX_TYPE;
  117. import static org.sonar.server.es.searchrequest.TopAggregationDefinition.NON_STICKY;
  118. import static org.sonar.server.es.searchrequest.TopAggregationDefinition.STICKY;
  119. import static org.sonar.server.es.searchrequest.TopAggregationHelper.NO_EXTRA_FILTER;
  120. import static org.sonar.server.es.searchrequest.TopAggregationHelper.NO_OTHER_SUBAGGREGATION;
  121. import static org.sonar.server.issue.index.IssueIndex.Facet.ASSIGNED_TO_ME;
  122. import static org.sonar.server.issue.index.IssueIndex.Facet.ASSIGNEES;
  123. import static org.sonar.server.issue.index.IssueIndex.Facet.AUTHOR;
  124. import static org.sonar.server.issue.index.IssueIndex.Facet.CLEAN_CODE_ATTRIBUTE_CATEGORY;
  125. import static org.sonar.server.issue.index.IssueIndex.Facet.CODE_VARIANTS;
  126. import static org.sonar.server.issue.index.IssueIndex.Facet.CREATED_AT;
  127. import static org.sonar.server.issue.index.IssueIndex.Facet.CWE;
  128. import static org.sonar.server.issue.index.IssueIndex.Facet.DIRECTORIES;
  129. import static org.sonar.server.issue.index.IssueIndex.Facet.FILES;
  130. import static org.sonar.server.issue.index.IssueIndex.Facet.IMPACT_SEVERITY;
  131. import static org.sonar.server.issue.index.IssueIndex.Facet.IMPACT_SOFTWARE_QUALITY;
  132. import static org.sonar.server.issue.index.IssueIndex.Facet.LANGUAGES;
  133. import static org.sonar.server.issue.index.IssueIndex.Facet.OWASP_ASVS_40;
  134. import static org.sonar.server.issue.index.IssueIndex.Facet.OWASP_TOP_10;
  135. import static org.sonar.server.issue.index.IssueIndex.Facet.OWASP_TOP_10_2021;
  136. import static org.sonar.server.issue.index.IssueIndex.Facet.PCI_DSS_32;
  137. import static org.sonar.server.issue.index.IssueIndex.Facet.PCI_DSS_40;
  138. import static org.sonar.server.issue.index.IssueIndex.Facet.PROJECT_UUIDS;
  139. import static org.sonar.server.issue.index.IssueIndex.Facet.RESOLUTIONS;
  140. import static org.sonar.server.issue.index.IssueIndex.Facet.RULES;
  141. import static org.sonar.server.issue.index.IssueIndex.Facet.SANS_TOP_25;
  142. import static org.sonar.server.issue.index.IssueIndex.Facet.SCOPES;
  143. import static org.sonar.server.issue.index.IssueIndex.Facet.SEVERITIES;
  144. import static org.sonar.server.issue.index.IssueIndex.Facet.SONARSOURCE_SECURITY;
  145. import static org.sonar.server.issue.index.IssueIndex.Facet.STATUSES;
  146. import static org.sonar.server.issue.index.IssueIndex.Facet.TAGS;
  147. import static org.sonar.server.issue.index.IssueIndex.Facet.TYPES;
  148. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID;
  149. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_AUTHOR_LOGIN;
  150. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID;
  151. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_CLEAN_CODE_ATTRIBUTE_CATEGORY;
  152. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_CODE_VARIANTS;
  153. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID;
  154. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_CWE;
  155. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_DIRECTORY_PATH;
  156. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_EFFORT;
  157. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_FILE_PATH;
  158. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_FUNC_CLOSED_AT;
  159. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT;
  160. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_FUNC_UPDATED_AT;
  161. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_IMPACTS;
  162. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_IMPACT_SEVERITY;
  163. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY;
  164. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_IS_MAIN_BRANCH;
  165. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_KEY;
  166. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_LANGUAGE;
  167. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_LINE;
  168. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_NEW_CODE_REFERENCE;
  169. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_OWASP_ASVS_40;
  170. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10;
  171. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10_2021;
  172. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_PCI_DSS_32;
  173. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_PCI_DSS_40;
  174. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID;
  175. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_RESOLUTION;
  176. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_RULE_UUID;
  177. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SANS_TOP_25;
  178. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SCOPE;
  179. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SEVERITY;
  180. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SEVERITY_VALUE;
  181. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SQ_SECURITY_CATEGORY;
  182. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_STATUS;
  183. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_TAGS;
  184. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_TYPE;
  185. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_VULNERABILITY_PROBABILITY;
  186. import static org.sonar.server.issue.index.IssueIndexDefinition.TYPE_ISSUE;
  187. import static org.sonar.server.security.SecurityReviewRating.computePercent;
  188. import static org.sonar.server.security.SecurityReviewRating.computeRating;
  189. import static org.sonar.server.security.SecurityStandards.CWES_BY_CWE_TOP_25;
  190. import static org.sonar.server.security.SecurityStandards.OWASP_ASVS_40_REQUIREMENTS_BY_LEVEL;
  191. import static org.sonar.server.view.index.ViewIndexDefinition.TYPE_VIEW;
  192. import static org.sonarqube.ws.client.issue.IssuesWsParameters.FACET_MODE_EFFORT;
  193. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASSIGNEES;
  194. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_AUTHOR;
  195. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES;
  196. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CODE_VARIANTS;
  197. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AT;
  198. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CWE;
  199. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_DIRECTORIES;
  200. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_FILES;
  201. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IMPACT_SEVERITIES;
  202. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IMPACT_SOFTWARE_QUALITIES;
  203. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_LANGUAGES;
  204. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_ASVS_40;
  205. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_TOP_10;
  206. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_TOP_10_2021;
  207. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PCI_DSS_32;
  208. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PCI_DSS_40;
  209. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RESOLUTIONS;
  210. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES;
  211. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SANS_TOP_25;
  212. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SCOPES;
  213. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SEVERITIES;
  214. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SONARSOURCE_SECURITY;
  215. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_STATUSES;
  216. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TAGS;
  217. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TYPES;
  218. /**
  219. * The unique entry-point to interact with Elasticsearch index "issues".
  220. * All the requests are listed here.
  221. */
  222. public class IssueIndex {
  223. public static final String FACET_PROJECTS = "projects";
  224. public static final String FACET_ASSIGNED_TO_ME = "assigned_to_me";
  225. private static final int DEFAULT_FACET_SIZE = 15;
  226. private static final int MAX_FACET_SIZE = 100;
  227. private static final String AGG_VULNERABILITIES = "vulnerabilities";
  228. private static final String AGG_SEVERITIES = "severities";
  229. private static final String AGG_TO_REVIEW_SECURITY_HOTSPOTS = "toReviewSecurityHotspots";
  230. private static final String AGG_IN_REVIEW_SECURITY_HOTSPOTS = "inReviewSecurityHotspots";
  231. private static final String AGG_REVIEWED_SECURITY_HOTSPOTS = "reviewedSecurityHotspots";
  232. private static final String AGG_DISTRIBUTION = "distribution";
  233. private static final BoolQueryBuilder NON_RESOLVED_VULNERABILITIES_FILTER = boolQuery()
  234. .filter(termQuery(FIELD_ISSUE_TYPE, VULNERABILITY.name()))
  235. .mustNot(existsQuery(FIELD_ISSUE_RESOLUTION));
  236. private static final BoolQueryBuilder IN_REVIEW_HOTSPOTS_FILTER = boolQuery()
  237. .filter(termQuery(FIELD_ISSUE_TYPE, SECURITY_HOTSPOT.name()))
  238. .filter(termQuery(FIELD_ISSUE_STATUS, Issue.STATUS_IN_REVIEW))
  239. .mustNot(existsQuery(FIELD_ISSUE_RESOLUTION));
  240. private static final BoolQueryBuilder TO_REVIEW_HOTSPOTS_FILTER = boolQuery()
  241. .filter(termQuery(FIELD_ISSUE_TYPE, SECURITY_HOTSPOT.name()))
  242. .filter(termQuery(FIELD_ISSUE_STATUS, Issue.STATUS_TO_REVIEW))
  243. .mustNot(existsQuery(FIELD_ISSUE_RESOLUTION));
  244. private static final BoolQueryBuilder REVIEWED_HOTSPOTS_FILTER = boolQuery()
  245. .filter(termQuery(FIELD_ISSUE_TYPE, SECURITY_HOTSPOT.name()))
  246. .filter(termQuery(FIELD_ISSUE_STATUS, Issue.STATUS_REVIEWED))
  247. .filter(termQuery(FIELD_ISSUE_RESOLUTION, Issue.RESOLUTION_FIXED));
  248. private static final Object[] NO_SELECTED_VALUES = {0};
  249. private static final SimpleFieldTopAggregationDefinition EFFORT_TOP_AGGREGATION = new SimpleFieldTopAggregationDefinition(FIELD_ISSUE_EFFORT, NON_STICKY);
  250. public enum Facet {
  251. SEVERITIES(PARAM_SEVERITIES, FIELD_ISSUE_SEVERITY, STICKY, Severity.ALL.size()),
  252. IMPACT_SOFTWARE_QUALITY(PARAM_IMPACT_SOFTWARE_QUALITIES, FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY, STICKY, SoftwareQuality.values().length),
  253. IMPACT_SEVERITY(PARAM_IMPACT_SEVERITIES, FIELD_ISSUE_IMPACT_SEVERITY, STICKY,
  254. org.sonar.api.issue.impact.Severity.values().length),
  255. CLEAN_CODE_ATTRIBUTE_CATEGORY(PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES, FIELD_ISSUE_CLEAN_CODE_ATTRIBUTE_CATEGORY, STICKY, CleanCodeAttributeCategory.values().length),
  256. STATUSES(PARAM_STATUSES, FIELD_ISSUE_STATUS, STICKY, Issue.STATUSES.size()),
  257. // Resolutions facet returns one more element than the number of resolutions to take into account unresolved issues
  258. RESOLUTIONS(PARAM_RESOLUTIONS, FIELD_ISSUE_RESOLUTION, STICKY, Issue.RESOLUTIONS.size() + 1),
  259. TYPES(PARAM_TYPES, FIELD_ISSUE_TYPE, STICKY, RuleType.values().length),
  260. SCOPES(PARAM_SCOPES, FIELD_ISSUE_SCOPE, STICKY, MAX_FACET_SIZE),
  261. LANGUAGES(PARAM_LANGUAGES, FIELD_ISSUE_LANGUAGE, STICKY, MAX_FACET_SIZE),
  262. RULES(PARAM_RULES, FIELD_ISSUE_RULE_UUID, STICKY, MAX_FACET_SIZE),
  263. TAGS(PARAM_TAGS, FIELD_ISSUE_TAGS, STICKY, MAX_FACET_SIZE),
  264. AUTHOR(PARAM_AUTHOR, FIELD_ISSUE_AUTHOR_LOGIN, STICKY, MAX_FACET_SIZE),
  265. PROJECT_UUIDS(FACET_PROJECTS, FIELD_ISSUE_PROJECT_UUID, STICKY, MAX_FACET_SIZE),
  266. FILES(PARAM_FILES, FIELD_ISSUE_FILE_PATH, STICKY, MAX_FACET_SIZE),
  267. DIRECTORIES(PARAM_DIRECTORIES, FIELD_ISSUE_DIRECTORY_PATH, STICKY, MAX_FACET_SIZE),
  268. ASSIGNEES(PARAM_ASSIGNEES, FIELD_ISSUE_ASSIGNEE_UUID, STICKY, MAX_FACET_SIZE),
  269. ASSIGNED_TO_ME(FACET_ASSIGNED_TO_ME, FIELD_ISSUE_ASSIGNEE_UUID, STICKY, 1),
  270. PCI_DSS_32(PARAM_PCI_DSS_32, FIELD_ISSUE_PCI_DSS_32, STICKY, DEFAULT_FACET_SIZE),
  271. PCI_DSS_40(PARAM_PCI_DSS_40, FIELD_ISSUE_PCI_DSS_40, STICKY, DEFAULT_FACET_SIZE),
  272. OWASP_ASVS_40(PARAM_OWASP_ASVS_40, FIELD_ISSUE_OWASP_ASVS_40, STICKY, DEFAULT_FACET_SIZE),
  273. OWASP_TOP_10(PARAM_OWASP_TOP_10, FIELD_ISSUE_OWASP_TOP_10, STICKY, DEFAULT_FACET_SIZE),
  274. OWASP_TOP_10_2021(PARAM_OWASP_TOP_10_2021, FIELD_ISSUE_OWASP_TOP_10_2021, STICKY, DEFAULT_FACET_SIZE),
  275. SANS_TOP_25(PARAM_SANS_TOP_25, FIELD_ISSUE_SANS_TOP_25, STICKY, DEFAULT_FACET_SIZE),
  276. CWE(PARAM_CWE, FIELD_ISSUE_CWE, STICKY, DEFAULT_FACET_SIZE),
  277. CREATED_AT(PARAM_CREATED_AT, FIELD_ISSUE_FUNC_CREATED_AT, NON_STICKY),
  278. SONARSOURCE_SECURITY(PARAM_SONARSOURCE_SECURITY, FIELD_ISSUE_SQ_SECURITY_CATEGORY, STICKY, DEFAULT_FACET_SIZE),
  279. CODE_VARIANTS(PARAM_CODE_VARIANTS, FIELD_ISSUE_CODE_VARIANTS, STICKY, MAX_FACET_SIZE);
  280. private final String name;
  281. private final SimpleFieldTopAggregationDefinition topAggregation;
  282. private final Integer numberOfTerms;
  283. Facet(String name, String fieldName, boolean sticky, int numberOfTerms) {
  284. this.name = name;
  285. this.topAggregation = new SimpleFieldTopAggregationDefinition(fieldName, sticky);
  286. this.numberOfTerms = numberOfTerms;
  287. }
  288. Facet(String name, String fieldName, boolean sticky) {
  289. this.name = name;
  290. this.topAggregation = new SimpleFieldTopAggregationDefinition(fieldName, sticky);
  291. this.numberOfTerms = null;
  292. }
  293. public String getName() {
  294. return name;
  295. }
  296. public String getFieldName() {
  297. return topAggregation.getFilterScope().getFieldName();
  298. }
  299. public TopAggregationDefinition.FilterScope getFilterScope() {
  300. return topAggregation.getFilterScope();
  301. }
  302. public SimpleFieldTopAggregationDefinition getTopAggregationDef() {
  303. return topAggregation;
  304. }
  305. public int getNumberOfTerms() {
  306. checkState(numberOfTerms != null, "numberOfTerms should have been provided in constructor");
  307. return numberOfTerms;
  308. }
  309. }
  310. private static final Map<String, Facet> FACETS_BY_NAME = Arrays.stream(Facet.values())
  311. .collect(Collectors.toMap(Facet::getName, Function.identity()));
  312. private static final String SUBSTRING_MATCH_REGEXP = ".*%s.*";
  313. // TODO to be documented
  314. // TODO move to Facets ?
  315. private static final String FACET_SUFFIX_MISSING = "_missing";
  316. private static final String IS_ASSIGNED_FILTER = "__isAssigned";
  317. private static final SumAggregationBuilder EFFORT_AGGREGATION = AggregationBuilders.sum(FACET_MODE_EFFORT).field(FIELD_ISSUE_EFFORT);
  318. private static final BucketOrder EFFORT_AGGREGATION_ORDER = BucketOrder.aggregation(FACET_MODE_EFFORT, false);
  319. private static final Duration TWENTY_DAYS = Duration.standardDays(20L);
  320. private static final Duration TWENTY_WEEKS = Duration.standardDays(20L * 7L);
  321. private static final Duration TWENTY_MONTHS = Duration.standardDays(20L * 30L);
  322. private static final String AGG_COUNT = "count";
  323. private final Sorting sorting;
  324. private final EsClient client;
  325. private final System2 system;
  326. private final UserSession userSession;
  327. private final WebAuthorizationTypeSupport authorizationTypeSupport;
  328. public IssueIndex(EsClient client, System2 system, UserSession userSession, WebAuthorizationTypeSupport authorizationTypeSupport) {
  329. this.client = client;
  330. this.system = system;
  331. this.userSession = userSession;
  332. this.authorizationTypeSupport = authorizationTypeSupport;
  333. this.sorting = new Sorting();
  334. this.sorting.add(IssueQuery.SORT_BY_STATUS, FIELD_ISSUE_STATUS);
  335. this.sorting.add(IssueQuery.SORT_BY_STATUS, FIELD_ISSUE_KEY);
  336. this.sorting.add(IssueQuery.SORT_BY_SEVERITY, FIELD_ISSUE_SEVERITY_VALUE);
  337. this.sorting.add(IssueQuery.SORT_BY_SEVERITY, FIELD_ISSUE_KEY);
  338. this.sorting.add(IssueQuery.SORT_BY_CREATION_DATE, FIELD_ISSUE_FUNC_CREATED_AT);
  339. this.sorting.add(IssueQuery.SORT_BY_CREATION_DATE, FIELD_ISSUE_KEY);
  340. this.sorting.add(IssueQuery.SORT_BY_UPDATE_DATE, FIELD_ISSUE_FUNC_UPDATED_AT);
  341. this.sorting.add(IssueQuery.SORT_BY_UPDATE_DATE, FIELD_ISSUE_KEY);
  342. this.sorting.add(IssueQuery.SORT_BY_CLOSE_DATE, FIELD_ISSUE_FUNC_CLOSED_AT);
  343. this.sorting.add(IssueQuery.SORT_BY_CLOSE_DATE, FIELD_ISSUE_KEY);
  344. this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, FIELD_ISSUE_PROJECT_UUID);
  345. this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, FIELD_ISSUE_FILE_PATH);
  346. this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, FIELD_ISSUE_LINE);
  347. this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, FIELD_ISSUE_SEVERITY_VALUE).reverse();
  348. this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, FIELD_ISSUE_KEY);
  349. this.sorting.add(IssueQuery.SORT_HOTSPOTS, FIELD_ISSUE_VULNERABILITY_PROBABILITY).reverse();
  350. this.sorting.add(IssueQuery.SORT_HOTSPOTS, FIELD_ISSUE_SQ_SECURITY_CATEGORY);
  351. this.sorting.add(IssueQuery.SORT_HOTSPOTS, FIELD_ISSUE_RULE_UUID);
  352. this.sorting.add(IssueQuery.SORT_HOTSPOTS, FIELD_ISSUE_PROJECT_UUID);
  353. this.sorting.add(IssueQuery.SORT_HOTSPOTS, FIELD_ISSUE_FILE_PATH);
  354. this.sorting.add(IssueQuery.SORT_HOTSPOTS, FIELD_ISSUE_LINE);
  355. this.sorting.add(IssueQuery.SORT_HOTSPOTS, FIELD_ISSUE_KEY);
  356. // by default order by created date, project, file, line and issue key (in order to be deterministic when same ms)
  357. this.sorting.addDefault(FIELD_ISSUE_FUNC_CREATED_AT).reverse();
  358. this.sorting.addDefault(FIELD_ISSUE_PROJECT_UUID);
  359. this.sorting.addDefault(FIELD_ISSUE_FILE_PATH);
  360. this.sorting.addDefault(FIELD_ISSUE_LINE);
  361. this.sorting.addDefault(FIELD_ISSUE_KEY);
  362. }
  363. public SearchResponse search(IssueQuery query, SearchOptions options) {
  364. SearchRequest requestBuilder = EsClient.prepareSearch(TYPE_ISSUE.getMainType());
  365. SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
  366. requestBuilder.source(sourceBuilder);
  367. configureSorting(query, sourceBuilder);
  368. configurePagination(options, sourceBuilder);
  369. configureRouting(query, options, requestBuilder);
  370. AllFilters allFilters = createAllFilters(query);
  371. RequestFiltersComputer filterComputer = newFilterComputer(options, allFilters);
  372. configureTopAggregations(query, options, sourceBuilder, allFilters, filterComputer);
  373. configureQuery(sourceBuilder, filterComputer);
  374. configureTopFilters(sourceBuilder, filterComputer);
  375. sourceBuilder.fetchSource(false)
  376. .trackTotalHits(true);
  377. return client.search(requestBuilder);
  378. }
  379. private void configureTopAggregations(IssueQuery query, SearchOptions options, SearchSourceBuilder esRequest, AllFilters allFilters, RequestFiltersComputer filterComputer) {
  380. TopAggregationHelper aggregationHelper = newAggregationHelper(filterComputer, query);
  381. configureTopAggregations(aggregationHelper, query, options, allFilters, esRequest);
  382. }
  383. private static void configureQuery(SearchSourceBuilder esRequest, RequestFiltersComputer filterComputer) {
  384. QueryBuilder esQuery = filterComputer.getQueryFilters()
  385. .map(t -> (QueryBuilder) boolQuery().must(matchAllQuery()).filter(t))
  386. .orElse(matchAllQuery());
  387. esRequest.query(esQuery);
  388. }
  389. private static void configureTopFilters(SearchSourceBuilder esRequest, RequestFiltersComputer filterComputer) {
  390. filterComputer.getPostFilters().ifPresent(esRequest::postFilter);
  391. }
  392. /**
  393. * Optimization - do not send ES request to all shards when scope is restricted
  394. * to a set of projects. Because project UUID is used for routing, the request
  395. * can be sent to only the shards containing the specified projects.
  396. * Note that sticky facets may involve all projects, so this optimization must be
  397. * disabled when facets are enabled.
  398. */
  399. private static void configureRouting(IssueQuery query, SearchOptions options, SearchRequest searchRequest) {
  400. Collection<String> uuids = query.projectUuids();
  401. if (!uuids.isEmpty() && options.getFacets().isEmpty()) {
  402. searchRequest.routing(uuids.stream().map(AuthorizationDoc::idOf).toArray(String[]::new));
  403. }
  404. }
  405. private static void configurePagination(SearchOptions options, SearchSourceBuilder esSearch) {
  406. esSearch.from(options.getOffset()).size(options.getLimit());
  407. }
  408. private AllFilters createAllFilters(IssueQuery query) {
  409. AllFilters filters = RequestFiltersComputer.newAllFilters();
  410. filters.addFilter("__indexType", new SimpleFieldFilterScope(FIELD_INDEX_TYPE), termQuery(FIELD_INDEX_TYPE, TYPE_ISSUE.getName()));
  411. filters.addFilter("__authorization", new SimpleFieldFilterScope("parent"), createAuthorizationFilter());
  412. // Issue is assigned Filter
  413. if (Boolean.TRUE.equals(query.assigned())) {
  414. filters.addFilter(IS_ASSIGNED_FILTER, Facet.ASSIGNEES.getFilterScope(), existsQuery(FIELD_ISSUE_ASSIGNEE_UUID));
  415. } else if (Boolean.FALSE.equals(query.assigned())) {
  416. filters.addFilter(IS_ASSIGNED_FILTER, ASSIGNEES.getFilterScope(), boolQuery().mustNot(existsQuery(FIELD_ISSUE_ASSIGNEE_UUID)));
  417. }
  418. // Issue is Resolved Filter
  419. if (Boolean.TRUE.equals(query.resolved())) {
  420. filters.addFilter("__isResolved", Facet.RESOLUTIONS.getFilterScope(), existsQuery(FIELD_ISSUE_RESOLUTION));
  421. } else if (Boolean.FALSE.equals(query.resolved())) {
  422. filters.addFilter("__isResolved", Facet.RESOLUTIONS.getFilterScope(), boolQuery().mustNot(existsQuery(FIELD_ISSUE_RESOLUTION)));
  423. }
  424. // Field Filters
  425. filters.addFilter(FIELD_ISSUE_KEY, new SimpleFieldFilterScope(FIELD_ISSUE_KEY), createTermsFilter(FIELD_ISSUE_KEY, query.issueKeys()));
  426. filters.addFilter(FIELD_ISSUE_ASSIGNEE_UUID, ASSIGNEES.getFilterScope(), createTermsFilter(FIELD_ISSUE_ASSIGNEE_UUID, query.assignees()));
  427. filters.addFilter(FIELD_ISSUE_SCOPE, SCOPES.getFilterScope(), createTermsFilter(FIELD_ISSUE_SCOPE, query.scopes()));
  428. filters.addFilter(FIELD_ISSUE_LANGUAGE, LANGUAGES.getFilterScope(), createTermsFilter(FIELD_ISSUE_LANGUAGE, query.languages()));
  429. filters.addFilter(FIELD_ISSUE_TAGS, TAGS.getFilterScope(), createTermsFilter(FIELD_ISSUE_TAGS, query.tags()));
  430. filters.addFilter(FIELD_ISSUE_TYPE, TYPES.getFilterScope(), createTermsFilter(FIELD_ISSUE_TYPE, query.types()));
  431. filters.addFilter(
  432. FIELD_ISSUE_CLEAN_CODE_ATTRIBUTE_CATEGORY,
  433. CLEAN_CODE_ATTRIBUTE_CATEGORY.getFilterScope(),
  434. createTermsFilter(FIELD_ISSUE_CLEAN_CODE_ATTRIBUTE_CATEGORY, query.cleanCodeAttributesCategories()));
  435. filters.addFilter(
  436. FIELD_ISSUE_RESOLUTION, RESOLUTIONS.getFilterScope(),
  437. createTermsFilter(FIELD_ISSUE_RESOLUTION, query.resolutions()));
  438. filters.addFilter(
  439. FIELD_ISSUE_AUTHOR_LOGIN, AUTHOR.getFilterScope(),
  440. createTermsFilter(FIELD_ISSUE_AUTHOR_LOGIN, query.authors()));
  441. filters.addFilter(
  442. FIELD_ISSUE_RULE_UUID, RULES.getFilterScope(), createTermsFilter(
  443. FIELD_ISSUE_RULE_UUID,
  444. query.ruleUuids()));
  445. filters.addFilter(FIELD_ISSUE_STATUS, STATUSES.getFilterScope(), createTermsFilter(FIELD_ISSUE_STATUS, query.statuses()));
  446. filters.addFilter(FIELD_ISSUE_CODE_VARIANTS, CODE_VARIANTS.getFilterScope(), createTermsFilter(FIELD_ISSUE_CODE_VARIANTS, query.codeVariants()));
  447. // security category
  448. addSecurityCategoryPrefixFilter(FIELD_ISSUE_PCI_DSS_32, PCI_DSS_32, query.pciDss32(), filters);
  449. addSecurityCategoryPrefixFilter(FIELD_ISSUE_PCI_DSS_40, PCI_DSS_40, query.pciDss40(), filters);
  450. addOwaspAsvsFilter(FIELD_ISSUE_OWASP_ASVS_40, OWASP_ASVS_40, query, filters);
  451. addSecurityCategoryFilter(FIELD_ISSUE_OWASP_TOP_10, OWASP_TOP_10, query.owaspTop10(), filters);
  452. addSecurityCategoryFilter(FIELD_ISSUE_OWASP_TOP_10_2021, OWASP_TOP_10_2021, query.owaspTop10For2021(), filters);
  453. addSecurityCategoryFilter(FIELD_ISSUE_SANS_TOP_25, SANS_TOP_25, query.sansTop25(), filters);
  454. addSecurityCategoryFilter(FIELD_ISSUE_CWE, CWE, query.cwe(), filters);
  455. addSecurityCategoryFilter(FIELD_ISSUE_SQ_SECURITY_CATEGORY, SONARSOURCE_SECURITY, query.sonarsourceSecurity(), filters);
  456. addSeverityFilter(query, filters);
  457. addImpactFilters(query, filters);
  458. addComponentRelatedFilters(query, filters);
  459. addDatesFilter(filters, query);
  460. addCreatedAfterByProjectsFilter(filters, query);
  461. addNewCodeReferenceFilter(filters, query);
  462. addNewCodeReferenceFilterByProjectsFilter(filters, query);
  463. return filters;
  464. }
  465. private static void addOwaspAsvsFilter(String fieldName, Facet facet, IssueQuery query, AllFilters allFilters) {
  466. if (!CollectionUtils.isEmpty(query.owaspAsvs40())) {
  467. Set<String> requirements = calculateRequirementsForOwaspAsvs40Params(query);
  468. QueryBuilder securityCategoryFilter = termsQuery(fieldName, requirements);
  469. allFilters.addFilter(
  470. fieldName,
  471. facet.getFilterScope(),
  472. boolQuery()
  473. .must(securityCategoryFilter)
  474. .must(termsQuery(FIELD_ISSUE_TYPE, VULNERABILITY.name(), SECURITY_HOTSPOT.name())));
  475. }
  476. }
  477. private static Set<String> calculateRequirementsForOwaspAsvs40Params(IssueQuery query) {
  478. int level = query.getOwaspAsvsLevel().orElse(3);
  479. List<String> levelRequirements = OWASP_ASVS_40_REQUIREMENTS_BY_LEVEL.get(level);
  480. return query.owaspAsvs40().stream()
  481. .flatMap(value -> {
  482. // it's a specific category required
  483. if (value.contains(".")) {
  484. return Stream.of(value).filter(levelRequirements::contains);
  485. } else {
  486. return SecurityStandards.getRequirementsForCategoryAndLevel(value, level).stream();
  487. }
  488. }).collect(Collectors.toSet());
  489. }
  490. private static void addSecurityCategoryFilter(String fieldName, Facet facet, Collection<String> values, AllFilters allFilters) {
  491. QueryBuilder securityCategoryFilter = createTermsFilter(fieldName, values);
  492. if (securityCategoryFilter != null) {
  493. allFilters.addFilter(
  494. fieldName,
  495. facet.getFilterScope(),
  496. boolQuery()
  497. .must(securityCategoryFilter)
  498. .must(termsQuery(FIELD_ISSUE_TYPE, VULNERABILITY.name(), SECURITY_HOTSPOT.name())));
  499. }
  500. }
  501. /**
  502. * <p>Builds the Elasticsearch boolean query to filter the PCI DSS categories.</p>
  503. *
  504. * <p>The PCI DSS security report handles all the subcategories as one level. This means that subcategory 1.1 doesn't include the issues from 1.1.1.
  505. * Taking this into account, the search filter follows the same logic and uses prefix matching for top-level categories and exact matching for subcategories</p>
  506. *
  507. * <p>Example</p>
  508. * <p>List of PCI DSS categories in issues: {1.5.8, 1.5.9, 1.6.7}
  509. * <ul>
  510. * <li>Search: {1}, returns {1.5.8, 1.5.9, 1.6.7}</li>
  511. * <li>Search: {1.5.8}, returns {1.5.8}</li>
  512. * <li>Search: {1.5}, returns {}</li>
  513. * </ul>
  514. * </p>
  515. *
  516. * @param fieldName The PCI DSS version, e.g. pciDss-3.2
  517. * @param facet The facet used for the filter
  518. * @param values The PCI DSS categories to search for
  519. * @param allFilters Object that holds all the filters for the Elastic search call
  520. */
  521. private static void addSecurityCategoryPrefixFilter(String fieldName, Facet facet, Collection<String> values, AllFilters allFilters) {
  522. if (values.isEmpty()) {
  523. return;
  524. }
  525. BoolQueryBuilder boolQueryBuilder = boolQuery()
  526. // ensures that at least one "should" query is matched. Without it, "should" queries are optional, when a "must" is also present.
  527. .minimumShouldMatch(1)
  528. // the field type must be vulnerability or security hotspot
  529. .must(termsQuery(FIELD_ISSUE_TYPE, VULNERABILITY.name(), SECURITY_HOTSPOT.name()));
  530. // for top level categories a prefix query is added, while for subcategories a term query is used for exact matching
  531. values.stream().map(v -> choosePrefixQuery(fieldName, v)).forEach(boolQueryBuilder::should);
  532. allFilters.addFilter(
  533. fieldName,
  534. facet.getFilterScope(),
  535. boolQueryBuilder);
  536. }
  537. private static QueryBuilder choosePrefixQuery(String fieldName, String value) {
  538. return value.contains(".") ? createTermFilter(fieldName, value) : createPrefixFilter(fieldName, value + ".");
  539. }
  540. private static void addSeverityFilter(IssueQuery query, AllFilters allFilters) {
  541. QueryBuilder severityFieldFilter = createTermsFilter(FIELD_ISSUE_SEVERITY, query.severities());
  542. if (severityFieldFilter != null) {
  543. allFilters.addFilter(
  544. FIELD_ISSUE_SEVERITY,
  545. SEVERITIES.getFilterScope(),
  546. boolQuery()
  547. .must(severityFieldFilter)
  548. // Ignore severity of Security HotSpots
  549. .mustNot(termQuery(FIELD_ISSUE_TYPE, SECURITY_HOTSPOT.name())));
  550. }
  551. }
  552. private static void addImpactFilters(IssueQuery query, AllFilters allFilters) {
  553. if (query.impactSoftwareQualities().isEmpty() && query.impactSeverities().isEmpty()) {
  554. return;
  555. }
  556. if (!query.impactSoftwareQualities().isEmpty()) {
  557. allFilters.addFilter(
  558. FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY,
  559. IMPACT_SOFTWARE_QUALITY.getFilterScope(),
  560. nestedQuery(
  561. FIELD_ISSUE_IMPACTS,
  562. termsQuery(FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY, query.impactSoftwareQualities()),
  563. ScoreMode.Avg));
  564. }
  565. if (!query.impactSeverities().isEmpty()) {
  566. allFilters.addFilter(
  567. FIELD_ISSUE_IMPACT_SEVERITY,
  568. IMPACT_SEVERITY.getFilterScope(),
  569. nestedQuery(
  570. FIELD_ISSUE_IMPACTS,
  571. termsQuery(FIELD_ISSUE_IMPACT_SEVERITY, query.impactSeverities()),
  572. ScoreMode.Avg));
  573. }
  574. }
  575. private static void addComponentRelatedFilters(IssueQuery query, AllFilters filters) {
  576. addCommonComponentRelatedFilters(query, filters);
  577. if (query.viewUuids().isEmpty()) {
  578. addBranchComponentRelatedFilters(query, filters);
  579. } else {
  580. addViewRelatedFilters(query, filters);
  581. }
  582. }
  583. private static void addCommonComponentRelatedFilters(IssueQuery query, AllFilters filters) {
  584. filters.addFilter(FIELD_ISSUE_COMPONENT_UUID, new SimpleFieldFilterScope(FIELD_ISSUE_COMPONENT_UUID),
  585. createTermsFilter(FIELD_ISSUE_COMPONENT_UUID, query.componentUuids()));
  586. if (!Boolean.TRUE.equals(query.onComponentOnly())) {
  587. filters.addFilter(
  588. FIELD_ISSUE_PROJECT_UUID, new SimpleFieldFilterScope(FIELD_ISSUE_PROJECT_UUID),
  589. createTermsFilter(FIELD_ISSUE_PROJECT_UUID, query.projectUuids()));
  590. filters.addFilter(
  591. FIELD_ISSUE_DIRECTORY_PATH, new SimpleFieldFilterScope(FIELD_ISSUE_DIRECTORY_PATH),
  592. createTermsFilter(FIELD_ISSUE_DIRECTORY_PATH, query.directories()));
  593. filters.addFilter(
  594. FIELD_ISSUE_FILE_PATH, new SimpleFieldFilterScope(FIELD_ISSUE_FILE_PATH),
  595. createTermsFilter(FIELD_ISSUE_FILE_PATH, query.files()));
  596. }
  597. }
  598. private static void addBranchComponentRelatedFilters(IssueQuery query, AllFilters allFilters) {
  599. if (Boolean.TRUE.equals(query.onComponentOnly())) {
  600. return;
  601. }
  602. if (query.isMainBranch() != null) {
  603. allFilters.addFilter(
  604. "__is_main_branch", new SimpleFieldFilterScope(FIELD_ISSUE_IS_MAIN_BRANCH),
  605. createTermFilter(FIELD_ISSUE_IS_MAIN_BRANCH, query.isMainBranch().toString()));
  606. }
  607. allFilters.addFilter(
  608. FIELD_ISSUE_BRANCH_UUID, new SimpleFieldFilterScope(FIELD_ISSUE_BRANCH_UUID),
  609. createTermFilter(FIELD_ISSUE_BRANCH_UUID, query.branchUuid()));
  610. }
  611. private static void addViewRelatedFilters(IssueQuery query, AllFilters allFilters) {
  612. if (Boolean.TRUE.equals(query.onComponentOnly())) {
  613. return;
  614. }
  615. Collection<String> viewUuids = query.viewUuids();
  616. String branchUuid = query.branchUuid();
  617. boolean onApplicationBranch = branchUuid != null && !viewUuids.isEmpty();
  618. if (onApplicationBranch) {
  619. allFilters.addFilter("__view", new SimpleFieldFilterScope("view"), createViewFilter(singletonList(query.branchUuid())));
  620. } else {
  621. allFilters.addFilter("__view", new SimpleFieldFilterScope("view"), createViewFilter(viewUuids));
  622. }
  623. }
  624. @CheckForNull
  625. private static QueryBuilder createViewFilter(Collection<String> viewUuids) {
  626. if (viewUuids.isEmpty()) {
  627. return null;
  628. }
  629. BoolQueryBuilder viewsFilter = boolQuery();
  630. for (String viewUuid : viewUuids) {
  631. viewsFilter.should(QueryBuilders.termsLookupQuery(FIELD_ISSUE_BRANCH_UUID,
  632. new TermsLookup(
  633. TYPE_VIEW.getIndex().getName(),
  634. viewUuid,
  635. ViewIndexDefinition.FIELD_PROJECTS)));
  636. }
  637. return viewsFilter;
  638. }
  639. private static RequestFiltersComputer newFilterComputer(SearchOptions options, AllFilters allFilters) {
  640. Collection<String> facetNames = options.getFacets();
  641. Set<TopAggregationDefinition<?>> facets = Stream.concat(
  642. Stream.of(EFFORT_TOP_AGGREGATION),
  643. facetNames.stream()
  644. .map(FACETS_BY_NAME::get)
  645. .filter(Objects::nonNull)
  646. .map(Facet::getTopAggregationDef))
  647. .collect(Collectors.toSet());
  648. return new RequestFiltersComputer(allFilters, facets);
  649. }
  650. private static TopAggregationHelper newAggregationHelper(RequestFiltersComputer filterComputer, IssueQuery query) {
  651. if (hasQueryEffortFacet(query)) {
  652. return new TopAggregationHelper(filterComputer, new SubAggregationHelper(EFFORT_AGGREGATION, EFFORT_AGGREGATION_ORDER));
  653. }
  654. return new TopAggregationHelper(filterComputer, new SubAggregationHelper());
  655. }
  656. private static AggregationBuilder addEffortAggregationIfNeeded(IssueQuery query, AggregationBuilder aggregation) {
  657. if (hasQueryEffortFacet(query)) {
  658. aggregation.subAggregation(EFFORT_AGGREGATION);
  659. }
  660. return aggregation;
  661. }
  662. private static boolean hasQueryEffortFacet(IssueQuery query) {
  663. return FACET_MODE_EFFORT.equals(query.facetMode());
  664. }
  665. @CheckForNull
  666. private static QueryBuilder createTermsFilter(String field, Collection<?> values) {
  667. return values.isEmpty() ? null : termsQuery(field, values);
  668. }
  669. @CheckForNull
  670. private static QueryBuilder createTermFilter(String field, @Nullable String value) {
  671. return value == null ? null : termQuery(field, value);
  672. }
  673. private static QueryBuilder createPrefixFilter(String field, String value) {
  674. return prefixQuery(field, value);
  675. }
  676. private void configureSorting(IssueQuery query, SearchSourceBuilder esRequest) {
  677. createSortBuilders(query).forEach(esRequest::sort);
  678. }
  679. private List<FieldSortBuilder> createSortBuilders(IssueQuery query) {
  680. String sortField = query.sort();
  681. if (sortField != null) {
  682. boolean asc = Boolean.TRUE.equals(query.asc());
  683. return sorting.fill(sortField, asc);
  684. }
  685. return sorting.fillDefault();
  686. }
  687. private QueryBuilder createAuthorizationFilter() {
  688. return authorizationTypeSupport.createQueryFilter();
  689. }
  690. private void addDatesFilter(AllFilters filters, IssueQuery query) {
  691. PeriodStart createdAfter = query.createdAfter();
  692. Date createdBefore = query.createdBefore();
  693. validateCreationDateBounds(createdBefore, createdAfter != null ? createdAfter.date() : null);
  694. if (createdAfter != null) {
  695. filters.addFilter(
  696. "__createdAfter", CREATED_AT.getFilterScope(),
  697. QueryBuilders
  698. .rangeQuery(FIELD_ISSUE_FUNC_CREATED_AT)
  699. .from(createdAfter.date().getTime(), createdAfter.inclusive()));
  700. }
  701. if (createdBefore != null) {
  702. filters.addFilter(
  703. "__createdBefore", CREATED_AT.getFilterScope(),
  704. QueryBuilders
  705. .rangeQuery(FIELD_ISSUE_FUNC_CREATED_AT)
  706. .lt(createdBefore.getTime()));
  707. }
  708. Date createdAt = query.createdAt();
  709. if (createdAt != null) {
  710. filters.addFilter(
  711. "__createdAt", CREATED_AT.getFilterScope(),
  712. termQuery(FIELD_ISSUE_FUNC_CREATED_AT, createdAt.getTime()));
  713. }
  714. }
  715. private static void addNewCodeReferenceFilter(AllFilters filters, IssueQuery query) {
  716. Boolean newCodeOnReference = query.newCodeOnReference();
  717. if (newCodeOnReference != null) {
  718. filters.addFilter(
  719. FIELD_ISSUE_NEW_CODE_REFERENCE, new SimpleFieldFilterScope(FIELD_ISSUE_NEW_CODE_REFERENCE),
  720. termQuery(FIELD_ISSUE_NEW_CODE_REFERENCE, true));
  721. }
  722. }
  723. private static void addNewCodeReferenceFilterByProjectsFilter(AllFilters allFilters, IssueQuery query) {
  724. Collection<String> newCodeOnReferenceByProjectUuids = query.newCodeOnReferenceByProjectUuids();
  725. BoolQueryBuilder boolQueryBuilder = boolQuery();
  726. if (!newCodeOnReferenceByProjectUuids.isEmpty()) {
  727. newCodeOnReferenceByProjectUuids.forEach(projectOrProjectBranchUuid -> boolQueryBuilder.should(boolQuery()
  728. .filter(termQuery(FIELD_ISSUE_BRANCH_UUID, projectOrProjectBranchUuid))
  729. .filter(termQuery(FIELD_ISSUE_NEW_CODE_REFERENCE, true))));
  730. allFilters.addFilter("__is_new_code_reference_by_project_uuids",
  731. new SimpleFieldFilterScope("newCodeReferenceByProjectUuids"), boolQueryBuilder);
  732. }
  733. }
  734. private static void addCreatedAfterByProjectsFilter(AllFilters allFilters, IssueQuery query) {
  735. Map<String, PeriodStart> createdAfterByProjectUuids = query.createdAfterByProjectUuids();
  736. BoolQueryBuilder boolQueryBuilder = boolQuery();
  737. createdAfterByProjectUuids.forEach((projectOrProjectBranchUuid, createdAfterDate) -> boolQueryBuilder.should(boolQuery()
  738. .filter(termQuery(FIELD_ISSUE_BRANCH_UUID, projectOrProjectBranchUuid))
  739. .filter(rangeQuery(FIELD_ISSUE_FUNC_CREATED_AT).from(createdAfterDate.date().getTime(), createdAfterDate.inclusive()))));
  740. allFilters.addFilter("__created_after_by_project_uuids", new SimpleFieldFilterScope("createdAfterByProjectUuids"), boolQueryBuilder);
  741. }
  742. private void validateCreationDateBounds(@Nullable Date createdBefore, @Nullable Date createdAfter) {
  743. Preconditions.checkArgument(createdAfter == null || createdAfter.compareTo(new Date(system.now())) <= 0,
  744. "Start bound cannot be in the future");
  745. Preconditions.checkArgument(createdAfter == null || createdBefore == null || createdAfter.before(createdBefore),
  746. "Start bound cannot be larger or equal to end bound");
  747. }
  748. private void configureTopAggregations(TopAggregationHelper aggregationHelper, IssueQuery query, SearchOptions options,
  749. AllFilters queryFilters, SearchSourceBuilder esRequest) {
  750. addFacetIfNeeded(options, aggregationHelper, esRequest, STATUSES, NO_SELECTED_VALUES);
  751. addFacetIfNeeded(options, aggregationHelper, esRequest, PROJECT_UUIDS, query.projectUuids().toArray());
  752. addFacetIfNeeded(options, aggregationHelper, esRequest, DIRECTORIES, query.directories().toArray());
  753. addFacetIfNeeded(options, aggregationHelper, esRequest, FILES, query.files().toArray());
  754. addFacetIfNeeded(options, aggregationHelper, esRequest, SCOPES, query.scopes().toArray());
  755. addFacetIfNeeded(options, aggregationHelper, esRequest, LANGUAGES, query.languages().toArray());
  756. addFacetIfNeeded(options, aggregationHelper, esRequest, RULES, query.ruleUuids().toArray());
  757. addFacetIfNeeded(options, aggregationHelper, esRequest, AUTHOR, query.authors().toArray());
  758. addFacetIfNeeded(options, aggregationHelper, esRequest, TAGS, query.tags().toArray());
  759. addFacetIfNeeded(options, aggregationHelper, esRequest, TYPES, query.types().toArray());
  760. addFacetIfNeeded(options, aggregationHelper, esRequest, CODE_VARIANTS, query.codeVariants().toArray());
  761. addFacetIfNeeded(options, aggregationHelper, esRequest, CLEAN_CODE_ATTRIBUTE_CATEGORY, query.cleanCodeAttributesCategories().toArray());
  762. addSecurityCategoryFacetIfNeeded(PARAM_PCI_DSS_32, PCI_DSS_32, options, aggregationHelper, esRequest, query.pciDss32().toArray());
  763. addSecurityCategoryFacetIfNeeded(PARAM_PCI_DSS_40, PCI_DSS_40, options, aggregationHelper, esRequest, query.pciDss40().toArray());
  764. addSecurityCategoryFacetIfNeeded(PARAM_OWASP_ASVS_40, OWASP_ASVS_40, options, aggregationHelper, esRequest, query.owaspAsvs40().toArray());
  765. addSecurityCategoryFacetIfNeeded(PARAM_OWASP_TOP_10, OWASP_TOP_10, options, aggregationHelper, esRequest, query.owaspTop10().toArray());
  766. addSecurityCategoryFacetIfNeeded(PARAM_OWASP_TOP_10_2021, OWASP_TOP_10_2021, options, aggregationHelper, esRequest, query.owaspTop10For2021().toArray());
  767. addSecurityCategoryFacetIfNeeded(PARAM_SANS_TOP_25, SANS_TOP_25, options, aggregationHelper, esRequest, query.sansTop25().toArray());
  768. addSecurityCategoryFacetIfNeeded(PARAM_CWE, CWE, options, aggregationHelper, esRequest, query.cwe().toArray());
  769. addSecurityCategoryFacetIfNeeded(PARAM_SONARSOURCE_SECURITY, SONARSOURCE_SECURITY, options, aggregationHelper, esRequest, query.sonarsourceSecurity().toArray());
  770. addSeverityFacetIfNeeded(options, aggregationHelper, esRequest);
  771. addImpactSoftwareQualityFacetIfNeeded(options, query, aggregationHelper, esRequest);
  772. addImpactSeverityFacetIfNeeded(options, query, aggregationHelper, esRequest);
  773. addResolutionFacetIfNeeded(options, query, aggregationHelper, esRequest);
  774. addAssigneesFacetIfNeeded(options, query, aggregationHelper, esRequest);
  775. addCreatedAtFacetIfNeeded(options, query, aggregationHelper, queryFilters, esRequest);
  776. addAssignedToMeFacetIfNeeded(options, aggregationHelper, esRequest);
  777. addEffortTopAggregation(aggregationHelper, esRequest);
  778. }
  779. private static void addFacetIfNeeded(SearchOptions options, TopAggregationHelper aggregationHelper,
  780. SearchSourceBuilder esRequest, Facet facet, Object[] selectedValues) {
  781. if (!options.getFacets().contains(facet.getName())) {
  782. return;
  783. }
  784. FilterAggregationBuilder topAggregation = aggregationHelper.buildTermTopAggregation(
  785. facet.getName(), facet.getTopAggregationDef(), facet.getNumberOfTerms(),
  786. NO_EXTRA_FILTER,
  787. t -> aggregationHelper.getSubAggregationHelper().buildSelectedItemsAggregation(facet.getName(), facet.getTopAggregationDef(), selectedValues)
  788. .ifPresent(t::subAggregation));
  789. esRequest.aggregation(topAggregation);
  790. }
  791. private static void addSecurityCategoryFacetIfNeeded(String param, Facet facet, SearchOptions options, TopAggregationHelper aggregationHelper, SearchSourceBuilder esRequest,
  792. Object[] selectedValues) {
  793. if (!options.getFacets().contains(param)) {
  794. return;
  795. }
  796. AggregationBuilder aggregation = aggregationHelper.buildTermTopAggregation(
  797. facet.getName(), facet.getTopAggregationDef(), facet.getNumberOfTerms(),
  798. filter -> filter.must(termQuery(FIELD_ISSUE_TYPE, VULNERABILITY.name())),
  799. t -> aggregationHelper.getSubAggregationHelper().buildSelectedItemsAggregation(facet.getName(), facet.getTopAggregationDef(), selectedValues)
  800. .ifPresent(t::subAggregation));
  801. esRequest.aggregation(aggregation);
  802. }
  803. private static void addSeverityFacetIfNeeded(SearchOptions options, TopAggregationHelper aggregationHelper, SearchSourceBuilder esRequest) {
  804. if (!options.getFacets().contains(PARAM_SEVERITIES)) {
  805. return;
  806. }
  807. AggregationBuilder aggregation = aggregationHelper.buildTermTopAggregation(
  808. SEVERITIES.getName(), SEVERITIES.getTopAggregationDef(), SEVERITIES.getNumberOfTerms(),
  809. // Ignore severity of Security HotSpots
  810. filter -> filter.mustNot(termQuery(FIELD_ISSUE_TYPE, SECURITY_HOTSPOT.name())),
  811. NO_OTHER_SUBAGGREGATION);
  812. esRequest.aggregation(aggregation);
  813. }
  814. private static void addImpactSoftwareQualityFacetIfNeeded(SearchOptions options, IssueQuery query, TopAggregationHelper aggregationHelper, SearchSourceBuilder esRequest) {
  815. if (!options.getFacets().contains(PARAM_IMPACT_SOFTWARE_QUALITIES)) {
  816. return;
  817. }
  818. Function<SoftwareQuality, BoolQueryBuilder> mainQuery = softwareQuality -> boolQuery()
  819. .filter(termQuery(FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY, softwareQuality.name()));
  820. FiltersAggregator.KeyedFilter[] keyedFilters = Arrays.stream(SoftwareQuality.values())
  821. .map(softwareQuality -> new FiltersAggregator.KeyedFilter(softwareQuality.name(),
  822. query.impactSeverities().isEmpty() ? mainQuery.apply(softwareQuality)
  823. : mainQuery.apply(softwareQuality)
  824. .filter(termsQuery(FIELD_ISSUE_IMPACT_SEVERITY, query.impactSeverities()))))
  825. .toArray(FiltersAggregator.KeyedFilter[]::new);
  826. AggregationBuilder aggregation = aggregationHelper.buildTopAggregation(
  827. IMPACT_SOFTWARE_QUALITY.getName(), IMPACT_SOFTWARE_QUALITY.getTopAggregationDef(),
  828. NO_EXTRA_FILTER,
  829. t -> t.subAggregation(AggregationBuilders.nested("nested_" + IMPACT_SOFTWARE_QUALITY.getName(), FIELD_ISSUE_IMPACTS)
  830. .subAggregation(filters(IMPACT_SOFTWARE_QUALITY.getName(), keyedFilters))));
  831. esRequest.aggregation(aggregation);
  832. }
  833. private static void addImpactSeverityFacetIfNeeded(SearchOptions options, IssueQuery query, TopAggregationHelper aggregationHelper, SearchSourceBuilder esRequest) {
  834. if (!options.getFacets().contains(PARAM_IMPACT_SEVERITIES)) {
  835. return;
  836. }
  837. Function<org.sonar.api.issue.impact.Severity, BoolQueryBuilder> mainQuery = softwareQuality -> boolQuery()
  838. .filter(termQuery(FIELD_ISSUE_IMPACT_SEVERITY, softwareQuality.name()));
  839. FiltersAggregator.KeyedFilter[] keyedFilters = Arrays.stream(org.sonar.api.issue.impact.Severity.values())
  840. .map(severity -> new FiltersAggregator.KeyedFilter(severity.name(),
  841. query.impactSoftwareQualities().isEmpty() ? mainQuery.apply(severity)
  842. : mainQuery.apply(severity)
  843. .filter(termsQuery(FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY, query.impactSoftwareQualities()))))
  844. .toArray(FiltersAggregator.KeyedFilter[]::new);
  845. AggregationBuilder aggregation = aggregationHelper.buildTopAggregation(
  846. IMPACT_SEVERITY.getName(), IMPACT_SEVERITY.getTopAggregationDef(),
  847. NO_EXTRA_FILTER,
  848. t -> t.subAggregation(AggregationBuilders.nested("nested_" + IMPACT_SEVERITY.getName(), FIELD_ISSUE_IMPACTS)
  849. .subAggregation(filters(IMPACT_SEVERITY.getName(), keyedFilters)
  850. // we want to count the number of issues for each severity, so we need to reverse the nested aggregation
  851. .subAggregation(reverseNested("reverse_nested_" + IMPACT_SOFTWARE_QUALITY.getName())))));
  852. esRequest.aggregation(aggregation);
  853. }
  854. private static void addResolutionFacetIfNeeded(SearchOptions options, IssueQuery query, TopAggregationHelper aggregationHelper, SearchSourceBuilder esRequest) {
  855. if (!options.getFacets().contains(PARAM_RESOLUTIONS)) {
  856. return;
  857. }
  858. AggregationBuilder aggregation = aggregationHelper.buildTermTopAggregation(
  859. RESOLUTIONS.getName(), RESOLUTIONS.getTopAggregationDef(), RESOLUTIONS.getNumberOfTerms(),
  860. NO_EXTRA_FILTER,
  861. t ->
  862. // add aggregation of type "missing" to return count of unresolved issues in the facet
  863. t.subAggregation(
  864. addEffortAggregationIfNeeded(query, AggregationBuilders
  865. .missing(RESOLUTIONS.getName() + FACET_SUFFIX_MISSING)
  866. .field(RESOLUTIONS.getFieldName()))));
  867. esRequest.aggregation(aggregation);
  868. }
  869. private static void addAssigneesFacetIfNeeded(SearchOptions options, IssueQuery query, TopAggregationHelper aggregationHelper, SearchSourceBuilder esRequest) {
  870. if (!options.getFacets().contains(PARAM_ASSIGNEES)) {
  871. return;
  872. }
  873. Consumer<FilterAggregationBuilder> assigneeAggregations = t -> {
  874. // optional second aggregation to return the issue count for selected assignees (if any)
  875. Object[] assignees = query.assignees().toArray();
  876. aggregationHelper.getSubAggregationHelper().buildSelectedItemsAggregation(ASSIGNEES.getName(), ASSIGNEES.getTopAggregationDef(), assignees)
  877. .ifPresent(t::subAggregation);
  878. // third aggregation to always return the count of unassigned in the assignee facet
  879. t.subAggregation(addEffortAggregationIfNeeded(query, AggregationBuilders
  880. .missing(ASSIGNEES.getName() + FACET_SUFFIX_MISSING)
  881. .field(ASSIGNEES.getFieldName())));
  882. };
  883. AggregationBuilder aggregation = aggregationHelper.buildTermTopAggregation(
  884. ASSIGNEES.getName(), ASSIGNEES.getTopAggregationDef(), ASSIGNEES.getNumberOfTerms(),
  885. NO_EXTRA_FILTER, assigneeAggregations);
  886. esRequest.aggregation(aggregation);
  887. }
  888. private void addCreatedAtFacetIfNeeded(SearchOptions options, IssueQuery query, TopAggregationHelper aggregationHelper, AllFilters allFilters,
  889. SearchSourceBuilder esRequest) {
  890. if (options.getFacets().contains(PARAM_CREATED_AT)) {
  891. getCreatedAtFacet(query, aggregationHelper, allFilters).ifPresent(esRequest::aggregation);
  892. }
  893. }
  894. private Optional<AggregationBuilder> getCreatedAtFacet(IssueQuery query, TopAggregationHelper aggregationHelper, AllFilters allFilters) {
  895. long startTime;
  896. boolean startInclusive;
  897. PeriodStart createdAfter = query.createdAfter();
  898. if (createdAfter == null) {
  899. OptionalLong minDate = getMinCreatedAt(allFilters);
  900. if (!minDate.isPresent()) {
  901. return Optional.empty();
  902. }
  903. startTime = minDate.getAsLong();
  904. startInclusive = true;
  905. } else {
  906. startTime = createdAfter.date().getTime();
  907. startInclusive = createdAfter.inclusive();
  908. }
  909. Date createdBefore = query.createdBefore();
  910. long endTime = createdBefore == null ? system.now() : createdBefore.getTime();
  911. Duration timeSpan = new Duration(startTime, endTime);
  912. DateHistogramInterval bucketSize = computeDateHistogramBucketSize(timeSpan);
  913. FilterAggregationBuilder topAggregation = aggregationHelper.buildTopAggregation(
  914. CREATED_AT.getName(),
  915. CREATED_AT.getTopAggregationDef(),
  916. NO_EXTRA_FILTER,
  917. t -> {
  918. AggregationBuilder dateHistogram = AggregationBuilders.dateHistogram(CREATED_AT.getName())
  919. .field(CREATED_AT.getFieldName())
  920. .calendarInterval(bucketSize)
  921. .minDocCount(0L)
  922. .format(DateUtils.DATETIME_FORMAT)
  923. .timeZone(Optional.ofNullable(query.timeZone()).orElse(system.getDefaultTimeZone().toZoneId()))
  924. // ES dateHistogram bounds are inclusive while createdBefore parameter is exclusive
  925. .extendedBounds(new LongBounds(startInclusive ? startTime : (startTime + 1), endTime - 1L));
  926. addEffortAggregationIfNeeded(query, dateHistogram);
  927. t.subAggregation(dateHistogram);
  928. });
  929. return Optional.of(topAggregation);
  930. }
  931. private static DateHistogramInterval computeDateHistogramBucketSize(Duration timeSpan) {
  932. if (timeSpan.isShorterThan(TWENTY_DAYS)) {
  933. return DateHistogramInterval.DAY;
  934. }
  935. if (timeSpan.isShorterThan(TWENTY_WEEKS)) {
  936. return DateHistogramInterval.WEEK;
  937. }
  938. if (timeSpan.isShorterThan(TWENTY_MONTHS)) {
  939. return DateHistogramInterval.MONTH;
  940. }
  941. return DateHistogramInterval.YEAR;
  942. }
  943. private OptionalLong getMinCreatedAt(AllFilters filters) {
  944. String facetNameAndField = CREATED_AT.getFieldName();
  945. SearchSourceBuilder sourceBuilder = new SearchSourceBuilder()
  946. .size(0);
  947. BoolQueryBuilder esFilter = boolQuery();
  948. filters.stream().filter(Objects::nonNull).forEach(esFilter::must);
  949. if (esFilter.hasClauses()) {
  950. sourceBuilder.query(QueryBuilders.boolQuery().filter(esFilter));
  951. }
  952. sourceBuilder.aggregation(AggregationBuilders.min(facetNameAndField).field(facetNameAndField));
  953. SearchRequest request = EsClient.prepareSearch(TYPE_ISSUE.getMainType())
  954. .source(sourceBuilder);
  955. Min minValue = client.search(request).getAggregations().get(facetNameAndField);
  956. double actualValue = minValue.getValue();
  957. if (Double.isInfinite(actualValue)) {
  958. return OptionalLong.empty();
  959. }
  960. return OptionalLong.of((long) actualValue);
  961. }
  962. private void addAssignedToMeFacetIfNeeded(SearchOptions options, TopAggregationHelper aggregationHelper, SearchSourceBuilder esRequest) {
  963. String uuid = userSession.getUuid();
  964. if (options.getFacets().contains(ASSIGNED_TO_ME.getName()) && !StringUtils.isEmpty(uuid)) {
  965. AggregationBuilder aggregation = aggregationHelper.buildTopAggregation(
  966. ASSIGNED_TO_ME.getName(),
  967. ASSIGNED_TO_ME.getTopAggregationDef(),
  968. NO_EXTRA_FILTER,
  969. t ->
  970. // add sub-aggregation to return issue count for current user
  971. aggregationHelper.getSubAggregationHelper()
  972. .buildSelectedItemsAggregation(ASSIGNED_TO_ME.getName(), ASSIGNED_TO_ME.getTopAggregationDef(), new String[] {uuid})
  973. .ifPresent(t::subAggregation));
  974. esRequest.aggregation(aggregation);
  975. }
  976. }
  977. private static void addEffortTopAggregation(TopAggregationHelper aggregationHelper, SearchSourceBuilder esRequest) {
  978. AggregationBuilder topAggregation = aggregationHelper.buildTopAggregation(
  979. FACET_MODE_EFFORT,
  980. EFFORT_TOP_AGGREGATION,
  981. NO_EXTRA_FILTER,
  982. t -> t.subAggregation(EFFORT_AGGREGATION));
  983. esRequest.aggregation(topAggregation);
  984. }
  985. public List<String> searchTags(IssueQuery query, @Nullable String textQuery, int size) {
  986. Terms terms = listTermsMatching(FIELD_ISSUE_TAGS, query, textQuery, BucketOrder.key(true), size);
  987. return EsUtils.termsKeys(terms);
  988. }
  989. public Map<String, Long> countTags(IssueQuery query, int maxNumberOfTags) {
  990. Terms terms = listTermsMatching(FIELD_ISSUE_TAGS, query, null, BucketOrder.count(false), maxNumberOfTags);
  991. return EsUtils.termsToMap(terms);
  992. }
  993. public List<String> searchAuthors(IssueQuery query, @Nullable String textQuery, int maxNumberOfAuthors) {
  994. Terms terms = listTermsMatching(FIELD_ISSUE_AUTHOR_LOGIN, query, textQuery, BucketOrder.key(true), maxNumberOfAuthors);
  995. return EsUtils.termsKeys(terms);
  996. }
  997. private Terms listTermsMatching(String fieldName, IssueQuery query, @Nullable String textQuery, BucketOrder termsOrder, int size) {
  998. SearchRequest requestBuilder = EsClient.prepareSearch(TYPE_ISSUE.getMainType());
  999. SearchSourceBuilder sourceBuilder = new SearchSourceBuilder()
  1000. // Avoids returning search hits
  1001. .size(0);
  1002. requestBuilder.source(sourceBuilder);
  1003. sourceBuilder.query(boolQuery().must(QueryBuilders.matchAllQuery()).filter(createBoolFilter(query)));
  1004. TermsAggregationBuilder aggreg = AggregationBuilders.terms("_ref")
  1005. .field(fieldName)
  1006. .size(size)
  1007. .order(termsOrder)
  1008. .minDocCount(1L);
  1009. if (textQuery != null) {
  1010. aggreg.includeExclude(new IncludeExclude(format(SUBSTRING_MATCH_REGEXP, escapeSpecialRegexChars(textQuery)), null));
  1011. }
  1012. sourceBuilder.aggregation(aggreg);
  1013. SearchResponse searchResponse = client.search(requestBuilder);
  1014. return searchResponse.getAggregations().get("_ref");
  1015. }
  1016. private BoolQueryBuilder createBoolFilter(IssueQuery query) {
  1017. BoolQueryBuilder boolQuery = boolQuery();
  1018. createAllFilters(query).stream()
  1019. .filter(Objects::nonNull)
  1020. .forEach(boolQuery::must);
  1021. return boolQuery;
  1022. }
  1023. public List<ProjectStatistics> searchProjectStatistics(List<String> projectUuids, List<Long> froms, @Nullable String assigneeUuid) {
  1024. checkState(projectUuids.size() == froms.size(),
  1025. "Expected same size for projectUuids (had size %s) and froms (had size %s)", projectUuids.size(), froms.size());
  1026. if (projectUuids.isEmpty()) {
  1027. return Collections.emptyList();
  1028. }
  1029. SearchSourceBuilder sourceBuilder = new SearchSourceBuilder()
  1030. .query(
  1031. boolQuery()
  1032. .mustNot(existsQuery(FIELD_ISSUE_RESOLUTION))
  1033. .filter(termQuery(FIELD_ISSUE_ASSIGNEE_UUID, assigneeUuid))
  1034. .mustNot(termQuery(FIELD_ISSUE_TYPE, SECURITY_HOTSPOT.name())))
  1035. .size(0);
  1036. IntStream.range(0, projectUuids.size()).forEach(i -> {
  1037. String projectUuid = projectUuids.get(i);
  1038. long from = froms.get(i);
  1039. sourceBuilder
  1040. .aggregation(AggregationBuilders
  1041. .filter(projectUuid, boolQuery()
  1042. .filter(termQuery(FIELD_ISSUE_PROJECT_UUID, projectUuid))
  1043. .filter(rangeQuery(FIELD_ISSUE_FUNC_CREATED_AT).gte(from)))
  1044. .subAggregation(
  1045. AggregationBuilders.terms("branchUuid").field(FIELD_ISSUE_BRANCH_UUID)
  1046. .subAggregation(
  1047. AggregationBuilders.count(AGG_COUNT).field(FIELD_ISSUE_KEY))
  1048. .subAggregation(
  1049. AggregationBuilders.max("maxFuncCreatedAt").field(FIELD_ISSUE_FUNC_CREATED_AT))));
  1050. });
  1051. SearchRequest requestBuilder = EsClient.prepareSearch(TYPE_ISSUE.getMainType());
  1052. requestBuilder.source(sourceBuilder);
  1053. SearchResponse response = client.search(requestBuilder);
  1054. return response.getAggregations().asList().stream()
  1055. .map(ParsedFilter.class::cast)
  1056. .flatMap(projectBucket -> ((ParsedStringTerms) projectBucket.getAggregations().get("branchUuid")).getBuckets().stream()
  1057. .flatMap(branchBucket -> {
  1058. long count = ((ParsedValueCount) branchBucket.getAggregations().get(AGG_COUNT)).getValue();
  1059. if (count < 1L) {
  1060. return Stream.empty();
  1061. }
  1062. long lastIssueDate = (long) ((ParsedMax) branchBucket.getAggregations().get("maxFuncCreatedAt")).getValue();
  1063. return Stream.of(new ProjectStatistics(branchBucket.getKeyAsString(), count, lastIssueDate));
  1064. }))
  1065. .toList();
  1066. }
  1067. public List<SecurityStandardCategoryStatistics> getCweTop25Reports(String projectUuid, boolean isViewOrApp) {
  1068. SearchSourceBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp);
  1069. CWES_BY_CWE_TOP_25.keySet()
  1070. .forEach(cweYear -> request.aggregation(
  1071. newSecurityReportSubAggregations(
  1072. AggregationBuilders.filter(cweYear, boolQuery().filter(existsQuery(FIELD_ISSUE_CWE))),
  1073. true,
  1074. CWES_BY_CWE_TOP_25.get(cweYear))));
  1075. List<SecurityStandardCategoryStatistics> result = search(request, true, null);
  1076. for (SecurityStandardCategoryStatistics cweReport : result) {
  1077. Set<String> foundRules = cweReport.getChildren().stream()
  1078. .map(SecurityStandardCategoryStatistics::getCategory)
  1079. .collect(Collectors.toSet());
  1080. CWES_BY_CWE_TOP_25.get(cweReport.getCategory()).stream()
  1081. .filter(rule -> !foundRules.contains(rule))
  1082. .forEach(rule -> cweReport.getChildren().add(emptyCweStatistics(rule)));
  1083. }
  1084. return result;
  1085. }
  1086. private static SecurityStandardCategoryStatistics emptyCweStatistics(String rule) {
  1087. return new SecurityStandardCategoryStatistics(rule, 0, OptionalInt.of(1), 0, 0, 1, null, null);
  1088. }
  1089. public List<SecurityStandardCategoryStatistics> getSonarSourceReport(String projectUuid, boolean isViewOrApp, boolean includeCwe) {
  1090. SearchSourceBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp);
  1091. Arrays.stream(SQCategory.values())
  1092. .forEach(sonarsourceCategory -> request.aggregation(
  1093. newSecurityReportSubAggregations(
  1094. AggregationBuilders.filter(sonarsourceCategory.getKey(), boolQuery().filter(termQuery(FIELD_ISSUE_SQ_SECURITY_CATEGORY, sonarsourceCategory.getKey()))),
  1095. includeCwe,
  1096. SecurityStandards.CWES_BY_SQ_CATEGORY.get(sonarsourceCategory))));
  1097. return search(request, includeCwe, null);
  1098. }
  1099. public List<SecurityStandardCategoryStatistics> getPciDssReport(String projectUuid, boolean isViewOrApp, PciDssVersion version) {
  1100. SearchSourceBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp);
  1101. Arrays.stream(PciDss.values())
  1102. .forEach(pciDss -> request.aggregation(
  1103. newSecurityReportSubAggregations(
  1104. AggregationBuilders.filter(pciDss.category(), boolQuery().filter(prefixQuery(version.prefix(), pciDss.category() + "."))), version.prefix())));
  1105. return searchWithDistribution(request, version.label(), null);
  1106. }
  1107. public List<SecurityStandardCategoryStatistics> getOwaspAsvsReport(String projectUuid, boolean isViewOrApp, RulesDefinition.OwaspAsvsVersion version, Integer level) {
  1108. SearchSourceBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp);
  1109. Arrays.stream(SecurityStandards.OwaspAsvs.values())
  1110. .forEach(owaspAsvs -> request.aggregation(
  1111. newSecurityReportSubAggregations(
  1112. AggregationBuilders.filter(
  1113. owaspAsvs.category(),
  1114. boolQuery().filter(termsQuery(version.prefix(), SecurityStandards.getRequirementsForCategoryAndLevel(owaspAsvs, level)))),
  1115. version.prefix())));
  1116. return searchWithDistribution(request, version.label(), level);
  1117. }
  1118. public List<SecurityStandardCategoryStatistics> getOwaspAsvsReportGroupedByLevel(String projectUuid, boolean isViewOrApp, RulesDefinition.OwaspAsvsVersion version, int level) {
  1119. SearchSourceBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp);
  1120. request.aggregation(
  1121. newSecurityReportSubAggregations(
  1122. AggregationBuilders.filter(
  1123. "l" + level,
  1124. boolQuery().filter(termsQuery(version.prefix(), SecurityStandards.OWASP_ASVS_REQUIREMENTS_BY_LEVEL.get(version).get(level)))),
  1125. version.prefix()));
  1126. return searchWithLevelDistribution(request, version.label(), Integer.toString(level));
  1127. }
  1128. public List<SecurityStandardCategoryStatistics> getOwaspTop10Report(String projectUuid, boolean isViewOrApp, boolean includeCwe, OwaspTop10Version version) {
  1129. SearchSourceBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp);
  1130. IntStream.rangeClosed(1, 10).mapToObj(i -> "a" + i)
  1131. .forEach(owaspCategory -> request.aggregation(
  1132. newSecurityReportSubAggregations(
  1133. AggregationBuilders.filter(owaspCategory, boolQuery().filter(termQuery(version.prefix(), owaspCategory))),
  1134. includeCwe,
  1135. null)));
  1136. return search(request, includeCwe, version.label());
  1137. }
  1138. private List<SecurityStandardCategoryStatistics> searchWithLevelDistribution(SearchSourceBuilder sourceBuilder, String version, @Nullable String level) {
  1139. return getSearchResponse(sourceBuilder)
  1140. .getAggregations().asList().stream()
  1141. .map(c -> processSecurityReportIssueSearchResultsWithLevelDistribution((ParsedFilter) c, version, level))
  1142. .toList();
  1143. }
  1144. private List<SecurityStandardCategoryStatistics> searchWithDistribution(SearchSourceBuilder sourceBuilder, String version, @Nullable Integer level) {
  1145. return getSearchResponse(sourceBuilder)
  1146. .getAggregations().asList().stream()
  1147. .map(c -> processSecurityReportIssueSearchResultsWithDistribution((ParsedFilter) c, version, level))
  1148. .toList();
  1149. }
  1150. private List<SecurityStandardCategoryStatistics> search(SearchSourceBuilder sourceBuilder, boolean includeDistribution, @Nullable String version) {
  1151. return getSearchResponse(sourceBuilder)
  1152. .getAggregations().asList().stream()
  1153. .map(c -> processSecurityReportIssueSearchResults((ParsedFilter) c, includeDistribution, version))
  1154. .toList();
  1155. }
  1156. private SearchResponse getSearchResponse(SearchSourceBuilder sourceBuilder) {
  1157. SearchRequest request = EsClient.prepareSearch(TYPE_ISSUE.getMainType())
  1158. .source(sourceBuilder);
  1159. return client.search(request);
  1160. }
  1161. private static SecurityStandardCategoryStatistics processSecurityReportIssueSearchResultsWithDistribution(ParsedFilter categoryFilter, String version, @Nullable Integer level) {
  1162. var list = ((ParsedStringTerms) categoryFilter.getAggregations().get(AGG_DISTRIBUTION)).getBuckets();
  1163. List<SecurityStandardCategoryStatistics> children = list.stream()
  1164. .filter(categoryBucket -> StringUtils.startsWith(categoryBucket.getKeyAsString(), categoryFilter.getName() + "."))
  1165. .filter(categoryBucket -> level == null || OWASP_ASVS_40_REQUIREMENTS_BY_LEVEL.get(level).contains(categoryBucket.getKeyAsString()))
  1166. .map(categoryBucket -> processSecurityReportCategorySearchResults(categoryBucket, categoryBucket.getKeyAsString(), null, null))
  1167. .toList();
  1168. return processSecurityReportCategorySearchResults(categoryFilter, categoryFilter.getName(), children, version);
  1169. }
  1170. private static SecurityStandardCategoryStatistics processSecurityReportIssueSearchResultsWithLevelDistribution(ParsedFilter categoryFilter, String version, String level) {
  1171. var list = ((ParsedStringTerms) categoryFilter.getAggregations().get(AGG_DISTRIBUTION)).getBuckets();
  1172. List<SecurityStandardCategoryStatistics> children = list.stream()
  1173. .filter(categoryBucket -> OWASP_ASVS_40_REQUIREMENTS_BY_LEVEL.get(Integer.parseInt(level)).contains(categoryBucket.getKeyAsString()))
  1174. .map(categoryBucket -> processSecurityReportCategorySearchResults(categoryBucket, categoryBucket.getKeyAsString(), null, null))
  1175. .toList();
  1176. return processSecurityReportCategorySearchResults(categoryFilter, categoryFilter.getName(), children, version);
  1177. }
  1178. private static SecurityStandardCategoryStatistics processSecurityReportIssueSearchResults(ParsedFilter categoryBucket, boolean includeDistribution, String version) {
  1179. List<SecurityStandardCategoryStatistics> children = new ArrayList<>();
  1180. if (includeDistribution) {
  1181. Stream<? extends Terms.Bucket> stream = ((ParsedStringTerms) categoryBucket.getAggregations().get(AGG_DISTRIBUTION)).getBuckets().stream();
  1182. children = stream.map(cweBucket -> processSecurityReportCategorySearchResults(cweBucket, cweBucket.getKeyAsString(), null, null))
  1183. .collect(toCollection(ArrayList<SecurityStandardCategoryStatistics>::new));
  1184. }
  1185. return processSecurityReportCategorySearchResults(categoryBucket, categoryBucket.getName(), children, version);
  1186. }
  1187. private static SecurityStandardCategoryStatistics processSecurityReportCategorySearchResults(HasAggregations categoryBucket, String categoryName,
  1188. @Nullable List<SecurityStandardCategoryStatistics> children, @Nullable String version) {
  1189. List<? extends Terms.Bucket> severityBuckets = ((ParsedStringTerms) ((ParsedFilter) categoryBucket.getAggregations().get(AGG_VULNERABILITIES)).getAggregations()
  1190. .get(AGG_SEVERITIES)).getBuckets();
  1191. long vulnerabilities = severityBuckets.stream().mapToLong(b -> ((ParsedValueCount) b.getAggregations().get(AGG_COUNT)).getValue()).sum();
  1192. // Worst severity having at least one issue
  1193. OptionalInt severityRating = severityBuckets.stream()
  1194. .filter(b -> ((ParsedValueCount) b.getAggregations().get(AGG_COUNT)).getValue() != 0)
  1195. .mapToInt(b -> Severity.ALL.indexOf(b.getKeyAsString()) + 1)
  1196. .max();
  1197. long toReviewSecurityHotspots = ((ParsedValueCount) ((ParsedFilter) categoryBucket.getAggregations().get(AGG_TO_REVIEW_SECURITY_HOTSPOTS)).getAggregations().get(AGG_COUNT))
  1198. .getValue();
  1199. long reviewedSecurityHotspots = ((ParsedValueCount) ((ParsedFilter) categoryBucket.getAggregations().get(AGG_REVIEWED_SECURITY_HOTSPOTS)).getAggregations().get(AGG_COUNT))
  1200. .getValue();
  1201. Optional<Double> percent = computePercent(toReviewSecurityHotspots, reviewedSecurityHotspots);
  1202. Integer securityReviewRating = computeRating(percent.orElse(null)).getIndex();
  1203. return new SecurityStandardCategoryStatistics(categoryName, vulnerabilities, severityRating, toReviewSecurityHotspots,
  1204. reviewedSecurityHotspots, securityReviewRating, children, version);
  1205. }
  1206. private static AggregationBuilder newSecurityReportSubAggregations(AggregationBuilder categoriesAggs, String securityStandardVersionPrefix) {
  1207. AggregationBuilder aggregationBuilder = addSecurityReportIssueCountAggregations(categoriesAggs);
  1208. final TermsAggregationBuilder distributionAggregation = AggregationBuilders.terms(AGG_DISTRIBUTION)
  1209. .field(securityStandardVersionPrefix)
  1210. // 100 should be enough to display all the requirements per category. If not, the UI will be broken anyway
  1211. .size(MAX_FACET_SIZE);
  1212. categoriesAggs.subAggregation(addSecurityReportIssueCountAggregations(distributionAggregation));
  1213. return aggregationBuilder;
  1214. }
  1215. private static AggregationBuilder newSecurityReportSubAggregations(AggregationBuilder categoriesAggs, boolean includeCwe, @Nullable Collection<String> cwesInCategory) {
  1216. AggregationBuilder aggregationBuilder = addSecurityReportIssueCountAggregations(categoriesAggs);
  1217. if (includeCwe) {
  1218. final TermsAggregationBuilder cwesAgg = AggregationBuilders.terms(AGG_DISTRIBUTION)
  1219. .field(FIELD_ISSUE_CWE)
  1220. // 100 should be enough to display all CWEs. If not, the UI will be broken anyway
  1221. .size(MAX_FACET_SIZE);
  1222. if (cwesInCategory != null) {
  1223. cwesAgg.includeExclude(new IncludeExclude(cwesInCategory.toArray(new String[0]), new String[0]));
  1224. }
  1225. categoriesAggs.subAggregation(addSecurityReportIssueCountAggregations(cwesAgg));
  1226. }
  1227. return aggregationBuilder;
  1228. }
  1229. private static AggregationBuilder addSecurityReportIssueCountAggregations(AggregationBuilder categoryAggs) {
  1230. return categoryAggs
  1231. .subAggregation(
  1232. AggregationBuilders.filter(AGG_VULNERABILITIES, NON_RESOLVED_VULNERABILITIES_FILTER)
  1233. .subAggregation(
  1234. AggregationBuilders.terms(AGG_SEVERITIES).field(FIELD_ISSUE_SEVERITY)
  1235. .subAggregation(
  1236. AggregationBuilders.count(AGG_COUNT).field(FIELD_ISSUE_KEY))))
  1237. .subAggregation(AggregationBuilders.filter(AGG_TO_REVIEW_SECURITY_HOTSPOTS, TO_REVIEW_HOTSPOTS_FILTER)
  1238. .subAggregation(
  1239. AggregationBuilders.count(AGG_COUNT).field(FIELD_ISSUE_KEY)))
  1240. .subAggregation(AggregationBuilders.filter(AGG_IN_REVIEW_SECURITY_HOTSPOTS, IN_REVIEW_HOTSPOTS_FILTER)
  1241. .subAggregation(
  1242. AggregationBuilders.count(AGG_COUNT).field(FIELD_ISSUE_KEY)))
  1243. .subAggregation(AggregationBuilders.filter(AGG_REVIEWED_SECURITY_HOTSPOTS, REVIEWED_HOTSPOTS_FILTER)
  1244. .subAggregation(
  1245. AggregationBuilders.count(AGG_COUNT).field(FIELD_ISSUE_KEY)));
  1246. }
  1247. private static SearchSourceBuilder prepareNonClosedVulnerabilitiesAndHotspotSearch(String projectUuid, boolean isViewOrApp) {
  1248. BoolQueryBuilder componentFilter = boolQuery();
  1249. if (isViewOrApp) {
  1250. componentFilter.filter(QueryBuilders.termsLookupQuery(FIELD_ISSUE_BRANCH_UUID,
  1251. new TermsLookup(
  1252. TYPE_VIEW.getIndex().getName(),
  1253. projectUuid,
  1254. ViewIndexDefinition.FIELD_PROJECTS)));
  1255. } else {
  1256. componentFilter.filter(termQuery(FIELD_ISSUE_BRANCH_UUID, projectUuid));
  1257. }
  1258. SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
  1259. return sourceBuilder
  1260. .query(
  1261. componentFilter
  1262. .should(NON_RESOLVED_VULNERABILITIES_FILTER)
  1263. .should(TO_REVIEW_HOTSPOTS_FILTER)
  1264. .should(IN_REVIEW_HOTSPOTS_FILTER)
  1265. .should(REVIEWED_HOTSPOTS_FILTER)
  1266. .minimumShouldMatch(1))
  1267. .size(0);
  1268. }
  1269. }