@dbt-tools/core 0.3.2
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/LICENSE +201 -0
- package/README.md +51 -0
- package/dist/analysis/analysis-snapshot.d.ts +145 -0
- package/dist/analysis/analysis-snapshot.js +615 -0
- package/dist/analysis/dependency-service.d.ts +56 -0
- package/dist/analysis/dependency-service.js +75 -0
- package/dist/analysis/execution-analyzer.d.ts +85 -0
- package/dist/analysis/execution-analyzer.js +245 -0
- package/dist/analysis/manifest-graph.d.ts +118 -0
- package/dist/analysis/manifest-graph.js +651 -0
- package/dist/analysis/run-results-search.d.ts +56 -0
- package/dist/analysis/run-results-search.js +127 -0
- package/dist/analysis/sql-analyzer.d.ts +30 -0
- package/dist/analysis/sql-analyzer.js +218 -0
- package/dist/browser.d.ts +11 -0
- package/dist/browser.js +17 -0
- package/dist/errors/error-handler.d.ts +26 -0
- package/dist/errors/error-handler.js +59 -0
- package/dist/formatting/field-filter.d.ts +29 -0
- package/dist/formatting/field-filter.js +112 -0
- package/dist/formatting/graph-export.d.ts +9 -0
- package/dist/formatting/graph-export.js +147 -0
- package/dist/formatting/output-formatter.d.ts +77 -0
- package/dist/formatting/output-formatter.js +160 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +38 -0
- package/dist/introspection/schema-generator.d.ts +29 -0
- package/dist/introspection/schema-generator.js +275 -0
- package/dist/io/artifact-loader.d.ts +27 -0
- package/dist/io/artifact-loader.js +142 -0
- package/dist/types.d.ts +43 -0
- package/dist/types.js +2 -0
- package/dist/validation/input-validator.d.ts +39 -0
- package/dist/validation/input-validator.js +167 -0
- package/dist/version.d.ts +28 -0
- package/dist/version.js +60 -0
- package/package.json +47 -0
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildAnalysisSnapshotFromParsedArtifacts = buildAnalysisSnapshotFromParsedArtifacts;
|
|
4
|
+
exports.buildAnalysisSnapshotFromArtifacts = buildAnalysisSnapshotFromArtifacts;
|
|
5
|
+
const run_results_search_1 = require("./run-results-search");
|
|
6
|
+
const execution_analyzer_1 = require("./execution-analyzer");
|
|
7
|
+
const manifest_graph_1 = require("./manifest-graph");
|
|
8
|
+
const RESOURCE_TYPE_ORDER = [
|
|
9
|
+
"model",
|
|
10
|
+
"source",
|
|
11
|
+
"test",
|
|
12
|
+
"metric",
|
|
13
|
+
"semantic_model",
|
|
14
|
+
"exposure",
|
|
15
|
+
"seed",
|
|
16
|
+
"snapshot",
|
|
17
|
+
"unit_test",
|
|
18
|
+
"analysis",
|
|
19
|
+
"macro",
|
|
20
|
+
];
|
|
21
|
+
function now() {
|
|
22
|
+
return performance.now();
|
|
23
|
+
}
|
|
24
|
+
function statusTone(status) {
|
|
25
|
+
const normalized = status === null || status === void 0 ? void 0 : status.trim().toLowerCase();
|
|
26
|
+
if (!normalized)
|
|
27
|
+
return "neutral";
|
|
28
|
+
if (["success", "pass", "passed"].includes(normalized)) {
|
|
29
|
+
return "positive";
|
|
30
|
+
}
|
|
31
|
+
if (["warn", "warning"].includes(normalized)) {
|
|
32
|
+
return "warning";
|
|
33
|
+
}
|
|
34
|
+
if (["error", "fail", "failed", "run error"].includes(normalized)) {
|
|
35
|
+
return "danger";
|
|
36
|
+
}
|
|
37
|
+
return "neutral";
|
|
38
|
+
}
|
|
39
|
+
function statusLabel(status) {
|
|
40
|
+
if (!status)
|
|
41
|
+
return "Unknown";
|
|
42
|
+
return status
|
|
43
|
+
.split(/[\s_-]+/)
|
|
44
|
+
.filter(Boolean)
|
|
45
|
+
.map((part) => part[0].toUpperCase() + part.slice(1).toLowerCase())
|
|
46
|
+
.join(" ");
|
|
47
|
+
}
|
|
48
|
+
function inferPackageNameFromUniqueId(uniqueId) {
|
|
49
|
+
var _a;
|
|
50
|
+
const parts = uniqueId.split(".");
|
|
51
|
+
if (parts.length < 2)
|
|
52
|
+
return "";
|
|
53
|
+
return (_a = parts[1]) !== null && _a !== void 0 ? _a : "";
|
|
54
|
+
}
|
|
55
|
+
function inferResourceTypeFromId(uniqueId) {
|
|
56
|
+
var _a;
|
|
57
|
+
const prefix = (_a = uniqueId.split(".")[0]) !== null && _a !== void 0 ? _a : "";
|
|
58
|
+
const known = new Set([
|
|
59
|
+
"model",
|
|
60
|
+
"test",
|
|
61
|
+
"unit_test",
|
|
62
|
+
"seed",
|
|
63
|
+
"snapshot",
|
|
64
|
+
"source",
|
|
65
|
+
"source_freshness",
|
|
66
|
+
"exposure",
|
|
67
|
+
"metric",
|
|
68
|
+
"semantic_model",
|
|
69
|
+
"analysis",
|
|
70
|
+
"macro",
|
|
71
|
+
]);
|
|
72
|
+
return known.has(prefix) ? prefix : "operation";
|
|
73
|
+
}
|
|
74
|
+
function resourceTypeLabel(resourceType) {
|
|
75
|
+
return resourceType
|
|
76
|
+
.split("_")
|
|
77
|
+
.map((part) => part[0].toUpperCase() + part.slice(1))
|
|
78
|
+
.join(" ");
|
|
79
|
+
}
|
|
80
|
+
function normalizeStringArray(value) {
|
|
81
|
+
return Array.isArray(value)
|
|
82
|
+
? value.filter((entry) => typeof entry === "string")
|
|
83
|
+
: [];
|
|
84
|
+
}
|
|
85
|
+
function buildMetricDefinition(attributes) {
|
|
86
|
+
const primaryMeasure = typeof attributes.metric_measure === "string"
|
|
87
|
+
? attributes.metric_measure
|
|
88
|
+
: null;
|
|
89
|
+
const measures = normalizeStringArray(attributes.metric_input_measures);
|
|
90
|
+
return {
|
|
91
|
+
kind: "metric",
|
|
92
|
+
label: typeof attributes.label === "string" ? attributes.label : null,
|
|
93
|
+
description: typeof attributes.description === "string"
|
|
94
|
+
? attributes.description
|
|
95
|
+
: null,
|
|
96
|
+
metricType: typeof attributes.metric_type === "string"
|
|
97
|
+
? attributes.metric_type
|
|
98
|
+
: null,
|
|
99
|
+
expression: typeof attributes.metric_expression === "string"
|
|
100
|
+
? attributes.metric_expression
|
|
101
|
+
: null,
|
|
102
|
+
sourceReference: typeof attributes.metric_source_reference === "string"
|
|
103
|
+
? attributes.metric_source_reference
|
|
104
|
+
: typeof attributes.metric_measure === "string"
|
|
105
|
+
? attributes.metric_measure
|
|
106
|
+
: null,
|
|
107
|
+
filters: normalizeStringArray(attributes.metric_filters),
|
|
108
|
+
timeGranularity: typeof attributes.metric_time_granularity === "string"
|
|
109
|
+
? attributes.metric_time_granularity
|
|
110
|
+
: null,
|
|
111
|
+
measures: measures.length > 0
|
|
112
|
+
? measures
|
|
113
|
+
: primaryMeasure != null
|
|
114
|
+
? [primaryMeasure]
|
|
115
|
+
: [],
|
|
116
|
+
metrics: normalizeStringArray(attributes.metric_input_metrics),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function buildSemanticModelDefinition(attributes) {
|
|
120
|
+
return {
|
|
121
|
+
kind: "semantic_model",
|
|
122
|
+
label: typeof attributes.label === "string" ? attributes.label : null,
|
|
123
|
+
description: typeof attributes.description === "string"
|
|
124
|
+
? attributes.description
|
|
125
|
+
: null,
|
|
126
|
+
sourceReference: typeof attributes.semantic_model_reference === "string"
|
|
127
|
+
? attributes.semantic_model_reference
|
|
128
|
+
: null,
|
|
129
|
+
defaultTimeDimension: typeof attributes.semantic_model_default_time_dimension === "string"
|
|
130
|
+
? attributes.semantic_model_default_time_dimension
|
|
131
|
+
: null,
|
|
132
|
+
entities: normalizeStringArray(attributes.semantic_model_entities),
|
|
133
|
+
measures: normalizeStringArray(attributes.semantic_model_measures),
|
|
134
|
+
dimensions: normalizeStringArray(attributes.semantic_model_dimensions),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function buildResourceDefinition(resourceType, attributes) {
|
|
138
|
+
if (resourceType === "metric") {
|
|
139
|
+
return buildMetricDefinition(attributes);
|
|
140
|
+
}
|
|
141
|
+
if (resourceType === "semantic_model") {
|
|
142
|
+
return buildSemanticModelDefinition(attributes);
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
function sortByResourceType(a, b) {
|
|
147
|
+
const aIndex = RESOURCE_TYPE_ORDER.indexOf(a);
|
|
148
|
+
const bIndex = RESOURCE_TYPE_ORDER.indexOf(b);
|
|
149
|
+
if (aIndex === -1 && bIndex === -1)
|
|
150
|
+
return a.localeCompare(b);
|
|
151
|
+
if (aIndex === -1)
|
|
152
|
+
return 1;
|
|
153
|
+
if (bIndex === -1)
|
|
154
|
+
return -1;
|
|
155
|
+
return aIndex - bIndex;
|
|
156
|
+
}
|
|
157
|
+
function sortResources(a, b) {
|
|
158
|
+
const typeOrder = sortByResourceType(a.resourceType, b.resourceType);
|
|
159
|
+
if (typeOrder !== 0)
|
|
160
|
+
return typeOrder;
|
|
161
|
+
return a.name.localeCompare(b.name);
|
|
162
|
+
}
|
|
163
|
+
function buildResourcesAndDependencyIndex(graph, executionById) {
|
|
164
|
+
const resources = [];
|
|
165
|
+
const dependencyIndex = {};
|
|
166
|
+
const graphologyGraph = graph.getGraph();
|
|
167
|
+
graphologyGraph.forEachNode((uniqueId, attributes) => {
|
|
168
|
+
const execution = executionById.get(uniqueId);
|
|
169
|
+
resources.push({
|
|
170
|
+
uniqueId,
|
|
171
|
+
name: String(attributes.name || uniqueId),
|
|
172
|
+
resourceType: String(attributes.resource_type || "unknown"),
|
|
173
|
+
packageName: String(attributes.package_name || ""),
|
|
174
|
+
path: typeof attributes.path === "string" ? attributes.path : null,
|
|
175
|
+
originalFilePath: typeof attributes.original_file_path === "string"
|
|
176
|
+
? attributes.original_file_path
|
|
177
|
+
: null,
|
|
178
|
+
patchPath: typeof attributes.patch_path === "string"
|
|
179
|
+
? attributes.patch_path
|
|
180
|
+
: null,
|
|
181
|
+
database: typeof attributes.database === "string" ? attributes.database : null,
|
|
182
|
+
schema: typeof attributes.schema === "string" ? attributes.schema : null,
|
|
183
|
+
description: typeof attributes.description === "string"
|
|
184
|
+
? attributes.description
|
|
185
|
+
: null,
|
|
186
|
+
compiledCode: typeof attributes.compiled_code === "string"
|
|
187
|
+
? attributes.compiled_code
|
|
188
|
+
: null,
|
|
189
|
+
rawCode: typeof attributes.raw_code === "string" ? attributes.raw_code : null,
|
|
190
|
+
definition: buildResourceDefinition(String(attributes.resource_type || "unknown"), attributes),
|
|
191
|
+
status: (execution === null || execution === void 0 ? void 0 : execution.status) ? statusLabel(execution.status) : null,
|
|
192
|
+
statusTone: statusTone(execution === null || execution === void 0 ? void 0 : execution.status),
|
|
193
|
+
executionTime: typeof (execution === null || execution === void 0 ? void 0 : execution.execution_time) === "number"
|
|
194
|
+
? execution.execution_time
|
|
195
|
+
: null,
|
|
196
|
+
threadId: typeof (execution === null || execution === void 0 ? void 0 : execution.thread_id) === "string" ? execution.thread_id : null,
|
|
197
|
+
});
|
|
198
|
+
const upstream = graph.getUpstream(uniqueId);
|
|
199
|
+
const downstream = graph.getDownstream(uniqueId);
|
|
200
|
+
dependencyIndex[uniqueId] = {
|
|
201
|
+
upstreamCount: upstream.length,
|
|
202
|
+
downstreamCount: downstream.length,
|
|
203
|
+
upstream: upstream.slice(0, 8).map((entry) => {
|
|
204
|
+
const attrs = graphologyGraph.getNodeAttributes(entry.nodeId);
|
|
205
|
+
return {
|
|
206
|
+
uniqueId: entry.nodeId,
|
|
207
|
+
name: String((attrs === null || attrs === void 0 ? void 0 : attrs.name) || entry.nodeId),
|
|
208
|
+
resourceType: String((attrs === null || attrs === void 0 ? void 0 : attrs.resource_type) || "unknown"),
|
|
209
|
+
depth: entry.depth,
|
|
210
|
+
};
|
|
211
|
+
}),
|
|
212
|
+
downstream: downstream.slice(0, 8).map((entry) => {
|
|
213
|
+
const attrs = graphologyGraph.getNodeAttributes(entry.nodeId);
|
|
214
|
+
return {
|
|
215
|
+
uniqueId: entry.nodeId,
|
|
216
|
+
name: String((attrs === null || attrs === void 0 ? void 0 : attrs.name) || entry.nodeId),
|
|
217
|
+
resourceType: String((attrs === null || attrs === void 0 ? void 0 : attrs.resource_type) || "unknown"),
|
|
218
|
+
depth: entry.depth,
|
|
219
|
+
};
|
|
220
|
+
}),
|
|
221
|
+
};
|
|
222
|
+
});
|
|
223
|
+
resources.sort(sortResources);
|
|
224
|
+
return { resources, dependencyIndex };
|
|
225
|
+
}
|
|
226
|
+
function buildResourceGroups(resources) {
|
|
227
|
+
var _a;
|
|
228
|
+
const groupedResources = new Map();
|
|
229
|
+
for (const resource of resources) {
|
|
230
|
+
const current = (_a = groupedResources.get(resource.resourceType)) !== null && _a !== void 0 ? _a : [];
|
|
231
|
+
current.push(resource);
|
|
232
|
+
groupedResources.set(resource.resourceType, current);
|
|
233
|
+
}
|
|
234
|
+
return [...groupedResources.entries()]
|
|
235
|
+
.sort(([a], [b]) => sortByResourceType(a, b))
|
|
236
|
+
.map(([resourceType, grouped]) => ({
|
|
237
|
+
resourceType,
|
|
238
|
+
label: resourceTypeLabel(resourceType),
|
|
239
|
+
count: grouped.length,
|
|
240
|
+
attentionCount: grouped.filter((r) => r.statusTone === "danger" || r.statusTone === "warning").length,
|
|
241
|
+
resources: grouped,
|
|
242
|
+
}));
|
|
243
|
+
}
|
|
244
|
+
function buildManifestEntryLookup(manifestJson) {
|
|
245
|
+
const lookup = new Map();
|
|
246
|
+
const addEntries = (value) => {
|
|
247
|
+
if (value == null || typeof value !== "object")
|
|
248
|
+
return;
|
|
249
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
250
|
+
if (entry != null && typeof entry === "object") {
|
|
251
|
+
lookup.set(key, entry);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
addEntries(manifestJson.nodes);
|
|
256
|
+
addEntries(manifestJson.sources);
|
|
257
|
+
addEntries(manifestJson.unit_tests);
|
|
258
|
+
if (manifestJson.disabled != null &&
|
|
259
|
+
typeof manifestJson.disabled === "object") {
|
|
260
|
+
for (const [key, entries] of Object.entries(manifestJson.disabled)) {
|
|
261
|
+
if (!Array.isArray(entries))
|
|
262
|
+
continue;
|
|
263
|
+
for (const entry of entries) {
|
|
264
|
+
if (entry != null && typeof entry === "object") {
|
|
265
|
+
lookup.set(key, entry);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return lookup;
|
|
271
|
+
}
|
|
272
|
+
function getManifestAttrs(uniqueId, graphologyGraph, manifestEntryLookup) {
|
|
273
|
+
if (graphologyGraph.hasNode(uniqueId)) {
|
|
274
|
+
return graphologyGraph.getNodeAttributes(uniqueId);
|
|
275
|
+
}
|
|
276
|
+
return manifestEntryLookup.get(uniqueId);
|
|
277
|
+
}
|
|
278
|
+
function resolveTestParentFromManifest(graph, graphologyGraph, manifestEntryLookup, testUniqueId) {
|
|
279
|
+
var _a, _b;
|
|
280
|
+
const upstream = graph.getUpstream(testUniqueId);
|
|
281
|
+
const direct = upstream.filter((u) => u.depth === 1);
|
|
282
|
+
const candidates = direct.length > 0 ? direct : upstream;
|
|
283
|
+
for (const u of candidates) {
|
|
284
|
+
const uAttrs = getManifestAttrs(u.nodeId, graphologyGraph, manifestEntryLookup);
|
|
285
|
+
const uType = String((_a = uAttrs === null || uAttrs === void 0 ? void 0 : uAttrs.resource_type) !== null && _a !== void 0 ? _a : "");
|
|
286
|
+
if (uType !== "test" && uType !== "unit_test" && uType !== "") {
|
|
287
|
+
return u.nodeId;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const testAttrs = manifestEntryLookup.get(testUniqueId);
|
|
291
|
+
const attachedNode = typeof (testAttrs === null || testAttrs === void 0 ? void 0 : testAttrs.attached_node) === "string"
|
|
292
|
+
? testAttrs.attached_node
|
|
293
|
+
: null;
|
|
294
|
+
if (attachedNode != null) {
|
|
295
|
+
return attachedNode;
|
|
296
|
+
}
|
|
297
|
+
const dependsOn = testAttrs === null || testAttrs === void 0 ? void 0 : testAttrs.depends_on;
|
|
298
|
+
if (Array.isArray(dependsOn === null || dependsOn === void 0 ? void 0 : dependsOn.nodes)) {
|
|
299
|
+
for (const parentId of dependsOn.nodes) {
|
|
300
|
+
if (typeof parentId !== "string")
|
|
301
|
+
continue;
|
|
302
|
+
const parentAttrs = getManifestAttrs(parentId, graphologyGraph, manifestEntryLookup);
|
|
303
|
+
const parentType = String((_b = parentAttrs === null || parentAttrs === void 0 ? void 0 : parentAttrs.resource_type) !== null && _b !== void 0 ? _b : inferResourceTypeFromId(parentId));
|
|
304
|
+
if (parentType !== "test" && parentType !== "unit_test") {
|
|
305
|
+
return parentId;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
function manifestDisplayPath(attrs) {
|
|
312
|
+
if (typeof (attrs === null || attrs === void 0 ? void 0 : attrs.original_file_path) === "string") {
|
|
313
|
+
return attrs.original_file_path;
|
|
314
|
+
}
|
|
315
|
+
if (typeof (attrs === null || attrs === void 0 ? void 0 : attrs.path) === "string") {
|
|
316
|
+
return attrs.path;
|
|
317
|
+
}
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
function enrichGanttItemRow(item, graph, graphologyGraph, manifestEntryLookup) {
|
|
321
|
+
const attrs = getManifestAttrs(item.unique_id, graphologyGraph, manifestEntryLookup);
|
|
322
|
+
const rtRaw = attrs === null || attrs === void 0 ? void 0 : attrs.resource_type;
|
|
323
|
+
const resourceType = typeof rtRaw === "string" && rtRaw
|
|
324
|
+
? rtRaw
|
|
325
|
+
: inferResourceTypeFromId(item.unique_id);
|
|
326
|
+
const parentId = resourceType === "test" || resourceType === "unit_test"
|
|
327
|
+
? resolveTestParentFromManifest(graph, graphologyGraph, manifestEntryLookup, item.unique_id)
|
|
328
|
+
: null;
|
|
329
|
+
const pkg = typeof (attrs === null || attrs === void 0 ? void 0 : attrs.package_name) === "string" && attrs.package_name.length > 0
|
|
330
|
+
? attrs.package_name
|
|
331
|
+
: inferPackageNameFromUniqueId(item.unique_id);
|
|
332
|
+
const mat = attrs === null || attrs === void 0 ? void 0 : attrs.materialized;
|
|
333
|
+
const materialized = typeof mat === "string" && mat.trim() !== "" ? mat : null;
|
|
334
|
+
return Object.assign(Object.assign({}, item), { resourceType, packageName: pkg, path: manifestDisplayPath(attrs), parentId,
|
|
335
|
+
materialized });
|
|
336
|
+
}
|
|
337
|
+
function statusSeverity(status) {
|
|
338
|
+
const normalized = status === null || status === void 0 ? void 0 : status.trim().toLowerCase();
|
|
339
|
+
if (!normalized)
|
|
340
|
+
return 0;
|
|
341
|
+
if (["error", "fail", "failed", "run error"].includes(normalized)) {
|
|
342
|
+
return 3;
|
|
343
|
+
}
|
|
344
|
+
if (["warn", "warning"].includes(normalized)) {
|
|
345
|
+
return 2;
|
|
346
|
+
}
|
|
347
|
+
if (["success", "pass", "passed"].includes(normalized)) {
|
|
348
|
+
return 1;
|
|
349
|
+
}
|
|
350
|
+
return 0;
|
|
351
|
+
}
|
|
352
|
+
function pickRepresentativeStatus(items) {
|
|
353
|
+
var _a, _b;
|
|
354
|
+
let bestStatus = (_b = (_a = items[0]) === null || _a === void 0 ? void 0 : _a.status) !== null && _b !== void 0 ? _b : "unknown";
|
|
355
|
+
let bestSeverity = statusSeverity(bestStatus);
|
|
356
|
+
for (const item of items.slice(1)) {
|
|
357
|
+
const severity = statusSeverity(item.status);
|
|
358
|
+
if (severity > bestSeverity) {
|
|
359
|
+
bestSeverity = severity;
|
|
360
|
+
bestStatus = item.status;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return bestStatus;
|
|
364
|
+
}
|
|
365
|
+
function compareGanttItems(a, b) {
|
|
366
|
+
const startDiff = a.start - b.start;
|
|
367
|
+
if (startDiff !== 0)
|
|
368
|
+
return startDiff;
|
|
369
|
+
const durationDiff = b.duration - a.duration;
|
|
370
|
+
if (durationDiff !== 0)
|
|
371
|
+
return durationDiff;
|
|
372
|
+
return a.name.localeCompare(b.name);
|
|
373
|
+
}
|
|
374
|
+
function buildSyntheticSourceRows(enrichedGanttData, graphologyGraph) {
|
|
375
|
+
var _a, _b;
|
|
376
|
+
const existingIds = new Set(enrichedGanttData.map((item) => item.unique_id));
|
|
377
|
+
const testsBySourceId = new Map();
|
|
378
|
+
for (const item of enrichedGanttData) {
|
|
379
|
+
if ((item.resourceType !== "test" && item.resourceType !== "unit_test") ||
|
|
380
|
+
item.parentId == null ||
|
|
381
|
+
!graphologyGraph.hasNode(item.parentId)) {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
const parentAttrs = graphologyGraph.getNodeAttributes(item.parentId);
|
|
385
|
+
if (String((_a = parentAttrs === null || parentAttrs === void 0 ? void 0 : parentAttrs.resource_type) !== null && _a !== void 0 ? _a : "") !== "source") {
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
const existing = (_b = testsBySourceId.get(item.parentId)) !== null && _b !== void 0 ? _b : [];
|
|
389
|
+
existing.push(item);
|
|
390
|
+
testsBySourceId.set(item.parentId, existing);
|
|
391
|
+
}
|
|
392
|
+
const syntheticRows = [];
|
|
393
|
+
for (const [sourceId, tests] of testsBySourceId.entries()) {
|
|
394
|
+
if (existingIds.has(sourceId) || tests.length === 0) {
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
const sourceAttrs = graphologyGraph.getNodeAttributes(sourceId);
|
|
398
|
+
const sortedTests = [...tests].sort(compareGanttItems);
|
|
399
|
+
const start = Math.min(...sortedTests.map((item) => item.start));
|
|
400
|
+
const end = Math.max(...sortedTests.map((item) => item.end));
|
|
401
|
+
syntheticRows.push({
|
|
402
|
+
unique_id: sourceId,
|
|
403
|
+
name: typeof (sourceAttrs === null || sourceAttrs === void 0 ? void 0 : sourceAttrs.name) === "string" && sourceAttrs.name.length > 0
|
|
404
|
+
? sourceAttrs.name
|
|
405
|
+
: sourceId,
|
|
406
|
+
start,
|
|
407
|
+
end,
|
|
408
|
+
duration: Math.max(0, end - start),
|
|
409
|
+
status: pickRepresentativeStatus(sortedTests),
|
|
410
|
+
resourceType: "source",
|
|
411
|
+
packageName: typeof (sourceAttrs === null || sourceAttrs === void 0 ? void 0 : sourceAttrs.package_name) === "string" &&
|
|
412
|
+
sourceAttrs.package_name.length > 0
|
|
413
|
+
? sourceAttrs.package_name
|
|
414
|
+
: inferPackageNameFromUniqueId(sourceId),
|
|
415
|
+
path: manifestDisplayPath(sourceAttrs),
|
|
416
|
+
parentId: null,
|
|
417
|
+
compileStart: null,
|
|
418
|
+
compileEnd: null,
|
|
419
|
+
executeStart: null,
|
|
420
|
+
executeEnd: null,
|
|
421
|
+
materialized: null,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
return syntheticRows.sort(compareGanttItems);
|
|
425
|
+
}
|
|
426
|
+
function buildStatusBreakdown(summary, nodeExecutions) {
|
|
427
|
+
var _a, _b;
|
|
428
|
+
const durationByStatus = new Map();
|
|
429
|
+
for (const execution of nodeExecutions) {
|
|
430
|
+
const status = statusLabel(execution.status);
|
|
431
|
+
durationByStatus.set(status, ((_a = durationByStatus.get(status)) !== null && _a !== void 0 ? _a : 0) + ((_b = execution.execution_time) !== null && _b !== void 0 ? _b : 0));
|
|
432
|
+
}
|
|
433
|
+
return Object.entries(summary.nodes_by_status)
|
|
434
|
+
.map(([status, count]) => {
|
|
435
|
+
var _a;
|
|
436
|
+
return ({
|
|
437
|
+
status: statusLabel(status),
|
|
438
|
+
count,
|
|
439
|
+
duration: (_a = durationByStatus.get(statusLabel(status))) !== null && _a !== void 0 ? _a : 0,
|
|
440
|
+
share: summary.total_nodes > 0 ? count / summary.total_nodes : 0,
|
|
441
|
+
tone: statusTone(status),
|
|
442
|
+
});
|
|
443
|
+
})
|
|
444
|
+
.sort((a, b) => b.count - a.count);
|
|
445
|
+
}
|
|
446
|
+
function buildTimelineAdjacency(graphologyGraph, executedUniqueIds) {
|
|
447
|
+
const out = {};
|
|
448
|
+
for (const id of executedUniqueIds) {
|
|
449
|
+
out[id] = graphologyGraph.hasNode(id)
|
|
450
|
+
? {
|
|
451
|
+
inbound: [...graphologyGraph.inboundNeighbors(id)],
|
|
452
|
+
outbound: [...graphologyGraph.outboundNeighbors(id)],
|
|
453
|
+
}
|
|
454
|
+
: { inbound: [], outbound: [] };
|
|
455
|
+
}
|
|
456
|
+
return out;
|
|
457
|
+
}
|
|
458
|
+
function buildThreadStats(executions) {
|
|
459
|
+
var _a, _b;
|
|
460
|
+
const threadAggregation = new Map();
|
|
461
|
+
for (const execution of executions) {
|
|
462
|
+
const threadId = (_a = execution.threadId) !== null && _a !== void 0 ? _a : "unknown";
|
|
463
|
+
const current = (_b = threadAggregation.get(threadId)) !== null && _b !== void 0 ? _b : {
|
|
464
|
+
count: 0,
|
|
465
|
+
totalExecutionTime: 0,
|
|
466
|
+
};
|
|
467
|
+
current.count += 1;
|
|
468
|
+
current.totalExecutionTime += execution.executionTime;
|
|
469
|
+
threadAggregation.set(threadId, current);
|
|
470
|
+
}
|
|
471
|
+
return [...threadAggregation.entries()]
|
|
472
|
+
.map(([threadId, value]) => ({
|
|
473
|
+
threadId,
|
|
474
|
+
count: value.count,
|
|
475
|
+
totalExecutionTime: value.totalExecutionTime,
|
|
476
|
+
}))
|
|
477
|
+
.sort((a, b) => b.totalExecutionTime - a.totalExecutionTime);
|
|
478
|
+
}
|
|
479
|
+
function buildProjectName(manifestJson) {
|
|
480
|
+
const metaMaybe = manifestJson.metadata;
|
|
481
|
+
if (metaMaybe !== null &&
|
|
482
|
+
typeof metaMaybe === "object" &&
|
|
483
|
+
"project_name" in metaMaybe &&
|
|
484
|
+
typeof metaMaybe.project_name === "string" &&
|
|
485
|
+
metaMaybe.project_name !== "") {
|
|
486
|
+
return metaMaybe.project_name;
|
|
487
|
+
}
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
function buildInvocationId(runResultsJson) {
|
|
491
|
+
const metadata = runResultsJson.metadata != null &&
|
|
492
|
+
typeof runResultsJson.metadata === "object"
|
|
493
|
+
? runResultsJson.metadata
|
|
494
|
+
: null;
|
|
495
|
+
return typeof (metadata === null || metadata === void 0 ? void 0 : metadata.invocation_id) === "string"
|
|
496
|
+
? metadata.invocation_id
|
|
497
|
+
: null;
|
|
498
|
+
}
|
|
499
|
+
function buildWarehouseType(manifestJson) {
|
|
500
|
+
const metaMaybe = manifestJson.metadata;
|
|
501
|
+
if (metaMaybe !== null &&
|
|
502
|
+
typeof metaMaybe === "object" &&
|
|
503
|
+
"adapter_type" in metaMaybe &&
|
|
504
|
+
typeof metaMaybe.adapter_type === "string" &&
|
|
505
|
+
metaMaybe.adapter_type !== "") {
|
|
506
|
+
return metaMaybe.adapter_type;
|
|
507
|
+
}
|
|
508
|
+
return null;
|
|
509
|
+
}
|
|
510
|
+
function buildAnalysisSnapshotFromParsedArtifacts(manifestJson, runResultsJson, manifest, runResults) {
|
|
511
|
+
var _a, _b, _c, _d;
|
|
512
|
+
const graphStart = now();
|
|
513
|
+
const graph = new manifest_graph_1.ManifestGraph(manifest);
|
|
514
|
+
const analyzer = new execution_analyzer_1.ExecutionAnalyzer(runResults, graph);
|
|
515
|
+
const graphBuildMs = now() - graphStart;
|
|
516
|
+
const snapshotStart = now();
|
|
517
|
+
const projectName = buildProjectName(manifestJson);
|
|
518
|
+
const warehouseType = buildWarehouseType(manifestJson);
|
|
519
|
+
const summary = analyzer.getSummary();
|
|
520
|
+
const manifestEntryLookup = buildManifestEntryLookup(manifestJson);
|
|
521
|
+
const ganttData = analyzer.getGanttData();
|
|
522
|
+
const nodeExecutions = analyzer.getNodeExecutions();
|
|
523
|
+
const startTimestamps = nodeExecutions
|
|
524
|
+
.map((e) => (e.started_at ? new Date(e.started_at).getTime() : null))
|
|
525
|
+
.filter((t) => t !== null);
|
|
526
|
+
const runStartedAt = startTimestamps.length > 0 ? Math.min(...startTimestamps) : null;
|
|
527
|
+
const bottlenecks = (0, run_results_search_1.detectBottlenecks)(summary.node_executions, {
|
|
528
|
+
mode: "top_n",
|
|
529
|
+
top: 5,
|
|
530
|
+
graph,
|
|
531
|
+
});
|
|
532
|
+
const graphSummary = graph.getSummary();
|
|
533
|
+
const ganttById = new Map(ganttData.map((item) => [item.unique_id, item]));
|
|
534
|
+
const executionById = new Map(nodeExecutions.map((e) => [e.unique_id, e]));
|
|
535
|
+
const { resources, dependencyIndex } = buildResourcesAndDependencyIndex(graph, executionById);
|
|
536
|
+
const graphologyGraph = graph.getGraph();
|
|
537
|
+
const enrichedGanttData = ganttData.map((item) => enrichGanttItemRow(item, graph, graphologyGraph, manifestEntryLookup));
|
|
538
|
+
const syntheticSourceRows = buildSyntheticSourceRows(enrichedGanttData, graphologyGraph);
|
|
539
|
+
const timelineGanttData = [...enrichedGanttData, ...syntheticSourceRows].sort(compareGanttItems);
|
|
540
|
+
const timelineAdjacency = buildTimelineAdjacency(graphologyGraph, timelineGanttData.map((g) => g.unique_id));
|
|
541
|
+
const executions = nodeExecutions
|
|
542
|
+
.map((execution) => {
|
|
543
|
+
var _a, _b, _c;
|
|
544
|
+
const attrs = graphologyGraph.hasNode(execution.unique_id)
|
|
545
|
+
? graphologyGraph.getNodeAttributes(execution.unique_id)
|
|
546
|
+
: undefined;
|
|
547
|
+
const gantt = ganttById.get(execution.unique_id);
|
|
548
|
+
return {
|
|
549
|
+
uniqueId: execution.unique_id,
|
|
550
|
+
name: String((attrs === null || attrs === void 0 ? void 0 : attrs.name) || execution.unique_id),
|
|
551
|
+
resourceType: String((attrs === null || attrs === void 0 ? void 0 : attrs.resource_type) || inferResourceTypeFromId(execution.unique_id)),
|
|
552
|
+
packageName: (() => {
|
|
553
|
+
const pkg = attrs === null || attrs === void 0 ? void 0 : attrs.package_name;
|
|
554
|
+
if (typeof pkg === "string" && pkg.length > 0)
|
|
555
|
+
return pkg;
|
|
556
|
+
return inferPackageNameFromUniqueId(execution.unique_id);
|
|
557
|
+
})(),
|
|
558
|
+
path: typeof (attrs === null || attrs === void 0 ? void 0 : attrs.original_file_path) === "string"
|
|
559
|
+
? attrs.original_file_path
|
|
560
|
+
: typeof (attrs === null || attrs === void 0 ? void 0 : attrs.path) === "string"
|
|
561
|
+
? attrs.path
|
|
562
|
+
: null,
|
|
563
|
+
status: statusLabel(execution.status),
|
|
564
|
+
statusTone: statusTone(execution.status),
|
|
565
|
+
executionTime: (_a = execution.execution_time) !== null && _a !== void 0 ? _a : 0,
|
|
566
|
+
threadId: typeof execution.thread_id === "string" ? execution.thread_id : null,
|
|
567
|
+
start: (_b = gantt === null || gantt === void 0 ? void 0 : gantt.start) !== null && _b !== void 0 ? _b : null,
|
|
568
|
+
end: (_c = gantt === null || gantt === void 0 ? void 0 : gantt.end) !== null && _c !== void 0 ? _c : null,
|
|
569
|
+
};
|
|
570
|
+
})
|
|
571
|
+
.sort((a, b) => b.executionTime - a.executionTime);
|
|
572
|
+
const resourceGroups = buildResourceGroups(resources);
|
|
573
|
+
const statusBreakdown = buildStatusBreakdown(summary, nodeExecutions);
|
|
574
|
+
const threadStats = buildThreadStats(executions);
|
|
575
|
+
const selectedResourceId = (_d = (_b = (_a = resources.find((r) => r.resourceType === "model")) === null || _a === void 0 ? void 0 : _a.uniqueId) !== null && _b !== void 0 ? _b : (_c = resources[0]) === null || _c === void 0 ? void 0 : _c.uniqueId) !== null && _d !== void 0 ? _d : null;
|
|
576
|
+
const analysis = {
|
|
577
|
+
summary,
|
|
578
|
+
projectName,
|
|
579
|
+
warehouseType,
|
|
580
|
+
runStartedAt,
|
|
581
|
+
ganttData: timelineGanttData,
|
|
582
|
+
bottlenecks,
|
|
583
|
+
graphSummary: {
|
|
584
|
+
totalNodes: graphSummary.total_nodes,
|
|
585
|
+
totalEdges: graphSummary.total_edges,
|
|
586
|
+
hasCycles: graphSummary.has_cycles,
|
|
587
|
+
nodesByType: graphSummary.nodes_by_type,
|
|
588
|
+
},
|
|
589
|
+
resources,
|
|
590
|
+
resourceGroups,
|
|
591
|
+
executions,
|
|
592
|
+
statusBreakdown,
|
|
593
|
+
threadStats,
|
|
594
|
+
dependencyIndex,
|
|
595
|
+
timelineAdjacency,
|
|
596
|
+
selectedResourceId,
|
|
597
|
+
invocationId: buildInvocationId(runResultsJson),
|
|
598
|
+
};
|
|
599
|
+
return {
|
|
600
|
+
analysis,
|
|
601
|
+
timings: {
|
|
602
|
+
graphBuildMs,
|
|
603
|
+
snapshotBuildMs: now() - snapshotStart,
|
|
604
|
+
},
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
async function buildAnalysisSnapshotFromArtifacts(manifestJson, runResultsJson) {
|
|
608
|
+
const [{ parseManifest }, { parseRunResults }] = await Promise.all([
|
|
609
|
+
import("dbt-artifacts-parser/manifest"),
|
|
610
|
+
import("dbt-artifacts-parser/run_results"),
|
|
611
|
+
]);
|
|
612
|
+
const manifest = parseManifest(manifestJson);
|
|
613
|
+
const runResults = parseRunResults(runResultsJson);
|
|
614
|
+
return buildAnalysisSnapshotFromParsedArtifacts(manifestJson, runResultsJson, manifest, runResults).analysis;
|
|
615
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ManifestGraph } from "./manifest-graph";
|
|
2
|
+
/**
|
|
3
|
+
* Dependency analysis result (flat format)
|
|
4
|
+
*/
|
|
5
|
+
export interface DependencyResult {
|
|
6
|
+
resource_id: string;
|
|
7
|
+
direction: "upstream" | "downstream";
|
|
8
|
+
build_order?: boolean;
|
|
9
|
+
dependencies: Array<{
|
|
10
|
+
unique_id: string;
|
|
11
|
+
resource_type: string;
|
|
12
|
+
name: string;
|
|
13
|
+
package_name: string;
|
|
14
|
+
depth: number;
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}>;
|
|
17
|
+
count: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Nested dependency node for tree format
|
|
21
|
+
*/
|
|
22
|
+
export interface DependencyTreeNode {
|
|
23
|
+
unique_id: string;
|
|
24
|
+
resource_type: string;
|
|
25
|
+
name: string;
|
|
26
|
+
package_name: string;
|
|
27
|
+
depth: number;
|
|
28
|
+
dependencies: DependencyTreeNode[];
|
|
29
|
+
[key: string]: unknown;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Dependency result in tree format
|
|
33
|
+
*/
|
|
34
|
+
export interface DependencyResultTree {
|
|
35
|
+
resource_id: string;
|
|
36
|
+
direction: "upstream" | "downstream";
|
|
37
|
+
dependencies: DependencyTreeNode[];
|
|
38
|
+
count: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* DependencyService wraps ManifestGraph dependency methods with formatting
|
|
42
|
+
* and field filtering capabilities.
|
|
43
|
+
*/
|
|
44
|
+
export declare class DependencyService {
|
|
45
|
+
/**
|
|
46
|
+
* Get dependencies for a resource with optional field filtering, depth limit, output format, and build order.
|
|
47
|
+
* @param depth - Optional max traversal depth; 1 = immediate neighbors, undefined = all levels
|
|
48
|
+
* @param format - Output structure: flat list or nested tree
|
|
49
|
+
* @param buildOrder - When true and direction is upstream, return dependencies in topological build order
|
|
50
|
+
*/
|
|
51
|
+
static getDependencies(graph: ManifestGraph, resourceId: string, direction: "upstream" | "downstream", fields?: string, depth?: number, format?: "flat" | "tree", buildOrder?: boolean): DependencyResult | DependencyResultTree;
|
|
52
|
+
/**
|
|
53
|
+
* Build nested tree from BFS entries with parent tracking
|
|
54
|
+
*/
|
|
55
|
+
private static getDependenciesTree;
|
|
56
|
+
}
|