@thoughtspot/ts-chart-sdk 0.0.2-alpha.9 → 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 (141) hide show
  1. package/README.md +126 -50
  2. package/dist/ts-chart-sdk.d.ts +327 -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 +15 -4
  8. package/lib/main/custom-chart-context.d.ts.map +1 -1
  9. package/lib/main/custom-chart-context.js +44 -16
  10. package/lib/main/custom-chart-context.js.map +1 -1
  11. package/lib/main/custom-chart-context.spec.js +301 -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 +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/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 +355 -6
  114. package/src/main/custom-chart-context.ts +181 -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 +54 -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,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;
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
+ /**
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;
63
169
  };
64
170
 
65
171
  export type CustomChartContextProps = {
@@ -116,6 +222,7 @@ export type CustomChartContextProps = {
116
222
  validateVisualProps?: (
117
223
  updatedVisualProps: VisualProps,
118
224
  chartModel: ChartModel,
225
+ activeColumnId?: string,
119
226
  ) => ValidationResponse;
120
227
 
121
228
  /**
@@ -141,8 +248,22 @@ export type CustomChartContextProps = {
141
248
  | VisualEditorDefinitionSetter
142
249
  | VisualPropEditorDefinition;
143
250
 
144
- // 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
+ */
145
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;
146
267
  };
147
268
 
148
269
  export type ValidationFunctions =
@@ -159,8 +280,18 @@ const DEFAULT_CHART_CONTEXT_PROPS: Partial<CustomChartContextProps> = {
159
280
  allowedConfigurations: {
160
281
  allowColumnNumberFormatting: false,
161
282
  allowColumnConditionalFormatting: false,
283
+ allowGradientColoring: false,
162
284
  allowMeasureNamesAndValues: false,
163
285
  },
286
+ chartConfigParameters: {
287
+ measureNameValueColumns: {
288
+ enableMeasureNameColumn: false,
289
+ enableMeasureValueColumn: false,
290
+ measureNameColumnAlias: 'Measure Name',
291
+ measureValueColumnAlias: 'Measure Values',
292
+ },
293
+ batchSizeLimit: 20000,
294
+ },
164
295
  };
165
296
 
