@finos/legend-lego 2.0.217 → 2.0.219
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/index.css +2 -2
- package/lib/index.css.map +1 -1
- package/lib/legend-ai/LegendAIDocEnrichment.d.ts +14 -0
- package/lib/legend-ai/LegendAIDocEnrichment.d.ts.map +1 -1
- package/lib/legend-ai/LegendAIDocEnrichment.js +140 -17
- package/lib/legend-ai/LegendAIDocEnrichment.js.map +1 -1
- package/lib/legend-ai/LegendAITypes.d.ts +30 -1
- package/lib/legend-ai/LegendAITypes.d.ts.map +1 -1
- package/lib/legend-ai/LegendAITypes.js +31 -0
- package/lib/legend-ai/LegendAITypes.js.map +1 -1
- package/lib/legend-ai/LegendAI_LegendApplicationPlugin_Extension.d.ts +2 -2
- package/lib/legend-ai/LegendAI_LegendApplicationPlugin_Extension.d.ts.map +1 -1
- package/lib/legend-ai/LegendAI_LegendApplicationPlugin_Extension.js +1 -1
- package/lib/legend-ai/LegendAI_LegendApplicationPlugin_Extension.js.map +1 -1
- package/lib/legend-ai/components/LegendAIChat.d.ts.map +1 -1
- package/lib/legend-ai/components/LegendAIChat.js +62 -38
- package/lib/legend-ai/components/LegendAIChat.js.map +1 -1
- package/lib/legend-ai/components/LegendAIChatHelpers.d.ts +5 -0
- package/lib/legend-ai/components/LegendAIChatHelpers.d.ts.map +1 -1
- package/lib/legend-ai/components/LegendAIChatHelpers.js +8 -1
- package/lib/legend-ai/components/LegendAIChatHelpers.js.map +1 -1
- package/lib/legend-ai/index.d.ts +3 -2
- package/lib/legend-ai/index.d.ts.map +1 -1
- package/lib/legend-ai/index.js +3 -2
- package/lib/legend-ai/index.js.map +1 -1
- package/lib/legend-ai/stores/LegendAIChatProcessors.d.ts +27 -2
- package/lib/legend-ai/stores/LegendAIChatProcessors.d.ts.map +1 -1
- package/lib/legend-ai/stores/LegendAIChatProcessors.js +329 -86
- package/lib/legend-ai/stores/LegendAIChatProcessors.js.map +1 -1
- package/lib/legend-ai/stores/LegendAIJoinAnalysis.d.ts +11 -0
- package/lib/legend-ai/stores/LegendAIJoinAnalysis.d.ts.map +1 -1
- package/lib/legend-ai/stores/LegendAIJoinAnalysis.js +53 -19
- package/lib/legend-ai/stores/LegendAIJoinAnalysis.js.map +1 -1
- package/lib/legend-ai/stores/LegendAISqlHelpers.d.ts +4 -1
- package/lib/legend-ai/stores/LegendAISqlHelpers.d.ts.map +1 -1
- package/lib/legend-ai/stores/LegendAISqlHelpers.js +25 -2
- package/lib/legend-ai/stores/LegendAISqlHelpers.js.map +1 -1
- package/lib/legend-ai/stores/LegendAISqlJoinSanitizers.js +4 -4
- package/lib/legend-ai/stores/LegendAISqlJoinSanitizers.js.map +1 -1
- package/package.json +3 -3
- package/src/legend-ai/LegendAIDocEnrichment.ts +189 -23
- package/src/legend-ai/LegendAITypes.ts +54 -1
- package/src/legend-ai/LegendAI_LegendApplicationPlugin_Extension.ts +2 -0
- package/src/legend-ai/components/LegendAIChat.tsx +114 -78
- package/src/legend-ai/components/LegendAIChatHelpers.ts +9 -3
- package/src/legend-ai/index.ts +7 -0
- package/src/legend-ai/stores/LegendAIChatProcessors.ts +476 -105
- package/src/legend-ai/stores/LegendAIJoinAnalysis.ts +82 -19
- package/src/legend-ai/stores/LegendAISqlHelpers.ts +32 -2
- package/src/legend-ai/stores/LegendAISqlJoinSanitizers.ts +4 -4
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
LegendAIThinkingStepStatus,
|
|
57
57
|
LegendAIMessageRole,
|
|
58
58
|
LegendAIErrorType,
|
|
59
|
+
LegendAIPythonCodeStatus,
|
|
59
60
|
TDSServiceSourceType,
|
|
60
61
|
LEGEND_AI_FEEDBACK_PROMPT,
|
|
61
62
|
classifyQuestionIntentFast,
|
|
@@ -65,6 +66,7 @@ import {
|
|
|
65
66
|
} from '../LegendAITypes.js';
|
|
66
67
|
import { useLegendAIChatState } from '../stores/LegendAIChatState.js';
|
|
67
68
|
import { looksLikeAccessError } from '../stores/LegendAIChatProcessors.js';
|
|
69
|
+
import { accessPointName } from '../stores/LegendAISqlHelpers.js';
|
|
68
70
|
import { LegendAIResultGrid } from './LegendAIResultGrid.js';
|
|
69
71
|
import { LegendAIAnalysisPanel } from './LegendAIAnalysisPanel.js';
|
|
70
72
|
import { LegendAIChatInput } from './LegendAIChatInput.js';
|
|
@@ -76,9 +78,12 @@ const COPY_FEEDBACK_DURATION_MS = 2000;
|
|
|
76
78
|
const CONTEXT_BANNER_AUTO_DISMISS_MS = 20000;
|
|
77
79
|
|
|
78
80
|
type LegendAIPythonCodeEntry =
|
|
79
|
-
| { status:
|
|
80
|
-
| {
|
|
81
|
-
|
|
81
|
+
| { status: LegendAIPythonCodeStatus.LOADING }
|
|
82
|
+
| {
|
|
83
|
+
status: LegendAIPythonCodeStatus.READY;
|
|
84
|
+
code: LegendAIPythonQueryCode | undefined;
|
|
85
|
+
}
|
|
86
|
+
| { status: LegendAIPythonCodeStatus.ERROR; errorMessage: string };
|
|
82
87
|
|
|
83
88
|
const LegendAIContextBanner = (props: {
|
|
84
89
|
message: string;
|
|
@@ -328,37 +333,48 @@ const AssistantMessageView = memo(function AssistantMessageView(props: {
|
|
|
328
333
|
|
|
329
334
|
const handleCopySql = useCallback(() => {
|
|
330
335
|
if (msg.sql) {
|
|
331
|
-
navigator.clipboard
|
|
332
|
-
|
|
336
|
+
navigator.clipboard
|
|
337
|
+
.writeText(msg.sql)
|
|
338
|
+
.then(() => {
|
|
339
|
+
setSqlCopied(true);
|
|
340
|
+
if (copyTimerRef.current !== undefined) {
|
|
341
|
+
clearTimeout(copyTimerRef.current);
|
|
342
|
+
}
|
|
343
|
+
copyTimerRef.current = setTimeout(() => {
|
|
344
|
+
setSqlCopied(false);
|
|
345
|
+
copyTimerRef.current = undefined;
|
|
346
|
+
}, COPY_FEEDBACK_DURATION_MS);
|
|
347
|
+
})
|
|
348
|
+
.catch(noop());
|
|
333
349
|
onLogTelemetryEvent?.({
|
|
334
350
|
type: LegendAIChatTelemetryEventType.ARTIFACT_COPIED,
|
|
335
351
|
artifact: LegendAITelemetryArtifact.SQL,
|
|
336
352
|
});
|
|
337
|
-
if (copyTimerRef.current !== undefined) {
|
|
338
|
-
clearTimeout(copyTimerRef.current);
|
|
339
|
-
}
|
|
340
|
-
copyTimerRef.current = setTimeout(() => {
|
|
341
|
-
setSqlCopied(false);
|
|
342
|
-
copyTimerRef.current = undefined;
|
|
343
|
-
}, COPY_FEEDBACK_DURATION_MS);
|
|
344
353
|
}
|
|
345
354
|
}, [msg.sql, onLogTelemetryEvent]);
|
|
346
355
|
|
|
347
356
|
const handleCopyPython = useCallback(() => {
|
|
348
|
-
if (
|
|
349
|
-
|
|
350
|
-
|
|
357
|
+
if (
|
|
358
|
+
pythonEntry?.status === LegendAIPythonCodeStatus.READY &&
|
|
359
|
+
pythonEntry.code
|
|
360
|
+
) {
|
|
361
|
+
navigator.clipboard
|
|
362
|
+
.writeText(pythonEntry.code.code)
|
|
363
|
+
.then(() => {
|
|
364
|
+
setPythonCopied(true);
|
|
365
|
+
if (pythonCopyTimerRef.current !== undefined) {
|
|
366
|
+
clearTimeout(pythonCopyTimerRef.current);
|
|
367
|
+
}
|
|
368
|
+
pythonCopyTimerRef.current = setTimeout(() => {
|
|
369
|
+
setPythonCopied(false);
|
|
370
|
+
pythonCopyTimerRef.current = undefined;
|
|
371
|
+
}, COPY_FEEDBACK_DURATION_MS);
|
|
372
|
+
})
|
|
373
|
+
.catch(noop());
|
|
351
374
|
onLogTelemetryEvent?.({
|
|
352
375
|
type: LegendAIChatTelemetryEventType.ARTIFACT_COPIED,
|
|
353
376
|
artifact: LegendAITelemetryArtifact.PYTHON,
|
|
354
377
|
});
|
|
355
|
-
if (pythonCopyTimerRef.current !== undefined) {
|
|
356
|
-
clearTimeout(pythonCopyTimerRef.current);
|
|
357
|
-
}
|
|
358
|
-
pythonCopyTimerRef.current = setTimeout(() => {
|
|
359
|
-
setPythonCopied(false);
|
|
360
|
-
pythonCopyTimerRef.current = undefined;
|
|
361
|
-
}, COPY_FEEDBACK_DURATION_MS);
|
|
362
378
|
}
|
|
363
379
|
}, [pythonEntry, onLogTelemetryEvent]);
|
|
364
380
|
|
|
@@ -414,7 +430,7 @@ const AssistantMessageView = memo(function AssistantMessageView(props: {
|
|
|
414
430
|
: { rowCount: msg.gridData.rowData.length }),
|
|
415
431
|
});
|
|
416
432
|
if (result instanceof Promise) {
|
|
417
|
-
result.catch(noop);
|
|
433
|
+
result.catch(noop());
|
|
418
434
|
}
|
|
419
435
|
},
|
|
420
436
|
[msg, onMessageFeedback, questionText],
|
|
@@ -732,31 +748,32 @@ const AssistantMessageView = memo(function AssistantMessageView(props: {
|
|
|
732
748
|
<CodeIcon />
|
|
733
749
|
</span>
|
|
734
750
|
<span>Python</span>
|
|
735
|
-
{pythonEntry?.status ===
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
<
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
751
|
+
{pythonEntry?.status === LegendAIPythonCodeStatus.READY &&
|
|
752
|
+
pythonEntry.code && (
|
|
753
|
+
<button
|
|
754
|
+
type="button"
|
|
755
|
+
className="legend-ai__sql-copy-btn"
|
|
756
|
+
title="Copy Python code"
|
|
757
|
+
aria-label="Copy Python code"
|
|
758
|
+
onClick={handleCopyPython}
|
|
759
|
+
>
|
|
760
|
+
{pythonCopied ? (
|
|
761
|
+
<span className="legend-ai__sql-copy-btn--copied">
|
|
762
|
+
<CheckIcon />
|
|
763
|
+
</span>
|
|
764
|
+
) : (
|
|
765
|
+
<CopyIcon />
|
|
766
|
+
)}
|
|
767
|
+
</button>
|
|
768
|
+
)}
|
|
752
769
|
</div>
|
|
753
|
-
{pythonEntry?.status ===
|
|
770
|
+
{pythonEntry?.status === LegendAIPythonCodeStatus.LOADING && (
|
|
754
771
|
<div className="legend-ai__python-panel-loading">
|
|
755
772
|
<LoadingIcon isLoading={true} />
|
|
756
773
|
<span>Generating Python…</span>
|
|
757
774
|
</div>
|
|
758
775
|
)}
|
|
759
|
-
{pythonEntry?.status ===
|
|
776
|
+
{pythonEntry?.status === LegendAIPythonCodeStatus.ERROR && (
|
|
760
777
|
<div className="legend-ai__python-panel-error">
|
|
761
778
|
<span>
|
|
762
779
|
Could not generate Python code. Try again in a moment.
|
|
@@ -775,7 +792,7 @@ const AssistantMessageView = memo(function AssistantMessageView(props: {
|
|
|
775
792
|
</button>
|
|
776
793
|
</div>
|
|
777
794
|
)}
|
|
778
|
-
{pythonEntry?.status ===
|
|
795
|
+
{pythonEntry?.status === LegendAIPythonCodeStatus.READY &&
|
|
779
796
|
pythonEntry.code === undefined && (
|
|
780
797
|
<div className="legend-ai__python-panel-loading">
|
|
781
798
|
<span>
|
|
@@ -783,28 +800,29 @@ const AssistantMessageView = memo(function AssistantMessageView(props: {
|
|
|
783
800
|
</span>
|
|
784
801
|
</div>
|
|
785
802
|
)}
|
|
786
|
-
{pythonEntry?.status ===
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
<
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
{pythonEntry.code.notebookUrl && (
|
|
794
|
-
<div className="legend-ai__python-panel-actions">
|
|
795
|
-
<a
|
|
796
|
-
className="legend-ai__permission-error-btn"
|
|
797
|
-
href={pythonEntry.code.notebookUrl}
|
|
798
|
-
target="_blank"
|
|
799
|
-
rel="noopener noreferrer"
|
|
800
|
-
>
|
|
801
|
-
<JupyterIcon />
|
|
802
|
-
<span>Launch Notebook</span>
|
|
803
|
-
</a>
|
|
803
|
+
{pythonEntry?.status === LegendAIPythonCodeStatus.READY &&
|
|
804
|
+
pythonEntry.code && (
|
|
805
|
+
<>
|
|
806
|
+
<div className="legend-ai__sql-scroll">
|
|
807
|
+
<pre className="legend-ai__sql-display">
|
|
808
|
+
{pythonEntry.code.code}
|
|
809
|
+
</pre>
|
|
804
810
|
</div>
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
811
|
+
{pythonEntry.code.notebookUrl && (
|
|
812
|
+
<div className="legend-ai__python-panel-actions">
|
|
813
|
+
<a
|
|
814
|
+
className="legend-ai__permission-error-btn"
|
|
815
|
+
href={pythonEntry.code.notebookUrl}
|
|
816
|
+
target="_blank"
|
|
817
|
+
rel="noopener noreferrer"
|
|
818
|
+
>
|
|
819
|
+
<JupyterIcon />
|
|
820
|
+
<span>Launch Notebook</span>
|
|
821
|
+
</a>
|
|
822
|
+
</div>
|
|
823
|
+
)}
|
|
824
|
+
</>
|
|
825
|
+
)}
|
|
808
826
|
</div>
|
|
809
827
|
)}
|
|
810
828
|
</div>
|
|
@@ -933,25 +951,40 @@ export const LegendAIChat = (props: LegendAIChatProps): React.ReactNode => {
|
|
|
933
951
|
>(new Map());
|
|
934
952
|
const pythonCodeByMessageIdRef = useRef(pythonCodeByMessageId);
|
|
935
953
|
pythonCodeByMessageIdRef.current = pythonCodeByMessageId;
|
|
954
|
+
/**
|
|
955
|
+
* Generates Python for the access point the answer actually queried. When the
|
|
956
|
+
* message names one that is not loaded, no code is offered rather than a guess.
|
|
957
|
+
*/
|
|
936
958
|
const requestPythonCode = useCallback(
|
|
937
959
|
async (msg: LegendAIAssistantMessage): Promise<void> => {
|
|
938
960
|
const existing = pythonCodeByMessageIdRef.current.get(msg.id);
|
|
939
|
-
if (
|
|
961
|
+
if (
|
|
962
|
+
existing?.status === LegendAIPythonCodeStatus.LOADING ||
|
|
963
|
+
existing?.status === LegendAIPythonCodeStatus.READY
|
|
964
|
+
) {
|
|
940
965
|
return;
|
|
941
966
|
}
|
|
942
967
|
setPythonCodeByMessageId((prev) => {
|
|
943
968
|
const next = new Map(prev);
|
|
944
|
-
next.set(msg.id, { status:
|
|
969
|
+
next.set(msg.id, { status: LegendAIPythonCodeStatus.LOADING });
|
|
945
970
|
return next;
|
|
946
971
|
});
|
|
947
|
-
const resolvedSet = new Set(
|
|
972
|
+
const resolvedSet = new Set(
|
|
973
|
+
msg.queriedAccessPoints.map((name) => name.toLowerCase()),
|
|
974
|
+
);
|
|
948
975
|
const service =
|
|
949
|
-
|
|
950
|
-
|
|
976
|
+
resolvedSet.size > 0
|
|
977
|
+
? services.find((s) =>
|
|
978
|
+
resolvedSet.has(accessPointName(s).toLowerCase()),
|
|
979
|
+
)
|
|
980
|
+
: services[0];
|
|
951
981
|
if (!service) {
|
|
952
982
|
setPythonCodeByMessageId((prev) => {
|
|
953
983
|
const next = new Map(prev);
|
|
954
|
-
next.set(msg.id, {
|
|
984
|
+
next.set(msg.id, {
|
|
985
|
+
status: LegendAIPythonCodeStatus.READY,
|
|
986
|
+
code: undefined,
|
|
987
|
+
});
|
|
955
988
|
return next;
|
|
956
989
|
});
|
|
957
990
|
return;
|
|
@@ -974,14 +1007,17 @@ export const LegendAIChat = (props: LegendAIChatProps): React.ReactNode => {
|
|
|
974
1007
|
const code = await plugin.generatePythonQueryCodeAsync(request);
|
|
975
1008
|
setPythonCodeByMessageId((prev) => {
|
|
976
1009
|
const next = new Map(prev);
|
|
977
|
-
next.set(msg.id, { status:
|
|
1010
|
+
next.set(msg.id, { status: LegendAIPythonCodeStatus.READY, code });
|
|
978
1011
|
return next;
|
|
979
1012
|
});
|
|
980
1013
|
} catch (error) {
|
|
981
1014
|
assertErrorThrown(error);
|
|
982
1015
|
setPythonCodeByMessageId((prev) => {
|
|
983
1016
|
const next = new Map(prev);
|
|
984
|
-
next.set(msg.id, {
|
|
1017
|
+
next.set(msg.id, {
|
|
1018
|
+
status: LegendAIPythonCodeStatus.ERROR,
|
|
1019
|
+
errorMessage: error.message,
|
|
1020
|
+
});
|
|
985
1021
|
return next;
|
|
986
1022
|
});
|
|
987
1023
|
}
|
|
@@ -999,13 +1035,13 @@ export const LegendAIChat = (props: LegendAIChatProps): React.ReactNode => {
|
|
|
999
1035
|
if (!onOpenInDataCube || msg.sql === null) {
|
|
1000
1036
|
return;
|
|
1001
1037
|
}
|
|
1002
|
-
const
|
|
1003
|
-
if (
|
|
1038
|
+
const apName = msg.queriedAccessPoints[0];
|
|
1039
|
+
if (apName === undefined) {
|
|
1004
1040
|
return;
|
|
1005
1041
|
}
|
|
1006
1042
|
const service =
|
|
1007
1043
|
services.find(
|
|
1008
|
-
(s) => s.
|
|
1044
|
+
(s) => accessPointName(s).toLowerCase() === apName.toLowerCase(),
|
|
1009
1045
|
) ?? services[0];
|
|
1010
1046
|
if (!service) {
|
|
1011
1047
|
return;
|
|
@@ -1038,7 +1074,7 @@ export const LegendAIChat = (props: LegendAIChatProps): React.ReactNode => {
|
|
|
1038
1074
|
assertErrorThrown(error);
|
|
1039
1075
|
}
|
|
1040
1076
|
try {
|
|
1041
|
-
onOpenInDataCube(
|
|
1077
|
+
onOpenInDataCube(apName, pureQuery);
|
|
1042
1078
|
} finally {
|
|
1043
1079
|
setOpeningDataCubeMessageIds((prev) => {
|
|
1044
1080
|
if (!prev.has(msg.id)) {
|
|
@@ -1119,13 +1155,13 @@ export const LegendAIChat = (props: LegendAIChatProps): React.ReactNode => {
|
|
|
1119
1155
|
);
|
|
1120
1156
|
const handleRequestPython = useCallback(
|
|
1121
1157
|
(message: LegendAIAssistantMessage): void => {
|
|
1122
|
-
requestPythonCode(message).catch(noop);
|
|
1158
|
+
requestPythonCode(message).catch(noop());
|
|
1123
1159
|
},
|
|
1124
1160
|
[requestPythonCode],
|
|
1125
1161
|
);
|
|
1126
1162
|
const handleOpenInDataCube = useCallback(
|
|
1127
1163
|
(message: LegendAIAssistantMessage): void => {
|
|
1128
|
-
handleOpenInDataCubeMsg(message).catch(noop);
|
|
1164
|
+
handleOpenInDataCubeMsg(message).catch(noop());
|
|
1129
1165
|
},
|
|
1130
1166
|
[handleOpenInDataCubeMsg],
|
|
1131
1167
|
);
|
|
@@ -69,10 +69,16 @@ const INFRASTRUCTURE_COLUMN_PATTERN =
|
|
|
69
69
|
/^lake_|^__lake|^vlf|^batch|_ap_lh_migration/i;
|
|
70
70
|
const AP_DATE_NAME_PATTERN = /(?:date|dt|time)$/i;
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Whether the column holds text. Unlike {@link isStringColumn} this keeps
|
|
74
|
+
* identifier columns, which are the usual join keys.
|
|
75
|
+
*/
|
|
76
|
+
export function isStringTypedColumn(c: TDSColumnSchema): boolean {
|
|
77
|
+
return STRING_TYPE_NAMES.has(c.type ?? '');
|
|
78
|
+
}
|
|
79
|
+
|
|
72
80
|
export function isStringColumn(c: TDSColumnSchema): boolean {
|
|
73
|
-
return (
|
|
74
|
-
STRING_TYPE_NAMES.has(c.type ?? '') && !c.name.toLowerCase().includes('id')
|
|
75
|
-
);
|
|
81
|
+
return isStringTypedColumn(c) && !c.name.toLowerCase().includes('id');
|
|
76
82
|
}
|
|
77
83
|
|
|
78
84
|
export function isNumericColumn(c: TDSColumnSchema): boolean {
|
package/src/legend-ai/index.ts
CHANGED
|
@@ -58,6 +58,8 @@ export {
|
|
|
58
58
|
attachMetadataOverview,
|
|
59
59
|
elapsedSeconds,
|
|
60
60
|
createMessagePair,
|
|
61
|
+
createAssistantMessage,
|
|
62
|
+
buildOrchestratorFallbackAction,
|
|
61
63
|
analyzeOrchestratorResults,
|
|
62
64
|
cleanLlmSqlResponse,
|
|
63
65
|
isValidSqlCorrection,
|
|
@@ -91,6 +93,10 @@ export {
|
|
|
91
93
|
sanitizeJoinSameKeyColumns,
|
|
92
94
|
wrapBareJoinAccessPoints,
|
|
93
95
|
} from './stores/LegendAISqlJoinSanitizers.js';
|
|
96
|
+
export {
|
|
97
|
+
accessPointName,
|
|
98
|
+
sharedColumnNames,
|
|
99
|
+
} from './stores/LegendAISqlHelpers.js';
|
|
94
100
|
export {
|
|
95
101
|
preFilterServicesByRelevance,
|
|
96
102
|
isFuzzyMatch,
|
|
@@ -117,6 +123,7 @@ export {
|
|
|
117
123
|
enrichColumnsFromElementDocs,
|
|
118
124
|
inferServiceRelationshipsFromAssociations,
|
|
119
125
|
extractLambdaPreFilters,
|
|
126
|
+
extractServicePreFilters,
|
|
120
127
|
extractModelContext,
|
|
121
128
|
buildEnrichedBusinessContext,
|
|
122
129
|
buildModelContextEnrichmentText,
|