Skip to content

Commit

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

It is a new feature based on 4089 PR. The new Checker Coverage statitics tab on the Statistics page can show 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 Apr 8, 2024
1 parent bc7bf7c commit aba1bd4
Show file tree
Hide file tree
Showing 28 changed files with 894 additions and 33 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
187 changes: 185 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,187 @@ 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:
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
5 changes: 4 additions & 1 deletion web/server/vue-cli/src/components/AnalysisInfoDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,8 @@ import {
default as AnalysisInfoHandlingAPIMixin,
CheckerInfoAvailability,
CountKeys,
GroupKeys
GroupKeys,
decideNegativeCheckerStatusAvailability
} from "@/mixins/api/analysis-info-handling.mixin";
export default {
Expand Down Expand Up @@ -272,6 +273,8 @@ export default {
var analysisInfo = await this.loadAnalysisInfo(
this.runId, this.runHistoryId, this.reportId);
decideNegativeCheckerStatusAvailability(
analysisInfo, this.runId, this.runHistoryId, this.reportId);
this.highlightedCmds = analysisInfo.cmds.map(cmd =>
this.highlightOptions(cmd));
analysisInfo.groupAndCountCheckers();
Expand Down
2 changes: 2 additions & 0 deletions web/server/vue-cli/src/components/CountChips.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
outlined
dark
small
@click="$emit('showing-good-click')"
>
<v-icon
start
Expand All @@ -40,6 +41,7 @@
outlined
dark
small
@click="$emit('showing-bad-click')"
>
<v-icon
start
Expand Down
Loading

0 comments on commit aba1bd4

Please sign in to comment.