Skip to content

Commit

Permalink
[GUI] New "Analysis statistics" tab to show all enabled checkers and …
Browse files Browse the repository at this point in the history
…their statistics

It is a new feature based on Ericsson#4089. The new Analysis statitics tab on the Statistics page is able to list all enabled checkers for runs that are selected (or for all runs if no run selected) in the report filter. The table lists all checkers that were enabled in at least one of runs according to the latest analysis. It also shows checker severity, status, number of closed and outstanding reports.

Status can inform the user that the specific checker was "Enabled in all" runs or "Enabled in all runs except these" where "runs" and "these" words are links to list appropriate runs.

Closed and outstanding report counts depend on review and detection status. These statistics represent the number of closed and outstanding reports that belong to runs that were created with new DB schema.
  • Loading branch information
cservakt committed Mar 12, 2024
1 parent fa1d14f commit 4403173
Show file tree
Hide file tree
Showing 24 changed files with 824 additions and 27 deletions.
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.55.0",
"version": "6.56.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.55.0'
api_version = '6.56.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.55.0'
api_version = '6.56.0'

setup(
name='codechecker_api_shared',
Expand Down
19 changes: 19 additions & 0 deletions web/api/report_server.thrift
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,17 @@ struct CheckerCount {
}
typedef list<CheckerCount> CheckerCounts

struct CheckerInfo {
1: string checkerName, // Analyzer name of the checker.
2: string analyzerName, // Analyzer name of the checker.
3: Severity severity, // Severity level of the checker.
4: list<i64> enabled, // Runs' names in which the checker enabled.
5: list<i64> disabled, // Runs' names in which the checker disabled.
6: i64 closed // Number of closed reports.
7: i64 outstanding // Number of outstanding reports.
}
typedef map<i64, CheckerInfo> CheckerInfos

struct CommentData {
1: i64 id,
2: string author,
Expand Down Expand Up @@ -852,6 +863,14 @@ service codeCheckerDBAccess {
5: i64 offset)
throws (1: codechecker_api_shared.RequestFailed requestError),

// It gives statistics for specific runs which checkers were
// enabled and how many reports are opened or closed.
// If the run id list is empty the statistics
// will be counted for all of the runs.
CheckerInfos getCheckerInfo(1: list<i64> runIds,
2: ReportFilter reportFilter)
throws (1: codechecker_api_shared.RequestFailed requestError),

// If the run id list is empty the metrics will be counted
// for all of the runs and in compare mode all of the runs
// will be used as a baseline excluding the runs in compare data.
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: 55
6: 56
}

# Used by the client to automatically identify the latest major and minor
Expand Down
190 changes: 188 additions & 2 deletions web/server/codechecker_server/api/report_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

import sqlalchemy
from sqlalchemy.sql.expression import or_, and_, not_, func, \
asc, desc, union_all, select, bindparam, literal_column
asc, desc, union_all, select, bindparam, literal_column, case, cast
from sqlalchemy.orm import contains_eager

import codechecker_api_shared
Expand All @@ -42,7 +42,8 @@
ReviewStatusRuleFilter, ReviewStatusRuleSortMode, \
ReviewStatusRuleSortType, RunData, RunFilter, RunHistoryData, \
RunReportCount, RunSortType, RunTagCount, \
SourceComponentData, SourceFileData, SortMode, SortType
SourceComponentData, SourceFileData, SortMode, SortType, \
ReviewStatus as API_ReviewStatus, DetectionStatus, CheckerInfo

from codechecker_common import util
from codechecker_common.logger import get_logger
Expand All @@ -58,6 +59,7 @@
from ..database.database import conv, DBSession, escape_like
from ..database.run_db_model import \
AnalysisInfo, AnalysisInfoChecker as DB_AnalysisInfoChecker, \
AnalysisInfoChecker, \
AnalyzerStatistic, \
BugPathEvent, BugReportPoint, \
CleanupPlan, CleanupPlanReportHash, Checker, Comment, \
Expand Down Expand Up @@ -2861,6 +2863,190 @@ def getCheckerCounts(self, run_ids, report_filter, cmp_data, limit,
results.append(checker_count)
return results

@exc_to_thrift_reqfail
@timeit
def getCheckerInfo(self, run_ids, report_filter):
self.__require_view()
with DBSession(self._Session) as session:
# filter_expression, join_tables = process_report_filter(
# session, run_ids, report_filter)

max_run_histories = session.query(
RunHistory.run_id,
func.max(RunHistory.id).label('max_run_history_id'),
) \
.filter(RunHistory.run_id.in_(run_ids) if run_ids else True) \
.group_by(RunHistory.run_id)

subquery = (
session.query(
(func.string_agg(
cast(Run.id, sqlalchemy.String).distinct(),
','
).label("run_id")
if session.bind.dialect.name == "postgresql"
else func.group_concat(Run.id.distinct()).label("run_id"))
if report_filter.isUnique
else Run.id.label("run_id"),
Checker.id.label("checker_id"),
Checker.checker_name,
Checker.analyzer_name,
Checker.severity,
Report.bug_id,
Report.detection_status,
Report.review_status,
)
.join(RunHistory)
.join(AnalysisInfo, RunHistory.analysis_info)
.join(AnalysisInfoChecker, (
(AnalysisInfo.id == AnalysisInfoChecker.analysis_info_id)
& (AnalysisInfoChecker.enabled.is_(True))))
.join(Checker, AnalysisInfoChecker.checker_id == Checker.id)
.outerjoin(Report, ((Checker.id == Report.checker_id)
& (Run.id == Report.run_id)))
.filter(RunHistory.id == max_run_histories.subquery()
.c.max_run_history_id)
)

if report_filter.isUnique:
subquery = subquery.group_by(
Checker.id,
Checker.checker_name,
Checker.analyzer_name,
Checker.severity,
Report.bug_id,
Report.detection_status,
Report.review_status
)

subquery = subquery.subquery()

query = (
session.query(
subquery.c.checker_id,
subquery.c.checker_name,
subquery.c.analyzer_name,
subquery.c.severity,
subquery.c.run_id,
case(
[
(
subquery.c.detection_status.in_(list(map(
detection_status_str,
(DetectionStatus.OFF,
DetectionStatus.UNAVAILABLE)
))),
False
)
],
else_=True
).label("isEnabled"),
case(
[
(
and_(
subquery.c.detection_status.in_(list(map(
detection_status_str,
(DetectionStatus.NEW,
DetectionStatus.UNRESOLVED,
DetectionStatus.REOPENED)
))),
subquery.c.review_status.in_(list(map(
review_status_str,
(API_ReviewStatus.UNREVIEWED,
API_ReviewStatus.CONFIRMED))))
),
True
)
],
else_=False
).label("isOpened"),
func.count(subquery.c.bug_id)
)
.group_by(
subquery.c.checker_id,
subquery.c.checker_name,
subquery.c.analyzer_name,
subquery.c.severity,
subquery.c.run_id,
case(
[
(
subquery.c.detection_status.in_(list(map(
detection_status_str,
(DetectionStatus.OFF,
DetectionStatus.UNAVAILABLE)
))),
False
)
],
else_=True
),
case(
[
(
and_(
subquery.c.detection_status.in_(list(map(
detection_status_str,
(DetectionStatus.NEW,
DetectionStatus.UNRESOLVED,
DetectionStatus.REOPENED)
))),
subquery.c.review_status.in_(list(map(
review_status_str,
(API_ReviewStatus.UNREVIEWED,
API_ReviewStatus.CONFIRMED))))
),
True
)
],
else_=False
)
)
)

checker_stats = {}
for checker_id, \
checker_name, \
analyzer_name, \
severity, \
run_ids, \
is_enabled, \
is_opened, \
cnt \
in query.all():
checker_stat = checker_stats[checker_id] \
if checker_id in checker_stats \
else CheckerInfo(
checkerName=checker_name,
analyzerName=analyzer_name,
enabled=[],
disabled=[runId for runId, _
in max_run_histories.all()],
severity=severity,
closed=0,
outstanding=0
)

if is_enabled:
for r in (run_ids.split(",")
if type(run_ids) is str
else [run_ids]):
run_id = int(r)
if run_id not in checker_stat.enabled:
checker_stat.enabled.append(run_id)
if run_id in checker_stat.disabled:
checker_stat.disabled.remove(run_id)

if is_enabled and is_opened:
checker_stat.outstanding += cnt
else:
checker_stat.closed += cnt

checker_stats[checker_id] = checker_stat

return checker_stats

@exc_to_thrift_reqfail
@timeit
def getAnalyzerNameCounts(self, run_ids, report_filter, cmp_data, limit,
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.55.0.tgz",
"codechecker-api": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.56.0.tgz",
"chart.js": "^2.9.4",
"chartjs-plugin-datalabels": "^0.7.0",
"codemirror": "^5.65.0",
Expand Down
Loading

0 comments on commit 4403173

Please sign in to comment.