@finos/legend-extension-dsl-data-space 10.4.248 → 10.4.249
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/lib/components/DataSpaceLegendAIIntegration.d.ts +1 -18
- package/lib/components/DataSpaceLegendAIIntegration.d.ts.map +1 -1
- package/lib/components/DataSpaceLegendAIIntegration.js +9 -350
- package/lib/components/DataSpaceLegendAIIntegration.js.map +1 -1
- package/lib/components/DataSpaceLegendAISchema.d.ts +43 -0
- package/lib/components/DataSpaceLegendAISchema.d.ts.map +1 -0
- package/lib/components/DataSpaceLegendAISchema.js +397 -0
- package/lib/components/DataSpaceLegendAISchema.js.map +1 -0
- package/lib/components/index.d.ts +1 -0
- package/lib/components/index.d.ts.map +1 -1
- package/lib/components/index.js +1 -0
- package/lib/components/index.js.map +1 -1
- package/lib/graph-manager/action/analytics/DataSpaceAnalysis.d.ts +5 -0
- package/lib/graph-manager/action/analytics/DataSpaceAnalysis.d.ts.map +1 -1
- package/lib/graph-manager/action/analytics/DataSpaceAnalysis.js +5 -0
- package/lib/graph-manager/action/analytics/DataSpaceAnalysis.js.map +1 -1
- package/lib/graph-manager/protocol/pure/DSL_DataSpace_PureProtocolProcessorPlugin.d.ts.map +1 -1
- package/lib/graph-manager/protocol/pure/DSL_DataSpace_PureProtocolProcessorPlugin.js +10 -10
- package/lib/graph-manager/protocol/pure/DSL_DataSpace_PureProtocolProcessorPlugin.js.map +1 -1
- package/lib/graph-manager/protocol/pure/v1/V1_DSL_DataSpace_PureGraphManagerExtension.d.ts.map +1 -1
- package/lib/graph-manager/protocol/pure/v1/V1_DSL_DataSpace_PureGraphManagerExtension.js +18 -5
- package/lib/graph-manager/protocol/pure/v1/V1_DSL_DataSpace_PureGraphManagerExtension.js.map +1 -1
- package/lib/graph-manager/protocol/pure/v1/engine/analytics/V1_DataSpaceAnalysis.d.ts +8 -0
- package/lib/graph-manager/protocol/pure/v1/engine/analytics/V1_DataSpaceAnalysis.d.ts.map +1 -1
- package/lib/graph-manager/protocol/pure/v1/engine/analytics/V1_DataSpaceAnalysis.js +13 -3
- package/lib/graph-manager/protocol/pure/v1/engine/analytics/V1_DataSpaceAnalysis.js.map +1 -1
- package/lib/index.css +1 -1
- package/lib/package.json +1 -1
- package/package.json +6 -6
- package/src/components/DataSpaceLegendAIIntegration.tsx +18 -482
- package/src/components/DataSpaceLegendAISchema.ts +562 -0
- package/src/components/index.ts +1 -0
- package/src/graph-manager/action/analytics/DataSpaceAnalysis.ts +6 -0
- package/src/graph-manager/protocol/pure/DSL_DataSpace_PureProtocolProcessorPlugin.ts +16 -13
- package/src/graph-manager/protocol/pure/v1/V1_DSL_DataSpace_PureGraphManagerExtension.ts +35 -14
- package/src/graph-manager/protocol/pure/v1/engine/analytics/V1_DataSpaceAnalysis.ts +33 -17
- package/tsconfig.json +1 -0
|
@@ -16,59 +16,29 @@
|
|
|
16
16
|
|
|
17
17
|
import { useState, useEffect, useMemo, useCallback } from 'react';
|
|
18
18
|
import { observer } from 'mobx-react-lite';
|
|
19
|
-
import {
|
|
20
|
-
type QueryExplicitExecutionContextInfo,
|
|
21
|
-
type MappingModelCoverageAnalysisResult,
|
|
22
|
-
Enumeration,
|
|
23
|
-
PRIMITIVE_TYPE,
|
|
24
|
-
RelationalDatabaseTableSpecification,
|
|
25
|
-
} from '@finos/legend-graph';
|
|
19
|
+
import { type QueryExplicitExecutionContextInfo } from '@finos/legend-graph';
|
|
26
20
|
import {
|
|
27
21
|
LegendAIChat,
|
|
28
22
|
LegendAIErrorBoundary,
|
|
29
23
|
findLegendAIPlugin,
|
|
30
|
-
extractParameterSchemas,
|
|
31
|
-
buildPropertyDocIndex,
|
|
32
|
-
enrichColumnsFromElementDocs,
|
|
33
|
-
inferServiceRelationshipsFromAssociations,
|
|
34
|
-
extractLambdaPreFilters,
|
|
35
|
-
extractModelContext,
|
|
36
24
|
LegendAIChatTelemetryEventType,
|
|
37
25
|
bridgeLegendAIServices,
|
|
38
26
|
useLegendAIChatTelemetryLogger,
|
|
39
27
|
LegendAIMessageFeedbackRating,
|
|
40
|
-
type TDSColumnSchema,
|
|
41
28
|
type TDSServiceSchema,
|
|
42
29
|
type LegendAIConfig,
|
|
43
|
-
type LegendAIProductMetadata,
|
|
44
30
|
type LegendAIOrchestratorDataProductCoordinates,
|
|
45
|
-
type LegendAIServiceSummary,
|
|
46
31
|
type LegendAIModelContext,
|
|
47
|
-
type LegendAIModelEntity,
|
|
48
|
-
type LegendAIModelProperty,
|
|
49
|
-
type LegendAIModelAssociation,
|
|
50
|
-
type LegendAIExecutableInfo,
|
|
51
|
-
type LegendAIColumnPropertyMapping,
|
|
52
|
-
type LegendAIParameterInfo,
|
|
53
32
|
type LegendAIMessageFeedback,
|
|
54
33
|
} from '@finos/legend-lego/legend-ai';
|
|
55
|
-
import {
|
|
56
|
-
import { assertErrorThrown, LogEvent } from '@finos/legend-shared';
|
|
34
|
+
import { LogEvent } from '@finos/legend-shared';
|
|
57
35
|
import type { DataSpaceViewerState } from '../stores/DataSpaceViewerState.js';
|
|
58
36
|
import { DSL_DATASPACE_EVENT } from '../__lib__/DSL_DataSpace_Event.js';
|
|
59
37
|
import {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
} from '../graph-manager/action/analytics/DataSpaceAnalysis.js';
|
|
65
|
-
import {
|
|
66
|
-
DataSpaceSupportEmail,
|
|
67
|
-
DataSpaceSupportCombinedInfo,
|
|
68
|
-
} from '../graph/metamodel/pure/model/packageableElements/dataSpace/DSL_DataSpace_DataSpace.js';
|
|
69
|
-
|
|
70
|
-
const MAX_QUERY_SCAN_LENGTH = 5_000;
|
|
71
|
-
const MAX_QUERY_TEMPLATE_LENGTH = 1000;
|
|
38
|
+
buildDataSpaceModelContext,
|
|
39
|
+
extractTDSServicesFromDataSpace,
|
|
40
|
+
extractMetadataFromDataSpace,
|
|
41
|
+
} from './DataSpaceLegendAISchema.js';
|
|
72
42
|
|
|
73
43
|
const DATA_SPACE_TELEMETRY_EVENT_KEYS: Record<
|
|
74
44
|
LegendAIChatTelemetryEventType,
|
|
@@ -97,387 +67,6 @@ const DATA_SPACE_TELEMETRY_EVENT_KEYS: Record<
|
|
|
97
67
|
[LegendAIChatTelemetryEventType.OPEN_IN_DATACUBE_CLICKED]:
|
|
98
68
|
DSL_DATASPACE_EVENT.LEGEND_AI_OPEN_IN_DATACUBE_CLICKED,
|
|
99
69
|
};
|
|
100
|
-
const ENTITY_PATH_PATTERN =
|
|
101
|
-
/(?<entityPath>[A-Za-z_]\w{0,63}(?:::[A-Za-z_]\w{0,63}){0,10})\.all\(\)/g;
|
|
102
|
-
|
|
103
|
-
// ────────────────────────────────────────────────────────────────────────────
|
|
104
|
-
// Diagram-based model context extraction (fallback when elementDocs is sparse)
|
|
105
|
-
// ────────────────────────────────────────────────────────────────────────────
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Extracts a {@link LegendAIModelContext} from diagram metamodel objects.
|
|
109
|
-
* Diagrams always contain the actual Class metamodel references so this
|
|
110
|
-
* works even when elementDocs documentation is empty or incomplete.
|
|
111
|
-
*/
|
|
112
|
-
export function extractModelContextFromDiagrams(
|
|
113
|
-
diagrams: DiagramAnalysisResult[],
|
|
114
|
-
): LegendAIModelContext {
|
|
115
|
-
const entityMap = new Map<string, LegendAIModelEntity>();
|
|
116
|
-
const associations: LegendAIModelAssociation[] = [];
|
|
117
|
-
const enumPaths = new Map<
|
|
118
|
-
string,
|
|
119
|
-
{ path: string; name: string; values: string[] }
|
|
120
|
-
>();
|
|
121
|
-
|
|
122
|
-
for (const { diagram } of diagrams) {
|
|
123
|
-
for (const classView of diagram.classViews) {
|
|
124
|
-
const cls = classView.class.value;
|
|
125
|
-
if (entityMap.has(cls.path)) {
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
const properties: LegendAIModelProperty[] = cls.properties.map((prop) => {
|
|
129
|
-
const rawType = prop.genericType.value.rawType;
|
|
130
|
-
const typePath = rawType.path;
|
|
131
|
-
if (rawType instanceof Enumeration && !enumPaths.has(typePath)) {
|
|
132
|
-
enumPaths.set(typePath, {
|
|
133
|
-
path: typePath,
|
|
134
|
-
name: rawType.name,
|
|
135
|
-
values: rawType.values.map((v) => v.name),
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
return {
|
|
139
|
-
name: prop.name,
|
|
140
|
-
type: typePath,
|
|
141
|
-
isCollection:
|
|
142
|
-
prop.multiplicity.upperBound === undefined ||
|
|
143
|
-
prop.multiplicity.upperBound > 1,
|
|
144
|
-
isOptional: prop.multiplicity.lowerBound === 0,
|
|
145
|
-
};
|
|
146
|
-
});
|
|
147
|
-
const entity: LegendAIModelEntity = {
|
|
148
|
-
path: cls.path,
|
|
149
|
-
name: cls.name,
|
|
150
|
-
properties,
|
|
151
|
-
};
|
|
152
|
-
entityMap.set(cls.path, entity);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
for (const assocView of diagram.associationViews) {
|
|
156
|
-
const assoc = assocView.association.value;
|
|
157
|
-
const [propA, propB] = assoc.properties;
|
|
158
|
-
associations.push({
|
|
159
|
-
name: assoc.name,
|
|
160
|
-
leftEntity: assocView.from.classView.value.class.value.path,
|
|
161
|
-
leftProperty: propA.name,
|
|
162
|
-
rightEntity: assocView.to.classView.value.class.value.path,
|
|
163
|
-
rightProperty: propB.name,
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const result: LegendAIModelContext = {
|
|
169
|
-
entities: Array.from(entityMap.values()),
|
|
170
|
-
associations,
|
|
171
|
-
};
|
|
172
|
-
if (enumPaths.size > 0) {
|
|
173
|
-
result.enumerations = Array.from(enumPaths.values());
|
|
174
|
-
}
|
|
175
|
-
return result;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Enriches a {@link LegendAIModelContext} with mapping coverage data.
|
|
180
|
-
* Marks entities that are root-mapped (directly queryable by the orchestrator)
|
|
181
|
-
* so entity resolution can prefer them.
|
|
182
|
-
*/
|
|
183
|
-
export function enrichModelContextWithMappingCoverage(
|
|
184
|
-
modelContext: LegendAIModelContext,
|
|
185
|
-
mappingCoverage: MappingModelCoverageAnalysisResult | undefined,
|
|
186
|
-
): void {
|
|
187
|
-
if (!mappingCoverage) {
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
for (const entity of modelContext.entities) {
|
|
191
|
-
const mapped = mappingCoverage.mappedEntities.find(
|
|
192
|
-
(me) => me.path === entity.path,
|
|
193
|
-
);
|
|
194
|
-
if (mapped?.info?.isRootEntity) {
|
|
195
|
-
entity.isRootMapped = true;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Builds column-to-property mappings by matching TDS result column names
|
|
202
|
-
* to model property names on the root entity.
|
|
203
|
-
* Uses case-insensitive, whitespace/underscore-normalized comparison.
|
|
204
|
-
*/
|
|
205
|
-
function buildColumnPropertyMappings(
|
|
206
|
-
columns: string[],
|
|
207
|
-
rootEntity: LegendAIModelEntity | undefined,
|
|
208
|
-
): LegendAIColumnPropertyMapping[] | undefined {
|
|
209
|
-
if (!rootEntity || rootEntity.properties.length === 0) {
|
|
210
|
-
return undefined;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const normalizeName = (name: string): string =>
|
|
214
|
-
name.toLowerCase().replaceAll(/[\s_]/g, '');
|
|
215
|
-
|
|
216
|
-
const propLookup = new Map<string, string>();
|
|
217
|
-
for (const prop of rootEntity.properties) {
|
|
218
|
-
propLookup.set(normalizeName(prop.name), prop.name);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const mappings: LegendAIColumnPropertyMapping[] = [];
|
|
222
|
-
for (const colName of columns) {
|
|
223
|
-
const propName = propLookup.get(normalizeName(colName));
|
|
224
|
-
if (propName && propName !== colName) {
|
|
225
|
-
mappings.push({ columnName: colName, propertyPath: propName });
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return mappings.length > 0 ? mappings : undefined;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function collectReferencedEntityPaths(queryStr: string): string[] {
|
|
233
|
-
const safeQuery = queryStr.slice(0, MAX_QUERY_SCAN_LENGTH);
|
|
234
|
-
// Reset lastIndex defensively — the regex is module-scoped and reused.
|
|
235
|
-
ENTITY_PATH_PATTERN.lastIndex = 0;
|
|
236
|
-
const paths: string[] = [];
|
|
237
|
-
const seen = new Set<string>();
|
|
238
|
-
let match: RegExpExecArray | null;
|
|
239
|
-
while ((match = ENTITY_PATH_PATTERN.exec(safeQuery)) !== null) {
|
|
240
|
-
const path = match.groups?.entityPath;
|
|
241
|
-
if (path && !seen.has(path)) {
|
|
242
|
-
seen.add(path);
|
|
243
|
-
paths.push(path);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
return paths;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function buildExecutableInfo(
|
|
250
|
-
exec: DataSpaceExecutableAnalysisResult,
|
|
251
|
-
entityMap: Map<string, LegendAIModelEntity>,
|
|
252
|
-
serviceParameters: Map<string, LegendAIParameterInfo[]>,
|
|
253
|
-
): LegendAIExecutableInfo | undefined {
|
|
254
|
-
if (
|
|
255
|
-
!(
|
|
256
|
-
exec.info instanceof DataSpaceServiceExecutableInfo ||
|
|
257
|
-
exec.info instanceof DataSpaceMultiExecutionServiceExecutableInfo
|
|
258
|
-
)
|
|
259
|
-
) {
|
|
260
|
-
return undefined;
|
|
261
|
-
}
|
|
262
|
-
const queryStr = exec.info.query;
|
|
263
|
-
const referencedEntityPaths = collectReferencedEntityPaths(queryStr);
|
|
264
|
-
const rootEntityPath = referencedEntityPaths[0];
|
|
265
|
-
if (!rootEntityPath) {
|
|
266
|
-
return undefined;
|
|
267
|
-
}
|
|
268
|
-
const info: LegendAIExecutableInfo = {
|
|
269
|
-
title: exec.title,
|
|
270
|
-
rootEntityPath,
|
|
271
|
-
queryTemplate:
|
|
272
|
-
queryStr.length > MAX_QUERY_TEMPLATE_LENGTH
|
|
273
|
-
? `${queryStr.slice(0, MAX_QUERY_TEMPLATE_LENGTH)}...`
|
|
274
|
-
: queryStr,
|
|
275
|
-
};
|
|
276
|
-
if (referencedEntityPaths.length > 1) {
|
|
277
|
-
info.referencedEntityPaths = referencedEntityPaths;
|
|
278
|
-
}
|
|
279
|
-
if (exec.description !== undefined) {
|
|
280
|
-
info.description = exec.description;
|
|
281
|
-
}
|
|
282
|
-
const reqParams = serviceParameters.get(exec.title);
|
|
283
|
-
if (reqParams && reqParams.length > 0) {
|
|
284
|
-
info.requiredParameters = reqParams;
|
|
285
|
-
}
|
|
286
|
-
if (exec.result instanceof DataSpaceExecutableTDSResult) {
|
|
287
|
-
const columnNames = exec.result.columns.map((c) => c.name);
|
|
288
|
-
const colMappings = buildColumnPropertyMappings(
|
|
289
|
-
columnNames,
|
|
290
|
-
entityMap.get(rootEntityPath),
|
|
291
|
-
);
|
|
292
|
-
if (colMappings) {
|
|
293
|
-
info.columnPropertyMappings = colMappings;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
return info;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
export function extractExecutableInfo(
|
|
300
|
-
executables: DataSpaceExecutableAnalysisResult[],
|
|
301
|
-
modelContext: LegendAIModelContext,
|
|
302
|
-
services: TDSServiceSchema[],
|
|
303
|
-
): LegendAIExecutableInfo[] {
|
|
304
|
-
const entityMap = new Map(modelContext.entities.map((e) => [e.path, e]));
|
|
305
|
-
const serviceParameters = new Map<string, LegendAIParameterInfo[]>();
|
|
306
|
-
for (const svc of services) {
|
|
307
|
-
if (svc.parameterSchemas && svc.parameterSchemas.length > 0) {
|
|
308
|
-
serviceParameters.set(
|
|
309
|
-
svc.title,
|
|
310
|
-
svc.parameterSchemas.map((p) => ({
|
|
311
|
-
name: p.name,
|
|
312
|
-
type: p.type ?? PRIMITIVE_TYPE.STRING,
|
|
313
|
-
})),
|
|
314
|
-
);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
const result: LegendAIExecutableInfo[] = [];
|
|
318
|
-
for (const exec of executables) {
|
|
319
|
-
const info = buildExecutableInfo(exec, entityMap, serviceParameters);
|
|
320
|
-
if (!info) {
|
|
321
|
-
continue;
|
|
322
|
-
}
|
|
323
|
-
result.push(info);
|
|
324
|
-
const queryableEntityPaths = info.referencedEntityPaths ?? [
|
|
325
|
-
info.rootEntityPath,
|
|
326
|
-
];
|
|
327
|
-
for (const entityPath of queryableEntityPaths) {
|
|
328
|
-
const entity = entityMap.get(entityPath);
|
|
329
|
-
if (entity) {
|
|
330
|
-
entity.isQueryable = true;
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
return result;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
export async function extractTDSServicesFromDataSpace(
|
|
338
|
-
viewerState: DataSpaceViewerState,
|
|
339
|
-
): Promise<TDSServiceSchema[]> {
|
|
340
|
-
const executables = viewerState.dataSpaceAnalysisResult.executables;
|
|
341
|
-
const graphManager = viewerState.graphManagerState.graphManager;
|
|
342
|
-
|
|
343
|
-
const nested = await Promise.all(
|
|
344
|
-
executables.map(async (exec): Promise<TDSServiceSchema[]> => {
|
|
345
|
-
if (
|
|
346
|
-
!(
|
|
347
|
-
exec.info instanceof DataSpaceServiceExecutableInfo ||
|
|
348
|
-
exec.info instanceof DataSpaceMultiExecutionServiceExecutableInfo
|
|
349
|
-
) ||
|
|
350
|
-
!(exec.result instanceof DataSpaceExecutableTDSResult)
|
|
351
|
-
) {
|
|
352
|
-
return [];
|
|
353
|
-
}
|
|
354
|
-
const info = exec.info;
|
|
355
|
-
const tdsResult = exec.result;
|
|
356
|
-
|
|
357
|
-
const { parameters, parameterSchemas, parameterExtractionFailed } =
|
|
358
|
-
await extractParameterSchemas(
|
|
359
|
-
info.query,
|
|
360
|
-
graphManager,
|
|
361
|
-
viewerState.graphManagerState,
|
|
362
|
-
);
|
|
363
|
-
|
|
364
|
-
// Extract hardcoded pre-filter constraints from the lambda
|
|
365
|
-
let preFilters: TDSServiceSchema['preFilters'];
|
|
366
|
-
try {
|
|
367
|
-
const rawLambda = await graphManager.pureCodeToLambda(info.query);
|
|
368
|
-
const extracted = extractLambdaPreFilters(rawLambda.body);
|
|
369
|
-
if (extracted.length > 0) {
|
|
370
|
-
preFilters = extracted;
|
|
371
|
-
}
|
|
372
|
-
} catch (error) {
|
|
373
|
-
assertErrorThrown(error);
|
|
374
|
-
// pre-filter extraction is best-effort — a service without pre-filters
|
|
375
|
-
// still works, the AI just won't know about hardcoded constraints.
|
|
376
|
-
viewerState.applicationStore.logService.debug(
|
|
377
|
-
LogEvent.create(DSL_DATASPACE_EVENT.ERROR_EXTRACT_LEGEND_AI_SERVICES),
|
|
378
|
-
error,
|
|
379
|
-
);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
const entry: TDSServiceSchema = {
|
|
383
|
-
title: exec.title,
|
|
384
|
-
pattern: info.pattern,
|
|
385
|
-
columns: tdsResult.columns.map((col) => {
|
|
386
|
-
const column: TDSColumnSchema = { name: col.name };
|
|
387
|
-
if (col.type !== undefined) {
|
|
388
|
-
column.type = col.type;
|
|
389
|
-
}
|
|
390
|
-
if (col.relationalType !== undefined) {
|
|
391
|
-
column.relationalType = col.relationalType;
|
|
392
|
-
}
|
|
393
|
-
if (col.documentation !== undefined) {
|
|
394
|
-
column.documentation = col.documentation;
|
|
395
|
-
}
|
|
396
|
-
if (col.sampleValues !== undefined) {
|
|
397
|
-
column.sampleValues = col.sampleValues;
|
|
398
|
-
}
|
|
399
|
-
return column;
|
|
400
|
-
}),
|
|
401
|
-
parameters,
|
|
402
|
-
...(parameterSchemas.length > 0 ? { parameterSchemas } : {}),
|
|
403
|
-
...(parameterExtractionFailed
|
|
404
|
-
? { parameterExtractionFailed: true }
|
|
405
|
-
: {}),
|
|
406
|
-
...(preFilters ? { preFilters } : {}),
|
|
407
|
-
};
|
|
408
|
-
if (exec.description !== undefined) {
|
|
409
|
-
entry.description = exec.description;
|
|
410
|
-
}
|
|
411
|
-
return [entry];
|
|
412
|
-
}),
|
|
413
|
-
);
|
|
414
|
-
const services = nested.flat();
|
|
415
|
-
|
|
416
|
-
// Enrich columns with documentation and nullability from elementDocs
|
|
417
|
-
const propIndex = buildPropertyDocIndex(
|
|
418
|
-
viewerState.dataSpaceAnalysisResult.elementDocs,
|
|
419
|
-
);
|
|
420
|
-
if (propIndex.size > 0) {
|
|
421
|
-
for (const svc of services) {
|
|
422
|
-
enrichColumnsFromElementDocs(svc.columns, propIndex);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
return services;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
function extractMetadataFromDataSpace(
|
|
430
|
-
viewerState: DataSpaceViewerState,
|
|
431
|
-
coordinates: string,
|
|
432
|
-
services: TDSServiceSchema[],
|
|
433
|
-
): LegendAIProductMetadata {
|
|
434
|
-
const result = viewerState.dataSpaceAnalysisResult;
|
|
435
|
-
|
|
436
|
-
let supportInfoText: string | undefined;
|
|
437
|
-
if (result.supportInfo instanceof DataSpaceSupportEmail) {
|
|
438
|
-
supportInfoText = result.supportInfo.address;
|
|
439
|
-
} else if (result.supportInfo instanceof DataSpaceSupportCombinedInfo) {
|
|
440
|
-
supportInfoText = (result.supportInfo.emails ?? []).join(', ');
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
const metadata: LegendAIProductMetadata = {
|
|
444
|
-
name: result.title ?? result.name,
|
|
445
|
-
coordinates,
|
|
446
|
-
serviceSummaries: result.executables.map((exec) => {
|
|
447
|
-
const summary: LegendAIServiceSummary = {
|
|
448
|
-
title: exec.title,
|
|
449
|
-
};
|
|
450
|
-
if (exec.description !== undefined) {
|
|
451
|
-
summary.description = exec.description;
|
|
452
|
-
}
|
|
453
|
-
if (exec.result instanceof DataSpaceExecutableTDSResult) {
|
|
454
|
-
summary.columnNames = exec.result.columns.map((col) => col.name);
|
|
455
|
-
}
|
|
456
|
-
return summary;
|
|
457
|
-
}),
|
|
458
|
-
tags: result.taggedValues.map((tv) => ({
|
|
459
|
-
profile: tv.profile,
|
|
460
|
-
value: tv.value,
|
|
461
|
-
})),
|
|
462
|
-
};
|
|
463
|
-
if (result.description !== undefined) {
|
|
464
|
-
metadata.description = result.description;
|
|
465
|
-
}
|
|
466
|
-
if (supportInfoText !== undefined) {
|
|
467
|
-
metadata.supportInfo = supportInfoText;
|
|
468
|
-
}
|
|
469
|
-
if (services.length >= 2) {
|
|
470
|
-
const serviceRels = inferServiceRelationshipsFromAssociations(
|
|
471
|
-
services,
|
|
472
|
-
result.elementDocs,
|
|
473
|
-
);
|
|
474
|
-
if (serviceRels.length > 0) {
|
|
475
|
-
metadata.serviceRelationships = serviceRels;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
return metadata;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
70
|
const DataSpaceLegendAIIntegrationInner = observer(
|
|
482
71
|
(props: {
|
|
483
72
|
dataSpaceViewerState: DataSpaceViewerState;
|
|
@@ -550,71 +139,18 @@ const DataSpaceLegendAIIntegrationInner = observer(
|
|
|
550
139
|
[dataSpaceViewerState, coordinates, services],
|
|
551
140
|
);
|
|
552
141
|
|
|
553
|
-
const modelContext: LegendAIModelContext | undefined = useMemo(
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
const diagramCtx = extractModelContextFromDiagrams(result.diagrams);
|
|
566
|
-
if (diagramCtx.entities.length > 0) {
|
|
567
|
-
ctx = diagramCtx;
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
if (!ctx) {
|
|
572
|
-
return undefined;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
const mappingPath =
|
|
576
|
-
dataSpaceViewerState.currentExecutionContext.mapping.path;
|
|
577
|
-
const mappingCoverage =
|
|
578
|
-
result.mappingToMappingCoverageResult?.get(mappingPath);
|
|
579
|
-
enrichModelContextWithMappingCoverage(ctx, mappingCoverage);
|
|
580
|
-
|
|
581
|
-
if (result.executables.length > 0) {
|
|
582
|
-
const execInfos = extractExecutableInfo(
|
|
583
|
-
result.executables,
|
|
584
|
-
ctx,
|
|
585
|
-
services,
|
|
586
|
-
);
|
|
587
|
-
if (execInfos.length > 0) {
|
|
588
|
-
ctx.executables = execInfos;
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
const physicalDatasets =
|
|
593
|
-
dataSpaceViewerState.currentExecutionContext.datasets;
|
|
594
|
-
if (physicalDatasets.length > 0) {
|
|
595
|
-
ctx.datasets = physicalDatasets.map((ds) =>
|
|
596
|
-
ds instanceof RelationalDatabaseTableSpecification
|
|
597
|
-
? {
|
|
598
|
-
name: ds.name,
|
|
599
|
-
database: ds.database,
|
|
600
|
-
schema: ds.schema,
|
|
601
|
-
table: ds.table,
|
|
602
|
-
}
|
|
603
|
-
: { name: ds.name },
|
|
604
|
-
);
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
if (result.description) {
|
|
608
|
-
ctx.dataspaceDescription = result.description;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
return ctx;
|
|
612
|
-
}, [
|
|
613
|
-
dataSpaceViewerState.dataSpaceAnalysisResult,
|
|
614
|
-
dataSpaceViewerState.currentExecutionContext.mapping,
|
|
615
|
-
dataSpaceViewerState.currentExecutionContext.datasets,
|
|
616
|
-
services,
|
|
617
|
-
]);
|
|
142
|
+
const modelContext: LegendAIModelContext | undefined = useMemo(
|
|
143
|
+
() => buildDataSpaceModelContext(dataSpaceViewerState, services),
|
|
144
|
+
// Deps are the observable inputs the builder reads; the viewer state
|
|
145
|
+
// identity itself is stable for the lifetime of the panel.
|
|
146
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
147
|
+
[
|
|
148
|
+
dataSpaceViewerState.dataSpaceAnalysisResult,
|
|
149
|
+
dataSpaceViewerState.currentExecutionContext.mapping,
|
|
150
|
+
dataSpaceViewerState.currentExecutionContext.datasets,
|
|
151
|
+
services,
|
|
152
|
+
],
|
|
153
|
+
);
|
|
618
154
|
|
|
619
155
|
const telemetryService =
|
|
620
156
|
dataSpaceViewerState.applicationStore.telemetryService;
|