Skip to content

Commit 03f1605

Browse files
authored
Merge pull request #1353 from okorach:migration-export-instantiated-issues
Migration-export-instantiated-issues
2 parents 8e93a1c + 106df68 commit 03f1605

File tree

12 files changed

+112
-116
lines changed

12 files changed

+112
-116
lines changed

sonar/aggregations.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
import sonar.platform as pf
3232

3333
import sonar.components as comp
34-
34+
from sonar import utilities
3535
from sonar.audit.rules import get_rule
3636
from sonar.audit.problem import Problem
3737

@@ -110,5 +110,4 @@ def count(api: str, endpoint: pf.Platform, params: types.ApiParams = None) -> in
110110
if params is None:
111111
params = {}
112112
params["ps"] = 1
113-
data = json.loads(endpoint.get(api, params=params).text)
114-
return data["paging"]["total"]
113+
return utilities.nbr_total_elements(json.loads(endpoint.get(api, params=params).text))

sonar/applications.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -443,8 +443,7 @@ def count(endpoint: pf.Platform) -> int:
443443
:rtype: int
444444
"""
445445
check_supported(endpoint)
446-
data = json.loads(endpoint.get(APIS["search"], params={"ps": 1, "filter": "qualifier = APP"}).text)
447-
return data["paging"]["total"]
446+
return util.nbr_total_elements(json.loads(endpoint.get(APIS["search"], params={"ps": 1, "filter": "qualifier = APP"}).text))
448447

449448

450449
def check_supported(endpoint: pf.Platform) -> None:

sonar/branches.py

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from sonar.util import types
3030
import sonar.logging as log
3131
import sonar.sqobject as sq
32-
from sonar import components, syncer, settings, exceptions
32+
from sonar import components, settings, exceptions
3333
from sonar import projects
3434
import sonar.utilities as util
3535

@@ -220,9 +220,6 @@ def export(self, export_settings: types.ConfigSettings) -> types.ObjectJsonRepr:
220220
:return: The branch new code period definition
221221
:rtype: str
222222
"""
223-
from sonar.issues import count as issue_count
224-
from sonar.hotspots import count as hotspot_count
225-
226223
log.debug("Exporting %s", str(self))
227224
data = {settings.NEW_CODE_PERIOD: self.new_code()}
228225
if self.is_main():
@@ -234,27 +231,7 @@ def export(self, export_settings: types.ConfigSettings) -> types.ObjectJsonRepr:
234231
if export_settings.get("FULL_EXPORT", True):
235232
data.update({"name": self.name, "project": self.concerned_object.key})
236233
if export_settings.get("MODE", "") == "MIGRATION":
237-
data["lastAnalysis"] = util.date_to_string(self.last_analysis())
238-
lang_distrib = self.get_measure("ncloc_language_distribution")
239-
loc_distrib = {}
240-
if lang_distrib:
241-
loc_distrib = {m.split("=")[0]: int(m.split("=")[1]) for m in lang_distrib.split(";")}
242-
loc_distrib["total"] = self.loc()
243-
data["ncloc"] = loc_distrib
244-
tpissues = self.count_third_party_issues()
245-
params = self.search_params()
246-
data["issues"] = {
247-
"thirdParty": tpissues if len(tpissues) > 0 else 0,
248-
"falsePositives": issue_count(self.endpoint, issueStatuses=["FALSE_POSITIVE"], **params),
249-
}
250-
status = "accepted" if self.endpoint.version() >= (10, 2, 0) else "wontFix"
251-
data["issues"][status] = issue_count(self.endpoint, issueStatuses=[status.upper()], **params)
252-
data["hotspots"] = {
253-
"acknowledged": hotspot_count(self.endpoint, resolution=["ACKNOWLEDGED"], **params),
254-
"safe": hotspot_count(self.endpoint, resolution=["SAFE"], **params),
255-
"fixed": hotspot_count(self.endpoint, resolution=["FIXED"], **params),
256-
}
257-
log.debug("%s has these notable issues %s", str(self), str(data["issues"]))
234+
data.update(self.migration_export())
258235
data = util.remove_nones(data)
259236
return None if len(data) == 0 else data
260237