166
297
  export class CustomChartContext {
@@ -269,6 +400,9 @@ export class CustomChartContext {
269
400
  this.hasInitializedPromise = new Promise((resolve, reject) => {
270
401
  this.triggerInitResolve = resolve;
271
402
  });
403
+ // Not using this.emitEvent as the context is not yet completely
404
+ // initialized, thus short circuiting.
405
+ postMessageToHostApp('', '*', null, ChartToTSEvent.InitStart);
272
406
  }
273
407
 
274
408
  /**
@@ -279,7 +413,7 @@ export class CustomChartContext {
279
413
  * @version SDK: 0.1 | ThoughtSpot:
280
414
  */
281
415
  public initialize = (): Promise<void> => {
282
- console.log('Chart Context: initialization start');
416
+ logger.log('Chart Context: initialization start');
283
417
  return this.hasInitializedPromise;
284
418
  };
285
419
 
@@ -311,7 +445,7 @@ export class CustomChartContext {
311
445
  */
312
446
  public off<T extends keyof TSToChartEventsPayloadMap>(eventType: T): void {
313
447
  if (_.isNil(this.eventListeners[eventType])) {
314
- console.log('No event listener found to remove');
448
+ logger.log('No event listener found to remove');
315
449
  this.eventListeners[eventType] = [];
316
450
  return;
317
451
  }
@@ -424,6 +558,7 @@ export class CustomChartContext {
424
558
  * @returns {VisualPropEditorDefinition}
425
559
  */
426
560
  private getVisualPropEditorDefinition = (
561
+ activeColumnId?: string,
427
562
  currentState: Partial<ChartModel> = {},
428
563
  ) => {
429
564
  if (_.isFunction(this.chartContextProps.visualPropEditorDefinition)) {
@@ -433,6 +568,7 @@ export class CustomChartContext {
433
568
  ...currentState,
434
569
  },
435
570
  this,
571
+ activeColumnId,
436
572
  );
437
573
  }
438
574
  return this.chartContextProps.visualPropEditorDefinition;
@@ -500,8 +636,10 @@ export class CustomChartContext {
500
636
  private validationsResponseProcessor(
501
637
  currentValidationState: Partial<ChartModel>,
502
638
  validationResponse: ValidationResponse,
639
+ activeColumnId?: string,
503
640
  ) {
504
641
  const visualPropEditorDefinition = this.getVisualPropEditorDefinition(
642
+ activeColumnId,
505
643
  currentValidationState,
506
644
  );
507
645
  const chartConfigEditorDefinition = this.getChartConfigEditorDefinition(
@@ -528,7 +666,7 @@ export class CustomChartContext {
528
666
  ...eventPayload: ChartToTSEventsPayloadMap[T]
529
667
  ): Promise<any> {
530
668
  if (!globalThis.isInitialized) {
531
- console.log(
669
+ logger.log(
532
670
  'Chart Context: not initialized the context, something went wrong',
533
671
  );
534
672
  return Promise.reject(new Error('Context not initialized'));
@@ -551,7 +689,7 @@ export class CustomChartContext {
551
689
  */
552
690
  private registerEventProcessor = () => {
553
691
  if (globalThis.isInitialized) {
554
- console.error(
692
+ logger.error(
555
693
  'The context is already initialized. you cannot have multiple contexts',
556
694
  );
557
695
  throw new Error(ErrorType.MultipleContextsNotSupported);
@@ -567,12 +705,12 @@ export class CustomChartContext {
567
705
  * @param event : Message Event Object
568
706
  */
569
707
  private eventProcessor = (data: any) => {
570
- console.log('Chart Context: message received:', data.eventType, data);
708
+ logger.log('Chart Context: message received:', data.eventType, data);
571
709
 
572
710
  const messageResponse = this.executeEventListenerCBs(data);
573
711
 
574
712
  // respond back to parent to confirm/ack the receipt
575
- return messageResponse;
713
+ return messageResponse || {};
576
714
  };
577
715
 
578
716
  /**
@@ -617,15 +755,17 @@ export class CustomChartContext {
617
755
  this.chartContextProps.validateVisualProps(
618
756
  payload.visualProps,
619
757
  this.chartModel,
758
+ payload?.activeColumnId,
620
759
  );
621
760
  if (validationResponse.isValid) {
622
761
  const currentVisualState = {
623
762
  visualProps: payload.visualProps,
624
763
  };
625
-
764
+ const activeColumnId = payload?.activeColumnId;
626
765
  return this.validationsResponseProcessor(
627
766
  currentVisualState,
628
767
  validationResponse,
768
+ activeColumnId,
629
769
  );
630
770
  }
631
771
  return validationResponse;
@@ -690,6 +830,28 @@ export class CustomChartContext {
690
830
  },
691
831
  );
692
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
+
693
855
  /**
694
856
  * This event is triggered when the TS app re-renders the chart
695
857
  */
@@ -728,7 +890,7 @@ export class CustomChartContext {
728
890
  isValid: true,
729
891
  };
730
892
  } catch (error: unknown) {
731
- console.log(
893
+ logger.log(
732
894
  'ContextMenuCustomAction: payload recieved:',
733
895
  payload,
734
896
  'CustomActionCallbackStore:',
@@ -771,7 +933,7 @@ export class CustomChartContext {
771
933
  isValid: true,
772
934
  };
773
935
  } catch (error: unknown) {
774
- console.log(
936
+ logger.log(
775
937
  'AxisMenuCustomAction: payload recieved:',
776
938
  payload,
777
939
  'CustomActionCallbackStore:',
@@ -859,7 +1021,9 @@ export class CustomChartContext {
859
1021
  this.containerEl = payload.containerElSelector
860
1022
  ? document.querySelector(payload.containerElSelector)
861
1023
  : null;
862
-
1024
+ setLocaleBasedStringFormats(
1025
+ this.appConfig.dateFormatsConfig?.tsLocaleBasedStringsFormats,
1026
+ );
863
1027
  return this.publishChartContextPropsToHost();
864
1028
  };
865
1029
 
@@ -901,6 +1065,8 @@ export class CustomChartContext {
901
1065
  this.getVisualPropEditorDefinition(),
902
1066
  allowedConfigurations:
903
1067
  this.chartContextProps.allowedConfigurations,
1068
+ chartConfigParameters:
1069
+ this.chartContextProps.chartConfigParameters,
904
1070
  };
905
1071
  };
906
1072
 
@@ -928,7 +1094,7 @@ export class CustomChartContext {
928
1094
  };
929
1095
  }
930
1096
 
931
- console.log(
1097
+ logger.log(
932
1098
  'ChartContext: Response:',
933
1099
  data.eventType,
934
1100
  response,
@@ -951,6 +1117,5 @@ export const getChartContext = async (
951
1117
  // wait for initialization here as the host app
952
1118
  // needs to first handshake with the client app.
953
1119
  await ctx.initialize();
954
-
955
1120
  return ctx;
956
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
+ }