@finos/legend-query-builder 0.6.26 → 0.6.28

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 (88) hide show
  1. package/lib/components/QueryBuilder.d.ts.map +1 -1
  2. package/lib/components/QueryBuilder.js +29 -28
  3. package/lib/components/QueryBuilder.js.map +1 -1
  4. package/lib/components/QueryBuilderComponentTestUtils.d.ts.map +1 -1
  5. package/lib/components/QueryBuilderComponentTestUtils.js +4 -5
  6. package/lib/components/QueryBuilderComponentTestUtils.js.map +1 -1
  7. package/lib/components/QueryBuilderConstantExpressionPanel.d.ts.map +1 -1
  8. package/lib/components/QueryBuilderConstantExpressionPanel.js +5 -4
  9. package/lib/components/QueryBuilderConstantExpressionPanel.js.map +1 -1
  10. package/lib/components/QueryBuilderDiffPanel.d.ts.map +1 -1
  11. package/lib/components/QueryBuilderDiffPanel.js +2 -2
  12. package/lib/components/QueryBuilderDiffPanel.js.map +1 -1
  13. package/lib/components/QueryBuilderParametersPanel.d.ts.map +1 -1
  14. package/lib/components/QueryBuilderParametersPanel.js +13 -8
  15. package/lib/components/QueryBuilderParametersPanel.js.map +1 -1
  16. package/lib/components/QueryBuilderPropertyExpressionEditor.d.ts.map +1 -1
  17. package/lib/components/QueryBuilderPropertyExpressionEditor.js +10 -2
  18. package/lib/components/QueryBuilderPropertyExpressionEditor.js.map +1 -1
  19. package/lib/components/QueryBuilderResultPanel.d.ts.map +1 -1
  20. package/lib/components/QueryBuilderResultPanel.js +17 -1
  21. package/lib/components/QueryBuilderResultPanel.js.map +1 -1
  22. package/lib/components/QueryBuilder_TestID.d.ts +3 -1
  23. package/lib/components/QueryBuilder_TestID.d.ts.map +1 -1
  24. package/lib/components/QueryBuilder_TestID.js +2 -0
  25. package/lib/components/QueryBuilder_TestID.js.map +1 -1
  26. package/lib/components/explorer/QueryBuilderExplorerPanel.d.ts.map +1 -1
  27. package/lib/components/explorer/QueryBuilderExplorerPanel.js +2 -2
  28. package/lib/components/explorer/QueryBuilderExplorerPanel.js.map +1 -1
  29. package/lib/components/explorer/QueryBuilderMilestoningEditor.d.ts.map +1 -1
  30. package/lib/components/explorer/QueryBuilderMilestoningEditor.js +2 -2
  31. package/lib/components/explorer/QueryBuilderMilestoningEditor.js.map +1 -1
  32. package/lib/components/fetch-structure/QueryBuilderTDSWindowPanel.d.ts.map +1 -1
  33. package/lib/components/fetch-structure/QueryBuilderTDSWindowPanel.js +2 -2
  34. package/lib/components/fetch-structure/QueryBuilderTDSWindowPanel.js.map +1 -1
  35. package/lib/components/shared/CustomDatePicker.d.ts +33 -0
  36. package/lib/components/shared/CustomDatePicker.d.ts.map +1 -1
  37. package/lib/components/shared/CustomDatePicker.js +4 -4
  38. package/lib/components/shared/CustomDatePicker.js.map +1 -1
  39. package/lib/components/shared/QueryBuilderVariableSelector.d.ts +1 -1
  40. package/lib/components/shared/QueryBuilderVariableSelector.d.ts.map +1 -1
  41. package/lib/components/shared/QueryBuilderVariableSelector.js +12 -1
  42. package/lib/components/shared/QueryBuilderVariableSelector.js.map +1 -1
  43. package/lib/components/watermark/QueryBuilderWatermark.d.ts.map +1 -1
  44. package/lib/components/watermark/QueryBuilderWatermark.js +2 -2
  45. package/lib/components/watermark/QueryBuilderWatermark.js.map +1 -1
  46. package/lib/index.css +2 -2
  47. package/lib/index.css.map +1 -1
  48. package/lib/index.d.ts +2 -0
  49. package/lib/index.d.ts.map +1 -1
  50. package/lib/index.js +2 -0
  51. package/lib/index.js.map +1 -1
  52. package/lib/package.json +9 -9
  53. package/lib/stores/QueryBuilderEvent.d.ts +25 -0
  54. package/lib/stores/QueryBuilderEvent.d.ts.map +1 -0
  55. package/lib/stores/QueryBuilderEvent.js +26 -0
  56. package/lib/stores/QueryBuilderEvent.js.map +1 -0
  57. package/lib/stores/QueryBuilderResultState.d.ts.map +1 -1
  58. package/lib/stores/QueryBuilderResultState.js +44 -6
  59. package/lib/stores/QueryBuilderResultState.js.map +1 -1
  60. package/lib/stores/QueryBuilderState.d.ts +3 -0
  61. package/lib/stores/QueryBuilderState.d.ts.map +1 -1
  62. package/lib/stores/QueryBuilderState.js +7 -0
  63. package/lib/stores/QueryBuilderState.js.map +1 -1
  64. package/lib/stores/QueryBuilderTelemetry.d.ts +33 -0
  65. package/lib/stores/QueryBuilderTelemetry.d.ts.map +1 -0
  66. package/lib/stores/QueryBuilderTelemetry.js +37 -0
  67. package/lib/stores/QueryBuilderTelemetry.js.map +1 -0
  68. package/package.json +16 -16
  69. package/src/components/QueryBuilder.tsx +25 -22
  70. package/src/components/QueryBuilderComponentTestUtils.tsx +6 -5
  71. package/src/components/QueryBuilderConstantExpressionPanel.tsx +12 -11
  72. package/src/components/QueryBuilderDiffPanel.tsx +2 -3
  73. package/src/components/QueryBuilderParametersPanel.tsx +13 -7
  74. package/src/components/QueryBuilderPropertyExpressionEditor.tsx +27 -7
  75. package/src/components/QueryBuilderResultPanel.tsx +17 -1
  76. package/src/components/QueryBuilder_TestID.ts +2 -0
  77. package/src/components/explorer/QueryBuilderExplorerPanel.tsx +2 -3
  78. package/src/components/explorer/QueryBuilderMilestoningEditor.tsx +2 -3
  79. package/src/components/fetch-structure/QueryBuilderTDSWindowPanel.tsx +3 -6
  80. package/src/components/shared/CustomDatePicker.tsx +3 -3
  81. package/src/components/shared/QueryBuilderVariableSelector.tsx +18 -4
  82. package/src/components/watermark/QueryBuilderWatermark.tsx +2 -6
  83. package/src/index.ts +2 -0
  84. package/src/stores/QueryBuilderEvent.ts +26 -0
  85. package/src/stores/QueryBuilderResultState.ts +74 -2
  86. package/src/stores/QueryBuilderState.ts +10 -0
  87. package/src/stores/QueryBuilderTelemetry.ts +83 -0
  88. package/tsconfig.json +2 -0
