@cyvest/cyvest-vis 4.4.0 → 5.0.0

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/package.json CHANGED
@@ -1,9 +1,22 @@
1
1
  {
2
2
  "name": "@cyvest/cyvest-vis",
3
- "version": "4.4.0",
3
+ "version": "5.0.0",
4
+ "type": "module",
4
5
  "main": "dist/index.cjs",
5
- "module": "dist/index.mjs",
6
+ "module": "dist/index.js",
6
7
  "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "require": {
15
+ "types": "./dist/index.d.cts",
16
+ "default": "./dist/index.cjs"
17
+ }
18
+ }
19
+ },
7
20
  "sideEffects": false,
8
21
  "peerDependencies": {
9
22
  "react": "^19.0.0",
@@ -13,7 +26,7 @@
13
26
  "@dagrejs/dagre": "^1.1.8",
14
27
  "@xyflow/react": "^12.10.0",
15
28
  "d3-force": "^3.0.0",
16
- "@cyvest/cyvest-js": "4.4.0"
29
+ "@cyvest/cyvest-js": "5.0.0"
17
30
  },
18
31
  "devDependencies": {
19
32
  "@types/d3-force": "^3.0.10",
@@ -594,9 +594,9 @@ export const CheckIcon: React.FC<IconProps> = ({
594
594
  );
595
595
 
596
596
  /**
597
- * Box icon for containers
597
+ * Tag icon for tags
598
598
  */
599
- export const BoxIcon: React.FC<IconProps> = ({
599
+ export const TagIcon: React.FC<IconProps> = ({
600
600
  size = defaultSize,
601
601
  color = defaultColor,
602
602
  className,
@@ -706,7 +706,7 @@ export const INVESTIGATION_ICON_MAP: Record<
706
706
  > = {
707
707
  root: CrosshairIcon,
708
708
  check: CheckIcon,
709
- container: BoxIcon,
709
+ tag: TagIcon,
710
710
  };
711
711
 
712
712
  /**
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * InvestigationGraph component - displays investigation structure with Dagre layout.
3
- * Shows root observable, checks, and containers in a hierarchical view.
3
+ * Shows root observable, checks, and tags in a hierarchical view.
4
4
  */
5
5
 
6
6
  import React, { useMemo, useCallback } from "react";
@@ -19,7 +19,8 @@ import {
19
19
  } from "@xyflow/react";
20
20
  import "@xyflow/react/dist/style.css";
21
21
 
22
- import type { CyvestInvestigation, Check, Container } from "@cyvest/cyvest-js";
22
+ import type { CyvestInvestigation, Check, Tag } from "@cyvest/cyvest-js";
23
+ import { getTagAncestors } from "@cyvest/cyvest-js";
23
24
 
24
25
  import type {
25
26
  InvestigationGraphProps,
@@ -55,21 +56,10 @@ const defaultEdgeOptions = {
55
56
  };
56
57
 
57
58
  /**
58
- * Flatten containers recursively to get all container keys.
59
+ * Get all tags as an array.
59
60
  */
60
- function flattenContainers(
61
- containers: Record<string, Container>
62
- ): Container[] {
63
- const result: Container[] = [];
64
-
65
- for (const container of Object.values(containers)) {
66
- result.push(container);
67
- if (container.sub_containers) {
68
- result.push(...flattenContainers(container.sub_containers));
69
- }
70
- }
71
-
72
- return result;
61
+ function getAllTags(tags: Record<string, Tag>): Tag[] {
62
+ return Object.values(tags);
73
63
  }
74
64
 
75
65
  /**
@@ -115,31 +105,27 @@ function createInvestigationGraph(
115
105
  draggable: true,
116
106
  });
117
107
 
118
- // Collect all check keys that belong to containers
108
+ // Collect all check keys that belong to tags
119
109
  // These checks should NOT have a direct link to the root node
120
- const allContainers = flattenContainers(investigation.containers);
121
- const checksInContainers = new Set<string>();
122
- for (const container of allContainers) {
123
- for (const checkKey of container.checks) {
124
- checksInContainers.add(checkKey);
110
+ const allTags = getAllTags(investigation.tags);
111
+ const checksInTags = new Set<string>();
112
+ for (const tag of allTags) {
113
+ for (const checkKey of tag.checks) {
114
+ checksInTags.add(checkKey);
125
115
  }
126
116
  }
127
117
 
128
118
  // Add check nodes
129
- // Group checks by scope for better organization
130
- const allChecks: Check[] = [];
131
- for (const checksForKey of Object.values(investigation.checks)) {
132
- allChecks.push(...checksForKey);
133
- }
119
+ const allChecks = Object.values(investigation.checks);
134
120
 
135
- // Create unique check nodes (by check_id to avoid duplicates)
121
+ // Create unique check nodes (by key to avoid duplicates)
136
122
  const seenCheckIds = new Set<string>();
137
123
  for (const check of allChecks) {
138
124
  if (seenCheckIds.has(check.key)) continue;
139
125
  seenCheckIds.add(check.key);
140
126
 
141
127
  const checkNodeData: InvestigationNodeData = {
142
- label: truncateLabel(check.check_id, 20),
128
+ label: truncateLabel(check.check_name, 20),
143
129
  nodeType: "check",
144
130
  level: check.level,
145
131
  score: check.score,
@@ -155,9 +141,9 @@ function createInvestigationGraph(
155
141
  draggable: true,
156
142
  });
157
143
 
158
- // Only create edge from root to check if check is NOT in a container
159
- // Checks in containers will be linked through their container instead
160
- if (!checksInContainers.has(check.key)) {
144
+ // Only create edge from root to check if check is NOT in a tag
145
+ // Checks in tags will be linked through their tag instead
146
+ if (!checksInTags.has(check.key)) {
161
147
  edges.push({
162
148
  id: `edge-root-${check.key}`,
163
149
  source: rootKey,
@@ -168,43 +154,79 @@ function createInvestigationGraph(
168
154
  }
169
155
  }
170
156
 
171
- // Add container nodes
172
- for (const container of allContainers) {
173
- const containerNodeData: InvestigationNodeData = {
174
- label: truncateLabel(
175
- container.path.split("/").pop() ?? container.path,
176
- 20
177
- ),
178
- nodeType: "container",
179
- level: container.aggregated_level,
180
- score: container.aggregated_score,
181
- path: container.path,
157
+ // Build hierarchical tag structure
158
+ // First, collect all tag names and find/create ancestor tags
159
+ const tagByName = new Map<string, Tag>();
160
+ for (const tag of allTags) {
161
+ tagByName.set(tag.name, tag);
162
+ }
163
+
164
+ // Collect all unique tag names including synthetic ancestors
165
+ const allTagNames = new Set<string>();
166
+ for (const tag of allTags) {
167
+ allTagNames.add(tag.name);
168
+ // Add ancestors (they may not exist as actual tags)
169
+ for (const ancestor of getTagAncestors(tag.name)) {
170
+ allTagNames.add(ancestor);
171
+ }
172
+ }
173
+
174
+ // Create tag nodes (real and synthetic)
175
+ for (const tagName of allTagNames) {
176
+ const realTag = tagByName.get(tagName);
177
+
178
+ const tagNodeData: InvestigationNodeData = {
179
+ label: truncateLabel(tagName.split(":").pop() ?? tagName, 20),
180
+ nodeType: "tag",
181
+ level: realTag?.direct_level ?? "INFO",
182
+ score: realTag?.direct_score ?? 0,
183
+ name: tagName,
182
184
  };
183
185
 
184
186
  nodes.push({
185
- id: `container-${container.key}`,
187
+ id: `tag-${tagName}`,
186
188
  type: "investigation",
187
189
  position: { x: 0, y: 0 },
188
- data: containerNodeData,
190
+ data: tagNodeData,
189
191
  selectable: true,
190
192
  draggable: true,
191
193
  });
194
+ }
192
195
 
193
- // Edge from root to container
194
- edges.push({
195
- id: `edge-root-container-${container.key}`,
196
- source: rootKey,
197
- target: `container-${container.key}`,
198
- type: "smoothstep",
199
- animated: false,
200
- });
196
+ // Create edges based on tag hierarchy
197
+ for (const tagName of allTagNames) {
198
+ const nodeId = `tag-${tagName}`;
199
+ const parts = tagName.split(":");
200
+
201
+ if (parts.length === 1) {
202
+ // Top-level tag, connect to root
203
+ edges.push({
204
+ id: `edge-root-tag-${tagName}`,
205
+ source: rootKey,
206
+ target: nodeId,
207
+ type: "smoothstep",
208
+ animated: false,
209
+ });
210
+ } else {
211
+ // Has a parent tag, connect to parent
212
+ const parentName = parts.slice(0, -1).join(":");
213
+ edges.push({
214
+ id: `edge-tag-${parentName}-${tagName}`,
215
+ source: `tag-${parentName}`,
216
+ target: nodeId,
217
+ type: "smoothstep",
218
+ animated: false,
219
+ });
220
+ }
221
+ }
201
222
 
202
- // Edges from container to its checks
203
- for (const checkKey of container.checks) {
223
+ // Create edges from tags to their checks (only for real tags with checks)
224
+ for (const tag of allTags) {
225
+ for (const checkKey of tag.checks) {
204
226
  if (seenCheckIds.has(checkKey)) {
205
227
  edges.push({
206
- id: `edge-container-check-${container.key}-${checkKey}`,
207
- source: `container-${container.key}`,
228
+ id: `edge-tag-check-${tag.name}-${checkKey}`,
229
+ source: `tag-${tag.name}`,
208
230
  target: `check-${checkKey}`,
209
231
  type: "smoothstep",
210
232
  animated: false,
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Custom node component for the Investigation Graph (Dagre layout).
3
- * Professional design with SVG icons for root, check, and container nodes.
3
+ * Professional design with SVG icons for root, check, and tag nodes.
4
4
  */
5
5
 
6
6
  import React, { memo, useMemo } from "react";
@@ -33,7 +33,7 @@ const NODE_CONFIG = {
33
33
  showIcon: false, // No icon for checks
34
34
  alignCenter: false, // Left-aligned
35
35
  },
36
- container: {
36
+ tag: {
37
37
  minWidth: 120,
38
38
  padding: "8px 14px",
39
39
  borderRadius: 16,
package/src/types.ts CHANGED
@@ -66,7 +66,7 @@ export type ObservableEdge = Edge<ObservableEdgeData>;
66
66
  /**
67
67
  * Node types for the investigation graph view.
68
68
  */
69
- export type InvestigationNodeType = "root" | "check" | "container";
69
+ export type InvestigationNodeType = "root" | "check" | "tag";
70
70
 
71
71
  /**
72
72
  * Data attached to investigation graph nodes.
@@ -74,7 +74,7 @@ export type InvestigationNodeType = "root" | "check" | "container";
74
74
  export interface InvestigationNodeData extends Record<string, unknown> {
75
75
  /** Display label */
76
76
  label: string;
77
- /** Node type (root, check, or container) */
77
+ /** Node type (root, check, or tag) */
78
78
  nodeType: InvestigationNodeType;
79
79
  /** Security level */
80
80
  level: Level;
@@ -82,8 +82,8 @@ export interface InvestigationNodeData extends Record<string, unknown> {
82
82
  score: number;
83
83
  /** Description (for checks) */
84
84
  description?: string;
85
- /** Path (for containers) */
86
- path?: string;
85
+ /** Name (for tags) */
86
+ name?: string;
87
87
  }
88
88
 
89
89
  /**