@finos/legend-query-builder 4.19.11 → 4.19.13

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 (118) hide show
  1. package/lib/__lib__/QueryBuilderDocumentation.d.ts +2 -1
  2. package/lib/__lib__/QueryBuilderDocumentation.d.ts.map +1 -1
  3. package/lib/__lib__/QueryBuilderDocumentation.js +1 -0
  4. package/lib/__lib__/QueryBuilderDocumentation.js.map +1 -1
  5. package/lib/__lib__/QueryBuilderEvent.d.ts +45 -1
  6. package/lib/__lib__/QueryBuilderEvent.d.ts.map +1 -1
  7. package/lib/__lib__/QueryBuilderEvent.js +48 -0
  8. package/lib/__lib__/QueryBuilderEvent.js.map +1 -1
  9. package/lib/__lib__/QueryBuilderTelemetryHelper.d.ts +162 -8
  10. package/lib/__lib__/QueryBuilderTelemetryHelper.d.ts.map +1 -1
  11. package/lib/__lib__/QueryBuilderTelemetryHelper.js +48 -0
  12. package/lib/__lib__/QueryBuilderTelemetryHelper.js.map +1 -1
  13. package/lib/components/QueryBuilder.d.ts.map +1 -1
  14. package/lib/components/QueryBuilder.js +9 -5
  15. package/lib/components/QueryBuilder.js.map +1 -1
  16. package/lib/components/QueryBuilderConstantExpressionPanel.d.ts.map +1 -1
  17. package/lib/components/QueryBuilderConstantExpressionPanel.js +18 -1
  18. package/lib/components/QueryBuilderConstantExpressionPanel.js.map +1 -1
  19. package/lib/components/QueryBuilderParametersPanel.d.ts.map +1 -1
  20. package/lib/components/QueryBuilderParametersPanel.js +18 -1
  21. package/lib/components/QueryBuilderParametersPanel.js.map +1 -1
  22. package/lib/components/QueryBuilderSideBar.d.ts.map +1 -1
  23. package/lib/components/QueryBuilderSideBar.js +19 -0
  24. package/lib/components/QueryBuilderSideBar.js.map +1 -1
  25. package/lib/components/fetch-structure/QueryBuilderGraphFetchTreePanel.d.ts.map +1 -1
  26. package/lib/components/fetch-structure/QueryBuilderGraphFetchTreePanel.js +42 -1
  27. package/lib/components/fetch-structure/QueryBuilderGraphFetchTreePanel.js.map +1 -1
  28. package/lib/components/fetch-structure/QueryBuilderPostFilterPanel.d.ts.map +1 -1
  29. package/lib/components/fetch-structure/QueryBuilderPostFilterPanel.js +25 -5
  30. package/lib/components/fetch-structure/QueryBuilderPostFilterPanel.js.map +1 -1
  31. package/lib/components/fetch-structure/QueryBuilderResultModifierPanel.d.ts.map +1 -1
  32. package/lib/components/fetch-structure/QueryBuilderResultModifierPanel.js +55 -0
  33. package/lib/components/fetch-structure/QueryBuilderResultModifierPanel.js.map +1 -1
  34. package/lib/components/fetch-structure/QueryBuilderTDSPanel.d.ts.map +1 -1
  35. package/lib/components/fetch-structure/QueryBuilderTDSPanel.js +95 -8
  36. package/lib/components/fetch-structure/QueryBuilderTDSPanel.js.map +1 -1
  37. package/lib/components/fetch-structure/QueryBuilderTDSWindowPanel.d.ts.map +1 -1
  38. package/lib/components/fetch-structure/QueryBuilderTDSWindowPanel.js +37 -4
  39. package/lib/components/fetch-structure/QueryBuilderTDSWindowPanel.js.map +1 -1
  40. package/lib/components/filter/QueryBuilderFilterPanel.d.ts.map +1 -1
  41. package/lib/components/filter/QueryBuilderFilterPanel.js +24 -5
  42. package/lib/components/filter/QueryBuilderFilterPanel.js.map +1 -1
  43. package/lib/components/result/QueryBuilderDataProductErrorPanel.d.ts +61 -0
  44. package/lib/components/result/QueryBuilderDataProductErrorPanel.d.ts.map +1 -0
  45. package/lib/components/result/QueryBuilderDataProductErrorPanel.js +176 -0
  46. package/lib/components/result/QueryBuilderDataProductErrorPanel.js.map +1 -0
  47. package/lib/components/result/QueryBuilderResultPanel.d.ts.map +1 -1
  48. package/lib/components/result/QueryBuilderResultPanel.js +24 -18
  49. package/lib/components/result/QueryBuilderResultPanel.js.map +1 -1
  50. package/lib/data-access-overview.css +1 -1
  51. package/lib/index.css +17 -1
  52. package/lib/index.css.map +1 -1
  53. package/lib/index.d.ts +1 -0
  54. package/lib/index.d.ts.map +1 -1
  55. package/lib/index.js +1 -0
  56. package/lib/index.js.map +1 -1
  57. package/lib/package.json +1 -1
  58. package/lib/stores/QueryBuilderResultState.d.ts +11 -1
  59. package/lib/stores/QueryBuilderResultState.d.ts.map +1 -1
  60. package/lib/stores/QueryBuilderResultState.js +79 -15
  61. package/lib/stores/QueryBuilderResultState.js.map +1 -1
  62. package/lib/stores/QueryBuilderState.d.ts +131 -9
  63. package/lib/stores/QueryBuilderState.d.ts.map +1 -1
  64. package/lib/stores/QueryBuilderState.js +124 -21
  65. package/lib/stores/QueryBuilderState.js.map +1 -1
  66. package/lib/stores/QueryBuilder_LegendApplicationPlugin_Extension.d.ts +13 -0
  67. package/lib/stores/QueryBuilder_LegendApplicationPlugin_Extension.d.ts.map +1 -1
  68. package/lib/stores/QueryLoaderState.d.ts +33 -5
  69. package/lib/stores/QueryLoaderState.d.ts.map +1 -1
  70. package/lib/stores/QueryLoaderState.js +10 -1
  71. package/lib/stores/QueryLoaderState.js.map +1 -1
  72. package/lib/stores/data-access/DataProductAccessInfo.d.ts +37 -0
  73. package/lib/stores/data-access/DataProductAccessInfo.d.ts.map +1 -0
  74. package/lib/stores/data-access/DataProductAccessInfo.js +17 -0
  75. package/lib/stores/data-access/DataProductAccessInfo.js.map +1 -0
  76. package/lib/stores/data-cube/QueryBuilderDataCubeEngine.js +1 -1
  77. package/lib/stores/data-cube/QueryBuilderDataCubeEngine.js.map +1 -1
  78. package/lib/stores/explorer/QueryBuilderExplorerState.js +1 -1
  79. package/lib/stores/explorer/QueryBuilderExplorerState.js.map +1 -1
  80. package/lib/stores/milestoning/QueryBuilderMilestoningHelper.d.ts.map +1 -1
  81. package/lib/stores/milestoning/QueryBuilderMilestoningHelper.js +34 -9
  82. package/lib/stores/milestoning/QueryBuilderMilestoningHelper.js.map +1 -1
  83. package/lib/stores/milestoning/QueryBuilderMilestoningState.d.ts +5 -0
  84. package/lib/stores/milestoning/QueryBuilderMilestoningState.d.ts.map +1 -1
  85. package/lib/stores/milestoning/QueryBuilderMilestoningState.js +13 -0
  86. package/lib/stores/milestoning/QueryBuilderMilestoningState.js.map +1 -1
  87. package/lib/stores/workflows/dataProduct/DataProductQueryBuilderState.d.ts +2 -0
  88. package/lib/stores/workflows/dataProduct/DataProductQueryBuilderState.d.ts.map +1 -1
  89. package/lib/stores/workflows/dataProduct/DataProductQueryBuilderState.js +50 -1
  90. package/lib/stores/workflows/dataProduct/DataProductQueryBuilderState.js.map +1 -1
  91. package/package.json +10 -10
  92. package/src/__lib__/QueryBuilderDocumentation.ts +1 -0
  93. package/src/__lib__/QueryBuilderEvent.ts +52 -0
  94. package/src/__lib__/QueryBuilderTelemetryHelper.ts +292 -8
  95. package/src/components/QueryBuilder.tsx +31 -23
  96. package/src/components/QueryBuilderConstantExpressionPanel.tsx +24 -2
  97. package/src/components/QueryBuilderParametersPanel.tsx +25 -2
  98. package/src/components/QueryBuilderSideBar.tsx +28 -0
  99. package/src/components/fetch-structure/QueryBuilderGraphFetchTreePanel.tsx +56 -1
  100. package/src/components/fetch-structure/QueryBuilderPostFilterPanel.tsx +31 -4
  101. package/src/components/fetch-structure/QueryBuilderResultModifierPanel.tsx +74 -0
  102. package/src/components/fetch-structure/QueryBuilderTDSPanel.tsx +123 -8
  103. package/src/components/fetch-structure/QueryBuilderTDSWindowPanel.tsx +49 -4
  104. package/src/components/filter/QueryBuilderFilterPanel.tsx +31 -4
  105. package/src/components/result/QueryBuilderDataProductErrorPanel.tsx +441 -0
  106. package/src/components/result/QueryBuilderResultPanel.tsx +49 -32
  107. package/src/index.ts +1 -0
  108. package/src/stores/QueryBuilderResultState.ts +107 -19
  109. package/src/stores/QueryBuilderState.ts +239 -29
  110. package/src/stores/QueryBuilder_LegendApplicationPlugin_Extension.ts +20 -0
  111. package/src/stores/QueryLoaderState.ts +47 -6
  112. package/src/stores/data-access/DataProductAccessInfo.ts +37 -0
  113. package/src/stores/data-cube/QueryBuilderDataCubeEngine.ts +1 -1
  114. package/src/stores/explorer/QueryBuilderExplorerState.ts +1 -1
  115. package/src/stores/milestoning/QueryBuilderMilestoningHelper.ts +63 -8
  116. package/src/stores/milestoning/QueryBuilderMilestoningState.ts +20 -0
  117. package/src/stores/workflows/dataProduct/DataProductQueryBuilderState.ts +68 -0
  118. package/tsconfig.json +2 -0
