Skip to content

Commit

Permalink
[feat] Guideline statistics
Browse files Browse the repository at this point in the history
The new `Guideline statistics` tab on the Statistics page can list all rules for the selected guidelines. The user can select multiple guidelines (but currently the only one is `sei-cert` and it is the default).

The table can show the checker statistics that are related to the specified guideline rule. Rules may connect to more than one checker or may not have any checker.

The checker statistics are calculated for runs that are selected (or for all runs if no run selected) in the report filter. It can show guideline name, guideline rule, checker name, checker severity, checker status, number of closed and outstanding reports.

The status informs the user about how many runs the given checker was enabled or disabled. Closed and outstanding report counts depend on review and detection status.

New config dir was created to store guideline files. Each yaml file represents a guideline an contains its rules. The `Guidelines` class can parse the yamls. We can reach the guideline data via `getGuidelineRules` API endpoint that can return a list of `Rules`.
  • Loading branch information
cservakt committed Oct 14, 2024
1 parent 2032d18 commit e1f8c18
Show file tree
Hide file tree
Showing 24 changed files with 1,627 additions and 26 deletions.
9 changes: 9 additions & 0 deletions analyzer/codechecker_analyzer/analyzer_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from codechecker_analyzer.arg import analyzer_binary
from codechecker_common import logger
from codechecker_common.checker_labels import CheckerLabels
from codechecker_common.guidelines import Guidelines
from codechecker_common.singleton import Singleton
from codechecker_common.util import load_json
from pathlib import Path
Expand Down Expand Up @@ -52,13 +53,17 @@ def __init__(self):
if 'CC_TEST_LABELS_DIR' in os.environ:
labels_dir = os.environ['CC_TEST_LABELS_DIR']

guidelines_dir = os.path.join(self._data_files_dir_path,
'config', 'guidelines')

cfg_dict = self.__get_package_config()
self.env_vars = cfg_dict['environment_variables']

lcfg_dict = self.__get_package_layout()
self.pckg_layout = lcfg_dict['runtime']

self._checker_labels = CheckerLabels(labels_dir)
self._guidelines = Guidelines(guidelines_dir)
self.__package_version = None
self.__package_build_date = None
self.__package_git_hash = None
Expand Down Expand Up @@ -370,6 +375,10 @@ def checker_plugin(self):
def checker_labels(self):
return self._checker_labels

@property
def guideline(self):
return self._guidelines


def get_context():
try:
Expand Down
79 changes: 79 additions & 0 deletions codechecker_common/guidelines.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# -------------------------------------------------------------------------
#
# Part of the CodeChecker project, under the Apache License v2.0 with
# LLVM Exceptions. See LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# -------------------------------------------------------------------------
import os
from typing import DefaultDict, Dict, Iterable, List
from collections import defaultdict

import yaml


class Guidelines:
def __init__(self, guidelines_dir: str):
if not os.path.isdir(guidelines_dir):
raise NotADirectoryError(
f'{guidelines_dir} is not a directory.')

self.__guidelines_dir = guidelines_dir

guideline_yaml_files = map(
lambda f: os.path.join(guidelines_dir, f),
os.listdir(guidelines_dir))

self.__all_rules = self.__union_guideline_files(guideline_yaml_files)

def __union_guideline_files(
self,
guideline_files: Iterable[str]
) -> Dict[str, DefaultDict[str, List[str]]]:
"""
This function creates a union object of the given guideline files. The
resulting object maps guidelines to the collection of their rules. E.g.:
{
"guideline1": [
{
"rule_id": ...
"rule_url": ...
"title": ...
},
{
...
}
],
"guideline2": [
...
],
}
"""
all_rules = defaultdict(list)

for guideline_file in guideline_files:
with open(guideline_file, "r") as gf:
guideline_data = yaml.safe_load(gf)

guideline_name = guideline_data.get("guideline")
rules = guideline_data.get("rules")

all_rules[guideline_name].extend(rules)

return all_rules


def rules_of_guideline(
self,
guidelineName: str,
) -> List[Dict[str, str]]:
"""
Return the list of rules of a guideline.
"""

guideline_rules = self.__all_rules.get(guidelineName)

return guideline_rules

def all_guideline_rules(self) -> Dict[str, str]:
return self.__all_rules
661 changes: 661 additions & 0 deletions config/guidelines/sei-cert.yaml

Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.
2 changes: 1 addition & 1 deletion web/api/js/codechecker-api-node/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codechecker-api",
"version": "6.58.0",
"version": "6.59.0",
"description": "Generated node.js compatible API stubs for CodeChecker server.",
"main": "lib",
"homepage": "https://github.com/Ericsson/codechecker",
Expand Down
Binary file modified web/api/py/codechecker_api/dist/codechecker_api.tar.gz
Binary file not shown.
2 changes: 1 addition & 1 deletion web/api/py/codechecker_api/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
with open('README.md', encoding='utf-8', errors="ignore") as f:
long_description = f.read()

api_version = '6.58.0'
api_version = '6.59.0'

