@preventive/triage 1.0.0-alpha.52 → 1.0.0-alpha.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/common/managed/finding-detail.d.ts +4 -2
- package/common/managed/finding-detail.js +26 -14
- package/common/managed/team-app.d.ts +2 -0
- package/common/managed/team-app.js +32 -3
- package/common/workspace-app.js +26 -6
- package/out/activity.js +2 -2
- package/out/{chunk-NCN3M5EP.js → chunk-JEFZTVNX.js} +1 -1
- package/out/{chunk-IMM274IW.js → chunk-NE2UGKRJ.js} +1 -1
- package/out/{chunk-OGAYMEER.js → chunk-TSIFIFBX.js} +41 -41
- package/out/{chunk-CHRBUGVN.js → chunk-YA4ETCFU.js} +1 -1
- package/out/client-managed.js +1 -1
- package/out/compare.js +1 -1
- package/out/dialogs.js +1 -1
- package/out/index.html +2 -2
- package/out/scan.js +1 -1
- package/out/view.js +1 -1
- package/package.json +2 -1
- package/server-managed/history-windows.ts +172 -0
- package/server-managed/repository-history.ts +2 -2
- package/server-managed/repository-pulse.ts +11 -2
- package/server-managed/team-reports.ts +7 -6
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export const FINDING_DETAIL_FIELDS: readonly string[]
|
|
2
|
-
export function liteReportData(data: unknown, options?: { fold?: boolean }): unknown
|
|
3
|
-
export function liteWorkspace<T extends { filename: string; data: unknown }>(reports: readonly T[], links: readonly (readonly string[])[]): {
|
|
2
|
+
export function liteReportData(data: unknown, options?: { fold?: boolean; shown?: ReadonlySet<string> | null }): unknown
|
|
3
|
+
export function liteWorkspace<T extends { filename: string; data: unknown }>(reports: readonly T[], links: readonly (readonly string[])[]): {
|
|
4
|
+
reports: T[]; folded: false | 'rows' | 'app'; lens: { drop: boolean; detail: boolean; upstream: boolean } | null
|
|
5
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { findingTitle, reportEntries, revalidateKindOf } from '@preventive/report'
|
|
2
|
-
import { managedTeamRevalidationConflicted } from './team-app.js'
|
|
2
|
+
import { managedTeamAppRows, managedTeamLens, managedTeamRevalidationConflicted } from './team-app.js'
|
|
3
3
|
import { liteName } from './lite-name.js'
|
|
4
4
|
|
|
5
5
|
// A finding's detail: what its popup, the views besides the board and an
|
|
@@ -8,7 +8,8 @@ import { liteName } from './lite-name.js'
|
|
|
8
8
|
// and an export tell (write-md-finding.js PLAIN_FIELDS, CODE_FIELDS, but for
|
|
9
9
|
// `priority`, which the board sorts and filters by), and the run meta its
|
|
10
10
|
// card's run line and a report's header read (its `model` and `type`, which
|
|
11
|
-
// the board's filters read, stay)
|
|
11
|
+
// the board's filters read, stay), and the link the report gave its location
|
|
12
|
+
// (`location`), which its card does not draw. A team's
|
|
12
13
|
// reports are first sent without it (server-managed/team-reports.ts), the
|
|
13
14
|
// board drawn from them, and their findings filled in from the whole reports
|
|
14
15
|
// (ui/view/managed-details.js).
|
|
@@ -16,7 +17,7 @@ export const FINDING_DETAIL_FIELDS = Object.freeze([
|
|
|
16
17
|
'description', 'evidence', 'impact', 'reproduction', 'recommendation', 'confidenceReason', 'revalidateVerdict', 'revalidateRecommendation',
|
|
17
18
|
'fileHash', 'exportName', 'methodName',
|
|
18
19
|
'severityReason', 'commitHash', 'auditedCommit', 'detectedAt', 'committedAt', 'dateCreated', 'status', 'branch', 'pocStatus', 'parent', 'slug', 'reportPath',
|
|
19
|
-
'category', 'discoveredIn', 'effort', 'exportsMode',
|
|
20
|
+
'category', 'discoveredIn', 'effort', 'exportsMode', 'location',
|
|
20
21
|
])
|
|
21
22
|
const DETAIL = new Set(FINDING_DETAIL_FIELDS)
|
|
22
23
|
// A finding without its detail. One without a title of its own is named by its
|
|
@@ -37,14 +38,20 @@ const isPass = finding => revalidateKindOf(finding) === 'revalidation'
|
|
|
37
38
|
// App mode shows of an entry holding the revalidation pass's row that row
|
|
38
39
|
// alone (ui/view/group.js foldedTabs): with `fold`, such an entry is sent as
|
|
39
40
|
// the pass's rows, the rows beneath them left for the whole reports
|
|
40
|
-
// (ui/view/managed-details.js).
|
|
41
|
-
|
|
41
|
+
// (ui/view/managed-details.js). With `shown`, the IDs of the findings one
|
|
42
|
+
// whose App view locks Confirmed shows (common/workspace-app.js
|
|
43
|
+
// workspaceAppRows), those alone are sent.
|
|
44
|
+
export function liteReportData(data, { fold = false, shown = null } = {}) {
|
|
42
45
|
const key = Array.isArray(data?.findings) ? 'findings' : Array.isArray(data?.groups) ? 'groups' : null
|
|
43
46
|
if (!key) return data
|
|
47
|
+
if (shown) {
|
|
48
|
+
const kept = entry => (Array.isArray(entry) ? entry : [entry]).filter(finding => shown.has(finding?.id))
|
|
49
|
+
return { ...data, [key]: data[key].map(kept).filter(entry => entry.length > 0).map(entry => entry.map(liteFinding)) }
|
|
50
|
+
}
|
|
44
51
|
const entries = data[key].map(entry => {
|
|
45
52
|
if (!Array.isArray(entry)) return liteFinding(entry)
|
|
46
|
-
const
|
|
47
|
-
return
|
|
53
|
+
const folded = fold && entry.length > 1 && entry.some(isPass) ? entry.filter(isPass) : entry
|
|
54
|
+
return folded.map(liteFinding)
|
|
48
55
|
})
|
|
49
56
|
return { ...data, [key]: entries }
|
|
50
57
|
}
|
|
@@ -52,12 +59,17 @@ export function liteReportData(data, { fold = false } = {}) {
|
|
|
52
59
|
const rows = data => (reportEntries(data) ?? []).flat().length
|
|
53
60
|
|
|
54
61
|
// A team's reports as its board is first sent them (server-managed/team-reports.ts),
|
|
55
|
-
// each `liteReportData
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
62
|
+
// each `liteReportData`: where the workspace's App view locks Confirmed, only
|
|
63
|
+
// the rows that view shows (`folded: 'app'`); else, where nothing in it
|
|
64
|
+
// contradicts the revalidation pass, which opens a view of it in App mode
|
|
65
|
+
// (ui/view/render.js configureReportRevalidation), its entries folded under
|
|
66
|
+
// their App rows (`folded: 'rows'`). Folded, the `lens` the rows left out
|
|
67
|
+
// tell (team-app.js managedTeamLens).
|
|
59
68
|
export function liteWorkspace(reports, links) {
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
|
|
69
|
+
const view = Object.assign(reports.map(({ filename, data }) => ({ filename, data })), { links })
|
|
70
|
+
const shown = managedTeamAppRows(view)
|
|
71
|
+
const fold = shown !== null || !managedTeamRevalidationConflicted(view)
|
|
72
|
+
const lite = reports.map(report => ({ ...report, data: liteReportData(report.data, { fold, shown }) }))
|
|
73
|
+
if (!fold || !lite.some((report, index) => rows(report.data) < rows(reports[index].data))) return { reports: lite, folded: false, lens: null }
|
|
74
|
+
return { reports: lite, folded: shown ? 'app' : 'rows', lens: managedTeamLens(view) }
|
|
63
75
|
}
|
|
@@ -4,3 +4,5 @@ export type TeamAppMetadata = { appMode: true; appFindings: number } | { appMode
|
|
|
4
4
|
export function managedTeamAppMetadata(workspace: readonly { filename: string; data: unknown }[] & { links?: readonly (readonly string[])[] },
|
|
5
5
|
options?: { countFindings?: boolean }): TeamAppMetadata
|
|
6
6
|
export function managedTeamRevalidationConflicted(workspace: readonly { filename: string; data: unknown }[] & { links?: readonly (readonly string[])[] }): boolean
|
|
7
|
+
export function managedTeamAppRows(workspace: readonly { filename: string; data: unknown }[] & { links?: readonly (readonly string[])[] }): Set<string> | null
|
|
8
|
+
export function managedTeamLens(workspace: readonly { filename: string; data: unknown }[] & { links?: readonly (readonly string[])[] }): { drop: boolean; detail: boolean; upstream: boolean }
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { inheritReportMeta, isAppFinding, reportEntries } from '@preventive/report'
|
|
1
|
+
import { inheritReportMeta, isAppFinding, reportEntries, revalidateKindOf } from '@preventive/report'
|
|
2
2
|
import { collectDuplicates } from '../../client/linked-findings.js'
|
|
3
|
+
import { dependencyDirectory, isDependencyFile } from '../../client/dependency-paths.js'
|
|
3
4
|
import { hasRevalidateStamp } from '../finding-filters.js'
|
|
4
|
-
import { workspaceAppMetadata } from '../workspace-app.js'
|
|
5
|
+
import { workspaceAppMetadata, workspaceAppRows } from '../workspace-app.js'
|
|
5
6
|
import { mergeReportGroups } from '../workspace-groups.js'
|
|
6
7
|
|
|
7
8
|
// The same classification as local workspaces, using only the complete,
|
|
@@ -22,6 +23,34 @@ export function managedTeamRevalidationConflicted(workspace) {
|
|
|
22
23
|
return mergeReportGroups(viewReports(workspace).reports).conflicts.size > 0
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
// The IDs of the findings the workspace's basic App view can show
|
|
27
|
+
// (common/workspace-app.js workspaceAppRows), or null.
|
|
28
|
+
export function managedTeamAppRows(workspace) {
|
|
29
|
+
const { reports, duplicates } = viewReports(workspace)
|
|
30
|
+
return workspaceAppRows(reports, id => [...duplicates.get(id) ?? []])
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const RULED_OUT = new Set(['refuted', 'unreachable'])
|
|
34
|
+
|
|
35
|
+
// What a view of the whole workspace offers beside the App view
|
|
36
|
+
// (ui/view/render.js), told by all its rows: whether the revalidation layer
|
|
37
|
+
// can come off (`drop`), whether its detail stop has rows to unfold
|
|
38
|
+
// (`detail`), and whether an upstream lens has ground (`upstream`).
|
|
39
|
+
export function managedTeamLens(workspace) {
|
|
40
|
+
const { reports } = viewReports(workspace)
|
|
41
|
+
const rows = reports.flatMap(report => report.groups.flat())
|
|
42
|
+
const directory = dependencyDirectory(reports)
|
|
43
|
+
const detail = mergeReportGroups(reports).groups.some(group => {
|
|
44
|
+
const kinds = group.map(revalidateKindOf)
|
|
45
|
+
return kinds.includes('partial') || kinds.includes('revalidation') && kinds.some(kind => kind !== 'revalidation' && !RULED_OUT.has(kind))
|
|
46
|
+
})
|
|
47
|
+
return {
|
|
48
|
+
drop: rows.some(hasRevalidateStamp),
|
|
49
|
+
detail,
|
|
50
|
+
upstream: rows.some(f => f.isUpstream === true || f.isApp === false && !('isUpstream' in f) && isDependencyFile(f.file, directory)),
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
25
54
|
// The workspace's reports as a view holds them (ui/view/ingest.js), with the
|
|
26
55
|
// duplicates its links name.
|
|
27
56
|
function viewReports(workspace) {
|
|
@@ -41,7 +70,7 @@ function viewReports(workspace) {
|
|
|
41
70
|
finding._sourcePass = judged.has(finding._source)
|
|
42
71
|
return finding
|
|
43
72
|
}))
|
|
44
|
-
reports.push({ fileName: filename, groups })
|
|
73
|
+
reports.push({ fileName: filename, groups, treeDependencies: data.treeDependencies ?? null })
|
|
45
74
|
}
|
|
46
75
|
return { reports, duplicates }
|
|
47
76
|
}
|
package/common/workspace-app.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { revalidateKindOf } from '@preventive/report'
|
|
2
|
-
import { canLockConfirmed } from './finding-filters.js'
|
|
2
|
+
import { activeRevalidateKinds, canLockConfirmed } from './finding-filters.js'
|
|
3
3
|
import { mergeReportGroups } from './workspace-groups.js'
|
|
4
4
|
import { mergeLinkedWorkspaceGroups } from './linked-workspace-groups.js'
|
|
5
5
|
|
|
@@ -27,13 +27,33 @@ function onlyUnjudgedImports(groups) {
|
|
|
27
27
|
// `countFindings` has a workspace that is not App count its rows too, after
|
|
28
28
|
// the same merging, for a caller that shows it collapsed (managed teams).
|
|
29
29
|
export function workspaceAppMetadata(reports, duplicatesOf = () => [], { countFindings = false } = {}) {
|
|
30
|
-
const {
|
|
31
|
-
|
|
32
|
-
const notApp = countFindings ? { appMode: false, findings: linked.length } : { appMode: false }
|
|
33
|
-
if (conflicts.size > 0) return notApp
|
|
34
|
-
if (!onlyUnjudgedImports(linked) && !canLockConfirmed(linked, { tabs: appTabs, kindOf: revalidateKindOf, severityMode: 'corrected' })) return notApp
|
|
30
|
+
const { linked, app } = appView(reports, duplicatesOf)
|
|
31
|
+
if (!app) return countFindings ? { appMode: false, findings: linked.length } : { appMode: false }
|
|
35
32
|
return {
|
|
36
33
|
appMode: true,
|
|
37
34
|
appFindings: linked.filter((group) => appTabs(group).some((f) => f.isApp)).length,
|
|
38
35
|
}
|
|
39
36
|
}
|
|
37
|
+
|
|
38
|
+
// The IDs of the findings a workspace's basic App view can show: of each row
|
|
39
|
+
// its locked Confirmed shows (ui/view/filters.js filterRevalidateKind), the
|
|
40
|
+
// App tabs, the rest folded under them; none the pass ruled out. Null where it
|
|
41
|
+
// has no such view, or one with no outcome to lock, which shows every row.
|
|
42
|
+
export function workspaceAppRows(reports, duplicatesOf = () => []) {
|
|
43
|
+
const { linked, app } = appView(reports, duplicatesOf)
|
|
44
|
+
if (!app || onlyUnjudgedImports(linked)) return null
|
|
45
|
+
const kinds = activeRevalidateKinds('confirmed', '')
|
|
46
|
+
const confirmed = (f) => kinds.includes(revalidateKindOf(f) || (f._source && !f._sourcePass ? 'revalidation' : ''))
|
|
47
|
+
const ids = new Set()
|
|
48
|
+
for (const group of linked) if (group.some(confirmed)) for (const f of appTabs(group)) ids.add(f.id)
|
|
49
|
+
return ids
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// A workspace's rows as its basic App view merges them, and whether it has one.
|
|
53
|
+
function appView(reports, duplicatesOf) {
|
|
54
|
+
const { groups, conflicts } = mergeReportGroups(reports, { hideRuledOut: true })
|
|
55
|
+
const linked = mergeLinkedWorkspaceGroups(groups, duplicatesOf, appTabs)
|
|
56
|
+
const app = conflicts.size === 0
|
|
57
|
+
&& (onlyUnjudgedImports(linked) || canLockConfirmed(linked, { tabs: appTabs, kindOf: revalidateKindOf, severityMode: 'corrected' }))
|
|
58
|
+
return { linked, app }
|
|
59
|
+
}
|