@@ -330,17 +307,19 @@ def sync(self, another_branch: Branch, sync_settings: types.ConfigSettings) -> t
330307
:return: sync report as tuple, with counts of successful and unsuccessful issue syncs
331308
:rtype: tuple(report, counters)
332309
"""
310+
from sonar.syncer import sync_lists
311+
333312
report, counters = [], {}
334313
log.info("Syncing %s (%s) and %s (%s) issues", str(self), self.endpoint.url, str(another_branch), another_branch.endpoint.url)
335-
(report, counters) = syncer.sync_lists(
314+
(report, counters) = sync_lists(
336315
list(self.get_issues().values()),
337316
list(another_branch.get_issues().values()),
338317
self,
339318
another_branch,
340319
sync_settings=sync_settings,
341320
)
342321
log.info("Syncing %s (%s) and %s (%s) hotspots", str(self), self.endpoint.url, str(another_branch), another_branch.endpoint.url)
343-
(tmp_report, tmp_counts) = syncer.sync_lists(
322+
(tmp_report, tmp_counts) = sync_lists(
344323
list(self.get_hotspots().values()),
345324
list(another_branch.get_hotspots().values()),
346325
self,

sonar/components.py

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
2424
"""
2525
from __future__ import annotations
26+
import math
2627
import json
2728