setup(
name='codechecker_api',
Expand Down
Binary file not shown.
2 changes: 1 addition & 1 deletion web/api/py/codechecker_api_shared/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
with open('README.md', encoding='utf-8', errors="ignore") as f:
long_description = f.read()

api_version = '6.58.0'
api_version = '6.59.0'

setup(
name='codechecker_api_shared',
Expand Down
16 changes: 16 additions & 0 deletions web/api/report_server.thrift
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,18 @@ struct Checker {
2: string checkerId,
}

struct Guideline {
1: string guidelineName
}

struct Rule {
1: string ruleId, // The identifier of the rule.
2: string title, // The rule summary.
3: string url, // The link of the rule page.
4: list<map<string, string>> checkers // List of checker names
}
typedef map<string, list<Rule>> GuidelineRules

service codeCheckerDBAccess {

// Gives back all analyzed runs.
Expand Down Expand Up @@ -779,6 +791,10 @@ service codeCheckerDBAccess {
// 'label1:value2', 'label2:value3'].
list<list<string>> getCheckerLabels(1: list<Checker> checkers)

// Return the list of rules to each guideline that given.
// If the guidelines param is empty, returning with all guideline rules.
GuidelineRules getGuidelineRules(1: list<Guideline> guidelines)

// returns the CodeChecker version that is running on the server
// !DEPRECATED Use ServerInfo API to get the package version.
string getPackageVersion();
Expand Down
2 changes: 1 addition & 1 deletion web/codechecker_web/shared/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# The newest supported minor version (value) for each supported major version
# (key) in this particular build.
SUPPORTED_VERSIONS = {
6: 58
6: 59
}

# Used by the client to automatically identify the latest major and minor
Expand Down
9 changes: 9 additions & 0 deletions web/codechecker_web/shared/webserver_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

from codechecker_common import logger
from codechecker_common.checker_labels import CheckerLabels
from codechecker_common.guidelines import Guidelines
from codechecker_common.singleton import Singleton
from codechecker_common.util import load_json

Expand Down Expand Up @@ -70,7 +71,11 @@ def __init__(self):
if 'CC_TEST_LABELS_DIR' in os.environ:
labels_dir = os.environ['CC_TEST_LABELS_DIR']

guidelines_dir = os.path.join(self._data_files_dir_path,
'config', 'guidelines')

self._checker_labels = CheckerLabels(labels_dir)
self._guidelines = Guidelines(guidelines_dir)
self.__system_comment_map = load_json(self.system_comment_map_file, {})
self.__git_commit_urls = self.__get_git_commit_urls()
self.__package_version = None
Expand Down Expand Up @@ -222,6 +227,10 @@ def config_migration_root(self):
def checker_labels(self):
return self._checker_labels

@property
def guideline(self):
return self._guidelines


def get_context():
try:
Expand Down
37 changes: 36 additions & 1 deletion web/server/codechecker_server/api/report_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
Order, \
ReportData, ReportDetails, ReportStatus, ReviewData, ReviewStatusRule, \
ReviewStatusRuleFilter, ReviewStatusRuleSortMode, \
ReviewStatusRuleSortType, RunData, RunFilter, RunHistoryData, \
ReviewStatusRuleSortType, Rule, RunData, RunFilter, RunHistoryData, \
RunReportCount, RunSortType, RunTagCount, \
ReviewStatus as API_ReviewStatus, \
SourceComponentData, SourceFileData, SortMode, SortType
Expand Down Expand Up @@ -2771,6 +2771,41 @@ def getCheckerLabels(

return labels

@exc_to_thrift_reqfail
@timeit
def getGuidelineRules(
self,
guidelines: List[ttypes.Guideline]
):
""" Return the list of rules to each guideline that given. """
guideline_rules = defaultdict(list)
for guideline in guidelines:
rules = self._context.guideline.rules_of_guideline(
guideline.guidelineName)
if not rules:
guideline_rules[guideline.guidelineName] = []
continue
for rule in rules:
checkers = [{
"checkerName": checker_name,
"severity": self._context.checker_labels.severity(
checker_name).lower()
} for checker_name in
self._context.checker_labels.checkers_by_labels(
[f"{guideline.guidelineName}: \
{rule['rule_id']}"])]

guideline_rules[guideline.guidelineName].append(
Rule(
ruleId=rule["rule_id"].lower(),
title=rule["title"],
url=rule["rule_url"],
checkers=checkers
)
)

return guideline_rules

@exc_to_thrift_reqfail
@timeit
def getSourceFileData(self, fileId, fileContent, encoding):
Expand Down
12 changes: 6 additions & 6 deletions web/server/vue-cli/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion web/server/vue-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
},
"dependencies": {
"@mdi/font": "^6.5.95",
"codechecker-api": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.58.0.tgz",
"codechecker-api": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.59.0.tgz",
"chart.js": "^2.9.4",
"chartjs-plugin-datalabels": "^0.7.0",
"codemirror": "^5.65.0",
Expand Down
2 changes: 1 addition & 1 deletion web/server/vue-cli/src/components/CountChips.vue
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export default {
},
props: {
tag: { type: String, default: "span" },
numGood: { type: Number, required: true },
numGood: { type: Number, default: 0 },
numBad: { type: Number, default: 0 },
numTotal: { type: Number, default: 0 },
goodText: { type: String, default: "" },
Expand Down
Loading

0 comments on commit e1f8c18

Please sign in to comment.