@principal-ai/principal-view-core 0.5.16 → 0.5.17
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/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/telemetry/coverage.d.ts +38 -0
- package/dist/telemetry/coverage.d.ts.map +1 -0
- package/dist/telemetry/coverage.js +113 -0
- package/dist/telemetry/coverage.js.map +1 -0
- package/package.json +1 -1
- package/src/index.ts +4 -0
- package/src/telemetry/coverage.ts +162 -0
- package/src/utils/GraphConverter.test.ts +79 -0
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export { CanvasConverter } from './utils/CanvasConverter';
|
|
|
18
18
|
export type { ReactFlowNode, ReactFlowEdge } from './utils/CanvasConverter';
|
|
19
19
|
export { EventValidator, createValidatedEmitter, EventValidationError } from './telemetry/event-validator';
|
|
20
20
|
export type { ValidationResult } from './telemetry/event-validator';
|
|
21
|
+
export { analyzeCoverage } from './telemetry/coverage';
|
|
22
|
+
export type { CoverageMetrics, NodeCoverage, CanvasNode as CoverageCanvasNode } from './telemetry/coverage';
|
|
21
23
|
export { generateTypes, TypeScriptGenerator, generatorRegistry } from './codegen/type-generator';
|
|
22
24
|
export type { CodegenOptions, CodegenResult, CodeGenerator } from './codegen/type-generator';
|
|
23
25
|
export { traceToCanvas, traceToCanvasJson } from './utils/TraceToCanvas';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,cAAc,SAAS,CAAC;AAGxB,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,YAAY,EACV,4BAA4B,EAC5B,6BAA6B,GAC9B,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AAGlF,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,YAAY,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAG1D,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAGxD,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAG5E,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAC3G,YAAY,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAGpE,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AACjG,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAG7F,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AACzE,YAAY,EACV,SAAS,EACT,WAAW,EACX,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EACV,aAAa,EACb,aAAa,EACb,eAAe,EACf,YAAY,EACZ,oBAAoB,EACpB,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,YAAY,EACV,mBAAmB,EACnB,eAAe,EACf,mBAAmB,EACnB,iBAAiB,EACjB,UAAU,EACV,eAAe,EACf,WAAW,EACX,WAAW,EACX,YAAY,EACZ,UAAU,EACV,eAAe,EACf,eAAe,EACf,aAAa,EACb,aAAa,EACb,kBAAkB,EAClB,eAAe,EACf,0BAA0B,GAC3B,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AACxF,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AACtF,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAG1D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAGrF,YAAY,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAC9E,OAAO,EAAE,yBAAyB,EAAE,MAAM,sCAAsC,CAAC;AAGjF,cAAc,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,cAAc,SAAS,CAAC;AAGxB,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,YAAY,EACV,4BAA4B,EAC5B,6BAA6B,GAC9B,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AAGlF,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,YAAY,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAG1D,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAGxD,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAG5E,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAC3G,YAAY,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAGpE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,UAAU,IAAI,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG5G,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AACjG,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAG7F,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AACzE,YAAY,EACV,SAAS,EACT,WAAW,EACX,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EACV,aAAa,EACb,aAAa,EACb,eAAe,EACf,YAAY,EACZ,oBAAoB,EACpB,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,YAAY,EACV,mBAAmB,EACnB,eAAe,EACf,mBAAmB,EACnB,iBAAiB,EACjB,UAAU,EACV,eAAe,EACf,WAAW,EACX,WAAW,EACX,YAAY,EACZ,UAAU,EACV,eAAe,EACf,eAAe,EACf,aAAa,EACb,aAAa,EACb,kBAAkB,EAClB,eAAe,EACf,0BAA0B,GAC3B,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AACxF,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AACtF,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAG1D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAGrF,YAAY,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAC9E,OAAO,EAAE,yBAAyB,EAAE,MAAM,sCAAsC,CAAC;AAGjF,cAAc,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,8 @@ export * from './types/canvas';
|
|
|
20
20
|
export { CanvasConverter } from './utils/CanvasConverter';
|
|
21
21
|
// Export telemetry event validation
|
|
22
22
|
export { EventValidator, createValidatedEmitter, EventValidationError } from './telemetry/event-validator';
|
|
23
|
+
// Export telemetry coverage analysis
|
|
24
|
+
export { analyzeCoverage } from './telemetry/coverage';
|
|
23
25
|
// Export code generation
|
|
24
26
|
export { generateTypes, TypeScriptGenerator, generatorRegistry } from './codegen/type-generator';
|
|
25
27
|
// Export trace-to-canvas conversion
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,mBAAmB;AACnB,cAAc,SAAS,CAAC;AAExB,sBAAsB;AACtB,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGlD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAMlE,iBAAiB;AACjB,OAAO,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AAElF,iDAAiD;AACjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAGpE,wBAAwB;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,oCAAoC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAG1D,oCAAoC;AACpC,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAG3G,yBAAyB;AACzB,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAGjG,oCAAoC;AACpC,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAQzE,qDAAqD;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAYlD,gCAAgC;AAChC,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAqB9D,+DAA+D;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAGtF,mCAAmC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAK5D,OAAO,EAAE,yBAAyB,EAAE,MAAM,sCAAsC,CAAC;AAEjF,sBAAsB;AACtB,cAAc,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,mBAAmB;AACnB,cAAc,SAAS,CAAC;AAExB,sBAAsB;AACtB,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGlD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAMlE,iBAAiB;AACjB,OAAO,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AAElF,iDAAiD;AACjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAGpE,wBAAwB;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,oCAAoC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAG1D,oCAAoC;AACpC,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAG3G,qCAAqC;AACrC,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGvD,yBAAyB;AACzB,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAGjG,oCAAoC;AACpC,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAQzE,qDAAqD;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAYlD,gCAAgC;AAChC,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAqB9D,+DAA+D;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAGtF,mCAAmC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAK5D,OAAO,EAAE,yBAAyB,EAAE,MAAM,sCAAsC,CAAC;AAEjF,sBAAsB;AACtB,cAAc,SAAS,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telemetry Coverage Analysis
|
|
3
|
+
*
|
|
4
|
+
* Measures observability coverage by analyzing which canvas nodes have
|
|
5
|
+
* OpenTelemetry instrumentation in their source files.
|
|
6
|
+
*/
|
|
7
|
+
export interface CanvasNode {
|
|
8
|
+
id: string;
|
|
9
|
+
text?: string;
|
|
10
|
+
anchors?: Array<{
|
|
11
|
+
path?: string;
|
|
12
|
+
}>;
|
|
13
|
+
[key: string]: any;
|
|
14
|
+
}
|
|
15
|
+
export interface Canvas {
|
|
16
|
+
nodes?: CanvasNode[];
|
|
17
|
+
[key: string]: any;
|
|
18
|
+
}
|
|
19
|
+
export interface NodeCoverage {
|
|
20
|
+
nodeId: string;
|
|
21
|
+
filePaths: string[];
|
|
22
|
+
hasInstrumentation: boolean;
|
|
23
|
+
instrumentedFiles: string[];
|
|
24
|
+
missingFiles: string[];
|
|
25
|
+
}
|
|
26
|
+
export interface CoverageMetrics {
|
|
27
|
+
totalNodes: number;
|
|
28
|
+
nodesWithFiles: number;
|
|
29
|
+
nodesWithInstrumentation: number;
|
|
30
|
+
coveragePercentage: number;
|
|
31
|
+
nodeCoverage: NodeCoverage[];
|
|
32
|
+
canvasFiles: string[];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Generate telemetry coverage report from canvas files
|
|
36
|
+
*/
|
|
37
|
+
export declare function analyzeCoverage(rootDir: string): Promise<CoverageMetrics>;
|
|
38
|
+
//# sourceMappingURL=coverage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coverage.d.ts","sourceRoot":"","sources":["../../src/telemetry/coverage.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,wBAAwB,EAAE,MAAM,CAAC;IACjC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,EAAE,YAAY,EAAE,CAAC;IAC7B,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAmFD;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAqC/E"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telemetry Coverage Analysis
|
|
3
|
+
*
|
|
4
|
+
* Measures observability coverage by analyzing which canvas nodes have
|
|
5
|
+
* OpenTelemetry instrumentation in their source files.
|
|
6
|
+
*/
|
|
7
|
+
import { readFile, access } from 'fs/promises';
|
|
8
|
+
import { resolve } from 'path';
|
|
9
|
+
import { glob } from 'glob';
|
|
10
|
+
/**
|
|
11
|
+
* Extract file paths from a canvas node anchors (REQUIRED)
|
|
12
|
+
*/
|
|
13
|
+
function extractFilePaths(node) {
|
|
14
|
+
const paths = [];
|
|
15
|
+
if (node.anchors) {
|
|
16
|
+
for (const anchor of node.anchors) {
|
|
17
|
+
if (anchor.path) {
|
|
18
|
+
paths.push(anchor.path);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return paths;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Check if a file has OpenTelemetry instrumentation
|
|
26
|
+
*/
|
|
27
|
+
async function hasInstrumentation(filePath) {
|
|
28
|
+
try {
|
|
29
|
+
const content = await readFile(filePath, 'utf-8');
|
|
30
|
+
const hasOtelImport = content.includes('@opentelemetry/api');
|
|
31
|
+
const hasTracer = /getTracer|startSpan|addEvent/.test(content);
|
|
32
|
+
const hasTestOtel = /['"]\.\.?\/.*test\/otel-setup['"]/.test(content);
|
|
33
|
+
return hasOtelImport || hasTracer || hasTestOtel;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Check if file exists
|
|
41
|
+
*/
|
|
42
|
+
async function fileExists(filePath) {
|
|
43
|
+
try {
|
|
44
|
+
await access(filePath);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Analyze coverage for a single canvas node
|
|
53
|
+
*/
|
|
54
|
+
async function analyzeNodeCoverage(node, rootDir) {
|
|
55
|
+
const filePaths = extractFilePaths(node);
|
|
56
|
+
const instrumentedFiles = [];
|
|
57
|
+
const missingFiles = [];
|
|
58
|
+
for (const path of filePaths) {
|
|
59
|
+
const fullPath = resolve(rootDir, path);
|
|
60
|
+
const exists = await fileExists(fullPath);
|
|
61
|
+
if (!exists) {
|
|
62
|
+
missingFiles.push(path);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const instrumented = await hasInstrumentation(fullPath);
|
|
66
|
+
if (instrumented) {
|
|
67
|
+
instrumentedFiles.push(path);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
nodeId: node.id,
|
|
72
|
+
filePaths,
|
|
73
|
+
hasInstrumentation: instrumentedFiles.length > 0,
|
|
74
|
+
instrumentedFiles,
|
|
75
|
+
missingFiles
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Generate telemetry coverage report from canvas files
|
|
80
|
+
*/
|
|
81
|
+
export async function analyzeCoverage(rootDir) {
|
|
82
|
+
const canvasFiles = await glob('**/*.otel.canvas', {
|
|
83
|
+
cwd: rootDir,
|
|
84
|
+
absolute: true,
|
|
85
|
+
dot: true,
|
|
86
|
+
ignore: ['**/node_modules/**']
|
|
87
|
+
});
|
|
88
|
+
const allNodeCoverage = [];
|
|
89
|
+
for (const canvasFile of canvasFiles) {
|
|
90
|
+
const content = await readFile(canvasFile, 'utf-8');
|
|
91
|
+
const canvas = JSON.parse(content);
|
|
92
|
+
if (!canvas.nodes || canvas.nodes.length === 0) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
for (const node of canvas.nodes) {
|
|
96
|
+
const coverage = await analyzeNodeCoverage(node, rootDir);
|
|
97
|
+
allNodeCoverage.push(coverage);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const nodesWithFiles = allNodeCoverage.filter(n => n.filePaths.length > 0);
|
|
101
|
+
const nodesWithInstrumentation = allNodeCoverage.filter(n => n.hasInstrumentation);
|
|
102
|
+
return {
|
|
103
|
+
totalNodes: allNodeCoverage.length,
|
|
104
|
+
nodesWithFiles: nodesWithFiles.length,
|
|
105
|
+
nodesWithInstrumentation: nodesWithInstrumentation.length,
|
|
106
|
+
coveragePercentage: nodesWithFiles.length > 0
|
|
107
|
+
? (nodesWithInstrumentation.length / nodesWithFiles.length) * 100
|
|
108
|
+
: 0,
|
|
109
|
+
nodeCoverage: allNodeCoverage,
|
|
110
|
+
canvasFiles: canvasFiles.map(f => f.replace(rootDir + '/', ''))
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=coverage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coverage.js","sourceRoot":"","sources":["../../src/telemetry/coverage.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AA+B5B;;GAEG;AACH,SAAS,gBAAgB,CAAC,IAAgB;IACxC,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAClC,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;gBAChB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,kBAAkB,CAAC,QAAgB;IAChD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAElD,MAAM,aAAa,GAAG,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAAC;QAC7D,MAAM,SAAS,GAAG,8BAA8B,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC/D,MAAM,WAAW,GAAG,mCAAmC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAEtE,OAAO,aAAa,IAAI,SAAS,IAAI,WAAW,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,UAAU,CAAC,QAAgB;IACxC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;QACvB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,mBAAmB,CAChC,IAAgB,EAChB,OAAe;IAEf,MAAM,SAAS,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,iBAAiB,GAAa,EAAE,CAAC;IACvC,MAAM,YAAY,GAAa,EAAE,CAAC;IAElC,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC;QAE1C,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACxB,SAAS;QACX,CAAC;QAED,MAAM,YAAY,GAAG,MAAM,kBAAkB,CAAC,QAAQ,CAAC,CAAC;QACxD,IAAI,YAAY,EAAE,CAAC;YACjB,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,OAAO;QACL,MAAM,EAAE,IAAI,CAAC,EAAE;QACf,SAAS;QACT,kBAAkB,EAAE,iBAAiB,CAAC,MAAM,GAAG,CAAC;QAChD,iBAAiB;QACjB,YAAY;KACb,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAAe;IACnD,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,kBAAkB,EAAE;QACjD,GAAG,EAAE,OAAO;QACZ,QAAQ,EAAE,IAAI;QACd,GAAG,EAAE,IAAI;QACT,MAAM,EAAE,CAAC,oBAAoB,CAAC;KAC/B,CAAC,CAAC;IAEH,MAAM,eAAe,GAAmB,EAAE,CAAC;IAE3C,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACrC,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACpD,MAAM,MAAM,GAAW,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAE3C,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/C,SAAS;QACX,CAAC;QAED,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YAChC,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC1D,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED,MAAM,cAAc,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC3E,MAAM,wBAAwB,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC;IAEnF,OAAO;QACL,UAAU,EAAE,eAAe,CAAC,MAAM;QAClC,cAAc,EAAE,cAAc,CAAC,MAAM;QACrC,wBAAwB,EAAE,wBAAwB,CAAC,MAAM;QACzD,kBAAkB,EAAE,cAAc,CAAC,MAAM,GAAG,CAAC;YAC3C,CAAC,CAAC,CAAC,wBAAwB,CAAC,MAAM,GAAG,cAAc,CAAC,MAAM,CAAC,GAAG,GAAG;YACjE,CAAC,CAAC,CAAC;QACL,YAAY,EAAE,eAAe;QAC7B,WAAW,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,GAAG,GAAG,EAAE,EAAE,CAAC,CAAC;KAChE,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -38,6 +38,10 @@ export type { ReactFlowNode, ReactFlowEdge } from './utils/CanvasConverter';
|
|
|
38
38
|
export { EventValidator, createValidatedEmitter, EventValidationError } from './telemetry/event-validator';
|
|
39
39
|
export type { ValidationResult } from './telemetry/event-validator';
|
|
40
40
|
|
|
41
|
+
// Export telemetry coverage analysis
|
|
42
|
+
export { analyzeCoverage } from './telemetry/coverage';
|
|
43
|
+
export type { CoverageMetrics, NodeCoverage, CanvasNode as CoverageCanvasNode } from './telemetry/coverage';
|
|
44
|
+
|
|
41
45
|
// Export code generation
|
|
42
46
|
export { generateTypes, TypeScriptGenerator, generatorRegistry } from './codegen/type-generator';
|
|
43
47
|
export type { CodegenOptions, CodegenResult, CodeGenerator } from './codegen/type-generator';
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telemetry Coverage Analysis
|
|
3
|
+
*
|
|
4
|
+
* Measures observability coverage by analyzing which canvas nodes have
|
|
5
|
+
* OpenTelemetry instrumentation in their source files.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFile, access } from 'fs/promises';
|
|
9
|
+
import { resolve } from 'path';
|
|
10
|
+
import { glob } from 'glob';
|
|
11
|
+
|
|
12
|
+
export interface CanvasNode {
|
|
13
|
+
id: string;
|
|
14
|
+
text?: string;
|
|
15
|
+
anchors?: Array<{ path?: string }>;
|
|
16
|
+
[key: string]: any;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface Canvas {
|
|
20
|
+
nodes?: CanvasNode[];
|
|
21
|
+
[key: string]: any;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface NodeCoverage {
|
|
25
|
+
nodeId: string;
|
|
26
|
+
filePaths: string[];
|
|
27
|
+
hasInstrumentation: boolean;
|
|
28
|
+
instrumentedFiles: string[];
|
|
29
|
+
missingFiles: string[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface CoverageMetrics {
|
|
33
|
+
totalNodes: number;
|
|
34
|
+
nodesWithFiles: number;
|
|
35
|
+
nodesWithInstrumentation: number;
|
|
36
|
+
coveragePercentage: number;
|
|
37
|
+
nodeCoverage: NodeCoverage[];
|
|
38
|
+
canvasFiles: string[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Extract file paths from a canvas node anchors (REQUIRED)
|
|
43
|
+
*/
|
|
44
|
+
function extractFilePaths(node: CanvasNode): string[] {
|
|
45
|
+
const paths: string[] = [];
|
|
46
|
+
|
|
47
|
+
if (node.anchors) {
|
|
48
|
+
for (const anchor of node.anchors) {
|
|
49
|
+
if (anchor.path) {
|
|
50
|
+
paths.push(anchor.path);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return paths;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Check if a file has OpenTelemetry instrumentation
|
|
60
|
+
*/
|
|
61
|
+
async function hasInstrumentation(filePath: string): Promise<boolean> {
|
|
62
|
+
try {
|
|
63
|
+
const content = await readFile(filePath, 'utf-8');
|
|
64
|
+
|
|
65
|
+
const hasOtelImport = content.includes('@opentelemetry/api');
|
|
66
|
+
const hasTracer = /getTracer|startSpan|addEvent/.test(content);
|
|
67
|
+
const hasTestOtel = /['"]\.\.?\/.*test\/otel-setup['"]/.test(content);
|
|
68
|
+
|
|
69
|
+
return hasOtelImport || hasTracer || hasTestOtel;
|
|
70
|
+
} catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check if file exists
|
|
77
|
+
*/
|
|
78
|
+
async function fileExists(filePath: string): Promise<boolean> {
|
|
79
|
+
try {
|
|
80
|
+
await access(filePath);
|
|
81
|
+
return true;
|
|
82
|
+
} catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Analyze coverage for a single canvas node
|
|
89
|
+
*/
|
|
90
|
+
async function analyzeNodeCoverage(
|
|
91
|
+
node: CanvasNode,
|
|
92
|
+
rootDir: string
|
|
93
|
+
): Promise<NodeCoverage> {
|
|
94
|
+
const filePaths = extractFilePaths(node);
|
|
95
|
+
const instrumentedFiles: string[] = [];
|
|
96
|
+
const missingFiles: string[] = [];
|
|
97
|
+
|
|
98
|
+
for (const path of filePaths) {
|
|
99
|
+
const fullPath = resolve(rootDir, path);
|
|
100
|
+
const exists = await fileExists(fullPath);
|
|
101
|
+
|
|
102
|
+
if (!exists) {
|
|
103
|
+
missingFiles.push(path);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const instrumented = await hasInstrumentation(fullPath);
|
|
108
|
+
if (instrumented) {
|
|
109
|
+
instrumentedFiles.push(path);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
nodeId: node.id,
|
|
115
|
+
filePaths,
|
|
116
|
+
hasInstrumentation: instrumentedFiles.length > 0,
|
|
117
|
+
instrumentedFiles,
|
|
118
|
+
missingFiles
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Generate telemetry coverage report from canvas files
|
|
124
|
+
*/
|
|
125
|
+
export async function analyzeCoverage(rootDir: string): Promise<CoverageMetrics> {
|
|
126
|
+
const canvasFiles = await glob('**/*.otel.canvas', {
|
|
127
|
+
cwd: rootDir,
|
|
128
|
+
absolute: true,
|
|
129
|
+
dot: true,
|
|
130
|
+
ignore: ['**/node_modules/**']
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const allNodeCoverage: NodeCoverage[] = [];
|
|
134
|
+
|
|
135
|
+
for (const canvasFile of canvasFiles) {
|
|
136
|
+
const content = await readFile(canvasFile, 'utf-8');
|
|
137
|
+
const canvas: Canvas = JSON.parse(content);
|
|
138
|
+
|
|
139
|
+
if (!canvas.nodes || canvas.nodes.length === 0) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
for (const node of canvas.nodes) {
|
|
144
|
+
const coverage = await analyzeNodeCoverage(node, rootDir);
|
|
145
|
+
allNodeCoverage.push(coverage);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const nodesWithFiles = allNodeCoverage.filter(n => n.filePaths.length > 0);
|
|
150
|
+
const nodesWithInstrumentation = allNodeCoverage.filter(n => n.hasInstrumentation);
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
totalNodes: allNodeCoverage.length,
|
|
154
|
+
nodesWithFiles: nodesWithFiles.length,
|
|
155
|
+
nodesWithInstrumentation: nodesWithInstrumentation.length,
|
|
156
|
+
coveragePercentage: nodesWithFiles.length > 0
|
|
157
|
+
? (nodesWithInstrumentation.length / nodesWithFiles.length) * 100
|
|
158
|
+
: 0,
|
|
159
|
+
nodeCoverage: allNodeCoverage,
|
|
160
|
+
canvasFiles: canvasFiles.map(f => f.replace(rootDir + '/', ''))
|
|
161
|
+
};
|
|
162
|
+
}
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
markTestFailed,
|
|
10
10
|
setSpanAttribute,
|
|
11
11
|
exportSpans,
|
|
12
|
+
log,
|
|
13
|
+
recordLog,
|
|
12
14
|
} from '../../test/otel-setup';
|
|
13
15
|
|
|
14
16
|
describe('GraphConverter', () => {
|
|
@@ -24,11 +26,25 @@ describe('GraphConverter', () => {
|
|
|
24
26
|
});
|
|
25
27
|
|
|
26
28
|
try {
|
|
29
|
+
// TRACE log at test start (automatically correlated with testSpan)
|
|
30
|
+
log('TRACE', 'Entering test case for simple config conversion', {
|
|
31
|
+
'service.name': 'graph-converter-service',
|
|
32
|
+
'service.version': '1.0.0',
|
|
33
|
+
});
|
|
34
|
+
|
|
27
35
|
// Setup - record as event
|
|
28
36
|
addEvent(testSpan, 'setup.started', {
|
|
29
37
|
description: 'Creating test configuration with 2 nodes and 1 edge',
|
|
30
38
|
});
|
|
31
39
|
|
|
40
|
+
// INFO log during setup
|
|
41
|
+
log('INFO', 'Initializing test configuration', {
|
|
42
|
+
'service.name': 'graph-converter-service',
|
|
43
|
+
}, {
|
|
44
|
+
'config.nodeCount': 2,
|
|
45
|
+
'config.edgeCount': 1,
|
|
46
|
+
});
|
|
47
|
+
|
|
32
48
|
const config: PathBasedGraphConfiguration = {
|
|
33
49
|
metadata: {
|
|
34
50
|
name: 'Test Config',
|
|
@@ -70,6 +86,15 @@ describe('GraphConverter', () => {
|
|
|
70
86
|
'config.edges': 1,
|
|
71
87
|
});
|
|
72
88
|
|
|
89
|
+
// DEBUG log with structured data after setup
|
|
90
|
+
log('DEBUG', {
|
|
91
|
+
phase: 'setup-complete',
|
|
92
|
+
nodeTypes: Object.keys(config.nodeTypes),
|
|
93
|
+
edgeTypes: Object.keys(config.edgeTypes),
|
|
94
|
+
}, {
|
|
95
|
+
'service.name': 'graph-converter-service',
|
|
96
|
+
});
|
|
97
|
+
|
|
73
98
|
// Execution - record as event
|
|
74
99
|
// Note: execution happens in GraphConverter.ts, not the test file
|
|
75
100
|
addEvent(testSpan, 'execution.started', {
|
|
@@ -78,8 +103,25 @@ describe('GraphConverter', () => {
|
|
|
78
103
|
'code.lineno': 15,
|
|
79
104
|
});
|
|
80
105
|
|
|
106
|
+
// INFO log before conversion
|
|
107
|
+
log('INFO', 'Starting graph conversion process', {
|
|
108
|
+
'service.name': 'graph-converter-service',
|
|
109
|
+
}, {
|
|
110
|
+
'operation': 'configToGraph',
|
|
111
|
+
'inputSize.bytes': JSON.stringify(config).length,
|
|
112
|
+
});
|
|
113
|
+
|
|
81
114
|
const result = GraphConverter.configToGraph(config);
|
|
82
115
|
|
|
116
|
+
// DEBUG log after conversion with result details
|
|
117
|
+
log('DEBUG', 'Conversion completed successfully', {
|
|
118
|
+
'service.name': 'graph-converter-service',
|
|
119
|
+
}, {
|
|
120
|
+
'output.nodeCount': result.nodes.length,
|
|
121
|
+
'output.edgeCount': result.edges.length,
|
|
122
|
+
'processingTime.ms': 5,
|
|
123
|
+
});
|
|
124
|
+
|
|
83
125
|
addEvent(testSpan, 'execution.complete', {
|
|
84
126
|
'result.nodes.count': result.nodes.length,
|
|
85
127
|
'result.edges.count': result.edges.length,
|
|
@@ -92,6 +134,13 @@ describe('GraphConverter', () => {
|
|
|
92
134
|
assertions: 'Verifying nodes and edges structure',
|
|
93
135
|
});
|
|
94
136
|
|
|
137
|
+
// INFO log at assertion phase
|
|
138
|
+
log('INFO', 'Running assertions on converted graph', {
|
|
139
|
+
'service.name': 'graph-converter-service',
|
|
140
|
+
}, {
|
|
141
|
+
'totalAssertions': 11,
|
|
142
|
+
});
|
|
143
|
+
|
|
95
144
|
expect(result.nodes).toHaveLength(2);
|
|
96
145
|
expect(result.edges).toHaveLength(1);
|
|
97
146
|
|
|
@@ -116,8 +165,24 @@ describe('GraphConverter', () => {
|
|
|
116
165
|
'assertions.failed': 0,
|
|
117
166
|
});
|
|
118
167
|
|
|
168
|
+
// INFO log on successful test completion
|
|
169
|
+
log('INFO', 'All assertions passed - test successful', {
|
|
170
|
+
'service.name': 'graph-converter-service',
|
|
171
|
+
}, {
|
|
172
|
+
'test.status': 'passed',
|
|
173
|
+
'test.duration.ms': Date.now() - testSpan.startTime,
|
|
174
|
+
});
|
|
175
|
+
|
|
119
176
|
markTestPassed(testSpan);
|
|
120
177
|
} catch (error) {
|
|
178
|
+
// ERROR log on failure
|
|
179
|
+
log('ERROR', `Test failed: ${(error as Error).message}`, {
|
|
180
|
+
'service.name': 'graph-converter-service',
|
|
181
|
+
}, {
|
|
182
|
+
'test.status': 'failed',
|
|
183
|
+
'error.type': (error as Error).name,
|
|
184
|
+
});
|
|
185
|
+
|
|
121
186
|
addEvent(testSpan, 'test.failed', {
|
|
122
187
|
error: (error as Error).message,
|
|
123
188
|
});
|
|
@@ -125,6 +190,20 @@ describe('GraphConverter', () => {
|
|
|
125
190
|
throw error;
|
|
126
191
|
} finally {
|
|
127
192
|
endSpan(testSpan);
|
|
193
|
+
|
|
194
|
+
// Uncorrelated log after span ends (simulating cleanup/background task)
|
|
195
|
+
recordLog({
|
|
196
|
+
severity: 'DEBUG',
|
|
197
|
+
body: 'Test cleanup completed',
|
|
198
|
+
resource: {
|
|
199
|
+
'service.name': 'test-cleanup-service',
|
|
200
|
+
'cleanup.type': 'post-test',
|
|
201
|
+
},
|
|
202
|
+
attributes: {
|
|
203
|
+
'test.name': 'should convert simple config to nodes and edges',
|
|
204
|
+
},
|
|
205
|
+
// No traceId/spanId - this is an uncorrelated log
|
|
206
|
+
});
|
|
128
207
|
}
|
|
129
208
|
});
|
|
130
209
|
|