@finos/legend-extension-dsl-data-quality 2.1.34 → 2.1.35

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.
Files changed (109) hide show
  1. package/lib/components/DSL_DataQuality_LegendStudioApplicationPlugin.d.ts.map +1 -1
  2. package/lib/components/DSL_DataQuality_LegendStudioApplicationPlugin.js +45 -13
  3. package/lib/components/DSL_DataQuality_LegendStudioApplicationPlugin.js.map +1 -1
  4. package/lib/components/DataQualityCustomSelector.d.ts +16 -1
  5. package/lib/components/DataQualityCustomSelector.d.ts.map +1 -1
  6. package/lib/components/DataQualityCustomSelector.js +29 -4
  7. package/lib/components/DataQualityCustomSelector.js.map +1 -1
  8. package/lib/components/DataQualityRelationComparisonEditor.d.ts +19 -0
  9. package/lib/components/DataQualityRelationComparisonEditor.d.ts.map +1 -0
  10. package/lib/components/DataQualityRelationComparisonEditor.js +239 -0
  11. package/lib/components/DataQualityRelationComparisonEditor.js.map +1 -0
  12. package/lib/components/DataQualityRelationGridResult.d.ts +3 -0
  13. package/lib/components/DataQualityRelationGridResult.d.ts.map +1 -1
  14. package/lib/components/DataQualityRelationGridResult.js +1 -1
  15. package/lib/components/DataQualityRelationGridResult.js.map +1 -1
  16. package/lib/components/DataQualityRelationLambdaGUIDataTypeHandlers.d.ts +0 -10
  17. package/lib/components/DataQualityRelationLambdaGUIDataTypeHandlers.d.ts.map +1 -1
  18. package/lib/components/DataQualityRelationLambdaGUIDataTypeHandlers.js +11 -24
  19. package/lib/components/DataQualityRelationLambdaGUIDataTypeHandlers.js.map +1 -1
  20. package/lib/components/DataQualityRelationLambdaGUIValidationEditor.d.ts.map +1 -1
  21. package/lib/components/DataQualityRelationLambdaGUIValidationEditor.js +6 -4
  22. package/lib/components/DataQualityRelationLambdaGUIValidationEditor.js.map +1 -1
  23. package/lib/components/states/DataQualityRelationComparisonConfigurationState.d.ts +111 -0
  24. package/lib/components/states/DataQualityRelationComparisonConfigurationState.d.ts.map +1 -0
  25. package/lib/components/states/DataQualityRelationComparisonConfigurationState.js +495 -0
  26. package/lib/components/states/DataQualityRelationComparisonConfigurationState.js.map +1 -0
  27. package/lib/data-quality-custom-selector.css +2 -2
  28. package/lib/data-quality-custom-selector.css.map +1 -1
  29. package/lib/graph/metamodel/DSL_DataQuality_HashUtils.d.ts +3 -1
  30. package/lib/graph/metamodel/DSL_DataQuality_HashUtils.d.ts.map +1 -1
  31. package/lib/graph/metamodel/DSL_DataQuality_HashUtils.js +2 -0
  32. package/lib/graph/metamodel/DSL_DataQuality_HashUtils.js.map +1 -1
  33. package/lib/graph/metamodel/DSL_DataQuality_PureGraphPlugin.d.ts.map +1 -1
  34. package/lib/graph/metamodel/DSL_DataQuality_PureGraphPlugin.js +2 -1
  35. package/lib/graph/metamodel/DSL_DataQuality_PureGraphPlugin.js.map +1 -1
  36. package/lib/graph/metamodel/pure/packageableElements/data-quality/DataQualityValidationConfiguration.d.ts +35 -0
  37. package/lib/graph/metamodel/pure/packageableElements/data-quality/DataQualityValidationConfiguration.d.ts.map +1 -1
  38. package/lib/graph/metamodel/pure/packageableElements/data-quality/DataQualityValidationConfiguration.js +37 -0
  39. package/lib/graph/metamodel/pure/packageableElements/data-quality/DataQualityValidationConfiguration.js.map +1 -1
  40. package/lib/graph-manager/DSL_DataQuality_GraphManagerHelper.d.ts +2 -1
  41. package/lib/graph-manager/DSL_DataQuality_GraphManagerHelper.d.ts.map +1 -1
  42. package/lib/graph-manager/DSL_DataQuality_GraphManagerHelper.js +2 -1
  43. package/lib/graph-manager/DSL_DataQuality_GraphManagerHelper.js.map +1 -1
  44. package/lib/graph-manager/DSL_DataQuality_GraphModifierHelper.d.ts +7 -1
  45. package/lib/graph-manager/DSL_DataQuality_GraphModifierHelper.d.ts.map +1 -1
  46. package/lib/graph-manager/DSL_DataQuality_GraphModifierHelper.js +18 -0
  47. package/lib/graph-manager/DSL_DataQuality_GraphModifierHelper.js.map +1 -1
  48. package/lib/graph-manager/DSL_DataQuality_PureGraphManagerPlugin.d.ts.map +1 -1
  49. package/lib/graph-manager/DSL_DataQuality_PureGraphManagerPlugin.js +5 -2
  50. package/lib/graph-manager/DSL_DataQuality_PureGraphManagerPlugin.js.map +1 -1
  51. package/lib/graph-manager/action/changeDetection/DSL_DataQuality_ObserverHelper.d.ts +3 -1
  52. package/lib/graph-manager/action/changeDetection/DSL_DataQuality_ObserverHelper.d.ts.map +1 -1
  53. package/lib/graph-manager/action/changeDetection/DSL_DataQuality_ObserverHelper.js +28 -1
  54. package/lib/graph-manager/action/changeDetection/DSL_DataQuality_ObserverHelper.js.map +1 -1
  55. package/lib/graph-manager/protocol/pure/DSL_DataQuality_PureGraphManagerExtension.d.ts +4 -1
  56. package/lib/graph-manager/protocol/pure/DSL_DataQuality_PureGraphManagerExtension.d.ts.map +1 -1
  57. package/lib/graph-manager/protocol/pure/DSL_DataQuality_PureGraphManagerExtension.js.map +1 -1
  58. package/lib/graph-manager/protocol/pure/DSL_DataQuality_PureProtocolProcessorPlugin.d.ts.map +1 -1
  59. package/lib/graph-manager/protocol/pure/DSL_DataQuality_PureProtocolProcessorPlugin.js +33 -5
  60. package/lib/graph-manager/protocol/pure/DSL_DataQuality_PureProtocolProcessorPlugin.js.map +1 -1
  61. package/lib/graph-manager/protocol/pure/v1/V1_DSL_Data_Quality_PureGraphManagerExtension.d.ts +26 -2
  62. package/lib/graph-manager/protocol/pure/v1/V1_DSL_Data_Quality_PureGraphManagerExtension.d.ts.map +1 -1
  63. package/lib/graph-manager/protocol/pure/v1/V1_DSL_Data_Quality_PureGraphManagerExtension.js +107 -3
  64. package/lib/graph-manager/protocol/pure/v1/V1_DSL_Data_Quality_PureGraphManagerExtension.js.map +1 -1
  65. package/lib/graph-manager/protocol/pure/v1/V1_DataQualityValidationConfiguration.d.ts +19 -0
  66. package/lib/graph-manager/protocol/pure/v1/V1_DataQualityValidationConfiguration.d.ts.map +1 -1
  67. package/lib/graph-manager/protocol/pure/v1/V1_DataQualityValidationConfiguration.js +37 -0
  68. package/lib/graph-manager/protocol/pure/v1/V1_DataQualityValidationConfiguration.js.map +1 -1
  69. package/lib/graph-manager/protocol/pure/v1/transformation/V1_DSL_DataQuality_ValueSpecificationBuilderHelper.d.ts +2 -1
  70. package/lib/graph-manager/protocol/pure/v1/transformation/V1_DSL_DataQuality_ValueSpecificationBuilderHelper.d.ts.map +1 -1
  71. package/lib/graph-manager/protocol/pure/v1/transformation/V1_DSL_DataQuality_ValueSpecificationBuilderHelper.js +26 -3
  72. package/lib/graph-manager/protocol/pure/v1/transformation/V1_DSL_DataQuality_ValueSpecificationBuilderHelper.js.map +1 -1
  73. package/lib/graph-manager/protocol/pure/v1/transformation/V1_DSL_DataQuality_ValueSpecificationTransformer.d.ts +3 -2
  74. package/lib/graph-manager/protocol/pure/v1/transformation/V1_DSL_DataQuality_ValueSpecificationTransformer.d.ts.map +1 -1
  75. package/lib/graph-manager/protocol/pure/v1/transformation/V1_DSL_DataQuality_ValueSpecificationTransformer.js +29 -2
  76. package/lib/graph-manager/protocol/pure/v1/transformation/V1_DSL_DataQuality_ValueSpecificationTransformer.js.map +1 -1
  77. package/lib/graph-manager/protocol/pure/v1/transformation/pureProtocol/V1_DSL_DataQuality_ProtocolHelper.d.ts +4 -1
  78. package/lib/graph-manager/protocol/pure/v1/transformation/pureProtocol/V1_DSL_DataQuality_ProtocolHelper.d.ts.map +1 -1
  79. package/lib/graph-manager/protocol/pure/v1/transformation/pureProtocol/V1_DSL_DataQuality_ProtocolHelper.js +37 -2
  80. package/lib/graph-manager/protocol/pure/v1/transformation/pureProtocol/V1_DSL_DataQuality_ProtocolHelper.js.map +1 -1
  81. package/lib/index.css +2 -2
  82. package/lib/index.css.map +1 -1
  83. package/lib/package.json +1 -1
  84. package/package.json +7 -7
  85. package/src/components/DSL_DataQuality_LegendStudioApplicationPlugin.tsx +64 -12
  86. package/src/components/DataQualityCustomSelector.tsx +111 -6
  87. package/src/components/DataQualityRelationComparisonEditor.tsx +795 -0
  88. package/src/components/DataQualityRelationGridResult.tsx +1 -1
  89. package/src/components/DataQualityRelationLambdaGUIDataTypeHandlers.tsx +9 -87
  90. package/src/components/DataQualityRelationLambdaGUIValidationEditor.tsx +16 -6
  91. package/src/components/states/DataQualityRelationComparisonConfigurationState.ts +747 -0
  92. package/src/graph/metamodel/DSL_DataQuality_HashUtils.ts +2 -0
  93. package/src/graph/metamodel/DSL_DataQuality_PureGraphPlugin.ts +2 -0
  94. package/src/graph/metamodel/pure/packageableElements/data-quality/DataQualityValidationConfiguration.ts +66 -0
  95. package/src/graph-manager/DSL_DataQuality_GraphManagerHelper.ts +13 -0
  96. package/src/graph-manager/DSL_DataQuality_GraphModifierHelper.ts +57 -0
  97. package/src/graph-manager/DSL_DataQuality_PureGraphManagerPlugin.ts +8 -0
  98. package/src/graph-manager/action/changeDetection/DSL_DataQuality_ObserverHelper.ts +42 -0
  99. package/src/graph-manager/protocol/pure/DSL_DataQuality_PureGraphManagerExtension.ts +16 -0
  100. package/src/graph-manager/protocol/pure/DSL_DataQuality_PureProtocolProcessorPlugin.ts +65 -0
  101. package/src/graph-manager/protocol/pure/v1/V1_DSL_Data_Quality_PureGraphManagerExtension.ts +171 -1
  102. package/src/graph-manager/protocol/pure/v1/V1_DataQualityValidationConfiguration.ts +49 -0
  103. package/src/graph-manager/protocol/pure/v1/transformation/V1_DSL_DataQuality_ValueSpecificationBuilderHelper.ts +39 -0
  104. package/src/graph-manager/protocol/pure/v1/transformation/V1_DSL_DataQuality_ValueSpecificationTransformer.ts +50 -0
  105. package/src/graph-manager/protocol/pure/v1/transformation/pureProtocol/V1_DSL_DataQuality_ProtocolHelper.ts +71 -0
  106. package/style/_data-quality-relation-comparison-editor.scss +361 -0
  107. package/style/data-quality-custom-selector.scss +23 -0
  108. package/style/index.scss +74 -0
  109. package/tsconfig.json +2 -0
