Skip to content

Commit 3764f92

Browse files
authored
Merge pull request #1315 from okorach:migration-export-3rd-party-issues
Export 3rd party plugin issues count
2 parents b0254b5 + a169194 commit 3764f92

File tree

6 files changed

+120
-10
lines changed

6 files changed

+120
-10
lines changed

deploy.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ rm -rf build dist
4545
python3 setup.py bdist_wheel
4646

4747
# Deploy locally for tests
48-
pip install --upgrade --force-reinstall dist/sonar-tools-*-py3-*.whl
48+
pip install --upgrade --force-reinstall dist/sonar_tools-*-py3-*.whl
4949

5050
if [ "$build_image" == "1" ]; then
5151
docker build -t sonar-tools:latest .
@@ -61,6 +61,6 @@ if [ "$release" = "1" ]; then
6161
echo "Confirm release [y/n] ?"
6262
read -r confirm
6363
if [ "$confirm" = "y" ]; then
64-
python3 -m twine upload dist/sonar-tools-*-py3-*.whl
64+
python3 -m twine upload dist/sonar_tools-*-py3-*.whl
6565
fi
6666
fi

sonar/branches.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ def export(self, export_settings: types.ConfigSettings) -> types.ObjectJsonRepr:
230230
data[settings.NEW_CODE_PERIOD] = self.new_code()
231231
if export_settings.get("FULL_EXPORT", True):
232232
data.update({"name": self.name, "project": self.concerned_object.key})
233-
if export_settings["MODE"] == "MIGRATION":
233+
if export_settings.get("MODE", "") == "MIGRATION":
234234
data["lastAnalysis"] = util.date_to_string(self.last_analysis())
235235
lang_distrib = self.get_measure("ncloc_language_distribution")
236236
loc_distrib = {}

sonar/components.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
import sonar.sqobject as sq
3333
import sonar.platform as pf
3434

35-
from sonar import settings, tasks, measures, utilities
35+
from sonar import settings, tasks, measures, utilities, rules, issues
3636

3737
import sonar.audit.problem as pb
3838

@@ -137,6 +137,19 @@ def get_issues(self, filters: types.ApiParams = None) -> dict[str, object]:
137137
self.nbr_issues = len(issue_list)
138138
return issue_list
139139

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
143+
144+
third_party_rules = rules.third_party(self.endpoint)
145+
params = utilities.replace_keys(_ALT_COMPONENTS, component_filter(self.endpoint), self.search_params())
146+
if filters is not None:
147+
params.update(filters)
148+
params["facets"] = "rules"
149+
params["rules"] = [r.key for r in third_party_rules]
150+
issues_count = {k: v for k, v in issues.count_by_rule(endpoint=self.endpoint, **params).items() if v > 0}
151+
return issues_count
152+
140153
def get_hotspots(self, filters: types.ApiParams = None) -> dict[str, object]:
141154
"""Returns list of hotspots for a component, optionally on branches or/and PRs"""
142155
from sonar.hotspots import component_filter, search
@@ -161,8 +174,8 @@ def get_measures(self, metrics_list: types.KeyList) -> dict[str, any]:
161174

162175
def get_measure(self, metric: str, fallback: int = None) -> any:
163176
"""Returns a component measure"""
164-
meas = self.get_measures(metric)
165-
return meas[metric].value if metric in meas and meas[metric].value is not None else fallback
177+
meas = self.get_measures([metric])
178+
return meas[metric].value if metric in meas and meas[metric] and meas[metric].value is not None else fallback
166179

167180
def loc(self) -> int:
168181
"""Returns a component nbr of LOC"""

