@thoughtspot/ts-chart-sdk 0.0.2-alpha.9 → 1.1.0

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 (141) hide show
  1. package/README.md +126 -50
  2. package/dist/ts-chart-sdk.d.ts +330 -32
  3. package/lib/index.d.ts +3 -0
  4. package/lib/index.d.ts.map +1 -1
  5. package/lib/index.js +3 -0
  6. package/lib/index.js.map +1 -1
  7. package/lib/main/custom-chart-context.d.ts +17 -4
  8. package/lib/main/custom-chart-context.d.ts.map +1 -1
  9. package/lib/main/custom-chart-context.js +46 -16
  10. package/lib/main/custom-chart-context.js.map +1 -1
  11. package/lib/main/custom-chart-context.spec.js +302 -4
  12. package/lib/main/custom-chart-context.spec.js.map +1 -1
  13. package/lib/main/logger.d.ts +13 -0
  14. package/lib/main/logger.d.ts.map +1 -0
  15. package/lib/main/logger.js +64 -0
  16. package/lib/main/logger.js.map +1 -0
  17. package/lib/main/logger.spec.d.ts +2 -0
  18. package/lib/main/logger.spec.d.ts.map +1 -0
  19. package/lib/main/logger.spec.js +88 -0
  20. package/lib/main/logger.spec.js.map +1 -0
  21. package/lib/main/util.d.ts +2 -0
  22. package/lib/main/util.d.ts.map +1 -1
  23. package/lib/main/util.js +11 -0
  24. package/lib/main/util.js.map +1 -1
  25. package/lib/main/util.spec.d.ts +2 -0
  26. package/lib/main/util.spec.d.ts.map +1 -0
  27. package/lib/main/util.spec.js +73 -0
  28. package/lib/main/util.spec.js.map +1 -0
  29. package/lib/react/use-custom-chart-context.d.ts.map +1 -1
  30. package/lib/react/use-custom-chart-context.js +3 -1
  31. package/lib/react/use-custom-chart-context.js.map +1 -1
  32. package/lib/react/use-custom-chart-context.spec.js +0 -1
  33. package/lib/react/use-custom-chart-context.spec.js.map +1 -1
  34. package/lib/react/use-custom-chart-context.util.d.ts.map +1 -1
  35. package/lib/react/use-custom-chart-context.util.js +5 -3
  36. package/lib/react/use-custom-chart-context.util.js.map +1 -1
  37. package/lib/types/answer-column.types.d.ts +7 -4
  38. package/lib/types/answer-column.types.d.ts.map +1 -1
  39. package/lib/types/answer-column.types.js +3 -3
  40. package/lib/types/answer-column.types.js.map +1 -1
  41. package/lib/types/chart-to-ts-event.types.d.ts +2 -0
  42. package/lib/types/chart-to-ts-event.types.d.ts.map +1 -1
  43. package/lib/types/chart-to-ts-event.types.js +1 -0
  44. package/lib/types/chart-to-ts-event.types.js.map +1 -1
  45. package/lib/types/common.types.d.ts +65 -6
  46. package/lib/types/common.types.d.ts.map +1 -1
  47. package/lib/types/common.types.js.map +1 -1
  48. package/lib/types/conditional-formatting.types.d.ts +1 -16
  49. package/lib/types/conditional-formatting.types.d.ts.map +1 -1
  50. package/lib/types/conditional-formatting.types.js.map +1 -1
  51. package/lib/types/configurator.types.d.ts +1 -1
  52. package/lib/types/configurator.types.d.ts.map +1 -1
  53. package/lib/types/number-formatting.types.d.ts +55 -0
  54. package/lib/types/number-formatting.types.d.ts.map +1 -0
  55. package/lib/types/number-formatting.types.js +23 -0
  56. package/lib/types/number-formatting.types.js.map +1 -0
  57. package/lib/types/ts-to-chart-event.types.d.ts +20 -6
  58. package/lib/types/ts-to-chart-event.types.d.ts.map +1 -1
  59. package/lib/types/ts-to-chart-event.types.js +1 -0
  60. package/lib/types/ts-to-chart-event.types.js.map +1 -1
  61. package/lib/types/visual-prop.types.d.ts +71 -3
  62. package/lib/types/visual-prop.types.d.ts.map +1 -1
  63. package/lib/types/visual-prop.types.js +32 -1
  64. package/lib/types/visual-prop.types.js.map +1 -1
  65. package/lib/utils/date-formatting.d.ts +85 -1
  66. package/lib/utils/date-formatting.d.ts.map +1 -1
  67. package/lib/utils/date-formatting.js +344 -9
  68. package/lib/utils/date-formatting.js.map +1 -1
  69. package/lib/utils/date-formatting.spec.js +301 -31
  70. package/lib/utils/date-formatting.spec.js.map +1 -1
  71. package/lib/utils/date-utils.d.ts +6 -0
  72. package/lib/utils/date-utils.d.ts.map +1 -0
  73. package/lib/utils/date-utils.js +20 -0
  74. package/lib/utils/date-utils.js.map +1 -0
  75. package/lib/utils/date-utils.spec.d.ts +2 -0
  76. package/lib/utils/date-utils.spec.d.ts.map +1 -0
  77. package/lib/utils/date-utils.spec.js +63 -0
  78. package/lib/utils/date-utils.spec.js.map +1 -0
  79. package/lib/utils/formatting-util.d.ts +10 -0
  80. package/lib/utils/formatting-util.d.ts.map +1 -0
  81. package/lib/utils/formatting-util.js +78 -0
  82. package/lib/utils/formatting-util.js.map +1 -0
  83. package/lib/utils/formatting-util.spec.d.ts +2 -0
  84. package/lib/utils/formatting-util.spec.d.ts.map +1 -0
  85. package/lib/utils/formatting-util.spec.js +247 -0
  86. package/lib/utils/formatting-util.spec.js.map +1 -0
  87. package/lib/utils/globalize-Initializer/globalize-utils.d.ts +16 -0
  88. package/lib/utils/globalize-Initializer/globalize-utils.d.ts.map +1 -0
  89. package/lib/utils/globalize-Initializer/globalize-utils.js +101 -0
  90. package/lib/utils/globalize-Initializer/globalize-utils.js.map +1 -0
  91. package/lib/utils/globalize-Initializer/globalize-utils.spec.d.ts +2 -0
  92. package/lib/utils/globalize-Initializer/globalize-utils.spec.d.ts.map +1 -0
  93. package/lib/utils/globalize-Initializer/globalize-utils.spec.js +149 -0
  94. package/lib/utils/globalize-Initializer/globalize-utils.spec.js.map +1 -0
  95. package/lib/utils/number-formatting/number-formatting-utils.d.ts +20 -0
  96. package/lib/utils/number-formatting/number-formatting-utils.d.ts.map +1 -0
  97. package/lib/utils/number-formatting/number-formatting-utils.js +159 -0
  98. package/lib/utils/number-formatting/number-formatting-utils.js.map +1 -0
  99. package/lib/utils/number-formatting/number-formatting-utils.spec.d.ts +2 -0
  100. package/lib/utils/number-formatting/number-formatting-utils.spec.d.ts.map +1 -0
  101. package/lib/utils/number-formatting/number-formatting-utils.spec.js +221 -0
  102. package/lib/utils/number-formatting/number-formatting-utils.spec.js.map +1 -0
  103. package/lib/utils/number-formatting/number-formatting.d.ts +5 -0
  104. package/lib/utils/number-formatting/number-formatting.d.ts.map +1 -0
  105. package/lib/utils/number-formatting/number-formatting.js +128 -0
  106. package/lib/utils/number-formatting/number-formatting.js.map +1 -0
  107. package/lib/utils/number-formatting/number-formatting.spec.d.ts +2 -0
  108. package/lib/utils/number-formatting/number-formatting.spec.d.ts.map +1 -0
  109. package/lib/utils/number-formatting/number-formatting.spec.js +159 -0
  110. package/lib/utils/number-formatting/number-formatting.spec.js.map +1 -0
  111. package/package.json +4 -1
  112. package/src/index.ts +3 -0
  113. package/src/main/custom-chart-context.spec.ts +356 -6
  114. package/src/main/custom-chart-context.ts +205 -16
  115. package/src/main/logger.spec.ts +114 -0
  116. package/src/main/logger.ts +97 -0
  117. package/src/main/util.spec.ts +94 -0
  118. package/src/main/util.ts +20 -0
  119. package/src/react/use-custom-chart-context.spec.tsx +0 -1
  120. package/src/react/use-custom-chart-context.tsx +4 -1
  121. package/src/react/use-custom-chart-context.util.ts +6 -3
  122. package/src/types/answer-column.types.ts +9 -6
  123. package/src/types/chart-to-ts-event.types.ts +7 -0
  124. package/src/types/common.types.ts +100 -10
  125. package/src/types/conditional-formatting.types.ts +2 -17
  126. package/src/types/configurator.types.ts +1 -1
  127. package/src/types/number-formatting.types.ts +80 -0
  128. package/src/types/ts-to-chart-event.types.ts +63 -5
  129. package/src/types/visual-prop.types.ts +197 -3
  130. package/src/utils/date-formatting.spec.ts +375 -32
  131. package/src/utils/date-formatting.ts +587 -14
  132. package/src/utils/date-utils.spec.ts +93 -0
  133. package/src/utils/date-utils.ts +69 -0
  134. package/src/utils/formatting-util.spec.ts +312 -0
  135. package/src/utils/formatting-util.ts +234 -0
  136. package/src/utils/globalize-Initializer/globalize-utils.spec.ts +192 -0
  137. package/src/utils/globalize-Initializer/globalize-utils.ts +216 -0
  138. package/src/utils/number-formatting/number-formatting-utils.spec.ts +296 -0
  139. package/src/utils/number-formatting/number-formatting-utils.ts +260 -0
  140. package/src/utils/number-formatting/number-formatting.spec.ts +243 -0
  141. package/src/utils/number-formatting/number-formatting.ts +264 -0