@@ -52,6 +52,7 @@ import { variableExpression_setName } from '../stores/shared/ValueSpecificationM
52
52
  import { LambdaParameterState } from '../stores/shared/LambdaParameterState.js';
53
53
  import { LambdaParameterValuesEditor } from './shared/LambdaParameterValuesEditor.js';
54
54
  import { VariableViewer } from './shared/QueryBuilderVariableSelector.js';
55
+ import { QUERY_BUILDER_TEST_ID } from './QueryBuilder_TestID.js';
55
56
 
56
57
  type MultiplicityOption = { label: string; value: Multiplicity };
57
58
 
@@ -71,12 +72,16 @@ const VariableExpressionEditor = observer(
71
72
  const { queryBuilderState, lambdaParameterState } = props;
72
73
  const applicationStore = useApplicationStore();
73
74
  const queryParametersState = queryBuilderState.parametersState;
75
+ const allVariableNames = queryBuilderState.allVariableNames;
74
76
  const isCreating =
75
77
  !queryParametersState.parameterStates.includes(lambdaParameterState);
76
78
  const varState = lambdaParameterState.parameter;
77
79
  const multiplity = varState.multiplicity;
78
80
  const validationMessage = !varState.name
79
81
  ? `Parameter name can't be empty`
82
+ : allVariableNames.filter((e) => e === varState.name).length >
83
+ (isCreating ? 0 : 1)
84
+ ? 'Parameter Name Already Exists'
80
85
  : (isCreating &&
81
86
  queryParametersState.parameterStates.find(
82
87
  (p) => p.parameter.name === varState.name,
@@ -200,13 +205,11 @@ const VariableExpressionEditor = observer(
200
205
  </ModalBody>
201
206
  <ModalFooter>
202
207
  {isCreating && (
203
- <button
204
- className="btn modal__footer__close-btn btn--dark"
208
+ <ModalFooterButton
209
+ text="Create"
210
+ inProgress={Boolean(validationMessage)}
205
211
  onClick={onAction}
206
- disabled={Boolean(validationMessage)}
207
- >
208
- Create
209
- </button>
212
+ />
210
213
  )}
211
214
  <ModalFooterButton onClick={close} text="Close" />
212
215
  </ModalFooter>
@@ -247,7 +250,10 @@ export const QueryBuilderParametersPanel = observer(
247
250
  };
248
251
 
249
252
  return (
250
- <div className="panel query-builder__variables">
253
+ <div
254
+ data-testid={QUERY_BUILDER_TEST_ID.QUERY_BUILDER_PARAMETERS}
255
+ className="panel query-builder__variables"
256
+ >
251
257
  <div className="panel__header">
252
258
  <div className="panel__header__title">
253
259
  <div className="panel__header__title__label">parameters</div>
@@ -25,6 +25,7 @@ import {
25
25
  ModalHeader,
26
26
  ModalBody,
27
27
  ModalFooter,
28
+ ModalFooterButton,
28
29
  } from '@finos/legend-art';
29
30
  import { observer } from 'mobx-react-lite';
30
31
  import {
@@ -304,7 +305,25 @@ export const QueryBuilderPropertyExpressionEditor = observer(
304
305
  const { propertyExpressionState } = props;
305
306
  const handleClose = (): void =>
306
307
  propertyExpressionState.setIsEditingDerivedProperty(false);
307
-
308
+ const isParameterCompatibleWithDerivedProperty = (
309
+ variable: VariableExpression,
310
+ derivedProperties: QueryBuilderDerivedPropertyExpressionState[],
311
+ ): boolean =>
312
+ Boolean(
313
+ derivedProperties.find((dp) => {
314
+ if (!variable.genericType?.value.rawType) {
315
+ return false;
316
+ }
317
+ return (
318
+ isSuperType(
319
+ dp.derivedProperty.genericType.value.rawType,
320
+ variable.genericType.value.rawType,
321
+ ) ||
322
+ dp.derivedProperty.genericType.value.rawType.name ===
323
+ variable.genericType.value.rawType.name
324
+ );
325
+ }),
326
+ );
308
327
  return (
309
328
  <Dialog
310
329
  open={Boolean(
@@ -334,16 +353,17 @@ export const QueryBuilderPropertyExpressionEditor = observer(
334
353
  <ModalBody className="query-builder__variables__modal__body">
335
354
  <VariableSelector
336
355
  queryBuilderState={propertyExpressionState.queryBuilderState}
356
+ filterBy={(v: VariableExpression) =>
357
+ isParameterCompatibleWithDerivedProperty(
358
+ v,
359
+ propertyExpressionState.derivedPropertyExpressionStates,
360
+ )
361
+ }
337
362
  />
338
363
  </ModalBody>
339
364
  </ModalBody>
340
365
  <ModalFooter>
341
- <button
342
- className="btn modal__footer__close-btn"
343
- onClick={handleClose}
344
- >
345
- Done
346
- </button>
366
+ <ModalFooterButton text="Done" onClick={handleClose} />
347
367
  </ModalFooter>
348
368
  </Modal>
349
369
  </Dialog>
@@ -326,6 +326,7 @@ const QueryBuilderGridResult = observer(
326
326
  const [cellDoubleClickedEvent, setCellDoubleClickedEvent] =
327
327
  useState<CellMouseOverEvent | null>(null);
328
328
  const columns = executionResult.result.columns;
329
+ let rowNumber = 1;
329
330
  const rowData = executionResult.result.rows.map((_row) => {
330
331
  const row: PlainObject = {};
331
332
  const cols = executionResult.result.columns;
@@ -334,7 +335,9 @@ const QueryBuilderGridResult = observer(
334
335
  // call `.toString()` to avoid this behavior.
335
336
  // See https://github.com/finos/legend-studio/issues/1008
336
337
  row[cols[idx] as string] = isBoolean(value) ? String(value) : value;
338
+ row.rowNumber = rowNumber;
337
339
  });
340
+ rowNumber += 1;
338
341
  return row;
339
342
  });
340
343
 
@@ -351,7 +354,8 @@ const QueryBuilderGridResult = observer(
351
354
  }
352
355
  disabled={
353
356
  !(fetchStructureImplementation instanceof QueryBuilderTDSState) ||
354
- !queryBuilderState.isQuerySupported
357
+ !queryBuilderState.isQuerySupported ||
358
+ !cellDoubleClickedEvent
355
359
  }
356
360
  menuProps={{ elevation: 7 }}
357
361
  key={executionResult._UUID}
@@ -359,7 +363,19 @@ const QueryBuilderGridResult = observer(
359
363
  >
360
364
  <AgGridReact
361
365
  rowData={rowData}
366
+ gridOptions={{
367
+ suppressScrollOnNewData: true,
368
+ getRowId: function (data) {
369
+ return data.data.rowNumber as string;
370
+ },
371
+ }}
362
372
  modules={[ClientSideRowModelModule]}
373
+ // Note: we use onCellMouseOver as a bit of a workaround
374
+ // since we use the context menu so we want the user to be
375
+ // able to right click any cell and have the context menu
376
+ // options use the data belonging to the row that they are
377
+ // in. hence why we set the cell every time we mouse over
378
+ // rather than making user click multiple times.
363
379
  onCellMouseOver={(event): void => {
364
380
  setCellDoubleClickedEvent(event);
365
381
  }}
@@ -27,4 +27,6 @@ export enum QUERY_BUILDER_TEST_ID {
27
27
  QUERY_BUILDER_EXPLORER = 'query__builder__explorer',
28
28
  QUERY_BUILDER_PROPERTY_SEARCH_PANEL = 'query__builder__property__search__panel',
29
29
  QUERY_BUILDER_RESULT_PANEL = 'query__builder__result__panel',
30
+ QUERY_BUILDER_PARAMETERS = 'query-builder__parameters',
31
+ QUERY_BUILDER_CONSTANTS = 'query-builder__constants',
30
32
  }
@@ -52,6 +52,7 @@ import {
52
52
  ModalBody,
53
53
  ModalFooter,
54
54
  ModalHeader,
55
+ ModalFooterButton,
55
56
  } from '@finos/legend-art';
56
57
  import {
57
58
  type QueryBuilderExplorerTreeDragSource,
@@ -266,9 +267,7 @@ const QueryBuilderExplorerPreviewDataModal = observer(
266
267
  )}
267
268
  </ModalBody>
268
269
  <ModalFooter>
269
- <button className="btn modal__footer__close-btn" onClick={close}>
270
- Close
271
- </button>
270
+ <ModalFooterButton text="Close" onClick={close} />
272
271
  </ModalFooter>
273
272
  </Modal>
274
273
  </Dialog>
@@ -35,6 +35,7 @@ import {
35
35
  Modal,
36
36
  ModalBody,
37
37
  ModalFooter,
38
+ ModalFooterButton,
38
39
  ModalHeader,
39
40
  PanelEntryDropZonePlaceholder,
40
41
  PanelFormSection,
@@ -279,9 +280,7 @@ export const MilestoningParametersEditor = observer(
279
280
  </div>
280
281
  </ModalBody>
281
282
  <ModalFooter>
282
- <button className="btn modal__footer__close-btn" onClick={close}>
283
- Close
284
- </button>
283
+ <ModalFooterButton text="Close" onClick={close} />
285
284
  </ModalFooter>
286
285
  </Modal>
287
286
  </Dialog>
@@ -42,6 +42,7 @@ import {
42
42
  Modal,
43
43
  ModalFooter,
44
44
  PanelFormSection,
45
+ ModalFooterButton,
45
46
  } from '@finos/legend-art';
46
47
  import { assertErrorThrown, guaranteeNonNullable } from '@finos/legend-shared';
47
48
  import { observer } from 'mobx-react-lite';
@@ -513,13 +514,9 @@ const QueryBuilderWindowColumnModalEditor = observer(
513
514
  </div>
514
515
  <ModalFooter>
515
516
  {createNewWindow ? (
516
- <button className="btn modal__footer__close-btn" onClick={create}>
517
- Create
518
- </button>
517
+ <ModalFooterButton text="Create" onClick={create} />
519
518
  ) : (
520
- <button className="btn modal__footer__close-btn" onClick={close}>
521
- Close
522
- </button>
519
+ <ModalFooterButton text="Close" onClick={close} />
523
520
  )}
524
521
  </ModalFooter>
525
522
  </Modal>
@@ -56,7 +56,7 @@ import {
56
56
  valueSpecification_setGenericType,
57
57
  } from '../../stores/shared/ValueSpecificationModifierHelper.js';
58
58
 
59
- enum CUSTOM_DATE_PICKER_OPTION {
59
+ export enum CUSTOM_DATE_PICKER_OPTION {
60
60
  ABSOLUTE_DATE = 'Absolute Date',
61
61
  ABSOLUTE_TIME = 'Absolute Time',
62
62
  TODAY = 'Today',
@@ -431,7 +431,7 @@ const buildPureDurationEnumValue = (
431
431
  };
432
432
 
433
433
  /**
434
- * Generate the pure date ajust() function based on the CustomDateOption.
434
+ * Generate the pure date adjust() function based on the CustomDateOption.
435
435
  */
436
436
  const buildPureAdjustDateFunction = (
437
437
  customDateOption: CustomDateOption,
@@ -634,7 +634,7 @@ const buildCustomDateOption = (
634
634
  /**
635
635
  * Build DatePickerOption from pure date functions or PrimitiveInstanceValue
636
636
  */
637
- const buildDatePickerOption = (
637
+ export const buildDatePickerOption = (
638
638
  valueSpecification: SimpleFunctionExpression | PrimitiveInstanceValue,
639
639
  ): DatePickerOption => {
640
640
  if (valueSpecification instanceof SimpleFunctionExpression) {
@@ -23,9 +23,10 @@ import {
23
23
  TimesIcon,
24
24
  useDragPreviewLayer,
25
25
  } from '@finos/legend-art';
26
- import type {
27
- ValueSpecification,
28
- VariableExpression,
26
+ import {
27
+ SimpleFunctionExpression,
28
+ type ValueSpecification,
29
+ type VariableExpression,
29
30
  } from '@finos/legend-graph';
30
31
  import { observer } from 'mobx-react-lite';
31
32
  import { useDrag } from 'react-dnd';
@@ -36,6 +37,7 @@ import {
36
37
  QUERY_BUILDER_VARIABLE_DND_TYPE,
37
38
  VariableInfoTooltip,
38
39
  } from './BasicValueSpecificationEditor.js';
40
+ import { buildDatePickerOption } from './CustomDatePicker.js';
39
41
 
40
42
  export const VariableViewer = observer(
41
43
  (props: {
@@ -50,8 +52,19 @@ export const VariableViewer = observer(
50
52
  }) => {
51
53
  const { variable, constantValue, actions, isReadOnly, queryBuilderState } =
52
54
  props;
55
+
56
+ const getNameOfValue = (value: ValueSpecification): string | undefined => {
57
+ if (value instanceof SimpleFunctionExpression) {
58
+ const possibleDateLabel = buildDatePickerOption(value).label;
59
+ if (possibleDateLabel) {
60
+ return possibleDateLabel;
61
+ }
62
+ }
63
+ return getValueSpecificationStringValue(value);
64
+ };
65
+
53
66
  const valueString = constantValue
54
- ? getValueSpecificationStringValue(constantValue)
67
+ ? getNameOfValue(constantValue)
55
68
  : undefined;
56
69
  const name = variable.name;
57
70
  const variableType = variable.genericType?.value.rawType;
@@ -159,6 +172,7 @@ export const VariableSelector = observer(
159
172
  queryBuilderState.constantState.constants.filter((c) =>
160
173
  filterBy ? filterBy(c.variable) : true,
161
174
  );
175
+
162
176
  return (
163
177
  <>
164
178
  <PanelFormListItems title="Available parameters">
@@ -25,6 +25,7 @@ import {
25
25
  ModalFooter,
26
26
  ModalHeader,
27
27
  PanelFormSection,
28
+ ModalFooterButton,
28
29
  } from '@finos/legend-art';
29
30
  import {
30
31
  areMultiplicitiesEqual,
@@ -169,12 +170,7 @@ export const QueryBuilderWatermarkEditor = observer(
169
170
  </PanelForm>
170
171
  </ModalBody>
171
172
  <ModalFooter>
172
- <button
173
- className="btn modal__footer__close-btn"
174
- onClick={handleClose}
175
- >
176
- Done
177
- </button>
173
+ <ModalFooterButton text="Done" onClick={handleClose} />
178
174
  </ModalFooter>
179
175
  </Modal>
180
176
  </Dialog>
package/src/index.ts CHANGED
@@ -49,6 +49,8 @@ export * from './stores/ServiceInfo.js';
49
49
  export * from './components/ServiceQuerySetupUtils.js';
50
50
  export * from './components/QuerySetupUtils.js';
51
51
  export * from './components/QueryBuilderTextEditor.js';
52
+ export * from './stores/QueryBuilderTelemetry.js';
53
+ export * from './stores/QueryBuilderEvent.js';
52
54
 
53
55
  export { TEST__setUpQueryBuilder } from './components/QueryBuilderComponentTestUtils.js';
54
56
 
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Copyright (c) 2020-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
+ export enum QUERY_BUILDER_EVENT {
18
+ RUN_QUERY__LAUNCH = 'editor.execution.run-query.launch',
19
+ GENERATE_EXECUTION_PLAN__LAUNCH = 'editor.execution.generate-plan.launch',
20
+ DEBUG_EXECUTION_PLAN__LAUNCH = 'editor.execution.debug-plan.launch',
21
+
22
+ RUN_QUERY__SUCCESS = 'editor.execution.run-query.success',
23
+ GENERATE_EXECUTION_PLAN__SUCCESS = 'editor.execution.generate-plan.success',
24
+ DEBUG_EXECUTION_PLAN__SUCCESS = 'editor.execution.debug-plan.success',
25
+ BUILD_EXECUTION_PLAN__SUCCESS = 'graph-manager.execution.build-plan.success',
26
+ }
@@ -25,6 +25,7 @@ import {
25
25
  downloadFileUsingDataURI,
26
26
  UnsupportedOperationError,
27
27
  ActionState,
28
+ StopWatch,
28
29
  } from '@finos/legend-shared';
29
30
  import type { QueryBuilderState } from './QueryBuilderState.js';
30
31
  import {
@@ -35,6 +36,7 @@ import {
35
36
  EXECUTION_SERIALIZATION_FORMAT,
36
37
  RawExecutionResult,
37
38
  buildRawLambdaFromLambdaFunction,
39
+ reportGraphAnalytics,
38
40
  } from '@finos/legend-graph';
39
41
  import { buildLambdaFunction } from './QueryBuilderValueSpecificationBuilder.js';
40
42
  import { ExecutionPlanState } from '@finos/legend-application';
@@ -43,6 +45,8 @@ import {
43
45
  getExecutionQueryFromRawLambda,
44
46
  } from './shared/LambdaParameterState.js';
45
47
  import type { LambdaFunctionBuilderOption } from './QueryBuilderValueSpecificationBuilderHelper.js';
48
+ import { QueryBuilderTelemetry } from './QueryBuilderTelemetry.js';
49
+ import { QUERY_BUILDER_EVENT } from './QueryBuilderEvent.js';
46
50
 
47
51
  const DEFAULT_LIMIT = 1000;
48
52
 
@@ -219,7 +223,21 @@ export class QueryBuilderResultState {
219
223
  this.queryBuilderState.parametersState.parameterStates,
220
224
  this.queryBuilderState.graphManagerState,
221
225
  );
222
- const startTime = Date.now();
226
+
227
+ QueryBuilderTelemetry.logEvent_QueryRunLaunched(
228
+ this.queryBuilderState.applicationStore.telemetryService,
229
+ this.queryBuilderState.applicationContext
230
+ ? {
231
+ applicationContext: this.queryBuilderState.applicationContext,
232
+ }
233
+ : {},
234
+ );
235
+
236
+ const stopWatch = new StopWatch();
237
+ const report = reportGraphAnalytics(
238
+ this.queryBuilderState.graphManagerState.graph,
239
+ );
240
+
223
241
  const promise =
224
242
  this.queryBuilderState.graphManagerState.graphManager.runQuery(
225
243
  query,
@@ -230,12 +248,22 @@ export class QueryBuilderResultState {
230
248
  parameterValues,
231
249
  },
232
250
  );
251
+
233
252
  this.setQueryRunPromise(promise);
234
253
  const result = (yield promise) as ExecutionResult;
235
254
  if (this.queryRunPromise === promise) {
236
255
  this.setExecutionResult(result);
237
256
  this.latestRunHashCode = currentHashCode;
238
- this.setExecutionDuration(Date.now() - startTime);
257
+ this.setExecutionDuration(stopWatch.elapsed);
258
+
259
+ report.timings = {
260
+ ...report.timings,
261
+ total: stopWatch.elapsed,
262
+ };
263
+ QueryBuilderTelemetry.logEvent_QueryRunSucceeded(
264
+ this.queryBuilderState.applicationStore.telemetryService,
265
+ report,
266
+ );
239
267
  }
240
268
  } catch (error) {
241
269
  assertErrorThrown(error);
@@ -263,25 +291,51 @@ export class QueryBuilderResultState {
263
291
  );
264
292
  const query = this.queryBuilderState.buildQuery();
265
293
  let rawPlan: RawExecutionPlan;
294
+
295
+ const stopWatch = new StopWatch();
296
+ const report = reportGraphAnalytics(
297
+ this.queryBuilderState.graphManagerState.graph,
298
+ );
299
+
266
300
  if (debug) {
301
+ QueryBuilderTelemetry.logEvent_ExecutionPlanDebugLaunched(
302
+ this.queryBuilderState.applicationStore.telemetryService,
303
+ this.queryBuilderState.applicationContext
304
+ ? {
305
+ applicationContext: this.queryBuilderState.applicationContext,
306
+ }
307
+ : {},
308
+ );
267
309
  const debugResult =
268
310
  (yield this.queryBuilderState.graphManagerState.graphManager.debugExecutionPlanGeneration(
269
311
  query,
270
312
  mapping,
271
313
  runtime,
272
314
  this.queryBuilderState.graphManagerState.graph,
315
+ report,
273
316
  )) as { plan: RawExecutionPlan; debug: string };
274
317
  rawPlan = debugResult.plan;
275
318
  this.executionPlanState.setDebugText(debugResult.debug);
276
319
  } else {
320
+ QueryBuilderTelemetry.logEvent_ExecutionPlanGenerationLaunched(
321
+ this.queryBuilderState.applicationStore.telemetryService,
322
+ this.queryBuilderState.applicationContext
323
+ ? {
324
+ applicationContext: this.queryBuilderState.applicationContext,
325
+ }
326
+ : {},
327
+ );
277
328
  rawPlan =
278
329
  (yield this.queryBuilderState.graphManagerState.graphManager.generateExecutionPlan(
279
330
  query,
280
331
  mapping,
281
332
  runtime,
282
333
  this.queryBuilderState.graphManagerState.graph,
334
+ report,
283
335
  )) as object;
284
336
  }
337
+
338
+ stopWatch.record();
285
339
  try {
286
340
  this.executionPlanState.setRawPlan(rawPlan);
287
341
  const plan =
@@ -293,6 +347,24 @@ export class QueryBuilderResultState {
293
347
  } catch {
294
348
  // do nothing
295
349
  }
350
+ stopWatch.record(QUERY_BUILDER_EVENT.BUILD_EXECUTION_PLAN__SUCCESS);
351
+
352
+ report.timings = {
353
+ ...report.timings,
354
+ ...Object.fromEntries(stopWatch.records),
355
+ total: stopWatch.elapsed,
356
+ };
357
+ if (debug) {
358
+ QueryBuilderTelemetry.logEvent_ExecutionPlanDebugSucceeded(
359
+ this.queryBuilderState.applicationStore.telemetryService,
360
+ report,
361
+ );
362
+ } else {
363
+ QueryBuilderTelemetry.logEvent_ExecutionPlanGenerationSucceeded(
364
+ this.queryBuilderState.applicationStore.telemetryService,
365
+ report,
366
+ );
367
+ }
296
368
  } catch (error) {
297
369
  assertErrorThrown(error);
298
370
  this.queryBuilderState.applicationStore.log.error(
@@ -99,6 +99,7 @@ export abstract class QueryBuilderState implements CommandRegistrar {
99
99
  textEditorState: QueryBuilderTextEditorState;
100
100
  unsupportedQueryState: QueryBuilderUnsupportedQueryState;
101
101
  observableContext: ObserverContext;
102
+ titleOfQuery: string | undefined;
102
103
 
103
104
  queryCompileState = ActionState.create();
104
105
  showFunctionsExplorerPanel = false;
@@ -110,6 +111,8 @@ export abstract class QueryBuilderState implements CommandRegistrar {
110
111
  mapping?: Mapping | undefined;
111
112
  runtimeValue?: Runtime | undefined;
112
113
 
114
+ applicationContext?: string | undefined;
115
+
113
116
  // NOTE: this makes it so that we need to import components in stores code,
114
117
  // we probably want to refactor to an extension mechanism
115
118
  TEMPORARY__setupPanelContentRenderer?: (() => React.ReactNode) | undefined;
@@ -126,6 +129,7 @@ export abstract class QueryBuilderState implements CommandRegistrar {
126
129
  fetchStructureState: observable,
127
130
  filterState: observable,
128
131
  watermarkState: observable,
132
+ titleOfQuery: observable,
129
133
  checkEntitlementsState: observable,
130
134
  resultState: observable,
131
135
  textEditorState: observable,
@@ -151,6 +155,8 @@ export abstract class QueryBuilderState implements CommandRegistrar {
151
155
  setMapping: action,
152
156
  setRuntimeValue: action,
153
157
 
158
+ setTitleOfQuery: action,
159
+
154
160
  resetQueryResult: action,
155
161
  resetQueryContent: action,
156
162
  changeClass: action,
@@ -253,6 +259,10 @@ export abstract class QueryBuilderState implements CommandRegistrar {
253
259
  this.runtimeValue = val;
254
260
  }
255
261
 
262
+ setTitleOfQuery(val: string | undefined): void {
263
+ this.titleOfQuery = val;
264
+ }
265
+
256
266
  get isQuerySupported(): boolean {
257
267
  return !this.unsupportedQueryState.rawLambda;
258
268
  }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Copyright (c) 2020-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 type { GraphManagerOperationReport } from '@finos/legend-graph';
18
+ import type { TelemetryService } from '@finos/legend-shared';
19
+ import { QUERY_BUILDER_EVENT } from './QueryBuilderEvent.js';
20
+
21
+ type LaunchQueryExecution_TelemteryData = {
22
+ applicationContext?: string | undefined;
23
+ };
24
+
25
+ type QueryExecution_TelemetryData = GraphManagerOperationReport & {
26
+ dependenciesCount: number;
27
+ };
28
+
29
+ export class QueryBuilderTelemetry {
30
+ static logEvent_QueryRunLaunched(
31
+ telemetryService: TelemetryService,
32
+ data: LaunchQueryExecution_TelemteryData,
33
+ ): void {
34
+ telemetryService.logEvent(QUERY_BUILDER_EVENT.RUN_QUERY__LAUNCH, data);
35
+ }
36
+
37
+ static logEvent_ExecutionPlanGenerationLaunched(
38
+ telemetryService: TelemetryService,
39
+ data: LaunchQueryExecution_TelemteryData,
40
+ ): void {
41
+ telemetryService.logEvent(
42
+ QUERY_BUILDER_EVENT.GENERATE_EXECUTION_PLAN__LAUNCH,
43
+ data,
44
+ );
45
+ }
46
+
47
+ static logEvent_ExecutionPlanDebugLaunched(
48
+ telemetryService: TelemetryService,
49
+ data: LaunchQueryExecution_TelemteryData,
50
+ ): void {
51
+ telemetryService.logEvent(
52
+ QUERY_BUILDER_EVENT.DEBUG_EXECUTION_PLAN__LAUNCH,
53
+ data,
54
+ );
55
+ }
56
+
57
+ static logEvent_QueryRunSucceeded(
58
+ telemetryService: TelemetryService,
59
+ data: QueryExecution_TelemetryData,
60
+ ): void {
61
+ telemetryService.logEvent(QUERY_BUILDER_EVENT.RUN_QUERY__SUCCESS, data);
62
+ }
63
+
64
+ static logEvent_ExecutionPlanGenerationSucceeded(
65
+ telemetryService: TelemetryService,
66
+ data: QueryExecution_TelemetryData,
67
+ ): void {
68
+ telemetryService.logEvent(
69
+ QUERY_BUILDER_EVENT.GENERATE_EXECUTION_PLAN__SUCCESS,
70
+ data,
71
+ );
72
+ }
73
+
74
+ static logEvent_ExecutionPlanDebugSucceeded(
75
+ telemetryService: TelemetryService,
76
+ data: QueryExecution_TelemetryData,
77
+ ): void {
78
+ telemetryService.logEvent(
79
+ QUERY_BUILDER_EVENT.DEBUG_EXECUTION_PLAN__SUCCESS,
80
+ data,
81
+ );
82
+ }
83
+ }
package/tsconfig.json CHANGED
@@ -53,6 +53,7 @@
53
53
  "./src/stores/QueryBuilderCommand.ts",
54
54
  "./src/stores/QueryBuilderConfig.ts",
55
55
  "./src/stores/QueryBuilderConstantsState.ts",
56
+ "./src/stores/QueryBuilderEvent.ts",
56
57
  "./src/stores/QueryBuilderGroupOperationHelper.ts",
57
58
  "./src/stores/QueryBuilderParametersState.ts",
58
59
  "./src/stores/QueryBuilderPreviewDataHelper.ts",
@@ -61,6 +62,7 @@
61
62
  "./src/stores/QueryBuilderState.ts",
62
63
  "./src/stores/QueryBuilderStateBuilder.ts",
63
64
  "./src/stores/QueryBuilderStateTestUtils.ts",
65
+ "./src/stores/QueryBuilderTelemetry.ts",
64
66
  "./src/stores/QueryBuilderTextEditorState.ts",
65
67
  "./src/stores/QueryBuilderTypeaheadHelper.ts",
66
68
  "./src/stores/QueryBuilderUnsupportedQueryState.ts",