sonar/issues.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -818,14 +818,40 @@ def count(endpoint: pf.Platform, **kwargs) -> int:
818818
params = {} if not kwargs else kwargs.copy()
819819
params["ps"] = 1
820820
try:
821-
log.info("Count params = %s", str(params))
821+
log.debug("Count params = %s", str(params))
822822
nbr_issues = len(search(endpoint=endpoint, params=params))
823823
except TooManyIssuesError as e:
824824
nbr_issues = e.nbr_issues
825825
log.debug("Issue search %s would return %d issues", str(kwargs), nbr_issues)
826826
return nbr_issues
827827

828828

829+
def count_by_rule(endpoint: pf.Platform, **kwargs) -> dict[str, int]:
830+
"""Returns number of issues of a search"""
831+
params = {} if not kwargs else kwargs.copy()
832+
params["ps"] = 1
833+
params["facets"] = "rules"
834+
SLICE_SIZE = 50 # Search rules facets by bulks of 50
835+
nbr_slices = 1
836+
if "rules" in params:
837+
nbr_slices = (len(params["rules"]) + SLICE_SIZE - 1) // SLICE_SIZE
838+
rulecount = {}
839+
for i in range(nbr_slices):
840+
sliced_params = params.copy()
841+
sliced_params["rules"] = ",".join(params["rules"][i * SLICE_SIZE : min((i + 1) * SLICE_SIZE - 1, len(params["rules"]))])
842+
# log.debug("COUNT params = %s", str(sliced_params))
843+
data = json.loads(endpoint.get(Issue.SEARCH_API, params=sliced_params).text)["facets"][0]["values"]
844+
# log.debug("COUNT data results = %s", str(data))
845+
for d in data:
846+
if d["val"] not in params["rules"]:
847+
continue
848+
if d["val"] not in rulecount:
849+
rulecount[d["val"]] = 0
850+
rulecount[d["val"]] += d["count"]
851+
log.debug("Rule counts = %s", util.json_dump(rulecount))
852+
return rulecount
853+
854+
829855
def get_object(endpoint: pf.Platform, key: str, data: ApiPayload = None, from_export: bool = False) -> Issue:
830856
"""Returns an issue from its key"""
831857
uu = sqobject.uuid(key, endpoint.url)

sonar/projects.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
from sonar.util import types
4343

4444
from sonar import exceptions, errcodes
45-
from sonar import sqobject, components, qualitygates, qualityprofiles, tasks, settings, webhooks, devops, syncer
45+
from sonar import sqobject, components, qualitygates, qualityprofiles, rules, tasks, settings, webhooks, devops, syncer
4646
import sonar.permissions.permissions as perms
4747
from sonar import pull_requests, branches
4848
import sonar.utilities as util
@@ -783,6 +783,20 @@ def get_issues(self, filters: dict[str, str] = None) -> dict[str, object]:
783783
findings_list = {**findings_list, **comp.get_issues()}
784784
return findings_list
785785