@@ -0,0 +1,795 @@
1
+ /**
2
+ * Copyright (c) 2026-present, Goldman Sachs
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import { observer } from 'mobx-react-lite';
18
+ import React, { useEffect, useMemo } from 'react';
19
+ import {
20
+ BlankPanelContent,
21
+ CaretDownIcon,
22
+ clsx,
23
+ ControlledDropdownMenu,
24
+ CustomSelectorInput,
25
+ Dialog,
26
+ ExclamationTriangleIcon,
27
+ MenuContent,
28
+ MenuContentItem,
29
+ Modal,
30
+ ModalBody,
31
+ ModalFooter,
32
+ ModalFooterButton,
33
+ ModalFooterStatus,
34
+ ModalHeader,
35
+ Panel,
36
+ PanelContent,
37
+ PanelFormBooleanField,
38
+ PanelFormSection,
39
+ PanelLoadingIndicator,
40
+ PauseCircleIcon,
41
+ PlayIcon,
42
+ RefreshIcon,
43
+ ResizablePanel,
44
+ ResizablePanelGroup,
45
+ ResizablePanelSplitter,
46
+ ResizablePanelSplitterLine,
47
+ } from '@finos/legend-art';
48
+ import { MD5HashStrategy } from '../graph/metamodel/pure/packageableElements/data-quality/DataQualityValidationConfiguration.js';
49
+ import {
50
+ DataQualityRelationComparisonConfigurationState,
51
+ DEFAULT_LIMIT,
52
+ RECONCILIATION_EXECUTION_TYPE,
53
+ } from './states/DataQualityRelationComparisonConfigurationState.js';
54
+ import { useEditorStore } from '@finos/legend-application-studio';
55
+ import {
56
+ DEFAULT_TAB_SIZE,
57
+ useApplicationStore,
58
+ } from '@finos/legend-application';
59
+ import {
60
+ BasicValueSpecificationEditor,
61
+ LambdaEditor,
62
+ LambdaParameterValuesEditor,
63
+ type LambdaParameterState,
64
+ getTDSColumnCustomizations,
65
+ getFilterTDSColumnCustomizations,
66
+ } from '@finos/legend-query-builder';
67
+ import { flowResult } from 'mobx';
68
+ import { DataQualityMultiCustomSelector } from './DataQualityCustomSelector.js';
69
+ import {
70
+ type ExecutionResult,
71
+ type ObserverContext,
72
+ PrimitiveType,
73
+ type PureModel,
74
+ TDSExecutionResult,
75
+ RawExecutionResult,
76
+ type ValueSpecification,
77
+ extractExecutionResultValues,
78
+ } from '@finos/legend-graph';
79
+ import {
80
+ guaranteeType,
81
+ prettyDuration,
82
+ prettyCONSTName,
83
+ returnUndefOnError,
84
+ } from '@finos/legend-shared';
85
+ import { CodeEditor } from '@finos/legend-lego/code-editor';
86
+ import { CODE_EDITOR_LANGUAGE } from '@finos/legend-code-editor';
87
+ import {
88
+ DataQualityResultCellRenderer,
89
+ getRowDataFromExecutionResult,
90
+ } from './DataQualityRelationGridResult.js';
91
+ import {
92
+ type DataGridColumnDefinition,
93
+ DataGrid,
94
+ } from '@finos/legend-lego/data-grid';
95
+
96
+ const ComparisonParameterSection = observer(
97
+ (props: {
98
+ title: string;
99
+ graph: PureModel;
100
+ observerContext: ObserverContext;
101
+ parameterStates: LambdaParameterState[];
102
+ }) => {
103
+ const { title, graph, observerContext, parameterStates } = props;
104
+
105
+ if (!parameterStates.length) {
106
+ return null;
107
+ }
108
+
109
+ return (
110
+ <div className="data-quality-relation-comparison-editor__parameters-modal__section">
111
+ <div className="data-quality-relation-comparison-editor__parameters-modal__section__title">
112
+ {title}
113
+ </div>
114
+ <div className="data-quality-relation-comparison-editor__parameters-modal__section__body">
115
+ {parameterStates.map((paramState) => {
116
+ const variableType =
117
+ paramState.variableType ?? PrimitiveType.STRING;
118
+ return (
119
+ <div
120
+ key={paramState.uuid}
121
+ className="panel__content__form__section"
122
+ >
123
+ <div className="lambda-parameter-values__value__label">
124
+ <div className="lambda-parameter-values__value__label__name">
125
+ {paramState.parameter.name}
126
+ </div>
127
+ <div className="lambda-parameter-values__value__label__type">
128
+ {variableType.name}
129
+ </div>
130
+ </div>
131
+ {paramState.value && (
132
+ <BasicValueSpecificationEditor
133
+ valueSpecification={paramState.value}
134
+ setValueSpecification={(val: ValueSpecification): void => {
135
+ paramState.setValue(val);
136
+ }}
137
+ graph={graph}
138
+ observerContext={observerContext}
139
+ typeCheckOption={{
140
+ expectedType: variableType,
141
+ match: variableType === PrimitiveType.DATETIME,
142
+ }}
143
+ className="query-builder__parameters__value__editor"
144
+ resetValue={(): void => undefined}
145
+ />
146
+ )}
147
+ </div>
148
+ );
149
+ })}
150
+ </div>
151
+ </div>
152
+ );
153
+ },
154
+ );
155
+
156
+ const DataQualityRelationComparisonParametersEditor = observer(
157
+ (props: { state: DataQualityRelationComparisonConfigurationState }) => {
158
+ const { state } = props;
159
+ const applicationStore = useApplicationStore();
160
+ const [isSubmitAction, setIsSubmitAction] = React.useState(false);
161
+ const [isClosingAction, setIsClosingAction] = React.useState(false);
162
+ const valuesEditorState = state.comparisonParametersEditorState;
163
+ const submitAction = valuesEditorState.submitAction;
164
+
165
+ const close = (): void => {
166
+ setIsClosingAction(true);
167
+ valuesEditorState.close();
168
+ };
169
+
170
+ const submit = applicationStore.guardUnhandledError(async () => {
171
+ if (submitAction) {
172
+ setIsSubmitAction(true);
173
+ close();
174
+ await submitAction.handler();
175
+ }
176
+ });
177
+
178
+ return (
179
+ <Dialog
180
+ open={Boolean(valuesEditorState.showModal)}
181
+ onClose={close}
182
+ classes={{
183
+ root: 'editor-modal__root-container',
184
+ container: 'editor-modal__container',
185
+ paper: 'editor-modal__content',
186
+ }}
187
+ >
188
+ <Modal
189
+ darkMode={
190
+ !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled
191
+ }
192
+ className="editor-modal lambda-parameter-values__modal data-quality-relation-comparison-editor__parameters-modal"
193
+ >
194
+ <ModalHeader title="Set Comparison Parameter Values" />
195
+ <ModalBody className="lambda-parameter-values__modal__body data-quality-relation-comparison-editor__parameters-modal__body">
196
+ <div className="data-quality-relation-comparison-editor__parameters-modal__description">
197
+ Source and target parameters are submitted independently for the
198
+ reconciliation run.
199
+ </div>
200
+ <div className="data-quality-relation-comparison-editor__parameters-modal__sections">
201
+ <ComparisonParameterSection
202
+ title="Source Query"
203
+ graph={state.editorStore.graphManagerState.graph}
204
+ observerContext={
205
+ state.editorStore.changeDetectionState.observerContext
206
+ }
207
+ parameterStates={state.sourceParametersState.parameterStates}
208
+ />
209
+ <ComparisonParameterSection
210
+ title="Target Query"
211
+ graph={state.editorStore.graphManagerState.graph}
212
+ observerContext={
213
+ state.editorStore.changeDetectionState.observerContext
214
+ }
215
+ parameterStates={state.targetParametersState.parameterStates}
216
+ />
217
+ </div>
218
+ </ModalBody>
219
+ <ModalFooter>
220
+ {isClosingAction && (
221
+ <ModalFooterStatus>Closing...</ModalFooterStatus>
222
+ )}
223
+ {submitAction && (
224
+ <ModalFooterButton
225
+ inProgressText={
226
+ isSubmitAction ? `${submitAction.label}...` : undefined
227
+ }
228
+ onClick={submit}
229
+ text={prettyCONSTName(submitAction.label)}
230
+ />
231
+ )}
232
+ <ModalFooterButton
233
+ inProgressText={isClosingAction ? 'Closing...' : undefined}
234
+ onClick={close}
235
+ text="Close"
236
+ type="secondary"
237
+ />
238
+ </ModalFooter>
239
+ </Modal>
240
+ </Dialog>
241
+ );
242
+ },
243
+ );
244
+
245
+ export const DataQualityRelationComparisonEditor = observer(() => {
246
+ const editorStore = useEditorStore();
247
+ const applicationStore = useApplicationStore();
248
+ const state = editorStore.tabManagerState.getCurrentEditorState(
249
+ DataQualityRelationComparisonConfigurationState,
250
+ );
251
+
252
+ const comparison = state.element;
253
+ const md5Strategy = guaranteeType(comparison.strategy, MD5HashStrategy);
254
+
255
+ const sourceColumnOptions = state.sourceColumnOptions;
256
+ const targetColumnOptions = state.targetColumnOptions;
257
+ const combinedColumnOptions = state.combinedColumnOptions;
258
+
259
+ const isRunning = state.isRunning;
260
+ const executionResult = state.executionResult;
261
+ const isFetchingColumns = state.fetchColumnsState.isInProgress;
262
+ const hasColumnFetchError = state.hasColumnFetchError;
263
+ const columnFetchError = state.columnFetchError;
264
+ const hasNoOverlappingColumns = state.hasNoOverlappingColumns;
265
+ const columnsDisabled = hasColumnFetchError || isFetchingColumns;
266
+
267
+ // Execution handlers
268
+ const cancelRun = applicationStore.guardUnhandledError(() =>
269
+ flowResult(state.cancelRun()),
270
+ );
271
+
272
+ const runReconciliation = applicationStore.guardUnhandledError(() =>
273
+ flowResult(state.handleRun(RECONCILIATION_EXECUTION_TYPE.RECONCILIATION)),
274
+ );
275
+
276
+ const runSourceQuery = applicationStore.guardUnhandledError(() =>
277
+ flowResult(state.handleRun(RECONCILIATION_EXECUTION_TYPE.SOURCE_QUERY)),
278
+ );
279
+
280
+ const runTargetQuery = applicationStore.guardUnhandledError(() =>
281
+ flowResult(state.handleRun(RECONCILIATION_EXECUTION_TYPE.TARGET_QUERY)),
282
+ );
283
+
284
+ const retryFetchColumns = applicationStore.guardUnhandledError(() =>
285
+ flowResult(state.retryFetchColumns()),
286
+ );
287
+
288
+ const getResultSetDescription = (
289
+ _executionResult: ExecutionResult,
290
+ ): string | undefined => {
291
+ const queryDuration = state.executionDuration
292
+ ? prettyDuration(state.executionDuration)
293
+ : undefined;
294
+ if (!queryDuration) {
295
+ return undefined;
296
+ }
297
+ const executionName =
298
+ state.lastExecutionType === RECONCILIATION_EXECUTION_TYPE.RECONCILIATION
299
+ ? 'Data Comparison'
300
+ : state.lastExecutionType === RECONCILIATION_EXECUTION_TYPE.SOURCE_QUERY
301
+ ? 'Source Query'
302
+ : 'Target Query';
303
+ return `${executionName} ran in ${queryDuration}`;
304
+ };
305
+
306
+ const resultDescription =
307
+ !isRunning && executionResult
308
+ ? getResultSetDescription(executionResult)
309
+ : undefined;
310
+
311
+ const darkMode =
312
+ !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled;
313
+ const [limitValue, setLimitValue] = React.useState(state.limit);
314
+ const inputRef = React.useRef<HTMLInputElement>(null);
315
+
316
+ const changeLimit: React.ChangeEventHandler<HTMLInputElement> = (event) => {
317
+ setLimitValue(parseInt(event.target.value, 10));
318
+ };
319
+
320
+ const getLimit = (): void => {
321
+ if (isNaN(limitValue) || limitValue === 0) {
322
+ setLimitValue(1000);
323
+ state.setLimit(1000);
324
+ } else {
325
+ state.setLimit(limitValue);
326
+ }
327
+ };
328
+
329
+ const onLimitKeyDown: React.KeyboardEventHandler<HTMLInputElement> = (
330
+ event,
331
+ ) => {
332
+ if (event.code === 'Enter') {
333
+ getLimit();
334
+ inputRef.current?.focus();
335
+ } else if (event.code === 'Escape') {
336
+ setLimitValue(state.limit);
337
+ inputRef.current?.select();
338
+ }
339
+ };
340
+
341
+ useEffect(() => {
342
+ setLimitValue(state.limit);
343
+ }, [state.limit]);
344
+
345
+ const renderResult = (): React.ReactNode => {
346
+ if (executionResult instanceof TDSExecutionResult) {
347
+ const colDefs = executionResult.result.columns.map(
348
+ (colName) =>
349
+ ({
350
+ minWidth: 50,
351
+ sortable: true,
352
+ resizable: true,
353
+ field: colName,
354
+ flex: 1,
355
+ ...getTDSColumnCustomizations(executionResult, colName),
356
+ ...getFilterTDSColumnCustomizations(executionResult, colName),
357
+ cellRenderer: DataQualityResultCellRenderer,
358
+ }) as DataGridColumnDefinition,
359
+ );
360
+ return (
361
+ <div className="data-quality-validation__result__values__table">
362
+ <div
363
+ className={clsx('data-quality-validation__result__tds-grid', {
364
+ 'ag-theme-balham': !darkMode,
365
+ 'ag-theme-balham-dark': darkMode,
366
+ })}
367
+ >
368
+ <DataGrid
369
+ rowData={getRowDataFromExecutionResult(executionResult)}
370
+ gridOptions={{
371
+ suppressScrollOnNewData: true,
372
+ getRowId: (data) => `${data.data.rowNumber}`,
373
+ rowSelection: {
374
+ mode: 'multiRow',
375
+ checkboxes: false,
376
+ headerCheckbox: false,
377
+ },
378
+ }}
379
+ onRowDataUpdated={(params) => {
380
+ params.api.refreshCells({ force: true });
381
+ }}
382
+ suppressFieldDotNotation={true}
383
+ suppressContextMenu={false}
384
+ columnDefs={colDefs}
385
+ />
386
+ </div>
387
+ </div>
388
+ );
389
+ }
390
+ if (executionResult instanceof RawExecutionResult) {
391
+ const val =
392
+ executionResult.value === null
393
+ ? 'null'
394
+ : executionResult.value.toString();
395
+ return (
396
+ <CodeEditor
397
+ language={CODE_EDITOR_LANGUAGE.TEXT}
398
+ inputValue={val}
399
+ isReadOnly={true}
400
+ />
401
+ );
402
+ } else if (executionResult !== undefined) {
403
+ const json =
404
+ returnUndefOnError(() =>
405
+ JSON.stringify(
406
+ extractExecutionResultValues(executionResult),
407
+ null,
408
+ DEFAULT_TAB_SIZE,
409
+ ),
410
+ ) ?? JSON.stringify(executionResult);
411
+ return (
412
+ <CodeEditor
413
+ language={CODE_EDITOR_LANGUAGE.JSON}
414
+ inputValue={json}
415
+ isReadOnly={true}
416
+ />
417
+ );
418
+ }
419
+ return <BlankPanelContent>No Data to Display</BlankPanelContent>;
420
+ };
421
+
422
+ useEffect(() => {
423
+ flowResult(
424
+ state.sourceLambdaEditorState.convertLambdaObjectToGrammarString({
425
+ pretty: true,
426
+ firstLoad: true,
427
+ }),
428
+ ).catch(applicationStore.alertUnhandledError);
429
+ flowResult(
430
+ state.targetLambdaEditorState.convertLambdaObjectToGrammarString({
431
+ pretty: true,
432
+ firstLoad: true,
433
+ }),
434
+ ).catch(applicationStore.alertUnhandledError);
435
+ }, [
436
+ applicationStore,
437
+ state.sourceLambdaEditorState,
438
+ state.targetLambdaEditorState,
439
+ ]);
440
+
441
+ const sourceHashColumnValue = md5Strategy.sourceHashColumn
442
+ ? {
443
+ value: md5Strategy.sourceHashColumn,
444
+ label: md5Strategy.sourceHashColumn,
445
+ }
446
+ : undefined;
447
+
448
+ const targetHashColumnValue = md5Strategy.targetHashColumn
449
+ ? {
450
+ value: md5Strategy.targetHashColumn,
451
+ label: md5Strategy.targetHashColumn,
452
+ }
453
+ : undefined;
454
+
455
+ const selectedKeyOptions = useMemo(() => {
456
+ const selectedKeys = new Set(comparison.keys);
457
+ return combinedColumnOptions.filter(({ value }) => selectedKeys.has(value));
458
+ }, [combinedColumnOptions, comparison.keys]);
459
+
460
+ const selectedColumnsToCompareOptions = useMemo(() => {
461
+ const selectedColumns = new Set(comparison.columnsToCompare);
462
+ return combinedColumnOptions.filter(({ value }) =>
463
+ selectedColumns.has(value),
464
+ );
465
+ }, [combinedColumnOptions, comparison.columnsToCompare]);
466
+
467
+ return (
468
+ <div className="data-quality-relation-comparison-editor">
469
+ <Panel>
470
+ <PanelLoadingIndicator isLoading={isRunning} />
471
+ <div className="panel__header">
472
+ <div className="panel__header__title">
473
+ <div className="panel__header__title__label">
474
+ dataQualityRelationComparison
475
+ </div>
476
+ <div className="panel__header__title__content">
477
+ {comparison.name}
478
+ </div>
479
+ </div>
480
+ </div>
481
+ <div className="data-quality-relation-comparison-editor__actions-bar">
482
+ <div className="data-quality-relation-comparison-editor__actions-bar__limit">
483
+ <div className="data-quality-relation-comparison-editor__actions-bar__limit__label">
484
+ preview row limit
485
+ </div>
486
+ <input
487
+ ref={inputRef}
488
+ className="input--dark data-quality-relation-comparison-editor__actions-bar__limit__input"
489
+ spellCheck={false}
490
+ type="number"
491
+ value={Number.isNaN(limitValue) ? '' : limitValue}
492
+ min={1}
493
+ placeholder={DEFAULT_LIMIT.toString()}
494
+ onChange={changeLimit}
495
+ onBlur={getLimit}
496
+ onKeyDown={onLimitKeyDown}
497
+ />
498
+ </div>
499
+ <div className="btn__dropdown-combo btn__dropdown-combo--primary">
500
+ {state.isRunning ? (
501
+ <button
502
+ className="btn__dropdown-combo__canceler data-quality-relation-comparison-editor__actions-bar__cancel-btn"
503
+ onClick={cancelRun}
504
+ tabIndex={-1}
505
+ >
506
+ <div className="btn--dark btn--caution btn__dropdown-combo__canceler__label data-quality-relation-comparison-editor__actions-bar__cancel-label">
507
+ <PauseCircleIcon className="btn__dropdown-combo__canceler__label__icon" />
508
+ <div className="btn__dropdown-combo__canceler__label__title">
509
+ Stop
510
+ </div>
511
+ </div>
512
+ </button>
513
+ ) : (
514
+ <div className="data-quality-relation-comparison-editor__actions-bar__run-group">
515
+ <button
516
+ className="btn__dropdown-combo__label data-quality-relation-comparison-editor__actions-bar__run-btn"
517
+ onClick={runReconciliation}
518
+ title="Run Data Comparison"
519
+ disabled={isRunning}
520
+ tabIndex={-1}
521
+ >
522
+ <PlayIcon className="btn__dropdown-combo__label__icon" />
523
+ <div className="btn__dropdown-combo__label__title">
524
+ Run Data Comparison
525
+ </div>
526
+ </button>
527
+ <ControlledDropdownMenu
528
+ className="btn__dropdown-combo__dropdown-btn data-quality-relation-comparison-editor__actions-bar__dropdown-btn"
529
+ disabled={isRunning}
530
+ content={
531
+ <MenuContent>
532
+ <MenuContentItem
533
+ className="btn__dropdown-combo__option"
534
+ onClick={runSourceQuery}
535
+ >
536
+ Run Source Query
537
+ </MenuContentItem>
538
+ <MenuContentItem
539
+ className="btn__dropdown-combo__option"
540
+ onClick={runTargetQuery}
541
+ >
542
+ Run Target Query
543
+ </MenuContentItem>
544
+ </MenuContent>
545
+ }
546
+ menuProps={{
547
+ anchorOrigin: {
548
+ vertical: 'bottom',
549
+ horizontal: 'right',
550
+ },
551
+ transformOrigin: {
552
+ vertical: 'top',
553
+ horizontal: 'right',
554
+ },
555
+ }}
556
+ >
557
+ <CaretDownIcon />
558
+ </ControlledDropdownMenu>
559
+ </div>
560
+ )}
561
+ </div>
562
+ </div>
563
+ <PanelContent>
564
+ <div className="data-quality-relation-comparison-editor__queries">
565
+ <ResizablePanelGroup orientation="vertical">
566
+ <ResizablePanel minSize={200}>
567
+ <div className="data-quality-relation-comparison-editor__query-panel">
568
+ <div className="data-quality-relation-comparison-editor__query-panel__header">
569
+ <div className="data-quality-relation-comparison-editor__query-panel__title">
570
+ SOURCE QUERY
571
+ </div>
572
+ </div>
573
+ <div
574
+ className={clsx(
575
+ 'data-quality-relation-comparison-editor__query-panel__content',
576
+ {
577
+ backdrop__element: Boolean(
578
+ state.sourceLambdaEditorState.parserError,
579
+ ),
580
+ },
581
+ )}
582
+ >
583
+ <LambdaEditor
584
+ className="data-quality-relation-comparison-editor__lambda-editor lambda-editor--dark"
585
+ disabled={
586
+ state.sourceLambdaEditorState
587
+ .isConvertingFunctionBodyToString
588
+ }
589
+ lambdaEditorState={state.sourceLambdaEditorState}
590
+ forceBackdrop={false}
591
+ autoFocus={false}
592
+ />
593
+ </div>
594
+ </div>
595
+ </ResizablePanel>
596
+ <ResizablePanelSplitter>
597
+ <ResizablePanelSplitterLine color="var(--color-dark-grey-250)" />
598
+ </ResizablePanelSplitter>
599
+ <ResizablePanel minSize={200}>
600
+ <div className="data-quality-relation-comparison-editor__query-panel">
601
+ <div className="data-quality-relation-comparison-editor__query-panel__header">
602
+ <div className="data-quality-relation-comparison-editor__query-panel__title">
603
+ TARGET QUERY
604
+ </div>
605
+ </div>
606
+ <div
607
+ className={clsx(
608
+ 'data-quality-relation-comparison-editor__query-panel__content',
609
+ {
610
+ backdrop__element: Boolean(
611
+ state.targetLambdaEditorState.parserError,
612
+ ),
613
+ },
614
+ )}
615
+ >
616
+ <LambdaEditor
617
+ className="data-quality-relation-comparison-editor__lambda-editor lambda-editor--dark"
618
+ disabled={
619
+ state.targetLambdaEditorState
620
+ .isConvertingFunctionBodyToString
621
+ }
622
+ lambdaEditorState={state.targetLambdaEditorState}
623
+ forceBackdrop={false}
624
+ autoFocus={false}
625
+ />
626
+ </div>
627
+ </div>
628
+ </ResizablePanel>
629
+ </ResizablePanelGroup>
630
+ </div>
631
+
632
+ <div className="data-quality-relation-comparison-editor__panel__content__form">
633
+ {hasColumnFetchError && (
634
+ <div className="data-quality-relation-comparison-editor__column-fetch-error">
635
+ <ExclamationTriangleIcon className="data-quality-relation-comparison-editor__column-fetch-error__icon" />
636
+ <span className="data-quality-relation-comparison-editor__column-fetch-error__message">
637
+ {columnFetchError}
638
+ </span>
639
+ <button
640
+ className="data-quality-relation-comparison-editor__column-fetch-error__retry-btn btn--dark btn--sm"
641
+ onClick={retryFetchColumns}
642
+ disabled={isFetchingColumns}
643
+ tabIndex={-1}
644
+ >
645
+ <RefreshIcon />
646
+ <span>Retry</span>
647
+ </button>
648
+ </div>
649
+ )}
650
+ {hasNoOverlappingColumns && (
651
+ <div className="data-quality-relation-comparison-editor__column-overlap-warning">
652
+ <ExclamationTriangleIcon className="data-quality-relation-comparison-editor__column-overlap-warning__icon" />
653
+ <span className="data-quality-relation-comparison-editor__column-overlap-warning__message">
654
+ No overlapping columns found between source and target
655
+ queries. The Keys and Columns to Compare selectors require at
656
+ least one common column name across both queries.
657
+ </span>
658
+ </div>
659
+ )}
660
+ <PanelFormSection>
661
+ <div className="panel__content__form__section__header__label">
662
+ Keys
663
+ </div>
664
+ <div className="panel__content__form__section__header__prompt">
665
+ Columns used as join keys between source and target
666
+ </div>
667
+ <DataQualityMultiCustomSelector
668
+ value={selectedKeyOptions}
669
+ onChange={(values) =>
670
+ state.setKeys(values.map((option) => option.value))
671
+ }
672
+ options={combinedColumnOptions}
673
+ placeholder="Select keys..."
674
+ disabled={columnsDisabled}
675
+ darkMode={darkMode}
676
+ />
677
+ </PanelFormSection>
678
+
679
+ <PanelFormSection>
680
+ <div className="panel__content__form__section__header__label">
681
+ Columns to Compare
682
+ </div>
683
+ <DataQualityMultiCustomSelector
684
+ value={selectedColumnsToCompareOptions}
685
+ onChange={(values) =>
686
+ state.setColumnsToCompare(
687
+ values.map((option) => option.value),
688
+ )
689
+ }
690
+ options={combinedColumnOptions}
691
+ placeholder="Select columns to compare..."
692
+ disabled={columnsDisabled}
693
+ darkMode={darkMode}
694
+ />
695
+ </PanelFormSection>
696
+
697
+ <PanelFormSection>
698
+ <div className="panel__content__form__section__header__label">
699
+ Source Hash Column
700
+ </div>
701
+ <div className="panel__content__form__section__header__prompt">
702
+ If a source hash column already exists you can specify it here
703
+ (optional)
704
+ </div>
705
+ <CustomSelectorInput
706
+ value={sourceHashColumnValue ?? null}
707
+ options={sourceColumnOptions}
708
+ onChange={(opt: { value: string; label: string } | null) => {
709
+ state.setSourceHashColumn(opt?.value);
710
+ }}
711
+ placeholder="Select source hash column..."
712
+ isClearable={true}
713
+ darkMode={darkMode}
714
+ disabled={columnsDisabled}
715
+ />
716
+ </PanelFormSection>
717
+ <PanelFormSection>
718
+ <div className="panel__content__form__section__header__label">
719
+ Target Hash Column
720
+ </div>
721
+ <div className="panel__content__form__section__header__prompt">
722
+ If a target hash column already exists you can specify it here
723
+ (optional)
724
+ </div>
725
+ <CustomSelectorInput
726
+ value={targetHashColumnValue ?? null}
727
+ options={targetColumnOptions}
728
+ onChange={(opt: { value: string; label: string } | null) => {
729
+ state.setTargetHashColumn(opt?.value);
730
+ }}
731
+ placeholder="Select target hash column..."
732
+ isClearable={true}
733
+ darkMode={darkMode}
734
+ disabled={columnsDisabled}
735
+ />
736
+ </PanelFormSection>
737
+ <PanelFormBooleanField
738
+ name="Aggregated Hash"
739
+ prompt="Compare data at a group level using keys, or compare entire datasets as a whole when no keys are provided."
740
+ value={md5Strategy.aggregatedHash}
741
+ isReadOnly={false}
742
+ update={(value) => state.setAggregatedHash(value)}
743
+ />
744
+ </div>
745
+
746
+ <div className="data-quality-relation-comparison-editor__result">
747
+ <div className="data-quality-relation-comparison-editor__result__header">
748
+ <div className="data-quality-relation-comparison-editor__result__header-group">
749
+ <div className="data-quality-relation-comparison-editor__result__title">
750
+ RESULT
751
+ </div>
752
+ {isRunning && (
753
+ <div className="data-quality-relation-comparison-editor__result__status">
754
+ {state.currentExecutionType ===
755
+ RECONCILIATION_EXECUTION_TYPE.RECONCILIATION
756
+ ? 'Running Data Comparison...'
757
+ : state.currentExecutionType ===
758
+ RECONCILIATION_EXECUTION_TYPE.SOURCE_QUERY
759
+ ? 'Running Source Query...'
760
+ : 'Running Target Query...'}
761
+ </div>
762
+ )}
763
+ <div className="data-quality-relation-comparison-editor__result__analytics">
764
+ {resultDescription ?? ''}
765
+ </div>
766
+ </div>
767
+ </div>
768
+ <div className="data-quality-relation-comparison-editor__result__content">
769
+ <div className="data-quality-relation-comparison-editor__result__viewer">
770
+ {renderResult()}
771
+ </div>
772
+ </div>
773
+ </div>
774
+ </PanelContent>
775
+ </Panel>
776
+ {state.sourceParametersState.parameterValuesEditorState.showModal && (
777
+ <LambdaParameterValuesEditor
778
+ graph={editorStore.graphManagerState.graph}
779
+ observerContext={editorStore.changeDetectionState.observerContext}
780
+ lambdaParametersState={state.sourceParametersState}
781
+ />
782
+ )}
783
+ {state.targetParametersState.parameterValuesEditorState.showModal && (
784
+ <LambdaParameterValuesEditor
785
+ graph={editorStore.graphManagerState.graph}
786
+ observerContext={editorStore.changeDetectionState.observerContext}
787
+ lambdaParametersState={state.targetParametersState}
788
+ />
789
+ )}
790
+ {state.comparisonParametersEditorState.showModal && (
791
+ <DataQualityRelationComparisonParametersEditor state={state} />
792
+ )}
793
+ </div>
794
+ );
795
+ });