@thoughtspot/ts-chart-sdk 0.0.2-alpha.8 → 1.0.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 (144) hide show
  1. package/README.md +126 -50
  2. package/dist/ts-chart-sdk.d.ts +337 -31
  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 +15 -3
  8. package/lib/main/custom-chart-context.d.ts.map +1 -1
  9. package/lib/main/custom-chart-context.js +45 -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 +15 -5
  38. package/lib/types/answer-column.types.d.ts.map +1 -1
  39. package/lib/types/answer-column.types.js +10 -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 -5
  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 +3 -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 +19 -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/conditional-formatting/conditional-formatting.spec.js +2 -1
  66. package/lib/utils/conditional-formatting/conditional-formatting.spec.js.map +1 -1
  67. package/lib/utils/date-formatting.d.ts +85 -1
  68. package/lib/utils/date-formatting.d.ts.map +1 -1
  69. package/lib/utils/date-formatting.js +344 -9
  70. package/lib/utils/date-formatting.js.map +1 -1
  71. package/lib/utils/date-formatting.spec.js +303 -32
  72. package/lib/utils/date-formatting.spec.js.map +1 -1
  73. package/lib/utils/date-utils.d.ts +6 -0
  74. package/lib/utils/date-utils.d.ts.map +1 -0
  75. package/lib/utils/date-utils.js +20 -0
  76. package/lib/utils/date-utils.js.map +1 -0
  77. package/lib/utils/date-utils.spec.d.ts +2 -0
  78. package/lib/utils/date-utils.spec.d.ts.map +1 -0
  79. package/lib/utils/date-utils.spec.js +63 -0
  80. package/lib/utils/date-utils.spec.js.map +1 -0
  81. package/lib/utils/formatting-util.d.ts +10 -0
  82. package/lib/utils/formatting-util.d.ts.map +1 -0
  83. package/lib/utils/formatting-util.js +78 -0
  84. package/lib/utils/formatting-util.js.map +1 -0
  85. package/lib/utils/formatting-util.spec.d.ts +2 -0
  86. package/lib/utils/formatting-util.spec.d.ts.map +1 -0
  87. package/lib/utils/formatting-util.spec.js +247 -0
  88. package/lib/utils/formatting-util.spec.js.map +1 -0
  89. package/lib/utils/globalize-Initializer/globalize-utils.d.ts +16 -0
  90. package/lib/utils/globalize-Initializer/globalize-utils.d.ts.map +1 -0
  91. package/lib/utils/globalize-Initializer/globalize-utils.js +101 -0
  92. package/lib/utils/globalize-Initializer/globalize-utils.js.map +1 -0
  93. package/lib/utils/globalize-Initializer/globalize-utils.spec.d.ts +2 -0
  94. package/lib/utils/globalize-Initializer/globalize-utils.spec.d.ts.map +1 -0
  95. package/lib/utils/globalize-Initializer/globalize-utils.spec.js +149 -0
  96. package/lib/utils/globalize-Initializer/globalize-utils.spec.js.map +1 -0
  97. package/lib/utils/number-formatting/number-formatting-utils.d.ts +20 -0
  98. package/lib/utils/number-formatting/number-formatting-utils.d.ts.map +1 -0
  99. package/lib/utils/number-formatting/number-formatting-utils.js +159 -0
  100. package/lib/utils/number-formatting/number-formatting-utils.js.map +1 -0
  101. package/lib/utils/number-formatting/number-formatting-utils.spec.d.ts +2 -0
  102. package/lib/utils/number-formatting/number-formatting-utils.spec.d.ts.map +1 -0
  103. package/lib/utils/number-formatting/number-formatting-utils.spec.js +221 -0
  104. package/lib/utils/number-formatting/number-formatting-utils.spec.js.map +1 -0
  105. package/lib/utils/number-formatting/number-formatting.d.ts +5 -0
  106. package/lib/utils/number-formatting/number-formatting.d.ts.map +1 -0
  107. package/lib/utils/number-formatting/number-formatting.js +128 -0
  108. package/lib/utils/number-formatting/number-formatting.js.map +1 -0
  109. package/lib/utils/number-formatting/number-formatting.spec.d.ts +2 -0
  110. package/lib/utils/number-formatting/number-formatting.spec.d.ts.map +1 -0
  111. package/lib/utils/number-formatting/number-formatting.spec.js +159 -0
  112. package/lib/utils/number-formatting/number-formatting.spec.js.map +1 -0
  113. package/package.json +5 -2
  114. package/src/index.ts +3 -0
  115. package/src/main/custom-chart-context.spec.ts +356 -6
  116. package/src/main/custom-chart-context.ts +182 -15
  117. package/src/main/logger.spec.ts +114 -0
  118. package/src/main/logger.ts +97 -0
  119. package/src/main/util.spec.ts +94 -0
  120. package/src/main/util.ts +20 -0
  121. package/src/react/use-custom-chart-context.spec.tsx +0 -1
  122. package/src/react/use-custom-chart-context.tsx +4 -1
  123. package/src/react/use-custom-chart-context.util.ts +6 -3
  124. package/src/types/answer-column.types.ts +29 -6
  125. package/src/types/chart-to-ts-event.types.ts +7 -0
  126. package/src/types/common.types.ts +103 -8
  127. package/src/types/conditional-formatting.types.ts +2 -17
  128. package/src/types/configurator.types.ts +15 -1
  129. package/src/types/number-formatting.types.ts +80 -0
  130. package/src/types/ts-to-chart-event.types.ts +54 -5
  131. package/src/types/visual-prop.types.ts +197 -3
  132. package/src/utils/conditional-formatting/conditional-formatting.spec.ts +2 -0
  133. package/src/utils/date-formatting.spec.ts +377 -32
  134. package/src/utils/date-formatting.ts +587 -14
  135. package/src/utils/date-utils.spec.ts +93 -0
  136. package/src/utils/date-utils.ts +69 -0
  137. package/src/utils/formatting-util.spec.ts +312 -0
  138. package/src/utils/formatting-util.ts +234 -0
  139. package/src/utils/globalize-Initializer/globalize-utils.spec.ts +192 -0
  140. package/src/utils/globalize-Initializer/globalize-utils.ts +216 -0
  141. package/src/utils/number-formatting/number-formatting-utils.spec.ts +296 -0
  142. package/src/utils/number-formatting/number-formatting-utils.ts +260 -0
  143. package/src/utils/number-formatting/number-formatting.spec.ts +243 -0
  144. 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,15 +52,120 @@ 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;
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
+ /**
109
+ * Configuration parameters for setting chart-specific options.
110
+ *
111
+ * @fileoverview
112
+ * This configuration includes settings for controlling measure name/value columns
113
+ * and batch size limits for data processing.
114
+ *
115
+ * @version SDK: 0.1 | ThoughtSpot:
116
+ */
117
+ export type ChartConfigParameters = {
118
+ /**
119
+ * Configurations related to measure name and value columns.
120
+ * These parameters allow for enabling/disabling and aliasing the columns
121
+ * used to represent measure names and values.
122
+ *
123
+ * @version SDK: 0.1 | ThoughtSpot:
124
+ */
125
+ measureNameValueColumns?: {
126
+ /**
127
+ * Enables or disables the measure_name column.
128
+ *
129
+ * @default false
130
+ * @version SDK: 0.1 | ThoughtSpot:
131
+ */
132
+ enableMeasureNameColumn?: boolean;
133
+
134
+ /**
135
+ * Enables or disables the measure_value column.
136
+ *
137
+ * @default false
138
+ * @version SDK: 0.1 | ThoughtSpot:
139
+ */
140
+ enableMeasureValueColumn?: boolean;
141
+
142
+ /**
143
+ * Alias for the measure_name column.
144
+ * This allows users to define a custom name for the measure_name column.
145
+ *
146
+ * @default 'Measure Name'
147
+ * @version SDK: 0.1 | ThoughtSpot:
148
+ */
149
+ measureNameColumnAlias?: string;
150
+
151
+ /**
152
+ * Alias for the measure_value column.
153
+ * This allows users to define a custom name for the measure_value column.
154
+ *
155
+ * @default 'Measure Value'
156
+ * @version SDK: 0.1 | ThoughtSpot:
157
+ */
158
+ measureValueColumnAlias?: string;
159
+ };
160
+
161
+ /**
162
+ * Limit on the batch size for data processing.
163
+ * This sets an upper limit on how much data can be processed in a single batch.
164
+ *
165
+ * @default 20000
166
+ * @version SDK: 0.1 | ThoughtSpot:
167
+ */
168
+ batchSizeLimit?: number;
62
169
  };