786+
def count_third_party_issues(self, filters: dict[str, str] = None) -> dict[str, int]:
787+
branches_or_prs = self.get_branches_and_prs(filters)
788+
if branches_or_prs is None:
789+
return super().count_third_party_issues(filters)
790+
issue_counts = {}
791+
for comp in branches_or_prs.values():
792+
if not comp:
793+
continue
794+
for k, total in comp.count_third_party_issues(filters):
795+
if k not in issue_counts:
796+
issue_counts[k] = 0
797+
issue_counts[k] += total
798+
return issue_counts
799+
786800
def __sync_community(self, another_project: object, sync_settings: types.ConfigSettings) -> tuple[list[dict[str, str]], dict[str, int]]:
787801
"""Syncs 2 projects findings on a community edition"""
788802
report, counters = [], {}
@@ -960,7 +974,7 @@ def export(self, export_settings: types.ConfigSettings, settings_list: dict[str,
960974
json_data["webhooks"] = hooks
961975
json_data = util.filter_export(json_data, _IMPORTABLE_PROPERTIES, export_settings.get("FULL_EXPORT", False))
962976

963-
if export_settings["MODE"] == "MIGRATION":
977+
if export_settings.get("MODE", "") == "MIGRATION":
964978
json_data["lastAnalysis"] = util.date_to_string(self.last_analysis())
965979
json_data["detectedCi"] = self.ci()
966980
json_data["revision"] = self.revision()
@@ -978,6 +992,8 @@ def export(self, export_settings: types.ConfigSettings, settings_list: dict[str,
978992
"lastTaskWarnings": last_task.warnings(),
979993
"taskHistory": [t._json for t in self.task_history()],
980994
}
995+
json_data["thirdPartyIssues"] = self.count_third_party_issues()
996+
log.info("%s has %d 3rd party issues", str(self), sum(v for v in json_data["thirdPartyIssues"].values()))
981997

982998
settings_dict = settings.get_bulk(endpoint=self.endpoint, component=self, settings_list=settings_list, include_not_set=False)
983999
# json_data.update({s.to_json() for s in settings_dict.values() if include_inherited or not s.inherited})

sonar/rules.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,52 @@
4141

4242
TYPES = ("BUG", "VULNERABILITY", "CODE_SMELL", "SECURITY_HOTSPOT")
4343

44+
SONAR_REPOS = {
45+
"abap",
46+
"apex",
47+
"azureresourcemanager",
48+
"c",
49+
"cloudformation",
50+
"cobol",
51+
"cpp",
52+
"csharpsquid",
53+
"roslyn.sonaranalyzer.security.cs",
54+
"css",
55+
"docker",
56+
"flex",
57+
"go",
58+
"java",
59+
"javabugs",
60+
"javasecurity",
61+
"javascript",
62+
"jssecurity",
63+
"jcl",
64+
"kotlin",
65+
"kubernetes",
66+
"objc",
67+
"php",
68+
"phpsecurity",
69+
"pli",
70+
"plsql",
71+
"python",
72+
"pythonbugs",
73+
"pythonsecurity",
74+
"rpg",
75+
"ruby",
76+
"scala",
77+
"secrets",
78+
"swift",
79+
"terraform",
80+
"text",
81+
"tsql",
82+
"typescript",
83+
"tssecurity",
84+
"vb",
85+
"vbnet",
86+
"Web",
87+
"xml",
88+
}
89+
4490

4591
class Rule(sq.SqObject):
4692
"""
@@ -205,7 +251,9 @@ def count(endpoint: platform.Platform, **params) -> int:
205251

206252
def get_list(endpoint: platform.Platform, **params) -> dict[str, Rule]:
207253
"""Returns a list of rules corresponding to certain csearch filters"""
208-
return search(endpoint, include_external="false", **params)
254+
if len(_OBJECTS) < 100:
255+
return search(endpoint, include_external="false", **params)
256+
return _OBJECTS
209257

210258

211259
def get_object(endpoint: platform.Platform, key: str) -> Optional[Rule]:
@@ -249,6 +297,8 @@ def export(endpoint: platform.Platform, export_settings: types.ConfigSettings, k
249297
rule_list["extended"] = extended_rules
250298
if len(other_rules) > 0 and full:
251299
rule_list["standard"] = other_rules
300+
if export_settings.get("MODE", "") == "MIGRATION":
301+
rule_list["thirdParty"] = {r.key: r.export() for r in third_party(endpoint=endpoint)}
252302
return rule_list
253303

254304

@@ -331,3 +381,8 @@ def convert_for_yaml(original_json: types.ObjectJsonRepr) -> types.ObjectJsonRep
331381
if category in original_json:
332382
new_json[category] = convert_rule_list_for_yaml(original_json[category])
333383
return new_json
384+
385+
386+
def third_party(endpoint: platform.Platform) -> list[Rule]:
387+
"""Returns the list of rules coming from 3rd party plugins"""
388+
return [r for r in get_list(endpoint=endpoint).values() if r.repo and r.repo not in SONAR_REPOS and not r.repo.startswith("external_")]

0 commit comments

Comments
 (0)