@preventive/triage 1.0.0-alpha.51 → 1.0.0-alpha.52

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.
@@ -1,5 +1,6 @@
1
1
  import { findingTitle, reportEntries, revalidateKindOf } from '@preventive/report'
2
2
  import { managedTeamRevalidationConflicted } from './team-app.js'
3
+ import { liteName } from './lite-name.js'
3
4
 
4
5
  // A finding's detail: what its popup, the views besides the board and an
5
6
  // export read, and the board does not. Its narrative and evidence, the hash
@@ -14,14 +15,10 @@ import { managedTeamRevalidationConflicted } from './team-app.js'
14
15
  export const FINDING_DETAIL_FIELDS = Object.freeze([
15
16
  'description', 'evidence', 'impact', 'reproduction', 'recommendation', 'confidenceReason', 'revalidateVerdict', 'revalidateRecommendation',
16
17
  'fileHash', 'exportName', 'methodName',
17
- 'commitHash', 'auditedCommit', 'detectedAt', 'committedAt', 'dateCreated', 'status', 'branch', 'pocStatus', 'parent', 'slug', 'reportPath',
18
+ 'severityReason', 'commitHash', 'auditedCommit', 'detectedAt', 'committedAt', 'dateCreated', 'status', 'branch', 'pocStatus', 'parent', 'slug', 'reportPath',
18
19
  'category', 'discoveredIn', 'effort', 'exportsMode',
19
20
  ])
20
21
  const DETAIL = new Set(FINDING_DETAIL_FIELDS)
21
- // As much of a name as a heading takes (@preventive/report write-md-finding.js
22
- // HEADING_MAX), about a board card's three lines.
23
- const NAME_CHARS = 120
24
-
25
22
  // A finding without its detail. One without a title of its own is named by its
26
23
  // description's first line (findingTitle), which it keeps as its description,
27
24
  // as much of it as a card shows.
@@ -30,7 +27,7 @@ function liteFinding(finding) {
30
27
  const lite = Object.fromEntries(Object.entries(finding).filter(([key]) => !DETAIL.has(key)))
31
28
  const titled = typeof finding.title === 'string' && finding.title.trim() !== ''
32
29
  const title = titled ? '' : findingTitle(finding)
33
- if (title) lite.description = title.length > NAME_CHARS ? `${title.slice(0, NAME_CHARS - 1)}…` : title
30
+ if (title) lite.description = liteName(title)
34
31
  return lite
35
32
  }
36
33
 
@@ -0,0 +1,2 @@
1
+ export function findingValueIndex(): { values: unknown[]; index<T>(data: T): T }
2
+ export function expandFindingValues<T>(data: T, values: unknown): T
@@ -0,0 +1,44 @@
1
+ // A finding's `package` and `repo`: the same few across a team's many
2
+ // findings. A team's reports are sent with each once, in the response's
3
+ // `values`, and each finding's as its index there
4
+ // (server-managed/team-reports.ts), put back as they are read
5
+ // (client/managed/session.js).
6
+ const FIELDS = ['package', 'repo']
7
+
8
+ const entriesKey = data => Array.isArray(data?.findings) ? 'findings' : Array.isArray(data?.groups) ? 'groups' : null
9
+
10
+ // `values`, and `index(data)`: a report's data with its findings' values as
11
+ // their index in it, one value at one index across every report it is given.
12
+ export function findingValueIndex() {
13
+ const indexes = new Map(), values = []
14
+ const indexOf = value => {
15
+ const key = JSON.stringify(value)
16
+ if (!indexes.has(key)) indexes.set(key, values.push(value) - 1)
17
+ return indexes.get(key)
18
+ }
19
+ const indexed = finding => {
20
+ if (!finding || typeof finding !== 'object' || !FIELDS.some(field => finding[field] != null)) return finding
21
+ const copy = { ...finding }
22
+ for (const field of FIELDS) if (finding[field] != null) copy[field] = indexOf(finding[field])
23
+ return copy
24
+ }
25
+ return {
26
+ values,
27
+ index(data) {
28
+ const key = entriesKey(data)
29
+ return key ? { ...data, [key]: data[key].map(entry => Array.isArray(entry) ? entry.map(indexed) : indexed(entry)) } : data
30
+ },
31
+ }
32
+ }
33
+
34
+ // A report's data, as read, its findings' values put in place of their index
35
+ // in a response's `values`; one sent without any as it is.
36
+ export function expandFindingValues(data, values) {
37
+ const key = entriesKey(data)
38
+ if (!key || !Array.isArray(values)) return data
39
+ for (const finding of data[key].flat()) {
40
+ if (!finding || typeof finding !== 'object') continue
41
+ for (const field of FIELDS) if (Number.isInteger(finding[field])) finding[field] = values[finding[field]]
42
+ }
43
+ return data
44
+ }
@@ -0,0 +1,2 @@
1
+ export function liteName(name: string): string
2
+ export function readLiteReportData<T>(data: T): T
@@ -0,0 +1,20 @@
1
+ import { reportEntries } from '@preventive/report'
2
+
3
+ // As much of a name as a heading takes (@preventive/report write-md-finding.js
4
+ // HEADING_MAX), about a board card's three lines. A board's copy of a team's
5
+ // reports (finding-detail.js) cuts one longer to end in `CUT`, an ellipsis
6
+ // once read.
7
+ const CUT = '...', NAME_CHARS = 120
8
+
9
+ export const liteName = name => name.length > NAME_CHARS ? `${name.slice(0, NAME_CHARS - CUT.length)}${CUT}` : name
10
+
11
+ // A report's data from its board's copy, as read: a name cut ends in an
12
+ // ellipsis. One as long as a cut leaves it and ending in `CUT` is one; one
13
+ // shorter ends as it does.
14
+ export function readLiteReportData(data) {
15
+ for (const finding of (reportEntries(data) ?? []).flat()) {
16
+ const name = finding?.description
17
+ if (typeof name === 'string' && name.length === NAME_CHARS && name.endsWith(CUT)) finding.description = `${name.slice(0, -CUT.length)}…`
18
+ }
19
+ return data
20
+ }