@@ -24,6 +24,8 @@ import {
24
24
  ActionState,
25
25
  StopWatch,
26
26
  getContentTypeFileExtension,
27
+ buildTelemetryErrorFields,
28
+ type TimingsRecord,
27
29
  } from '@finos/legend-shared';
28
30
  import type { QueryBuilderState } from './QueryBuilderState.js';
29
31
  import {
@@ -49,7 +51,10 @@ import {
49
51
  getExecutionQueryFromRawLambda,
50
52
  } from './shared/LambdaParameterState.js';
51
53
  import type { LambdaFunctionBuilderOption } from './QueryBuilderValueSpecificationBuilderHelper.js';
52
- import { QueryBuilderTelemetryHelper } from '../__lib__/QueryBuilderTelemetryHelper.js';
54
+ import {
55
+ QueryBuilderTelemetryHelper,
56
+ type QueryExecutionFailure_TelemetryData,
57
+ } from '../__lib__/QueryBuilderTelemetryHelper.js';
53
58
  import { QUERY_BUILDER_EVENT } from '../__lib__/QueryBuilderEvent.js';
54
59
  import { ExecutionPlanState } from './execution-plan/ExecutionPlanState.js';
55
60
  import type { DataGridColumnState } from '@finos/legend-lego/data-grid';
@@ -392,6 +397,27 @@ export class QueryBuilderResultState {
392
397
  return query;
393
398
  }
394
399
 
400
+ /**
401
+ * Builds the shared payload for query execution / plan / export failure
402
+ * telemetry, so all four failure callsites emit the same set of dimensions.
403
+ */
404
+ buildFailureTelemetryData(
405
+ error: Error,
406
+ extra?: {
407
+ executionDurationMs?: number | undefined;
408
+ executionTraceId?: string | undefined;
409
+ timings?: TimingsRecord | undefined;
410
+ },
411
+ ): QueryExecutionFailure_TelemetryData {
412
+ return {
413
+ ...buildTelemetryErrorFields(error),
414
+ queryInfo: this.queryBuilderState.safeGetQueryInfo(),
415
+ ...extra,
416
+ ...this.queryBuilderState.safeGetTelemetryContext(),
417
+ ...this.queryBuilderState.safeGetExtraTelemetryMetadata(),
418
+ };
419
+ }
420
+
395
421
  *exportData(format: string): GeneratorFn<void> {
396
422
  try {
397
423
  this.exportState.inProgress();
@@ -450,7 +476,7 @@ export class QueryBuilderResultState {
450
476
  const reportWithState = Object.assign(
451
477
  {},
452
478
  report,
453
- this.queryBuilderState.getStateInfo(),
479
+ this.queryBuilderState.safeGetTelemetryContext(),
454
480
  );
455
481
  QueryBuilderTelemetryHelper.logEvent_ExportQueryDataSucceeded(
456
482
  this.queryBuilderState.applicationStore.telemetryService,
@@ -464,6 +490,13 @@ export class QueryBuilderResultState {
464
490
  LogEvent.create(GRAPH_MANAGER_EVENT.EXECUTION_FAILURE),
465
491
  error,
466
492
  );
493
+ QueryBuilderTelemetryHelper.logEvent_ExportQueryDataFailed(
494
+ this.queryBuilderState.applicationStore.telemetryService,
495
+ this.buildFailureTelemetryData(error),
496
+ );
497
+ // without this the action state is left IN_PROGRESS forever when a
498
+ // download fails, unlike the `.then` branch which passes it
499
+ this.exportState.fail();
467
500
  });
468
501
  } catch (error) {
469
502
  this.exportState.fail();
@@ -475,12 +508,26 @@ export class QueryBuilderResultState {
475
508
  this.queryBuilderState.applicationStore.notificationService.notifyError(
476
509
  error,
477
510
  );
478
- this.exportState.complete();
511
+ QueryBuilderTelemetryHelper.logEvent_ExportQueryDataFailed(
512
+ this.queryBuilderState.applicationStore.telemetryService,
513
+ this.buildFailureTelemetryData(error),
514
+ );
515
+ // NOTE: no `complete()` here — it defaults to `hasSucceeded = true`, so
516
+ // calling it after `fail()` above flipped the state back to SUCCEEDED and
517
+ // masked the failure in the UI
479
518
  }
480
519
  }
481
520
 
482
521
  *runQuery(): GeneratorFn<void> {
483
522
  let promise;
523
+ // hoisted so the catch below can report the phase timings collected up to
524
+ // the point of failure — the absence of an engine server-call lap is itself
525
+ // the signal that the query never made it out of the client
526
+ // NOTE: `reportGraphAnalytics` widens the report with `dependenciesCount`;
527
+ // declaring the narrower `GraphManagerOperationReport` here would drop it
528
+ // from the success payload's type
529
+ let report: ReturnType<typeof reportGraphAnalytics> | undefined;
530
+ const stopWatch = new StopWatch();
484
531
  try {
485
532
  this.setIsRunningQuery(true);
486
533
  const currentHashCode = this.queryBuilderState.hashCode;
@@ -502,13 +549,18 @@ export class QueryBuilderResultState {
502
549
  this.queryBuilderState.graphManagerState,
503
550
  );
504
551
 
552
+ stopWatch.record(QUERY_BUILDER_EVENT.RUN_QUERY__PREPARE);
553
+
505
554
  QueryBuilderTelemetryHelper.logEvent_QueryRunLaunched(
506
555
  this.queryBuilderState.applicationStore.telemetryService,
507
- this.queryBuilderState.getExtraTelemetryMetadata(),
556
+ {
557
+ ...this.queryBuilderState.safeGetTelemetryContext(),
558
+ queryInfo: this.queryBuilderState.safeGetQueryInfo(),
559
+ ...this.queryBuilderState.safeGetExtraTelemetryMetadata(),
560
+ },
508
561
  );
509
562
 
510
- const stopWatch = new StopWatch();
511
- const report = reportGraphAnalytics(
563
+ report = reportGraphAnalytics(
512
564
  this.queryBuilderState.graphManagerState.graph,
513
565
  );
514
566
  const contextstate = this.queryBuilderState.executionContextState;
@@ -527,16 +579,25 @@ export class QueryBuilderResultState {
527
579
  floatingExecutionElements:
528
580
  this.queryBuilderState.floatingExecutionElements,
529
581
  },
582
+ // passing the report is what surfaces the graph manager's own engine
583
+ // laps (`V1_ENGINE_OPERATION_INPUT__SUCCESS` / `..._SERVER_CALL__SUCCESS`)
584
+ // in `report.timings` — without it they are computed and discarded
585
+ report,
530
586
  );
531
587
 
532
588
  this.setQueryRunPromise(promise);
533
589
  const result = (yield promise) as ExecutionResultWithMetadata;
534
590
  if (this.queryRunPromise === promise) {
591
+ // close the engine lap without naming it: the graph manager has already
592
+ // recorded that span in `report.timings`, so recording it again here
593
+ // would double-count it
594
+ stopWatch.record();
535
595
  this.processExecutionResult(result.executionResult);
536
596
  if (result.executionTraceId) {
537
597
  this.setExecutionTraceId(result.executionTraceId);
538
598
  }
539
599
  this.latestRunHashCode = currentHashCode;
600
+ stopWatch.record(QUERY_BUILDER_EVENT.RUN_QUERY__PROCESS_RESULT);
540
601
  this.setExecutionDuration(stopWatch.elapsed);
541
602
 
542
603
  report.timings =
@@ -544,12 +605,16 @@ export class QueryBuilderResultState {
544
605
  stopWatch,
545
606
  report.timings,
546
607
  );
547
- const reportWithState = Object.assign(
548
- {},
549
- report,
550
- this.queryBuilderState.getStateInfo(),
551
- this.queryBuilderState.getExtraTelemetryMetadata(),
552
- );
608
+ // NOTE: spread rather than `Object.assign` — with four sources the
609
+ // latter falls through to the `(...sources: any[]) => any` overload,
610
+ // which silently drops type checking on the whole payload
611
+ const reportWithState = {
612
+ ...report,
613
+ ...this.queryBuilderState.safeGetTelemetryContext(),
614
+ ...this.queryBuilderState.safeGetExtraTelemetryMetadata(),
615
+ queryInfo: this.queryBuilderState.safeGetQueryInfo(),
616
+ executionDurationMs: stopWatch.elapsed,
617
+ };
553
618
  QueryBuilderTelemetryHelper.logEvent_QueryRunSucceeded(
554
619
  this.queryBuilderState.applicationStore.telemetryService,
555
620
  reportWithState,
@@ -571,11 +636,18 @@ export class QueryBuilderResultState {
571
636
  }
572
637
  QueryBuilderTelemetryHelper.logEvent_QueryRunFailed(
573
638
  this.queryBuilderState.applicationStore.telemetryService,
574
- {
575
- errorMessage:
576
- error instanceof Error ? error.message : String(error),
577
- ...this.queryBuilderState.getExtraTelemetryMetadata(),
578
- },
639
+ this.buildFailureTelemetryData(error, {
640
+ executionDurationMs: stopWatch.elapsed,
641
+ executionTraceId:
642
+ error instanceof ExecutionError
643
+ ? error.executionTraceId
644
+ : undefined,
645
+ timings:
646
+ this.queryBuilderState.applicationStore.timeService.finalizeTimingsRecord(
647
+ stopWatch,
648
+ report?.timings,
649
+ ),
650
+ }),
579
651
  );
580
652
  }
581
653
  } finally {
@@ -594,7 +666,11 @@ export class QueryBuilderResultState {
594
666
  );
595
667
  QueryBuilderTelemetryHelper.logEvent_QueryRunCancelled(
596
668
  this.queryBuilderState.applicationStore.telemetryService,
597
- this.queryBuilderState.getExtraTelemetryMetadata(),
669
+ {
670
+ ...this.queryBuilderState.safeGetTelemetryContext(),
671
+ queryInfo: this.queryBuilderState.safeGetQueryInfo(),
672
+ ...this.queryBuilderState.safeGetExtraTelemetryMetadata(),
673
+ },
598
674
  );
599
675
  } catch (error) {
600
676
  // Don't notify users about success or failure
@@ -689,7 +765,7 @@ export class QueryBuilderResultState {
689
765
  const reportWithState = Object.assign(
690
766
  {},
691
767
  report,
692
- this.queryBuilderState.getStateInfo(),
768
+ this.queryBuilderState.safeGetTelemetryContext(),
693
769
  );
694
770
  if (debug) {
695
771
  QueryBuilderTelemetryHelper.logEvent_ExecutionPlanDebugSucceeded(
@@ -711,6 +787,18 @@ export class QueryBuilderResultState {
711
787
  this.queryBuilderState.applicationStore.notificationService.notifyError(
712
788
  error,
713
789
  );
790
+ const failurePayload = this.buildFailureTelemetryData(error);
791
+ if (debug) {
792
+ QueryBuilderTelemetryHelper.logEvent_ExecutionPlanDebugFailed(
793
+ this.queryBuilderState.applicationStore.telemetryService,
794
+ failurePayload,
795
+ );
796
+ } else {
797
+ QueryBuilderTelemetryHelper.logEvent_ExecutionPlanGenerationFailed(
798
+ this.queryBuilderState.applicationStore.telemetryService,
799
+ failurePayload,
800
+ );
801
+ }
714
802
  } finally {
715
803
  this.isGeneratingPlan = false;
716
804
  }
@@ -84,9 +84,10 @@ import {
84
84
  buildLambdaFunction,
85
85
  buildExecutionContextState,
86
86
  } from './QueryBuilderValueSpecificationBuilder.js';
87
- import type {
88
- CommandRegistrar,
89
- GenericLegendApplicationStore,
87
+ import {
88
+ type CommandRegistrar,
89
+ type GenericLegendApplicationStore,
90
+ APPLICATION_EVENT,
90
91
  } from '@finos/legend-application';
91
92
  import { QueryFunctionsExplorerState } from './explorer/QueryFunctionsExplorerState.js';
92
93
  import {
@@ -96,13 +97,17 @@ import {
96
97
  import type { QueryBuilderFilterOperator } from './filter/QueryBuilderFilterOperator.js';
97
98
  import { getQueryBuilderCoreFilterOperators } from './filter/QueryBuilderFilterOperatorLoader.js';
98
99
  import { QueryBuilderChangeDetectionState } from './QueryBuilderChangeDetectionState.js';
99
- import { QueryBuilderMilestoningState } from './milestoning/QueryBuilderMilestoningState.js';
100
+ import {
101
+ QueryBuilderMilestoningState,
102
+ type QueryBuilderMilestoningKind,
103
+ } from './milestoning/QueryBuilderMilestoningState.js';
100
104
  import { QUERY_BUILDER_STATE_HASH_STRUCTURE } from './QueryBuilderStateHashUtils.js';
101
105
  import { QUERY_BUILDER_COMMAND_KEY } from './QueryBuilderCommand.js';
102
106
  import { QueryBuilderWatermarkState } from './watermark/QueryBuilderWatermarkState.js';
103
107
  import { QueryBuilderConstantsState } from './QueryBuilderConstantsState.js';
104
108
  import { QueryBuilderCheckEntitlementsState } from './entitlements/QueryBuilderCheckEntitlementsState.js';
105
109
  import { QueryBuilderTDSState } from './fetch-structure/tds/QueryBuilderTDSState.js';
110
+ import { QueryBuilderRelationColumnProjectionColumnState } from './fetch-structure/tds/projection/QueryBuilderProjectionColumnState.js';
106
111
  import {
107
112
  QUERY_BUILDER_PURE_PATH,
108
113
  QUERY_BUILDER_SUPPORTED_GET_ALL_FUNCTIONS,
@@ -114,7 +119,10 @@ import {
114
119
  type QueryBuilderExecutionContextState,
115
120
  } from './QueryBuilderExecutionContextState.js';
116
121
  import type { QueryBuilderConfig } from '../graph-manager/QueryBuilderConfig.js';
117
- import { QUERY_BUILDER_EVENT } from '../__lib__/QueryBuilderEvent.js';
122
+ import {
123
+ QUERY_BUILDER_EVENT,
124
+ type QUERY_BUILDER_OPENED_FROM,
125
+ } from '../__lib__/QueryBuilderEvent.js';
118
126
  import { QUERY_BUILDER_SETTING_KEY } from '../__lib__/QueryBuilderSetting.js';
119
127
  import { QueryBuilderChangeHistoryState } from './QueryBuilderChangeHistoryState.js';
120
128
  import { type QueryBuilderWorkflowState } from './query-workflow/QueryBuilderWorkFlowState.js';
@@ -127,11 +135,79 @@ import type {
127
135
  DepotEntityWithOrigin,
128
136
  QueryableSourceInfo,
129
137
  } from '@finos/legend-storage';
138
+ import type { FETCH_STRUCTURE_IMPLEMENTATION } from './fetch-structure/QueryBuilderFetchStructureImplementationState.js';
130
139
 
131
- export type QueryableClassMappingRuntimeInfo = QueryableSourceInfo & {
132
- class: string;
133
- mapping: string;
134
- runtime: string;
140
+ /**
141
+ * The execution context the query builder has resolved to, as reported in
142
+ * telemetry under the `state` key. See
143
+ * {@link QueryBuilderState.getExecutionContextInfo}.
144
+ */
145
+ export type QueryBuilderExecutionContextInfo = {
146
+ class?: string | undefined;
147
+ mapping?: string | undefined;
148
+ runtime?: string | undefined;
149
+ /**
150
+ * `true` when the query runs against an inline (engineered) runtime rather
151
+ * than a `RuntimePointer`. Those have no element path to report, so `runtime`
152
+ * is absent — this flag keeps that population visible instead of leaving it
153
+ * indistinguishable from "no runtime selected yet".
154
+ */
155
+ isInlineRuntime?: boolean | undefined;
156
+ };
157
+
158
+ /**
159
+ * The shared envelope carried by every query builder telemetry event.
160
+ *
161
+ * The entry point the builder was opened with (`sourceInfo` — the route) is
162
+ * spread *flat* at the top level, while the execution context it resolved to is
163
+ * nested under `state`. Keeping them separate is what lets a dashboard tell
164
+ * "arrived on mapping X" apart from "currently querying mapping X"; merging the
165
+ * two into one flat object would collapse that distinction.
166
+ */
167
+ export type QueryBuilderTelemetryContext = QueryableSourceInfo &
168
+ /**
169
+ * `QueryableSourceInfo` is a marker interface with no declared members — the
170
+ * concrete keys (`sourceType`, `groupId`, `dataSpace`, `dataProduct`, …) vary
171
+ * by entry point and are only known to the application layer. The index
172
+ * signature is what lets those keys sit flat at the top level; it does mean
173
+ * top-level excess-property checking is off. The fields that matter are kept
174
+ * inside the strictly-typed `state` and `change` sub-objects for exactly this
175
+ * reason.
176
+ */
177
+ Record<PropertyKey, unknown> & {
178
+ state?: QueryBuilderExecutionContextInfo | undefined;
179
+ };
180
+
181
+ /**
182
+ * Summary of the current query builder authoring state, used to enrich
183
+ * telemetry (e.g. query-execution events) with lightweight, non-PII shape
184
+ * information such as which fetch structure is in use and how many columns
185
+ * / filters / parameters have been configured.
186
+ */
187
+ export type QueryBuilderQueryInfo = {
188
+ fetchStructureType: FETCH_STRUCTURE_IMPLEMENTATION | string;
189
+ /**
190
+ * `true` when the TDS query is authored against the typed relation function
191
+ * family (`->project`/`->groupBy`/etc. over relation columns). `false` for
192
+ * classic property-driven TDS. `undefined` when the fetch structure is not
193
+ * TDS or when there are no columns to determine yet.
194
+ */
195
+ isTypedFetchStructure?: boolean | undefined;
196
+ parameterCount: number;
197
+ constantCount: number;
198
+ hasFilter: boolean;
199
+ filterNodeCount: number;
200
+ watermarkEnabled: boolean;
201
+ milestoningKind: QueryBuilderMilestoningKind;
202
+ // TDS-specific fields (present when fetchStructureType is TABULAR_DATA_STRUCTURE)
203
+ projectionColumnCount?: number | undefined;
204
+ windowColumnCount?: number | undefined;
205
+ aggregationColumnCount?: number | undefined;
206
+ postFilterNodeCount?: number | undefined;
207
+ hasLimit?: boolean | undefined;
208
+ hasDistinct?: boolean | undefined;
209
+ sortColumnCount?: number | undefined;
210
+ hasSlice?: boolean | undefined;
135
211
  };
136
212
 
137
213
  export type QueryBuilderExtraFunctionAnalysisInfo = {
@@ -525,28 +601,162 @@ export abstract class QueryBuilderState implements CommandRegistrar {
525
601
  }
526
602
 
527
603
  /**
528
- * Gets information about the current queryBuilderState.
529
- * This information can be used as a part of analytics
604
+ * Gets the execution context the query builder has currently *resolved to*:
605
+ * the class being queried and the mapping/runtime it will execute against.
606
+ *
607
+ * This is deliberately distinct from `sourceInfo`, which records what the
608
+ * builder was *opened with* (the route). For a mapping-sourced query both are
609
+ * populated and initially identical, so a divergence means the user switched;
610
+ * for a data space / data product / service query only this side carries a
611
+ * mapping and runtime, because the entry point resolves to one.
612
+ *
613
+ * Degrades gracefully — whichever of class / mapping / runtime are known get
614
+ * reported, so events that fire while the user is still setting up (picking a
615
+ * class before a mapping, say) are not blank. Returns `undefined` only when
616
+ * none of the three has resolved.
530
617
  */
531
- getStateInfo(): QueryableClassMappingRuntimeInfo | undefined {
532
- if (this.sourceInfo) {
533
- const classPath = this.sourceClass?.path;
534
- const mappingPath = this.executionContextState.mapping?.path;
535
- const runtimePath =
536
- this.executionContextState.runtimeValue instanceof RuntimePointer
537
- ? this.executionContextState.runtimeValue.packageableRuntime.value
538
- .path
539
- : undefined;
540
- if (classPath && mappingPath && runtimePath) {
541
- const contextInfo = {
542
- class: classPath,
543
- mapping: mappingPath,
544
- runtime: runtimePath,
545
- };
546
- return Object.assign({}, this.sourceInfo, contextInfo);
547
- }
618
+ getExecutionContextInfo(): QueryBuilderExecutionContextInfo | undefined {
619
+ const classPath = this.sourceClass?.path;
620
+ const mappingPath = this.executionContextState.mapping?.path;
621
+ const runtimeValue = this.executionContextState.runtimeValue;
622
+ const runtimePath =
623
+ runtimeValue instanceof RuntimePointer
624
+ ? runtimeValue.packageableRuntime.value.path
625
+ : undefined;
626
+ const isInlineRuntime =
627
+ runtimeValue !== undefined && !(runtimeValue instanceof RuntimePointer);
628
+ if (!classPath && !mappingPath && !runtimePath && !isInlineRuntime) {
629
+ return undefined;
548
630
  }
549
- return undefined;
631
+ return {
632
+ class: classPath,
633
+ mapping: mappingPath,
634
+ runtime: runtimePath,
635
+ isInlineRuntime: isInlineRuntime ? true : undefined,
636
+ };
637
+ }
638
+
639
+ /**
640
+ * Gets a lightweight snapshot of the current query builder authoring state
641
+ * (fetch structure kind, filter/parameter/constant counts, milestoning /
642
+ * watermark configuration, etc.) for telemetry payloads. Kept intentionally
643
+ * shape-only — no user values, no identifiers.
644
+ */
645
+ getQueryInfo(): QueryBuilderQueryInfo {
646
+ const base: QueryBuilderQueryInfo = {
647
+ fetchStructureType: this.fetchStructureState.implementation.type,
648
+ parameterCount: this.parametersState.parameterStates.length,
649
+ constantCount: this.constantState.constants.length,
650
+ hasFilter: !this.filterState.isEmpty,
651
+ filterNodeCount: this.filterState.nodes.size,
652
+ watermarkEnabled: this.watermarkState.value !== undefined,
653
+ milestoningKind: this.milestoningState.milestoningKind,
654
+ };
655
+ if (
656
+ this.fetchStructureState.implementation instanceof QueryBuilderTDSState
657
+ ) {
658
+ const tdsState = this.fetchStructureState.implementation;
659
+ const modifier = tdsState.resultSetModifierState;
660
+ base.isTypedFetchStructure =
661
+ tdsState.projectionColumns.length > 0 &&
662
+ tdsState.projectionColumns.every(
663
+ (col) =>
664
+ col instanceof QueryBuilderRelationColumnProjectionColumnState,
665
+ );
666
+ base.projectionColumnCount = tdsState.projectionColumns.length;
667
+ base.windowColumnCount = tdsState.windowState.windowColumns.length;
668
+ base.aggregationColumnCount = tdsState.aggregationState.columns.length;
669
+ base.postFilterNodeCount = tdsState.postFilterState.nodes.size;
670
+ base.hasLimit = modifier.limit !== undefined;
671
+ base.hasDistinct = modifier.distinct;
672
+ base.sortColumnCount = modifier.sortColumns.length;
673
+ base.hasSlice = modifier.slice !== undefined;
674
+ }
675
+ return base;
676
+ }
677
+
678
+ /**
679
+ * Telemetry must never crash a user-visible action, so every `safeGet*`
680
+ * wrapper below swallows errors from its snapshot builder (state might be
681
+ * mid-construction, or a downstream computed getter might throw in edge
682
+ * cases) and logs them instead of throwing.
683
+ */
684
+ private safeGetTelemetry<T>(getter: () => T, fallback: T, label: string): T {
685
+ try {
686
+ return getter();
687
+ } catch (error) {
688
+ this.applicationStore.logService.warn(
689
+ LogEvent.create(APPLICATION_EVENT.GENERIC_FAILURE),
690
+ label,
691
+ error,
692
+ );
693
+ return fallback;
694
+ }
695
+ }
696
+
697
+ /**
698
+ * Builds the shared telemetry envelope every query builder event carries: the
699
+ * entry point the builder was opened with, spread flat, plus the execution
700
+ * context it resolved to, nested under `state`. Spread this into a payload
701
+ * rather than assembling the two halves by hand at each callsite.
702
+ */
703
+ safeGetTelemetryContext(): QueryBuilderTelemetryContext {
704
+ return this.safeGetTelemetry<QueryBuilderTelemetryContext>(
705
+ () => ({ ...this.sourceInfo, state: this.getExecutionContextInfo() }),
706
+ {},
707
+ 'Failed to build query builder telemetry context',
708
+ );
709
+ }
710
+
711
+ /**
712
+ * Reports that a query builder was opened, from whichever surface opened it.
713
+ * This is the canonical "a query builder exists" signal — hosts should prefer
714
+ * it over their own route-specific load events, which each cover only part of
715
+ * the population.
716
+ *
717
+ * Deliberately called by the host once the builder is *loaded*, rather than
718
+ * emitted from the constructor. The constructor runs before subclass field
719
+ * initializers, so the execution context would be unresolved and the `safeGet*`
720
+ * wrappers would silently report an empty envelope; it would also fire for
721
+ * derived states such as {@link INTERNAL__toBasicQueryBuilderState}, which are
722
+ * built on every data preview and are not user-facing opens.
723
+ *
724
+ * `extra` carries host-specific supplementary fields (load timings, and for
725
+ * Legend Query creators `restoredFromRecent`). It is untyped to keep this
726
+ * package host-agnostic.
727
+ */
728
+ logOpened(
729
+ openedFrom: QUERY_BUILDER_OPENED_FROM,
730
+ extra?: Record<string, unknown> | undefined,
731
+ ): void {
732
+ QueryBuilderTelemetryHelper.logEvent_QueryBuilderOpened(
733
+ this.applicationStore.telemetryService,
734
+ {
735
+ openedFrom,
736
+ ...this.safeGetTelemetryContext(),
737
+ ...extra,
738
+ },
739
+ );
740
+ }
741
+
742
+ /**
743
+ * Prefer this over the raw `getQueryInfo()` method on the hot path of
744
+ * execution telemetry.
745
+ */
746
+ safeGetQueryInfo(): QueryBuilderQueryInfo | undefined {
747
+ return this.safeGetTelemetry(
748
+ () => this.getQueryInfo(),
749
+ undefined,
750
+ 'Failed to build query builder telemetry query info',
751
+ );
752
+ }
753
+
754
+ safeGetExtraTelemetryMetadata(): Record<string, unknown> {
755
+ return this.safeGetTelemetry(
756
+ () => this.getExtraTelemetryMetadata(),
757
+ {},
758
+ 'Failed to build query builder extra telemetry metadata',
759
+ );
550
760
  }
551
761
 
552
762
  setIsAgentChatOpened(val: boolean): void {
@@ -21,6 +21,7 @@ import type {
21
21
  DataAccessState,
22
22
  DatasetAccessInfo,
23
23
  } from './data-access/DataAccessState.js';
24
+ import type { DataProductAccessInfo } from './data-access/DataProductAccessInfo.js';
24
25
 
25
26
  export type CuratedTemplateQuery = {
26
27
  id: string;
@@ -67,6 +68,16 @@ export type WarehouseEntitlementRender = {
67
68
  renderer: (dataAccessState: DataAccessState) => React.ReactNode;
68
69
  };
69
70
 
71
+ /**
72
+ * Builds the address where access to a data product can be requested (e.g. its
73
+ * Marketplace page). Returns `undefined` when the host application cannot produce
74
+ * one, in which case no request-access action is offered.
75
+ */
76
+ export type DataProductAccessRequestLinkBuilder = (
77
+ info: DataProductAccessInfo,
78
+ queryBuilderState: QueryBuilderState,
79
+ ) => string | undefined;
80
+
70
81
  export type QueryAgentChatRenderer = (
71
82
  queryBuilderState: QueryBuilderState,
72
83
  ) => React.ReactNode;
@@ -94,6 +105,9 @@ export type QueryBuilderMenuActionConfiguration = {
94
105
  queryBuilderState: QueryBuilderState,
95
106
  ) => React.ReactNode;
96
107
  disableMessage?: string | undefined;
108
+ getDisableMessage?:
109
+ | ((queryBuilderState: QueryBuilderState) => string | undefined)
110
+ | undefined;
97
111
  };
98
112
 
99
113
  export type QueryBuilderPropagateExecutionContextChangeHelper = (
@@ -140,6 +154,12 @@ export interface QueryBuilder_LegendApplicationPlugin_Extension
140
154
  */
141
155
  getExtraDatasetEntitlementAccessNotGrantedReportActionConfigurations?(): DatasetEntitlementAccessReportActionConfiguration[];
142
156
 
157
+ /**
158
+ * Get the list of builders for the address where access to a data product can be
159
+ * requested. The first one to produce an address wins.
160
+ */
161
+ getDataProductAccessRequestLinkBuilders?(): DataProductAccessRequestLinkBuilder[];
162
+
143
163
  /**
144
164
  * Get the list of query usage configurations
145
165
  */