@@ -35,6 +35,8 @@ import {
35
35
  ChartModelUpdateEventPayload,
36
36
  ContextMenuCustomActionPayload,
37
37
  DataUpdateEventPayload,
38
+ GetColumnDataPayload,
39
+ GetColumnDataResponsePayload,
38
40
  GetDataQueryPayload,
39
41
  GetDataQueryResponsePayload,
40
42
  InitializeEventPayload,
@@ -50,16 +52,127 @@ import {
50
52
  VisualEditorDefinitionSetter,
51
53
  VisualPropEditorDefinition,
52
54
  } from '../types/visual-prop.types';
55
+ import { setLocaleBasedStringFormats } from '../utils/number-formatting/number-formatting-utils';
56
+ import { create } from './logger';
53
57
  import {
54
58
  globalThis,
55
59
  initMessageListener,
56
60
  postMessageToHostApp,
57
61
  } from './post-message-event-bridge';
58
62
 
63
+ const logger = create('TsChartSDKContext');
64
+ /**
65
+ * Configuration for allowing or disallowing specific TS UI features.
66
+ *
67
+ * @fileoverview
68
+ * This configuration defines the ability to toggle specific features such as
69
+ * column number formatting, conditional formatting, and measure names/values.
70
+ *
71
+ * @version SDK: 0.1 | ThoughtSpot:
72
+ */
59
73
  export type AllowedConfigurations = {
60
- allowColumnNumberFormatting: boolean;
61
- allowColumnConditionalFormatting: boolean;
62
- allowMeasureNamesAndValues: boolean;
74
+ /**
75
+ * Allows column-level number formatting.
76
+ *
77
+ * @default false
78
+ * @version SDK: 0.1 | ThoughtSpot:
79
+ */
80
+ allowColumnNumberFormatting?: boolean;
81
+
82
+ /**
83
+ * Allows conditional formatting at the column level.
84
+ * This allows users to apply rules that visually highlight or differentiate data.
85
+ *
86
+ * @default false
87
+ * @version SDK: 0.1 | ThoughtSpot:
88
+ */
89
+ allowColumnConditionalFormatting?: boolean;
90
+
91
+ /**
92
+ * Allows Gradient coloring at the column level.
93
+ *
94
+ * @default false
95
+ * @version SDK: 0.1 | ThoughtSpot:
96
+ */
97
+ allowGradientColoring?: boolean;
98
+
99
+ /**
100
+ * Enables measure_name and measure_values in the chart configuration.
101
+ *
102
+ * @default false
103
+ * @version SDK: 0.1 | ThoughtSpot:
104
+ */
105
+ allowMeasureNamesAndValues?: boolean;
106
+ };
107
+ /**
108
+ * Key stored in persistedVisualPropKeys array will be preserved on changing the
109
+ * visualPropeditorDefinition, any other key (expect clientState) would not be preserved for visual props.
110
+ * ### NOTE: like clientState this variable should be a string, preferrably a result of JSON.stringify(<yourlocalClientState>)
111
+ * ### USE CASE: This is to maintain different clientSate for different custom charts developed by same TS custom chart developer.
112
+ * @version SDK: 0.1 | ThoughtSpot:
113
+ */
114
+ export type PersistedVisualPropKeys = string[];
115
+ /**
116
+ * Configuration parameters for setting chart-specific options.
117
+ *
118
+ * @fileoverview
119
+ * This configuration includes settings for controlling measure name/value columns
120
+ * and batch size limits for data processing.
121
+ *
122
+ * @version SDK: 0.1 | ThoughtSpot:
123
+ */
124
+ export type ChartConfigParameters = {
125
+ /**
126
+ * Configurations related to measure name and value columns.
127
+ * These parameters allow for enabling/disabling and aliasing the columns
128
+ * used to represent measure names and values.
129
+ *
130
+ * @version SDK: 0.1 | ThoughtSpot:
131
+ */
132
+ measureNameValueColumns?: {
133
+ /**
134
+ * Enables or disables the measure_name column.
135
+ *
136
+ * @default false
137
+ * @version SDK: 0.1 | ThoughtSpot:
138
+ */
139
+ enableMeasureNameColumn?: boolean;
140
+
141
+ /**
142
+ * Enables or disables the measure_value column.
143
+ *
144
+ * @default false
145
+ * @version SDK: 0.1 | ThoughtSpot:
146
+ */
147
+ enableMeasureValueColumn?: boolean;
148
+
149
+ /**
150
+ * Alias for the measure_name column.
151
+ * This allows users to define a custom name for the measure_name column.
152
+ *
153
+ * @default 'Measure Name'
154
+ * @version SDK: 0.1 | ThoughtSpot:
155
+ */
156
+ measureNameColumnAlias?: string;
157
+
158
+ /**
159
+ * Alias for the measure_value column.
160
+ * This allows users to define a custom name for the measure_value column.
161
+ *
162
+ * @default 'Measure Value'
163
+ * @version SDK: 0.1 | ThoughtSpot:
164
+ */
165
+ measureValueColumnAlias?: string;
166
+ };
167
+
168
+ /**
169
+ * Limit on the batch size for data processing.
170
+ * This sets an upper limit on how much data can be processed in a single batch.
171
+ *
172
+ * @default 20000
173
+ * @version SDK: 0.1 | ThoughtSpot:
174
+ */
175
+ batchSizeLimit?: number;
63
176
  };
64
177
 
65
178
  export type CustomChartContextProps = {
@@ -116,6 +229,7 @@ export type CustomChartContextProps = {
116
229
  validateVisualProps?: (
117
230
  updatedVisualProps: VisualProps,
118
231
  chartModel: ChartModel,
232
+ activeColumnId?: string,
119
233
  ) => ValidationResponse;
120
234
 
121
235
  /**
@@ -141,8 +255,36 @@ export type CustomChartContextProps = {
141
255
  | VisualEditorDefinitionSetter
142
256
  | VisualPropEditorDefinition;
143
257
 
144
- // Whether user wants thoughtspot default number and conditional formatting
258
+ /**
259
+ * Optional configuration to toggle native TS UI configurations, such as column number
260
+ * formatting and conditional formatting.
261
+ *
262
+ * @type {AllowedConfigurations}
263
+ * @version SDK: 0.1 | ThoughtSpot:
264
+ */
145
265
  allowedConfigurations?: AllowedConfigurations;
266
+ /**
267
+ /**
268
+ * Key stored in persistedVisualPropKeys array will be preserved on changing the
269
+ * visualPropeditorDefinition, any other key (expect clientState)
270
+ * would not be preserved for visual props.
271
+ * ### NOTE: like clientState this variable should be a string,
272
+ * preferrably a result of JSON.stringify(<yourlocalClientState>)
273
+ * ### USE CASE: This is to maintain different clientSate for different custom charts
274
+ * developed by same TS custom chart developer.
275
+ *
276
+ * @type {PersistedVisualPropKeys}
277
+ * @version SDK: 0.1 | ThoughtSpot:
278
+ */
279
+ persistedVisualPropKeys?: PersistedVisualPropKeys;
280
+ /**
281
+ * Optional parameters for configuring specific chart-related features, such as measure name
282
+ * and value columns.
283
+ *
284
+ * @type {ChartConfigParameters}
285
+ * @version SDK: 0.1 | ThoughtSpot:
286
+ */
287
+ chartConfigParameters?: ChartConfigParameters;
146
288
  };
147
289
 
148
290
  export type ValidationFunctions =
@@ -159,8 +301,19 @@ const DEFAULT_CHART_CONTEXT_PROPS: Partial<CustomChartContextProps> = {
159
301
  allowedConfigurations: {
160
302
  allowColumnNumberFormatting: false,
161
303
  allowColumnConditionalFormatting: false,
304
+ allowGradientColoring: false,
162
305
  allowMeasureNamesAndValues: false,
163
306
  },
307
+ persistedVisualPropKeys: undefined,
308
+ chartConfigParameters: {
309
+ measureNameValueColumns: {
310
+ enableMeasureNameColumn: false,
311
+ enableMeasureValueColumn: false,
312
+ measureNameColumnAlias: 'Measure Name',
313
+ measureValueColumnAlias: 'Measure Values',
314
+ },
315
+ batchSizeLimit: 20000,
316
+ },
164
317
  };
165
318
 
166
319
  export class CustomChartContext {
@@ -269,6 +422,9 @@ export class CustomChartContext {
269
422
  this.hasInitializedPromise = new Promise((resolve, reject) => {
270
423
  this.triggerInitResolve = resolve;
271
424
  });
425
+ // Not using this.emitEvent as the context is not yet completely
426
+ // initialized, thus short circuiting.
427
+ postMessageToHostApp('', '*', null, ChartToTSEvent.InitStart);
272
428
  }
273
429
 
274
430
  /**
@@ -279,7 +435,7 @@ export class CustomChartContext {
279
435
  * @version SDK: 0.1 | ThoughtSpot:
280
436
  */
281
437
  public initialize = (): Promise<void> => {
282
- console.log('Chart Context: initialization start');
438
+ logger.log('Chart Context: initialization start');
283
439
  return this.hasInitializedPromise;
284
440
  };
285
441
 
@@ -311,7 +467,7 @@ export class CustomChartContext {
311
467
  */
312
468
  public off<T extends keyof TSToChartEventsPayloadMap>(eventType: T): void {
313
469
  if (_.isNil(this.eventListeners[eventType])) {
314
- console.log('No event listener found to remove');
470
+ logger.log('No event listener found to remove');
315
471
  this.eventListeners[eventType] = [];
316
472
  return;
317
473
  }
@@ -424,6 +580,7 @@ export class CustomChartContext {
424
580
  * @returns {VisualPropEditorDefinition}
425
581
  */
426
582
  private getVisualPropEditorDefinition = (
583
+ activeColumnId?: string,
427
584
  currentState: Partial<ChartModel> = {},
428
585
  ) => {
429
586
  if (_.isFunction(this.chartContextProps.visualPropEditorDefinition)) {
@@ -433,6 +590,7 @@ export class CustomChartContext {
433
590
  ...currentState,
434
591
  },
435
592
  this,
593
+ activeColumnId,
436
594
  );
437
595
  }
438
596
  return this.chartContextProps.visualPropEditorDefinition;
@@ -500,8 +658,10 @@ export class CustomChartContext {
500
658
  private validationsResponseProcessor(
501
659
  currentValidationState: Partial<ChartModel>,
502
660
  validationResponse: ValidationResponse,
661
+ activeColumnId?: string,
503
662
  ) {
504
663
  const visualPropEditorDefinition = this.getVisualPropEditorDefinition(
664
+ activeColumnId,
505
665
  currentValidationState,
506
666
  );
507
667
  const chartConfigEditorDefinition = this.getChartConfigEditorDefinition(
@@ -528,7 +688,7 @@ export class CustomChartContext {
528
688
  ...eventPayload: ChartToTSEventsPayloadMap[T]
529
689
  ): Promise<any> {
530
690
  if (!globalThis.isInitialized) {
531
- console.log(
691
+ logger.log(
532
692
  'Chart Context: not initialized the context, something went wrong',
533
693
  );
534
694
  return Promise.reject(new Error('Context not initialized'));
@@ -551,7 +711,7 @@ export class CustomChartContext {
551
711
  */
552
712
  private registerEventProcessor = () => {
553
713
  if (globalThis.isInitialized) {
554
- console.error(
714
+ logger.error(
555
715
  'The context is already initialized. you cannot have multiple contexts',
556
716
  );
557
717
  throw new Error(ErrorType.MultipleContextsNotSupported);
@@ -567,12 +727,12 @@ export class CustomChartContext {
567
727
  * @param event : Message Event Object
568
728
  */
569
729
  private eventProcessor = (data: any) => {
570
- console.log('Chart Context: message received:', data.eventType, data);
730
+ logger.log('Chart Context: message received:', data.eventType, data);
571
731
 
572
732
  const messageResponse = this.executeEventListenerCBs(data);
573
733
 
574
734
  // respond back to parent to confirm/ack the receipt
575
- return messageResponse;
735
+ return messageResponse || {};
576
736
  };
577
737
 
578
738
  /**
@@ -617,15 +777,17 @@ export class CustomChartContext {
617
777
  this.chartContextProps.validateVisualProps(
618
778
  payload.visualProps,
619
779
  this.chartModel,
780
+ payload?.activeColumnId,
620
781
  );
621
782
  if (validationResponse.isValid) {
622
783
  const currentVisualState = {
623
784
  visualProps: payload.visualProps,
624
785
  };
625
-
786
+ const activeColumnId = payload?.activeColumnId;
626
787
  return this.validationsResponseProcessor(
627
788
  currentVisualState,
628
789
  validationResponse,
790
+ activeColumnId,
629
791
  );
630
792
  }
631
793
  return validationResponse;
@@ -690,6 +852,28 @@ export class CustomChartContext {
690
852
  },
691
853
  );
692
854
 
855
+ /**
856
+ * This event is triggered when the TS app asks for data in a specific columns
857
+ * for certain validations for settings and drop down options related to Conditional
858
+ * formatting and other advanced settings
859
+ */
860
+ this.onInternal(
861
+ TSToChartEvent.GetColumnData,
862
+ (payload: GetColumnDataPayload): GetColumnDataResponsePayload => {
863
+ const parsedData = this.chartModel.data?.[0].data;
864
+ const dataIdx = parsedData?.columns.findIndex(
865
+ (columnId) => columnId === payload.columnId,
866
+ );
867
+ const dataArray =
868
+ !_.isNil(dataIdx) && dataIdx > -1
869
+ ? parsedData?.dataValue.map((it) => it[dataIdx])
870
+ : [];
871
+ return {
872
+ data: dataArray,
873
+ };
874
+ },
875
+ );
876
+
693
877
  /**
694
878
  * This event is triggered when the TS app re-renders the chart
695
879
  */
@@ -728,7 +912,7 @@ export class CustomChartContext {
728
912
  isValid: true,
729
913
  };
730
914
  } catch (error: unknown) {
731
- console.log(
915
+ logger.log(
732
916
  'ContextMenuCustomAction: payload recieved:',
733
917
  payload,
734
918
  'CustomActionCallbackStore:',
@@ -771,7 +955,7 @@ export class CustomChartContext {
771
955
  isValid: true,
772
956
  };
773
957
  } catch (error: unknown) {
774
- console.log(
958
+ logger.log(
775
959
  'AxisMenuCustomAction: payload recieved:',
776
960
  payload,
777
961
  'CustomActionCallbackStore:',
@@ -859,7 +1043,9 @@ export class CustomChartContext {
859
1043
  this.containerEl = payload.containerElSelector
860
1044
  ? document.querySelector(payload.containerElSelector)
861
1045
  : null;
862
-
1046
+ setLocaleBasedStringFormats(
1047
+ this.appConfig.dateFormatsConfig?.tsLocaleBasedStringsFormats,
1048
+ );
863
1049
  return this.publishChartContextPropsToHost();
864
1050
  };
865
1051
 
@@ -901,6 +1087,10 @@ export class CustomChartContext {
901
1087
  this.getVisualPropEditorDefinition(),
902
1088
  allowedConfigurations:
903
1089
  this.chartContextProps.allowedConfigurations,
1090
+ chartConfigParameters:
1091
+ this.chartContextProps.chartConfigParameters,
1092
+ persistedVisualPropKeys:
1093
+ this.chartContextProps.persistedVisualPropKeys,
904
1094
  };
905
1095
  };
906
1096
 
@@ -928,7 +1118,7 @@ export class CustomChartContext {
928
1118
  };
929
1119
  }
930
1120
 
931
- console.log(
1121
+ logger.log(
932
1122
  'ChartContext: Response:',
933
1123
  data.eventType,
934
1124
  response,
@@ -951,6 +1141,5 @@ export const getChartContext = async (
951
1141
  // wait for initialization here as the host app
952
1142
  // needs to first handshake with the client app.
953
1143
  await ctx.initialize();
954
-
955
1144
  return ctx;
956
1145
  };
@@ -0,0 +1,114 @@
1
+ import { create, LogLevel, logMethods } from './logger';
2
+
3
+ jest.mock('./util', () => ({
4
+ getQueryParam: jest.fn().mockReturnValue('true'),
5
+ }));
6
+
7
+ describe('Logger', () => {
8
+ let originalConsole: Record<string, any>;
9
+ let logger: any;
10
+
11
+ beforeAll(() => {
12
+ // Save original console methods
13
+ originalConsole = {
14
+ error: console.error,
15
+ warn: console.warn,
16
+ info: console.info,
17
+ log: console.log,
18
+ trace: console.trace,
19
+ };
20
+ // Mock console methods
21
+ const error = jest.fn();
22
+ const warn = jest.fn();
23
+ const info = jest.fn();
24
+ const log = jest.fn();
25
+ const trace = jest.fn();
26
+ logMethods[LogLevel.ERROR] = error;
27
+ logMethods[LogLevel.WARN] = warn;
28
+ logMethods[LogLevel.INFO] = info;
29
+ logMethods[LogLevel.DEBUG] = log;
30
+ logMethods[LogLevel.TRACE] = trace;
31
+ console.error = error;
32
+ console.warn = warn;
33
+ console.info = info;
34
+ console.log = log;
35
+ console.trace = trace;
36
+ });
37
+
38
+ afterAll(() => {
39
+ // Restore original console methods
40
+ logMethods[LogLevel.ERROR] = originalConsole.error;
41
+ logMethods[LogLevel.WARN] = originalConsole.warn;
42
+ logMethods[LogLevel.INFO] = originalConsole.info;
43
+ logMethods[LogLevel.DEBUG] = originalConsole.log;
44
+ logMethods[LogLevel.TRACE] = originalConsole.trace;
45
+ console.error = originalConsole.error;
46
+ console.warn = originalConsole.warn;
47
+ console.info = originalConsole.info;
48
+ console.log = originalConsole.log;
49
+ console.trace = originalConsole.trace;
50
+ });
51
+
52
+ beforeEach(() => {
53
+ // Set debug to 'true' to enable logging
54
+ (global as any).window = {
55
+ location: { href: 'https://example.com/?debug=true' },
56
+ };
57
+ logger = create('TestLogger');
58
+ });
59
+
60
+ afterEach(() => {
61
+ jest.clearAllMocks();
62
+ });
63
+
64
+ describe('logMessages', () => {
65
+ it('should log messages with appropriate prefixes for DEBUG level', async () => {
66
+ await logger.debug('Debug message');
67
+ expect(console.log).toHaveBeenCalledWith(
68
+ 'DEBUG (TestLogger)(] Debug message)',
69
+ );
70
+ });
71
+
72
+ it('should log messages with appropriate prefixes for INFO level', async () => {
73
+ await logger.info('Info message');
74
+ expect(console.info).toHaveBeenCalledWith(
75
+ 'INFO (TestLogger)(] Info message)',
76
+ );
77
+ });
78
+
79
+ it('should log messages with appropriate prefixes for WARN level', async () => {
80
+ await logger.warn('Warn message');
81
+ expect(console.warn).toHaveBeenCalledWith(
82
+ 'WARN (TestLogger)(] Warn message)',
83
+ );
84
+ });
85
+
86
+ it('should log messages with appropriate prefixes for ERROR level', async () => {
87
+ await logger.error('Error message');
88
+ expect(console.error).toHaveBeenCalledWith(
89
+ 'ERROR (TestLogger)(] Error message)',
90
+ );
91
+ });
92
+
93
+ it('should log messages with appropriate prefixes for TRACE level', async () => {
94
+ await logger.trace('Trace message');
95
+ expect(console.trace).toHaveBeenCalledWith(
96
+ 'TRACE (TestLogger)(] Trace message)',
97
+ );
98
+ });
99
+ });
100
+
101
+ describe('create function', () => {
102
+ it('should create a new logger instance if one does not exist', () => {
103
+ const newLogger = create('NewLogger');
104
+ expect(newLogger).not.toBe(undefined);
105
+ expect(newLogger).toBeInstanceOf(Object);
106
+ });
107
+
108
+ it('should return the same logger instance if it already exists', () => {
109
+ const logger1 = create('SharedLogger');
110
+ const logger2 = create('SharedLogger');
111
+ expect(logger1).toBe(logger2);
112
+ });
113
+ });
114
+ });
@@ -0,0 +1,97 @@
1
+ import { getQueryParam } from './util';
2
+
3
+ const loggers: Record<string, any> = {};
4
+ const url = window.location.href;
5
+
6
+ export enum LogLevel {
7
+ SILENT = -1 as number,
8
+ ERROR = 0 as number,
9
+ WARN = 1 as number,
10
+ INFO = 2 as number,
11
+ DEBUG = 3 as number,
12
+ TRACE = 4 as number,
13
+ }
14
+
15
+ export const logMethods: { [key: number]: any } = {
16
+ [LogLevel.ERROR]: console.error,
17
+ [LogLevel.WARN]: console.warn,
18
+ [LogLevel.INFO]: console.info,
19
+ [LogLevel.DEBUG]: console.log,
20
+ [LogLevel.TRACE]: console.trace,
21
+ };
22
+
23
+ class Logger {
24
+ private msgPrefix: string;
25
+
26
+ constructor(name: string) {
27
+ this.msgPrefix = name ? `(${name})` : '';
28
+ }
29
+
30
+ private getFormattedMessage(msg: string, logLevel: LogLevel) {
31
+ return (
32
+ `${LogLevel[logLevel]} ${this.msgPrefix}` +
33
+ `(${msg ? `] ${msg}` : ''})`
34
+ );
35
+ }
36
+
37
+ public async logMessages(args: any, logLevel: LogLevel) {
38
+ const newArgs = args;
39
+ if (getQueryParam(url, 'debug') !== 'true') {
40
+ return;
41
+ }
42
+ const logFn = logMethods[logLevel];
43
+ newArgs[0] = this.getFormattedMessage(newArgs[0], logLevel);
44
+ if (logFn) {
45
+ logFn(...newArgs);
46
+ }
47
+ }
48
+
49
+ public async trace(...args: any) {
50
+ this.logMessages(args, LogLevel.TRACE);
51
+ }
52
+
53
+ /**
54
+ * Wrapper for log.log() with debug level
55
+ * @param msg
56
+ */
57
+ public async debug(...args: any) {
58
+ this.logMessages(args, LogLevel.DEBUG);
59
+ }
60
+
61
+ public async log(...args: any) {
62
+ this.debug(...args);
63
+ }
64
+
65
+ /**
66
+ * Wrapper for log.info() with info level
67
+ * @param msg
68
+ */
69
+ public async info(...args: any) {
70
+ this.logMessages(args, LogLevel.INFO);
71
+ }
72
+
73
+ /**
74
+ * Wrapper for log.warn() with warn level
75
+ *
76
+ * @param {string} msg The log message
77
+ */
78
+ public async warn(...args: any) {
79
+ this.logMessages(args, LogLevel.WARN);
80
+ }
81
+
82
+ /**
83
+ * Wrapper for log.error() with error level
84
+ * Error is propagated via callback
85
+ * @param {string} msg The log message
86
+ */
87
+ public async error(...args: any) {
88
+ const stackTrace = this.logMessages(args, LogLevel.ERROR);
89
+ }
90
+ }
91
+
92
+ export function create(name: string): any {
93
+ if (!loggers[name]) {
94
+ loggers[name] = new Logger(name);
95
+ }
96
+ return loggers[name];
97
+ }
@@ -0,0 +1,94 @@
1
+ import { getQueryParam, handleMissingValue, timeout } from './util';
2
+
3
+ describe('timeout function', () => {
4
+ jest.useFakeTimers();
5
+
6
+ it('should resolve with the original promise result if it completes before timeout', async () => {
7
+ const promise = Promise.resolve('success');
8
+ const result = timeout(promise, 1000);
9
+
10
+ await expect(result).resolves.toBe('success');
11
+ });
12
+
13
+ it('should reject with a timeout error if the promise takes too long', async () => {
14
+ const promise = new Promise((resolve) => {
15
+ setTimeout(resolve, 2000);
16
+ });
17
+ const result = timeout(promise, 1000);
18
+
19
+ jest.advanceTimersByTime(1000);
20
+
21
+ await expect(result).rejects.toThrow('Operation timed out.');
22
+ });
23
+
24
+ it('should use the custom error message when provided', async () => {
25
+ const promise = new Promise((resolve) => {
26
+ setTimeout(resolve, 2000);
27
+ });
28
+ const result = timeout(promise, 1000, 'Custom timeout message');
29
+
30
+ jest.advanceTimersByTime(1000);
31
+
32
+ await expect(result).rejects.toThrow('Custom timeout message');
33
+ });
34
+
35
+ it('should reject with the original promise error if it rejects before timeout', async () => {
36
+ const promise = Promise.reject(new Error('Original error'));
37
+ const result = timeout(promise, 1000);
38
+
39
+ await expect(result).rejects.toThrow('Original error');
40
+ });
41
+ });
42
+
43
+ describe('getQueryParam', () => {
44
+ it('should return the value of a query parameter if it exists', () => {
45
+ const url = 'https://example.com/?key=value';
46
+ expect(getQueryParam(url, 'key')).toBe('value');
47
+ });
48
+
49
+ it('should return an empty string if the query parameter does not exist', () => {
50
+ const url = 'https://example.com/';
51
+ expect(getQueryParam(url, 'missingKey')).toBe('');
52
+ });
53
+
54
+ it('should return "false" if the query parameter is "debug" and it is not present', () => {
55
+ const url = 'https://example.com/';
56
+ expect(getQueryParam(url, 'debug')).toBe('false');
57
+ });
58
+
59
+ it('should return the default value "false" if the query parameter is "debug" and is set to an empty value', () => {
60
+ const url = 'https://example.com/?debug=';
61
+ expect(getQueryParam(url, 'debug')).toBe('');
62
+ });
63
+
64
+ it('should handle complex URLs with multiple parameters correctly', () => {
65
+ const url = 'https://example.com/?key1=value1&key2=value2';
66
+ expect(getQueryParam(url, 'key2')).toBe('value2');
67
+ });
68
+ });
69
+
70
+ describe('handleMissingValue', () => {
71
+ it('should return "false" if paramKey is "debug" and paramValue is undefined', () => {
72
+ expect(handleMissingValue('debug', undefined)).toBe('false');
73
+ });
74
+
75
+ it('should return "false" if paramKey is "debug" and paramValue is null', () => {
76
+ expect(handleMissingValue('debug', null)).toBe('false');
77
+ });
78
+
79
+ it('should return an empty string if paramValue is undefined and paramKey is not "debug"', () => {
80
+ expect(handleMissingValue('param', undefined)).toBe('');
81
+ });
82
+
83
+ it('should return an empty string if paramValue is null and paramKey is not "debug"', () => {
84
+ expect(handleMissingValue('param', null)).toBe('');
85
+ });
86
+
87
+ it('should return the paramValue if paramValue is defined and not null', () => {
88
+ expect(handleMissingValue('param', 'value')).toBe('value');
89
+ });
90
+
91
+ it('should return the paramValue even if paramKey is "debug" and paramValue is defined', () => {
92
+ expect(handleMissingValue('debug', 'true')).toBe('true');
93
+ });
94
+ });