From 3855efbd4eeffccf31860b150cf607e030567ac8 Mon Sep 17 00:00:00 2001 From: xb058t Date: Thu, 19 Mar 2026 22:23:41 +0100 Subject: [PATCH 01/38] first implementation --- web/server/vue-cli/config/webpack.common.js | 3 +- web/server/vue-cli/src/main.js | 2 + web/server/vue-cli/src/views/Reports.vue | 393 +++++++++++++++++++- 3 files changed, 395 insertions(+), 3 deletions(-) diff --git a/web/server/vue-cli/config/webpack.common.js b/web/server/vue-cli/config/webpack.common.js index c2e09e6fdf..5c7e51afee 100644 --- a/web/server/vue-cli/config/webpack.common.js +++ b/web/server/vue-cli/config/webpack.common.js @@ -62,7 +62,8 @@ module.exports = { '@cc/report-server-types': join('codechecker-api', 'lib', 'report_server_types.js'), '@cc/shared-types': join('codechecker-api', 'lib', 'codechecker_api_shared_types.js'), 'thrift': join('thrift', 'lib', 'nodejs', 'lib', 'thrift', 'browser.js'), - 'Vuetify': join('vuetify', 'lib', 'components') + 'Vuetify': join('vuetify', 'lib', 'components'), + 'vue$': join('vue', 'dist', 'vue.esm.js') } }, module: { diff --git a/web/server/vue-cli/src/main.js b/web/server/vue-cli/src/main.js index 86b593e7b1..fde92d51ed 100644 --- a/web/server/vue-cli/src/main.js +++ b/web/server/vue-cli/src/main.js @@ -41,6 +41,8 @@ import { eventHub } from "@cc-api"; Vue.config.productionTip = false; +Vue.config.runtimeCompiler = true; + let isFirstRouterResolve = true; // Ensure we checked auth before each page load. diff --git a/web/server/vue-cli/src/views/Reports.vue b/web/server/vue-cli/src/views/Reports.vue index 97fcf184f2..c16e87c8de 100644 --- a/web/server/vue-cli/src/views/Reports.vue +++ b/web/server/vue-cli/src/views/Reports.vue @@ -2,6 +2,7 @@ + + {{ viewMode === "table" ? "Tree view" : "Table view" }} + + + + + + + + + + + +
+
+

Old - getRunResults

+ + + + +
+ +
+

New - getFileCounts

+ + + + +
+
+
@@ -170,10 +289,11 @@ From a10e03e5682243a02da89776ccee6cac0b9150cb Mon Sep 17 00:00:00 2001 From: xb058t Date: Mon, 23 Mar 2026 10:48:44 -0700 Subject: [PATCH 02/38] Fixed errors --- web/server/vue-cli/src/views/Reports.vue | 289 ++++++++++++----------- 1 file changed, 147 insertions(+), 142 deletions(-) diff --git a/web/server/vue-cli/src/views/Reports.vue b/web/server/vue-cli/src/views/Reports.vue index c16e87c8de..55deb98c4a 100644 --- a/web/server/vue-cli/src/views/Reports.vue +++ b/web/server/vue-cli/src/views/Reports.vue @@ -189,99 +189,102 @@ /> --> - - - - - -
-
-

Old - getRunResults

- - - - -
- -
-

New - getFileCounts

- - - - -
+ + + + + +
+
+

+ Old - getRunResults +

+ + + +
- + +
+

+ New - getFileCounts +

+ + + + +
+
@@ -333,16 +336,36 @@ import { SetCleanupPlanBtn } from "@/components/Report/CleanupPlan"; // ` // }; -// codechecker analyze enable all + store it + show in tree view DONE -// measure the time between current impelementation and the one with getCheckerCounts DONE -// implementation A and B for thesis documentation DONE +// codechecker analyze enable all + store it +// + show in tree view DONE +// measure the time between current +// impelementation and the one with +// getCheckerCounts DONE +// implementation A and B for thesis +// documentation DONE // getFileCounts(report server.thrift) DONE -// getCheckerCounts(report server.thrift) - if performance with getFileCounts is not good, we can try to implement this and use it instead, but it is needed to add new endpoint to get counts for all checkers and not only total count, so it is going to be filename, checker name -> count DONE -// Introduce new hash in table, in some cases when different checkers report to the same line they may report the same error, so it is needed to group them up -// In cases the reports are different we should do mapping table to a bug type and add that to hash again +// getCheckerCounts(report server.thrift) +// - if performance with getFileCounts is not +// good, we can try to implement this and use +// it instead, but it is needed to add new +// endpoint to get counts for all checkers +// and not only total count, so it is going +// to be filename, checker name -> count DONE +// Introduce new hash in table, in some cases +// when different checkers report to the same +// line they may report the same error, so it +// is needed to group them up +// In cases the reports are different we +// should do mapping table to a bug type and +// add that to hash again // Filename, line, bug type -> new hash -// replace to unique reports button to a drop down to be able to choose report hash and a new hash and then we count and sort, then on the top left corner the number of total reports is going to be completely unique +// replace to unique reports button to a drop +// down to be able to choose report hash and +// a new hash and then we count and sort, +// then on the top left corner the number of +// total reports is going to be completely +// unique const namespace = "report"; export default { @@ -750,36 +773,26 @@ export default { expandedItem.item.sameReports = sameReports; }); }, - loadFileCounts(){ - const start = performance.now(); - ccService.getClient().getFileCounts(this.runIds, this.reportFilter, - this.cmpData, 0, 0, handleThriftError(fileCounts => { - const apiEnd = performance.now(); - console.log("getFileCounts API time:", apiEnd - start, "ms"); - - this.allReportsRunResults = fileCounts || []; - - this.$nextTick(() => { - console.log("getFileCounts FULL time:", performance.now() - start, "ms"); - }); + loadFileCounts() { + ccService.getClient().getFileCounts( + this.runIds, this.reportFilter, + this.cmpData, 0, 0, + handleThriftError(fileCounts => { + this.allReportsRunResults = + fileCounts || []; })); }, loadReportsRunResults() { - const start = performance.now(); - ccService.getClient().getRunResults(this.runIds, 0, 0, this.sortType, - this.reportFilter, this.cmpData, this.getDetails, - handleThriftError(reports => { - const apiEnd = performance.now(); - console.log("getRunResults API time:", apiEnd - start, "ms"); - - this.allReportsRunResults = reports || []; - - this.$nextTick(() => { - console.log("getRunResults FULL time:", performance.now() - start, "ms"); - }); - }) - ); + ccService.getClient().getRunResults( + this.runIds, 0, 0, this.sortType, + this.reportFilter, this.cmpData, + this.getDetails, + handleThriftError(reports => { + this.allReportsRunResults = + reports || []; + }) + ); }, getSortMode() { @@ -890,27 +903,19 @@ export default { }); })); - const t1 = performance.now(); - ccService.getClient().getFileCounts(this.runIds, this.reportFilter, - this.cmpData, 0, 0, handleThriftError(fileCounts => { - console.log("getFileCounts API time:", - performance.now() - t1, "ms"); - console.log("getFileCounts total:", - Object.values(fileCounts || {}) - .reduce((sum, n) => sum + n, 0) - ); - this.allReportsFileCounts = fileCounts; + ccService.getClient().getFileCounts( + this.runIds, this.reportFilter, + this.cmpData, 0, 0, + handleThriftError(fileCounts => { + this.allReportsFileCounts = + fileCounts; })); - - const t2 = performance.now(); + ccService.getClient().getRunResults( this.runIds, 0, 0, sortType, - this.reportFilter, this.cmpData, getDetails, + this.reportFilter, this.cmpData, + getDetails, handleThriftError(reports => { - console.log("getRunResults API time:", - performance.now() - t2, "ms"); - console.log("getRunResults length:", - reports ? reports.length : 0); this.allReportsRunResults = reports; })); From 0122d55c75c789fe15a877a2735f8ece5ed6fd55 Mon Sep 17 00:00:00 2001 From: xb058t Date: Mon, 23 Mar 2026 10:55:35 -0700 Subject: [PATCH 03/38] Comment out Old-getRunResults tree and logic --- web/server/vue-cli/src/views/Reports.vue | 266 +++++++++++------------ 1 file changed, 128 insertions(+), 138 deletions(-) diff --git a/web/server/vue-cli/src/views/Reports.vue b/web/server/vue-cli/src/views/Reports.vue index 55deb98c4a..fa755e0962 100644 --- a/web/server/vue-cli/src/views/Reports.vue +++ b/web/server/vue-cli/src/views/Reports.vue @@ -226,65 +226,55 @@ --> -
+ + + -
-

- Old - getRunResults -

- - - - -
- -
-

- New - getFileCounts -

- - - - -
-
+ + @@ -584,68 +574,67 @@ export default { // return dirs; // }, - formattedDirectoriesForTreeViewRunResults() { - const items = []; - - this.allReportsRunResults.forEach(report => { - const pathParts = report.checkedFile.split("/").slice(0, -1); - let currentLevel = items; - let currentPath = ""; - pathParts.forEach(part => { - if (part === "") return; - - currentPath += "/" + part; - let existingPart = currentLevel.find( - item => item.name === part - ); - if (!existingPart) { - existingPart = { - name: part, - fullPath: currentPath, - children: [], - findings: 0 - }; - currentLevel.push(existingPart); - } - currentLevel = existingPart.children; - }); - - // append filename as a child of the last directory - const fileName = report.checkedFile - .split("/").slice(-1)[0]; - if (fileName) { - const filePath = currentPath + "/" + fileName; - const existingFile = currentLevel.find( - item => item.name === fileName - ); - if (existingFile) { - existingFile.findings += 1; - } else { - currentLevel.push({ - name: fileName, - fullPath: filePath, - children: [], - findings: 1 - }); - } - } - }); - - // count findings for directories - // try replacing with getCheckerCounts if performance is an issue - function countFindings(node) { - if (node.children.length === 0) { - return node.findings; - } else { - node.findings = node.children.reduce((sum, child) => { - return sum + countFindings(child); - }, 0); - return node.findings; - } - } - items.forEach(countFindings); - return items; - }, + // formattedDirectoriesForTreeViewRunResults() { + // const items = []; + // + // this.allReportsRunResults.forEach(report => { + // const pathParts = report.checkedFile + // .split("/").slice(0, -1); + // let currentLevel = items; + // let currentPath = ""; + // pathParts.forEach(part => { + // if (part === "") return; + // + // currentPath += "/" + part; + // let existingPart = currentLevel.find( + // item => item.name === part + // ); + // if (!existingPart) { + // existingPart = { + // name: part, + // fullPath: currentPath, + // children: [], + // findings: 0 + // }; + // currentLevel.push(existingPart); + // } + // currentLevel = existingPart.children; + // }); + // + // const fileName = report.checkedFile + // .split("/").slice(-1)[0]; + // if (fileName) { + // const filePath = currentPath + "/" + fileName; + // const existingFile = currentLevel.find( + // item => item.name === fileName + // ); + // if (existingFile) { + // existingFile.findings += 1; + // } else { + // currentLevel.push({ + // name: fileName, + // fullPath: filePath, + // children: [], + // findings: 1 + // }); + // } + // } + // }); + // + // function countFindings(node) { + // if (node.children.length === 0) { + // return node.findings; + // } else { + // node.findings = node.children.reduce( + // (sum, child) => { + // return sum + countFindings(child); + // }, 0); + // return node.findings; + // } + // } + // items.forEach(countFindings); + // return items; + // }, formattedDirectoriesForTreeViewFileCounts() { const items = []; @@ -778,22 +767,22 @@ export default { this.runIds, this.reportFilter, this.cmpData, 0, 0, handleThriftError(fileCounts => { - this.allReportsRunResults = + this.allReportsFileCounts = fileCounts || []; })); }, - loadReportsRunResults() { - ccService.getClient().getRunResults( - this.runIds, 0, 0, this.sortType, - this.reportFilter, this.cmpData, - this.getDetails, - handleThriftError(reports => { - this.allReportsRunResults = - reports || []; - }) - ); - }, + // loadReportsRunResults() { + // ccService.getClient().getRunResults( + // this.runIds, 0, 0, this.sortType, + // this.reportFilter, this.cmpData, + // this.getDetails, + // handleThriftError(reports => { + // this.allReportsRunResults = + // reports || []; + // }) + // ); + // }, getSortMode() { let type = null; @@ -911,13 +900,14 @@ export default { fileCounts; })); - ccService.getClient().getRunResults( - this.runIds, 0, 0, sortType, - this.reportFilter, this.cmpData, - getDetails, - handleThriftError(reports => { - this.allReportsRunResults = reports; - })); + // ccService.getClient().getRunResults( + // this.runIds, 0, 0, sortType, + // this.reportFilter, this.cmpData, + // getDetails, + // handleThriftError(reports => { + // this.allReportsRunResults = + // reports; + // })); } From 6f0f712bee776a319be265cd0c9da078b8c2f115 Mon Sep 17 00:00:00 2001 From: xb058t Date: Mon, 23 Mar 2026 10:59:43 -0700 Subject: [PATCH 04/38] keeping only neccesary code --- web/server/vue-cli/src/views/Reports.vue | 238 +---------------------- 1 file changed, 1 insertion(+), 237 deletions(-) diff --git a/web/server/vue-cli/src/views/Reports.vue b/web/server/vue-cli/src/views/Reports.vue index fa755e0962..6157c8d8e3 100644 --- a/web/server/vue-cli/src/views/Reports.vue +++ b/web/server/vue-cli/src/views/Reports.vue @@ -175,85 +175,6 @@ - - - - - - - - - - - -//
  • -// {{ name }} : {{ findings }} findings -//
  • -//
    -// -//
    -// -// ` -// }; - -// codechecker analyze enable all + store it -// + show in tree view DONE -// measure the time between current -// impelementation and the one with -// getCheckerCounts DONE -// implementation A and B for thesis -// documentation DONE -// getFileCounts(report server.thrift) DONE - -// getCheckerCounts(report server.thrift) -// - if performance with getFileCounts is not -// good, we can try to implement this and use -// it instead, but it is needed to add new -// endpoint to get counts for all checkers -// and not only total count, so it is going -// to be filename, checker name -> count DONE -// Introduce new hash in table, in some cases -// when different checkers report to the same -// line they may report the same error, so it -// is needed to group them up -// In cases the reports are different we -// should do mapping table to a bug type and -// add that to hash again -// Filename, line, bug type -> new hash -// replace to unique reports button to a drop -// down to be able to choose report hash and -// a new hash and then we count and sort, -// then on the top left corner the number of -// total reports is going to be completely -// unique const namespace = "report"; export default { @@ -385,7 +251,6 @@ export default { return { viewMode: "table", - // directories: [], headers: [ { text: "", @@ -460,7 +325,6 @@ export default { } ], reports: [], - allReportsRunResults: [], allReportsFileCounts: [], sameReports: {}, hasTimeStamp: true, @@ -557,85 +421,6 @@ export default { }); }, - // formattedDirectories() { - // const dirs = {}; - // this.reports.forEach(report => { - // const pathParts = report.checkedFile.split("/") - // .slice(0, -1); - // let currentDir = dirs; - // pathParts.forEach(part => { - // if (part === "") return; - // if (!currentDir[part]) { - // currentDir[part] = {}; - // } - // currentDir = currentDir[part]; - // }); - // }); - // return dirs; - // }, - - // formattedDirectoriesForTreeViewRunResults() { - // const items = []; - // - // this.allReportsRunResults.forEach(report => { - // const pathParts = report.checkedFile - // .split("/").slice(0, -1); - // let currentLevel = items; - // let currentPath = ""; - // pathParts.forEach(part => { - // if (part === "") return; - // - // currentPath += "/" + part; - // let existingPart = currentLevel.find( - // item => item.name === part - // ); - // if (!existingPart) { - // existingPart = { - // name: part, - // fullPath: currentPath, - // children: [], - // findings: 0 - // }; - // currentLevel.push(existingPart); - // } - // currentLevel = existingPart.children; - // }); - // - // const fileName = report.checkedFile - // .split("/").slice(-1)[0]; - // if (fileName) { - // const filePath = currentPath + "/" + fileName; - // const existingFile = currentLevel.find( - // item => item.name === fileName - // ); - // if (existingFile) { - // existingFile.findings += 1; - // } else { - // currentLevel.push({ - // name: fileName, - // fullPath: filePath, - // children: [], - // findings: 1 - // }); - // } - // } - // }); - // - // function countFindings(node) { - // if (node.children.length === 0) { - // return node.findings; - // } else { - // node.findings = node.children.reduce( - // (sum, child) => { - // return sum + countFindings(child); - // }, 0); - // return node.findings; - // } - // } - // items.forEach(countFindings); - // return items; - // }, - formattedDirectoriesForTreeViewFileCounts() { const items = []; @@ -772,18 +557,6 @@ export default { })); }, - // loadReportsRunResults() { - // ccService.getClient().getRunResults( - // this.runIds, 0, 0, this.sortType, - // this.reportFilter, this.cmpData, - // this.getDetails, - // handleThriftError(reports => { - // this.allReportsRunResults = - // reports || []; - // }) - // ); - // }, - getSortMode() { let type = null; switch (this.pagination.sortBy[0]) { @@ -900,18 +673,9 @@ export default { fileCounts; })); - // ccService.getClient().getRunResults( - // this.runIds, 0, 0, sortType, - // this.reportFilter, this.cmpData, - // getDetails, - // handleThriftError(reports => { - // this.allReportsRunResults = - // reports; - // })); - } - + } }; From 7284e0c29ace6eee64752dc8d4caa6145607d1c8 Mon Sep 17 00:00:00 2001 From: xb058t Date: Thu, 26 Mar 2026 09:57:01 +0100 Subject: [PATCH 05/38] adding tables into tree view --- web/server/vue-cli/src/views/Reports.vue | 335 ++++++++++++++++++----- 1 file changed, 262 insertions(+), 73 deletions(-) diff --git a/web/server/vue-cli/src/views/Reports.vue b/web/server/vue-cli/src/views/Reports.vue index 6157c8d8e3..a8d99784f2 100644 --- a/web/server/vue-cli/src/views/Reports.vue +++ b/web/server/vue-cli/src/views/Reports.vue @@ -175,27 +175,113 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + mdi-cursor-default-click + +
    Select a file from the tree to view its reports
    +
    +
    +
    +
    +
    @@ -206,7 +292,14 @@ import { Pane, Splitpanes } from "splitpanes"; import { mapGetters, mapMutations } from "vuex"; import { ccService, handleThriftError } from "@cc-api"; -import { Checker, Order, SortMode, SortType } from "@cc/report-server-types"; +import { + Checker, + MAX_QUERY_SIZE, + Order, + SortMode, + SortType, + ReportFilter as ThriftReportFilter +} from "@cc/report-server-types"; import { SET_REPORT_FILTER } from "@/store/mutations.type"; import { FillHeight } from "@/directives"; @@ -349,7 +442,11 @@ export default { initalized: false, checkerDocDialog: false, selectedChecker: null, - expanded: [] + expanded: [], + treeItems: [], + treeSelectedFile: null, + treeReports: [], + treeReportsLoading: false }; }, @@ -421,9 +518,121 @@ export default { }); }, - formattedDirectoriesForTreeViewFileCounts() { + treeTableHeaders() { + return [ + { + text: "Line", + value: "checkedFile", + sortable: false + }, + { + text: "Message", + value: "checkerMsg", + sortable: false + }, + { + text: "Checker name", + value: "checkerId", + sortable: false + }, + { + text: "Analyzer", + value: "analyzerName", + align: "center", + sortable: false + }, + { + text: "Severity", + value: "severity", + sortable: false + }, + { + text: "Bug path length", + value: "bugPathLength", + align: "center", + sortable: false + }, + { + text: "Latest review status", + value: "reviewData", + align: "center", + sortable: false + }, + { + text: "Latest detection status", + value: "detectionStatus", + align: "center", + sortable: false + } + ]; + }, + + formattedTreeReports() { + return this.treeReports.map((report, idx) => { + const detectionStatus = + this.detectionStatusFromCodeToString(report.detectionStatus); + const detectedAt = report.detectedAt + ? this.$options.filters.prettifyDate(report.detectedAt) : null; + const fixedAt = report.fixedAt + ? this.$options.filters.prettifyDate(report.fixedAt) : null; + + const detectionStatusTitle = [ + `Status: ${detectionStatus}`, + ...(detectedAt ? [ `Detected at: ${detectedAt}` ] : []), + ...(fixedAt ? [ `Fixed at: ${fixedAt}` ] : []) + ].join("\n"); + + const reportId = report.reportId + ? report.reportId.toString() : String(idx); + + return { + ...report, + "$detectionStatusTitle": detectionStatusTitle, + "$id": reportId + report.bugHash + }; + }); + }, + + }, + + watch: { + pagination: { + handler() { + this.updateUrl(); + if (this.initalized) { + this.fetchReports(); + } + }, + deep: true + }, + formattedReports: { + handler() { + this.hasTimeStamp = + this.formattedReports.some(report => report.timestamp); + + this.hasTestCase = + this.formattedReports.some(report => report.testcase); + + this.hasChronologicalOrder = + this.formattedReports.some(report => report["chronological_order"]); + } + }, + allReportsFileCounts: { + handler() { + this.buildTreeItems(); + }, + deep: true + } + }, + + methods: { + ...mapMutations(namespace, { + setReportFilter: SET_REPORT_FILTER + }), + + buildTreeItems() { const items = []; - + Object.entries( this.allReportsFileCounts || {} ).forEach(([ filePath, count ]) => { @@ -443,7 +652,8 @@ export default { name: part, fullPath: currentPath, children: [], - findings: 0 + findings: 0, + isDirectory: true }; currentLevel.push(existingPart); } @@ -482,61 +692,33 @@ export default { } } items.forEach(countFindings); - return items; - }, - - - }, - - watch: { - pagination: { - handler() { - this.updateUrl(); - if (this.initalized) { - this.fetchReports(); - } - }, - deep: true + this.treeItems = items; }, - formattedReports: { - handler() { - this.hasTimeStamp = - this.formattedReports.some(report => report.timestamp); - - this.hasTestCase = - this.formattedReports.some(report => report.testcase); - this.hasChronologicalOrder = - this.formattedReports.some(report => report["chronological_order"]); + onTreeFileClick(activeItems) { + if (!activeItems || activeItems.length === 0) { + this.treeSelectedFile = null; + this.treeReports = []; + return; } - } - }, - methods: { - ...mapMutations(namespace, { - setReportFilter: SET_REPORT_FILTER - }), + const item = activeItems[0]; + if (!item || item.isDirectory) return; - onTreeFileClick(activeItems) { - // activeItems is an array of item-key values (fullPath) - if (!activeItems || activeItems.length === 0) return; + this.treeSelectedFile = item.fullPath; + this.treeReportsLoading = true; - const filePath = activeItems[0]; - if (!filePath) return; + const filter = new ThriftReportFilter(this.reportFilter); + filter.filepath = [ item.fullPath ]; - // Find the FilePathFilter instance inside ReportFilter - // and call its setSelectedItems to select this file. - const filters = this.$refs.reportFilter.$refs.filters; - const filePathFilter = filters.find( - f => f.id === "filepath" + ccService.getClient().getRunResults( + this.runIds, MAX_QUERY_SIZE, 0, [], + filter, this.cmpData, false, + handleThriftError(reports => { + this.treeReports = reports; + this.treeReportsLoading = false; + }) ); - if (filePathFilter) { - filePathFilter.setSelectedItems([ - { id: filePath, title: filePath, count: "N/A" } - ]); - } - - this.viewMode = "table"; }, itemExpanded(expandedItem) { @@ -547,6 +729,7 @@ export default { expandedItem.item.sameReports = sameReports; }); }, + loadFileCounts() { ccService.getClient().getFileCounts( this.runIds, this.reportFilter, @@ -698,4 +881,10 @@ export default { cursor: pointer; } } + +.tree-split { + .splitpanes__pane { + background-color: inherit; + } +} From 4e9c279701d0fbaee1bbcb1fec337a1fd906f3ef Mon Sep 17 00:00:00 2001 From: xb058t Date: Thu, 26 Mar 2026 23:29:32 +0100 Subject: [PATCH 06/38] added severity and tickbox --- web/server/vue-cli/src/views/Reports.vue | 173 +++++++++++++++++++++-- 1 file changed, 162 insertions(+), 11 deletions(-) diff --git a/web/server/vue-cli/src/views/Reports.vue b/web/server/vue-cli/src/views/Reports.vue index a8d99784f2..ee17fe87f1 100644 --- a/web/server/vue-cli/src/views/Reports.vue +++ b/web/server/vue-cli/src/views/Reports.vue @@ -176,24 +176,44 @@ - + + @@ -316,12 +268,10 @@ import { mapGetters, mapMutations } from "vuex"; import { ccService, handleThriftError } from "@cc-api"; import { Checker, - MAX_QUERY_SIZE, Order, Severity, SortMode, - SortType, - ReportFilter as ThriftReportFilter + SortType } from "@cc/report-server-types"; import { SET_REPORT_FILTER } from "@/store/mutations.type"; @@ -441,7 +391,7 @@ export default { } ], reports: [], - allReportsFileCounts: [], + allReportsFileCounts: {}, sameReports: {}, hasTimeStamp: true, hasTestCase : true, @@ -467,11 +417,8 @@ export default { selectedChecker: null, expanded: [], treeItems: [], - treeSelectedFile: null, - treeReports: [], - treeReportsLoading: false, fileSeverities: {}, - treeSelection: [] + Severity: Severity }; }, @@ -543,81 +490,6 @@ export default { }); }, - treeTableHeaders() { - return [ - { - text: "Line", - value: "checkedFile", - sortable: false - }, - { - text: "Message", - value: "checkerMsg", - sortable: false - }, - { - text: "Checker name", - value: "checkerId", - sortable: false - }, - { - text: "Analyzer", - value: "analyzerName", - align: "center", - sortable: false - }, - { - text: "Severity", - value: "severity", - sortable: false - }, - { - text: "Bug path length", - value: "bugPathLength", - align: "center", - sortable: false - }, - { - text: "Latest review status", - value: "reviewData", - align: "center", - sortable: false - }, - { - text: "Latest detection status", - value: "detectionStatus", - align: "center", - sortable: false - } - ]; - }, - - formattedTreeReports() { - return this.treeReports.map((report, idx) => { - const detectionStatus = - this.detectionStatusFromCodeToString(report.detectionStatus); - const detectedAt = report.detectedAt - ? this.$options.filters.prettifyDate(report.detectedAt) : null; - const fixedAt = report.fixedAt - ? this.$options.filters.prettifyDate(report.fixedAt) : null; - - const detectionStatusTitle = [ - `Status: ${detectionStatus}`, - ...(detectedAt ? [ `Detected at: ${detectedAt}` ] : []), - ...(fixedAt ? [ `Fixed at: ${fixedAt}` ] : []) - ].join("\n"); - - const reportId = report.reportId - ? report.reportId.toString() : String(idx); - - return { - ...report, - "$detectionStatusTitle": detectionStatusTitle, - "$id": reportId + report.bugHash - }; - }); - }, - }, watch: { @@ -645,7 +517,6 @@ export default { allReportsFileCounts: { handler() { this.buildTreeItems(); - this.fetchFileSeverities(); }, deep: true } @@ -679,7 +550,7 @@ export default { fullPath: currentPath, children: [], findings: 0, - severities: [] + stats: {} }; currentLevel.push(existingPart); } @@ -688,28 +559,27 @@ export default { // append filename as a child of the last directory const fileName = filePath.split("/").slice(-1)[0]; - const sevs = this.fileSeverities[filePath] || []; + const fileStats = this.fileSeverities[filePath] || {}; if (fileName) { const existingFile = currentLevel.find( item => item.name === fileName ); if (existingFile) { existingFile.findings += count; - existingFile.severities = sevs; + existingFile.stats = fileStats; } else { currentLevel.push({ name: fileName, fullPath: filePath, children: [], findings: count, - severities: sevs + stats: fileStats }); } } }); - // count findings for directories - // try replacing with getCheckerCounts if performance is an issue + // count findings and aggregate stats for directories function countFindings(node) { if (node.children.length === 0) { return node.findings; @@ -717,17 +587,13 @@ export default { node.findings = node.children.reduce((sum, child) => { return sum + countFindings(child); }, 0); - const sevMap = {}; + const merged = {}; node.children.forEach(child => { - (child.severities || []).forEach(s => { - if (!sevMap[s.id]) { - sevMap[s.id] = { id: s.id, count: 0 }; - } - sevMap[s.id].count += s.count; + Object.keys(child.stats || {}).forEach(k => { + merged[k] = (merged[k] || 0) + child.stats[k]; }); }); - node.severities = Object.values(sevMap) - .sort((a, b) => b.id - a.id); + node.stats = merged; return node.findings; } } @@ -735,35 +601,6 @@ export default { this.treeItems = items; }, - onTreeFileClick(activeItems) { - if (!activeItems || activeItems.length === 0) { - this.treeSelectedFile = null; - this.treeReports = []; - return; - } - - const fullPath = activeItems[0]; - if (!fullPath) return; - - const isDir = this.isDirectory(fullPath); - const filterPath = isDir ? fullPath + "/*" : fullPath; - - this.treeSelectedFile = fullPath; - this.treeReportsLoading = true; - - const filter = new ThriftReportFilter(this.reportFilter); - filter.filepath = [ filterPath ]; - - ccService.getClient().getRunResults( - this.runIds, MAX_QUERY_SIZE, 0, [], - filter, this.cmpData, false, - handleThriftError(reports => { - this.treeReports = reports; - this.treeReportsLoading = false; - }) - ); - }, - itemExpanded(expandedItem) { if (expandedItem.item.sameReports) return; @@ -773,118 +610,72 @@ export default { }); }, - onTreeSelectionChange(selectedPaths) { - if (!selectedPaths || !selectedPaths.length) { - this.treeSelectedFile = null; - this.treeReports = []; - return; - } - - const paths = selectedPaths.map(fp => { - const isDir = this.isDirectory(fp); - return isDir ? fp + "/*" : fp; - }); - - this.treeSelectedFile = selectedPaths.join(", "); - this.treeReportsLoading = true; - - const filter = new ThriftReportFilter(this.reportFilter); - filter.filepath = paths; - - ccService.getClient().getRunResults( - this.runIds, MAX_QUERY_SIZE, 0, [], - filter, this.cmpData, false, - handleThriftError(reports => { - this.treeReports = reports; - this.treeReportsLoading = false; - }) - ); - }, - - toggleTreeItem(item) { - const idx = this.treeSelection.indexOf( - item.fullPath - ); - if (idx === -1) { - this.treeSelection = - [ ...this.treeSelection, item.fullPath ]; - } else { - this.treeSelection = - this.treeSelection.filter( - p => p !== item.fullPath - ); - } - this.onTreeSelectionChange(this.treeSelection); - }, - - isDirectory(fullPath) { - const find = nodes => { - for (const n of nodes) { - if (n.fullPath === fullPath) { - return n.children.length > 0; - } - if (n.children.length) { - const r = find(n.children); - if (r !== null) return r; - } + i64ToNum(val) { + if (val == null) return 0; + if (typeof val === "number") return val; + if (typeof val.toNumber === "function") + return val.toNumber(); + if (val.buffer) { + let n = 0; + for (let i = 0; i < val.buffer.length; i++) { + n = n * 256 + val.buffer[i]; } - return null; - }; - return !!find(this.treeItems); + return n; + } + return Number(val) || 0; }, fetchFileSeverities() { - const files = Object.keys( - this.allReportsFileCounts || {} - ); - if (!files.length) return; - - const sevMap = {}; - let pending = files.length; - - files.forEach(filePath => { - const filter = new ThriftReportFilter( - this.reportFilter - ); - filter.filepath = [ filePath ]; - filter.severity = null; + const PAGE = 500; + const allStats = {}; - ccService.getClient().getSeverityCounts( - this.runIds, filter, this.cmpData, + const fetchPage = offset => { + ccService.getClient().getFileCountsSummary( + this.runIds, this.reportFilter, + this.cmpData, PAGE, offset, handleThriftError(res => { - const sevs = []; - Object.keys(Severity).forEach(s => { - const id = Severity[s]; - const cnt = res[id]; - if (cnt) { - const n = cnt.toNumber - ? cnt.toNumber() : cnt; - if (n > 0) { - sevs.push({ id: id, count: n }); + const keys = Object.keys(res || {}); + + keys.forEach(filePath => { + const summary = res[filePath]; + const stats = {}; + + Object.keys(summary || {}).forEach(key => { + const n = this.i64ToNum(summary[key]); + if (!n) return; + + if (key === "reports") { + stats.reports = n; + } else if (key.startsWith("severity:")) { + const name = key.substring(9).toLowerCase(); + stats[name] = (stats[name] || 0) + n; + } else if (key.startsWith("review_status:")) { + const name = key.substring(14); + stats[name] = (stats[name] || 0) + n; } - } + }); + + allStats[filePath] = stats; }); - sevs.sort((a, b) => b.id - a.id); - sevMap[filePath] = sevs; - - pending--; - if (pending === 0) { - this.fileSeverities = - Object.assign({}, sevMap); - this.buildTreeItems(); + + if (keys.length >= PAGE) { + fetchPage(offset + PAGE); + } else { + this.fileSeverities = Object.assign({}, allStats); + + // Build allReportsFileCounts from the summary + // so the tree doesn't depend on getFileCounts. + const fileCounts = {}; + Object.keys(allStats).forEach(fp => { + fileCounts[fp] = allStats[fp].reports || 0; + }); + this.allReportsFileCounts = fileCounts; } - })); - }); - }, + }) + ); + }; - loadFileCounts() { - ccService.getClient().getFileCounts( - this.runIds, this.reportFilter, - this.cmpData, 0, 0, - handleThriftError(fileCounts => { - this.allReportsFileCounts = - fileCounts || []; - })); + fetchPage(0); }, getSortMode() { @@ -995,13 +786,7 @@ export default { }); })); - ccService.getClient().getFileCounts( - this.runIds, this.reportFilter, - this.cmpData, 0, 0, - handleThriftError(fileCounts => { - this.allReportsFileCounts = - fileCounts; - })); + this.fetchFileSeverities(); } @@ -1029,13 +814,56 @@ export default { } } -.tree-split { - .splitpanes__pane { - background-color: inherit; - } +.tree-view-container { + position: relative; + overflow-y: auto; + height: calc(100vh - 150px); +} + +.tree-header { + position: sticky; + top: 0; + z-index: 2; + background: white; + display: flex; + align-items: center; + padding: 4px 8px 4px 40px; + font-size: 0.75em; + font-weight: bold; + border-bottom: 1px solid rgba(0, 0, 0, 0.12); +} + +.tree-header-name { + flex: 1; + min-width: 200px; +} + +.tree-header-cell { + width: 50px; + text-align: center; + flex-shrink: 0; +} + +.tree-row { + display: flex; + align-items: center; + width: 100%; } .tree-item-label { + flex: 1; + min-width: 200px; font-size: 0.85em; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.tree-stat-cell { + width: 50px; + text-align: center; + flex-shrink: 0; + font-size: 0.8em; } + From 1811e89efa93857e9f3173d576bf53610c96d143 Mon Sep 17 00:00:00 2001 From: xb058t Date: Mon, 30 Mar 2026 18:51:00 +0200 Subject: [PATCH 09/38] on tree click --- web/server/vue-cli/src/views/Reports.vue | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/web/server/vue-cli/src/views/Reports.vue b/web/server/vue-cli/src/views/Reports.vue index bc1111655c..c3f756ffb0 100644 --- a/web/server/vue-cli/src/views/Reports.vue +++ b/web/server/vue-cli/src/views/Reports.vue @@ -223,7 +223,10 @@