63
170
 
64
171
  export type CustomChartContextProps = {
@@ -115,6 +222,7 @@ export type CustomChartContextProps = {
115
222
  validateVisualProps?: (
116
223
  updatedVisualProps: VisualProps,
117
224
  chartModel: ChartModel,
225
+ activeColumnId?: string,
118
226
  ) => ValidationResponse;
119
227
 
120
228
  /**
@@ -140,8 +248,22 @@ export type CustomChartContextProps = {
140
248
  | VisualEditorDefinitionSetter
141
249
  | VisualPropEditorDefinition;
142
250
 
143
- // Whether user wants thoughtspot default number and conditional formatting
251
+ /**
252
+ * Optional configuration to toggle native TS UI configurations, such as column number
253
+ * formatting and conditional formatting.
254
+ *
255
+ * @type {AllowedConfigurations}
256
+ * @version SDK: 0.1 | ThoughtSpot:
257
+ */
144
258
  allowedConfigurations?: AllowedConfigurations;
259
+ /**
260
+ * Optional parameters for configuring specific chart-related features, such as measure name
261
+ * and value columns.
262
+ *
263
+ * @type {ChartConfigParameters}
264
+ * @version SDK: 0.1 | ThoughtSpot:
265
+ */
266
+ chartConfigParameters?: ChartConfigParameters;
145
267
  };
146
268
 
147
269
  export type ValidationFunctions =
@@ -158,6 +280,17 @@ const DEFAULT_CHART_CONTEXT_PROPS: Partial<CustomChartContextProps> = {
158
280
  allowedConfigurations: {
159
281
  allowColumnNumberFormatting: false,
160
282
  allowColumnConditionalFormatting: false,
283
+ allowGradientColoring: false,
284
+ allowMeasureNamesAndValues: false,
285
+ },
286
+ chartConfigParameters: {
287
+ measureNameValueColumns: {
288
+ enableMeasureNameColumn: false,
289
+ enableMeasureValueColumn: false,
290
+ measureNameColumnAlias: 'Measure Name',
291
+ measureValueColumnAlias: 'Measure Values',
292
+ },
293
+ batchSizeLimit: 20000,
161
294
  },
162
295
  };
163
296
 
@@ -267,6 +400,9 @@ export class CustomChartContext {
267
400
  this.hasInitializedPromise = new Promise((resolve, reject) => {
268
401
  this.triggerInitResolve = resolve;
269
402
  });
403
+ // Not using this.emitEvent as the context is not yet completely
404
+ // initialized, thus short circuiting.
405
+ postMessageToHostApp('', '*', null, ChartToTSEvent.InitStart);
270
406
  }
271
407
 
272
408
  /**
@@ -277,7 +413,7 @@ export class CustomChartContext {
277
413
  * @version SDK: 0.1 | ThoughtSpot:
278
414
  */
279
415
  public initialize = (): Promise<void> => {
280
- console.log('Chart Context: initialization start');
416
+ logger.log('Chart Context: initialization start');
281
417
  return this.hasInitializedPromise;
282
418
  };
283
419
 
@@ -309,7 +445,7 @@ export class CustomChartContext {
309
445
  */
310
446
  public off<T extends keyof TSToChartEventsPayloadMap>(eventType: T): void {
311
447
  if (_.isNil(this.eventListeners[eventType])) {
312
- console.log('No event listener found to remove');
448
+ logger.log('No event listener found to remove');
313
449
  this.eventListeners[eventType] = [];
314
450
  return;
315
451
  }
@@ -422,6 +558,7 @@ export class CustomChartContext {
422
558
  * @returns {VisualPropEditorDefinition}
423
559
  */
424
560
  private getVisualPropEditorDefinition = (
561
+ activeColumnId?: string,
425
562
  currentState: Partial<ChartModel> = {},
426
563
  ) => {
427
564
  if (_.isFunction(this.chartContextProps.visualPropEditorDefinition)) {
@@ -431,6 +568,7 @@ export class CustomChartContext {
431
568
  ...currentState,
432
569
  },
433
570
  this,
571
+ activeColumnId,
434
572
  );
435
573
  }
436
574
  return this.chartContextProps.visualPropEditorDefinition;
@@ -498,8 +636,10 @@ export class CustomChartContext {
498
636
  private validationsResponseProcessor(
499
637
  currentValidationState: Partial<ChartModel>,
500
638
  validationResponse: ValidationResponse,
639
+ activeColumnId?: string,
501
640
  ) {
502
641
  const visualPropEditorDefinition = this.getVisualPropEditorDefinition(
642
+ activeColumnId,
503
643
  currentValidationState,
504
644
  );
505
645
  const chartConfigEditorDefinition = this.getChartConfigEditorDefinition(
@@ -526,7 +666,7 @@ export class CustomChartContext {
526
666
  ...eventPayload: ChartToTSEventsPayloadMap[T]
527
667
  ): Promise<any> {
528
668
  if (!globalThis.isInitialized) {
529
- console.log(
669
+ logger.log(
530
670
  'Chart Context: not initialized the context, something went wrong',
531
671
  );
532
672
  return Promise.reject(new Error('Context not initialized'));
@@ -549,7 +689,7 @@ export class CustomChartContext {
549
689
  */
550
690
  private registerEventProcessor = () => {
551
691
  if (globalThis.isInitialized) {
552
- console.error(
692
+ logger.error(
553
693
  'The context is already initialized. you cannot have multiple contexts',
554
694
  );
555
695
  throw new Error(ErrorType.MultipleContextsNotSupported);
@@ -565,12 +705,12 @@ export class CustomChartContext {
565
705
  * @param event : Message Event Object
566
706
  */
567
707
  private eventProcessor = (data: any) => {
568
- console.log('Chart Context: message received:', data.eventType, data);
708
+ logger.log('Chart Context: message received:', data.eventType, data);
569
709
 
570
710
  const messageResponse = this.executeEventListenerCBs(data);
571
711
 
572
712
  // respond back to parent to confirm/ack the receipt
573
- return messageResponse;
713
+ return messageResponse || {};
574
714
  };
575
715
 
576
716
  /**
@@ -615,15 +755,17 @@ export class CustomChartContext {
615
755
  this.chartContextProps.validateVisualProps(
616
756
  payload.visualProps,
617
757
  this.chartModel,
758
+ payload?.activeColumnId,
618
759
  );
619
760
  if (validationResponse.isValid) {
620
761
  const currentVisualState = {
621
762
  visualProps: payload.visualProps,
622
763
  };
623
-
764
+ const activeColumnId = payload?.activeColumnId;
624
765
  return this.validationsResponseProcessor(
625
766
  currentVisualState,
626
767
  validationResponse,
768
+ activeColumnId,
627
769
  );
628
770
  }
629
771
  return validationResponse;
@@ -688,6 +830,28 @@ export class CustomChartContext {
688
830
  },
689
831
  );
690
832
 
833
+ /**
834
+ * This event is triggered when the TS app asks for data in a specific columns
835
+ * for certain validations for settings and drop down options related to Conditional
836
+ * formatting and other advanced settings
837
+ */
838
+ this.onInternal(
839
+ TSToChartEvent.GetColumnData,
840
+ (payload: GetColumnDataPayload): GetColumnDataResponsePayload => {
841
+ const parsedData = this.chartModel.data?.[0].data;
842
+ const dataIdx = parsedData?.columns.findIndex(
843
+ (columnId) => columnId === payload.columnId,
844
+ );
845
+ const dataArray =
846
+ !_.isNil(dataIdx) && dataIdx > -1
847
+ ? parsedData?.dataValue.map((it) => it[dataIdx])
848
+ : [];
849
+ return {
850
+ data: dataArray,
851
+ };
852
+ },
853
+ );
854
+
691
855
  /**
692
856
  * This event is triggered when the TS app re-renders the chart
693
857
  */
@@ -726,7 +890,7 @@ export class CustomChartContext {
726
890
  isValid: true,
727
891
  };
728
892
  } catch (error: unknown) {
729
- console.log(
893
+ logger.log(
730
894
  'ContextMenuCustomAction: payload recieved:',
731
895
  payload,
732
896
  'CustomActionCallbackStore:',
@@ -769,7 +933,7 @@ export class CustomChartContext {
769
933
  isValid: true,
770
934
  };
771
935
  } catch (error: unknown) {
772
- console.log(
936
+ logger.log(
773
937
  'AxisMenuCustomAction: payload recieved:',
774
938
  payload,
775
939
  'CustomActionCallbackStore:',
@@ -857,7 +1021,9 @@ export class CustomChartContext {
857
1021
  this.containerEl = payload.containerElSelector
858
1022
  ? document.querySelector(payload.containerElSelector)
859
1023
  : null;
860
-
1024
+ setLocaleBasedStringFormats(
1025
+ this.appConfig.dateFormatsConfig?.tsLocaleBasedStringsFormats,
1026
+ );
861
1027
  return this.publishChartContextPropsToHost();
862
1028
  };
863
1029
 
@@ -899,6 +1065,8 @@ export class CustomChartContext {
899
1065
  this.getVisualPropEditorDefinition(),
900
1066
  allowedConfigurations:
901
1067
  this.chartContextProps.allowedConfigurations,
1068
+ chartConfigParameters:
1069
+ this.chartContextProps.chartConfigParameters,
902
1070
  };
903
1071
  };
904
1072
 
@@ -926,7 +1094,7 @@ export class CustomChartContext {
926
1094
  };
927
1095
  }
928
1096
 
929
- console.log(
1097
+ logger.log(
930
1098
  'ChartContext: Response:',
931
1099
  data.eventType,
932
1100
  response,
@@ -949,6 +1117,5 @@ export const getChartContext = async (
949
1117
  // wait for initialization here as the host app
950
1118
  // needs to first handshake with the client app.
951
1119
  await ctx.initialize();
952
-
953
1120
  return ctx;
954
1121
  };
@@ -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
+ });
package/src/main/util.ts CHANGED
@@ -9,3 +9,23 @@ export function timeout(promise: Promise<any>, ms: number, message?: string) {
9
9
  }),
10
10
  ]);
11
11
  }
12
+
13
+ export function handleMissingValue(
14
+ paramKey: string,
15
+ paramValue?: string | null,
16
+ ): string {
17
+ if (paramKey === 'debug') {
18
+ return paramValue ?? 'false';
19
+ }
20
+
21
+ return paramValue ?? '';
22
+ }
23
+
24
+ export function getQueryParam(url: string, paramName: string): string {
25
+ const urlObj = new URL(url);
26
+ const paramValue = handleMissingValue(
27
+ paramName,
28
+ urlObj.searchParams.get(paramName),
29
+ );
30
+ return paramValue;
31
+ }
@@ -236,7 +236,6 @@ describe('useChartContext emit', () => {
236
236
  });
237
237
  const promise = result.current.emitRenderStart();
238
238
  await expect(promise).rejects.toThrow('Context not initialized');
239
- expect(mockPostMessageToHost).not.toHaveBeenCalled();
240
239
  });
241
240
  });
242
241