2829
from datetime import datetime
@@ -104,9 +105,9 @@ def get_subcomponents(self, strategy: str = "children", with_issues: bool = Fals
104105
"metricKeys": "bugs,vulnerabilities,code_smells,security_hotspots",
105106
}
106107
data = json.loads(self.get("measures/component_tree", params=parms).text)
107-
nb_comp = data["paging"]["total"]
108+
nb_comp = utilities.nbr_total_elements(data)
108109
log.debug("Found %d subcomponents to %s", nb_comp, str(self))
109-
nb_pages = (nb_comp + 500 - 1) // 500
110+
nb_pages = math.ceil(nb_comp / 500)
110111
comp_list = {}
111112
parms["ps"] = 500
112113
for page in range(nb_pages):
@@ -126,29 +127,35 @@ def get_subcomponents(self, strategy: str = "children", with_issues: bool = Fals
126127

127128
def get_issues(self, filters: types.ApiParams = None) -> dict[str, object]:
128129
"""Returns list of issues for a component, optionally on branches or/and PRs"""
129-
from sonar.issues import component_filter, search_all
130+
from sonar.issues import search_all
130131

131132
log.info("Searching issues for %s with filters %s", str(self), str(filters))
132-
params = utilities.replace_keys(_ALT_COMPONENTS, component_filter(self.endpoint), self.search_params())
133+
params = self.search_params()
133134
if filters is not None:
134135
params.update(filters)
135136
params["additionalFields"] = "comments"
136137
issue_list = search_all(endpoint=self.endpoint, params=params)
137138
self.nbr_issues = len(issue_list)
138139
return issue_list
139140

140-
def count_third_party_issues(self, filters: types.ApiParams = None) -> dict[str, int]:
141-
"""Returns list of issues for a component, optionally on branches or/and PRs"""
142-
from sonar.issues import component_filter, count_by_rule
141+
def count_specific_rules_issues(self, ruleset: list[str], filters: types.ApiParams = None) -> dict[str, int]:
142+
"""Returns the count of issues of a component for a given ruleset"""
143+
from sonar.issues import count_by_rule
143144

144-
third_party_rules = rules.third_party(self.endpoint)
145-
params = utilities.replace_keys(_ALT_COMPONENTS, component_filter(self.endpoint), self.search_params())
145+
params = self.search_params()
146146
if filters is not None:
147147
params.update(filters)
148148
params["facets"] = "rules"
149-
params["rules"] = [r.key for r in third_party_rules]
150-
issues_count = {k: v for k, v in count_by_rule(endpoint=self.endpoint, **params).items() if v > 0}
151-
return issues_count
149+
params["rules"] = [r.key for r in ruleset]
150+
return {k: v for k, v in count_by_rule(endpoint=self.endpoint, **params).items() if v > 0}
151+
152+
def count_third_party_issues(self, filters: types.ApiParams = None) -> dict[str, int]:
153+
"""Returns the count of issues of a component corresponding to 3rd party rules"""
154+
return self.count_specific_rules_issues(ruleset=rules.third_party(self.endpoint), filters=filters)
155+
156+
def count_instantiated_rules_issues(self, filters: types.ApiParams = None) -> dict[str, int]:
157+
"""Returns the count of issues of a component corresponding to instantiated rules"""
158+
return self.count_specific_rules_issues(ruleset=rules.instantiated(self.endpoint), filters=filters)
152159

153160
def get_hotspots(self, filters: types.ApiParams = None) -> dict[str, object]:
154161
"""Returns list of hotspots for a component, optionally on branches or/and PRs"""
@@ -160,6 +167,35 @@ def get_hotspots(self, filters: types.ApiParams = None) -> dict[str, object]:
160167
params.update(filters)
161168
return search(endpoint=self.endpoint, filters=params)
162169

170+
def migration_export(self) -> dict[str, any]:
171+
from sonar.issues import count as issue_count
172+
from sonar.hotspots import count as hotspot_count
173+
174+
json_data = {"lastAnalysis": utilities.date_to_string(self.last_analysis())}
175+
lang_distrib = self.get_measure("ncloc_language_distribution")
176+
loc_distrib = {}
177+
if lang_distrib:
178+
loc_distrib = {m.split("=")[0]: int(m.split("=")[1]) for m in lang_distrib.split(";")}
179+
loc_distrib["total"] = self.loc()
180+
json_data["ncloc"] = loc_distrib
181+
tpissues = self.count_third_party_issues()
182+
inst_issues = self.count_instantiated_rules_issues()
183+
params = self.search_params()
184+
json_data["issues"] = {
185+
"thirdParty": tpissues if len(tpissues) > 0 else 0,
186+
"instantiatedRules": inst_issues if len(inst_issues) > 0 else 0,
187+
"falsePositives": issue_count(self.endpoint, issueStatuses=["FALSE_POSITIVE"], **params),
188+
}
189+
status = "accepted" if self.endpoint.version() >= (10, 2, 0) else "wontFix"
190+
json_data["issues"][status] = issue_count(self.endpoint, issueStatuses=[status.upper()], **params)
191+
json_data["hotspots"] = {
192+
"acknowledged": hotspot_count(self.endpoint, resolution=["ACKNOWLEDGED"], **params),
193+
"safe": hotspot_count(self.endpoint, resolution=["SAFE"], **params),
194+
"fixed": hotspot_count(self.endpoint, resolution=["FIXED"], **params),
195+
}
196+
log.debug("%s has these notable issues %s", str(self), str(json_data["issues"]))
197+
return json_data
198+
163199
def get_measures(self, metrics_list: types.KeyList) -> dict[str, any]:
164200
"""Retrieves a project list of measures
165201

sonar/hotspots.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
from __future__ import annotations
2323

24+
import math
2425
import json
2526
import re
2627
from http import HTTPStatus
@@ -71,7 +72,7 @@
7172
SEVERITIES = ()
7273

7374
# Filters for search of hotspots are different than for issues :-(
74-
_FILTERS_HOTSPOTS_REMAPPING = {"resolutions": "resolution", "statuses": "status", "componentsKey": PROJECT_FILTER_OLD, "components": PROJECT_FILTER}
75+
_FILTERS_HOTSPOTS_REMAPPING = {"resolutions": "resolution", "statuses": "status", "componentsKey": PROJECT_FILTER, "components": PROJECT_FILTER}
7576

7677
_OBJECTS = {}
7778

@@ -384,8 +385,7 @@ def search(endpoint: pf.Platform, filters: types.ApiParams = None) -> dict[str,
384385
:rtype: dict{<key>: <Hotspot>}
385386
"""
386387
hotspots_list = {}
387-
new_params = get_search_filters(endpoint=endpoint, params=filters)
388-
new_params = util.dict_remap(original_dict=new_params, remapping=_FILTERS_HOTSPOTS_REMAPPING)
388+
new_params = sanitize_search_filters(endpoint=endpoint, params=filters)
389389
filters_iterations = split_search_filters(new_params)
390390
ps = 500 if "ps" not in new_params else new_params["ps"]
391391
for inline_filters in filters_iterations:
@@ -395,16 +395,15 @@ def search(endpoint: pf.Platform, filters: types.ApiParams = None) -> dict[str,
395395
while True:
396396
inline_filters["p"] = p
397397
try:
398-
resp = endpoint.get("hotspots/search", params=inline_filters, mute=(HTTPStatus.NOT_FOUND,))
399-
data = json.loads(resp.text)
400-
nbr_hotspots = data["paging"]["total"]
398+
data = json.loads(endpoint.get("hotspots/search", params=inline_filters, mute=(HTTPStatus.NOT_FOUND,)).text)
399+
nbr_hotspots = util.nbr_total_elements(data)
401400
except HTTPError as e:
402401
if e.response.status_code == HTTPStatus.NOT_FOUND:
403402
log.warning("No hotspots found with search params %s", str(inline_filters))
404403
nbr_hotspots = 0
405404
return {}
406405
raise e
407-
nbr_pages = (nbr_hotspots + ps - 1) // ps
406+
nbr_pages = util.nbr_pages(data)
408407
log.debug("Number of hotspots: %d - Page: %d/%d", nbr_hotspots, inline_filters["p"], nbr_pages)
409408
if nbr_hotspots > 10000:
410409
raise TooManyHotspotsError(
@@ -432,7 +431,7 @@ def get_object(endpoint: pf.Platform, key: str, data: dict[str] = None, from_exp
432431
return _OBJECTS[uid]
433432

434433

435-
def get_search_filters(endpoint: pf.Platform, params: types.ApiParams) -> types.ApiParams:
434+
def sanitize_search_filters(endpoint: pf.Platform, params: types.ApiParams) -> types.ApiParams:
436435
"""Returns the filtered list of params that are allowed for api/hotspots/search"""
437436
log.debug("Sanitizing hotspot search criteria %s", str(params))
438437
if params is None:
@@ -446,6 +445,8 @@ def get_search_filters(endpoint: pf.Platform, params: types.ApiParams) -> types.
446445
criterias["status"] = "REVIEWED"
447446
if endpoint.version() <= (10, 2, 0):
448447
criterias = util.dict_remap(original_dict=criterias, remapping={PROJECT_FILTER: PROJECT_FILTER_OLD})
448+
else:
449+
criterias = util.dict_remap(original_dict=criterias, remapping={PROJECT_FILTER_OLD: PROJECT_FILTER})
449450
criterias = util.dict_subset(criterias, SEARCH_CRITERIAS)
450451
log.debug("Sanitized hotspot search criteria %s", str(criterias))
451452
return criterias

sonar/issues.py

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
from __future__ import annotations
2222

23+
import math
2324
from datetime import date, datetime, timedelta
2425
import json
2526
import re
@@ -750,18 +751,13 @@ def search(endpoint: pf.Platform, params: ApiParams = None, raise_error: bool =
750751
:raises: TooManyIssuesError if more than 10'000 issues found
751752
"""
752753
filters = pre_search_filters(endpoint=endpoint, params=params)
753-
# if endpoint.version() >= (10, 2, 0):
754-
# new_params = util.dict_remap_and_stringify(new_params, _FILTERS_10_2_REMAPPING)
755-
756-
log.debug("Search filters = %s", str(filters))
757-
if not filters:
758-
filters = {"ps": Issue.MAX_PAGE_SIZE}
759-
elif "ps" not in filters:
754+
if "ps" not in filters:
760755
filters["ps"] = Issue.MAX_PAGE_SIZE
761756

757+
log.debug("Search filters = %s", str(filters))
762758
issue_list = {}
763759
data = json.loads(endpoint.get(Issue.SEARCH_API, params=filters).text)
764-
nbr_issues = data["paging"]["total"]
760+
nbr_issues = util.nbr_total_elements(data)
765761
nbr_pages = util.nbr_pages(data)
766762
log.debug("Number of issues: %d - Nbr pages: %d", nbr_issues, nbr_pages)
767763

@@ -822,37 +818,32 @@ def get_newest_issue(endpoint: pf.Platform, params: ApiParams = None) -> Union[d
822818

823819
def count(endpoint: pf.Platform, **kwargs) -> int:
824820
"""Returns number of issues of a search"""
825-
params = {} if not kwargs else kwargs.copy()
826-
filters = pre_search_filters(endpoint=endpoint, params=params)
821+
filters = pre_search_filters(endpoint=endpoint, params=kwargs)
827822
filters["ps"] = 1
828-
nbr_issues = json.loads(endpoint.get(Issue.SEARCH_API, params=filters).text)["paging"]["total"]
823+
nbr_issues = util.nbr_total_elements(json.loads(endpoint.get(Issue.SEARCH_API, params=filters).text))
829824
log.debug("Count issues with filters %s returned %d issues", str(kwargs), nbr_issues)
830825
return nbr_issues
831826

832827

833828
def count_by_rule(endpoint: pf.Platform, **kwargs) -> dict[str, int]:
834829
"""Returns number of issues of a search"""
835-
params = {} if not kwargs else kwargs.copy()
836-
params["ps"] = 1
837-
params["facets"] = "rules"
838-
SLICE_SIZE = 50 # Search rules facets by bulks of 50
839830
nbr_slices = 1
840-
if "rules" in params:
841-
nbr_slices = (len(params["rules"]) + SLICE_SIZE - 1) // SLICE_SIZE
831+
SLICE_SIZE = 50 # Search rules facets by bulks of 50
832+
if "rules" in kwargs:
833+
ruleset = kwargs.pop("rules")
834+
nbr_slices = math.ceil(len(ruleset) / SLICE_SIZE)
835+
params = pre_search_filters(endpoint=endpoint, params=kwargs)
836+
params.update({"ps": 1, "facets": "rules"})
842837
rulecount = {}
843838
for i in range(nbr_slices):
844-
sliced_params = params.copy()
845-
sliced_params["rules"] = ",".join(params["rules"][i * SLICE_SIZE : min((i + 1) * SLICE_SIZE - 1, len(params["rules"]))])
846-
# log.debug("COUNT params = %s", str(sliced_params))
847-
data = json.loads(endpoint.get(Issue.SEARCH_API, params=sliced_params).text)["facets"][0]["values"]
848-
# log.debug("COUNT data results = %s", str(data))
839+
params["rules"] = ",".join(ruleset[i * SLICE_SIZE : min((i + 1) * SLICE_SIZE - 1, len(ruleset))])
840+
data = json.loads(endpoint.get(Issue.SEARCH_API, params=params).text)["facets"][0]["values"]
849841
for d in data:
850-
if d["val"] not in params["rules"]:
842+
if d["val"] not in ruleset:
851843
continue
852844
if d["val"] not in rulecount:
853845
rulecount[d["val"]] = 0
854846
rulecount[d["val"]] += d["count"]
855-
# log.debug("Rule counts = %s", util.json_dump(rulecount))
856847
return rulecount
857848

858849

@@ -870,7 +861,9 @@ def pre_search_filters(endpoint: pf.Platform, params: ApiParams) -> ApiParams:
870861
return {}
871862
log.debug("Sanitizing issue search filters %s", str(params))
872863
version = endpoint.version()
873-
filters = util.dict_remap(original_dict=params.copy(), remapping={"project": COMPONENT_FILTER})
864+
filters = util.dict_remap(
865+
original_dict=params.copy(), remapping={"project": COMPONENT_FILTER, "application": COMPONENT_FILTER, "portfolio": COMPONENT_FILTER}
866+
)
874867
filters = util.dict_subset(util.remove_nones(filters), _SEARCH_CRITERIAS)
875868
if version < (10, 2, 0):
876869
# Starting from 10.2 - "componentKeys" was renamed "components"

sonar/measures.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,7 @@ def count_history(self, params: ApiParams = None) -> int:
8383
if params is None:
8484
params = {}
8585
params.update({"component": self.concerned_object.key, "metrics": self.metric, "ps": 1})
86-
data = json.loads(self.get(Measure.API_HISTORY, params=params).text)
87-
return data["paging"]["total"]
86+
return util.nbr_total_elements(json.loads(self.get(Measure.API_HISTORY, params=params).text))
8887

8988
def search_history(self, params: ApiParams = None) -> dict[str, any]:
9089
"""Searches the history of the measure

0 commit comments

Comments
 (0)