@servicetitan/titan-chatbot-api 3.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 (160) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/api-client/__mocks__/chatbot-api-client.mock.d.ts +11 -0
  3. package/dist/api-client/__mocks__/chatbot-api-client.mock.d.ts.map +1 -0
  4. package/dist/api-client/__mocks__/chatbot-api-client.mock.js +47 -0
  5. package/dist/api-client/__mocks__/chatbot-api-client.mock.js.map +1 -0
  6. package/dist/api-client/base/chatbot-api-client.d.ts +27 -0
  7. package/dist/api-client/base/chatbot-api-client.d.ts.map +1 -0
  8. package/dist/api-client/base/chatbot-api-client.js +10 -0
  9. package/dist/api-client/base/chatbot-api-client.js.map +1 -0
  10. package/dist/api-client/help-center/__tests__/converter-from-models.test.d.ts +2 -0
  11. package/dist/api-client/help-center/__tests__/converter-from-models.test.d.ts.map +1 -0
  12. package/dist/api-client/help-center/__tests__/converter-from-models.test.js +34 -0
  13. package/dist/api-client/help-center/__tests__/converter-from-models.test.js.map +1 -0
  14. package/dist/api-client/help-center/__tests__/converter-to-models.test.d.ts +2 -0
  15. package/dist/api-client/help-center/__tests__/converter-to-models.test.d.ts.map +1 -0
  16. package/dist/api-client/help-center/__tests__/converter-to-models.test.js +82 -0
  17. package/dist/api-client/help-center/__tests__/converter-to-models.test.js.map +1 -0
  18. package/dist/api-client/help-center/chatbot-api-client.d.ts +31 -0
  19. package/dist/api-client/help-center/chatbot-api-client.d.ts.map +1 -0
  20. package/dist/api-client/help-center/chatbot-api-client.js +90 -0
  21. package/dist/api-client/help-center/chatbot-api-client.js.map +1 -0
  22. package/dist/api-client/help-center/converter-from-models.d.ts +13 -0
  23. package/dist/api-client/help-center/converter-from-models.d.ts.map +1 -0
  24. package/dist/api-client/help-center/converter-from-models.js +113 -0
  25. package/dist/api-client/help-center/converter-from-models.js.map +1 -0
  26. package/dist/api-client/help-center/converter-to-models.d.ts +13 -0
  27. package/dist/api-client/help-center/converter-to-models.d.ts.map +1 -0
  28. package/dist/api-client/help-center/converter-to-models.js +95 -0
  29. package/dist/api-client/help-center/converter-to-models.js.map +1 -0
  30. package/dist/api-client/help-center/index.d.ts +2 -0
  31. package/dist/api-client/help-center/index.d.ts.map +1 -0
  32. package/dist/api-client/help-center/index.js +2 -0
  33. package/dist/api-client/help-center/index.js.map +1 -0
  34. package/dist/api-client/help-center/native-client.d.ts +1260 -0
  35. package/dist/api-client/help-center/native-client.d.ts.map +1 -0
  36. package/dist/api-client/help-center/native-client.js +6169 -0
  37. package/dist/api-client/help-center/native-client.js.map +1 -0
  38. package/dist/api-client/index.d.ts +8 -0
  39. package/dist/api-client/index.d.ts.map +1 -0
  40. package/dist/api-client/index.js +8 -0
  41. package/dist/api-client/index.js.map +1 -0
  42. package/dist/api-client/models/__mocks__/models.mock.d.ts +13 -0
  43. package/dist/api-client/models/__mocks__/models.mock.d.ts.map +1 -0
  44. package/dist/api-client/models/__mocks__/models.mock.js +114 -0
  45. package/dist/api-client/models/__mocks__/models.mock.js.map +1 -0
  46. package/dist/api-client/models/index.d.ts +22 -0
  47. package/dist/api-client/models/index.d.ts.map +1 -0
  48. package/dist/api-client/models/index.js +15 -0
  49. package/dist/api-client/models/index.js.map +1 -0
  50. package/dist/api-client/titan-chat/chatbot-api-client.d.ts +34 -0
  51. package/dist/api-client/titan-chat/chatbot-api-client.d.ts.map +1 -0
  52. package/dist/api-client/titan-chat/chatbot-api-client.js +72 -0
  53. package/dist/api-client/titan-chat/chatbot-api-client.js.map +1 -0
  54. package/dist/api-client/titan-chat/index.d.ts +2 -0
  55. package/dist/api-client/titan-chat/index.d.ts.map +1 -0
  56. package/dist/api-client/titan-chat/index.js +2 -0
  57. package/dist/api-client/titan-chat/index.js.map +1 -0
  58. package/dist/api-client/titan-chat/native-client.d.ts +225 -0
  59. package/dist/api-client/titan-chat/native-client.d.ts.map +1 -0
  60. package/dist/api-client/titan-chat/native-client.js +931 -0
  61. package/dist/api-client/titan-chat/native-client.js.map +1 -0
  62. package/dist/api-client/utils/model-utils.d.ts +4 -0
  63. package/dist/api-client/utils/model-utils.d.ts.map +1 -0
  64. package/dist/api-client/utils/model-utils.js +58 -0
  65. package/dist/api-client/utils/model-utils.js.map +1 -0
  66. package/dist/index.d.ts +6 -0
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +7 -0
  69. package/dist/index.js.map +1 -0
  70. package/dist/models/chatbot-customizations.d.ts +15 -0
  71. package/dist/models/chatbot-customizations.d.ts.map +1 -0
  72. package/dist/models/chatbot-customizations.js +2 -0
  73. package/dist/models/chatbot-customizations.js.map +1 -0
  74. package/dist/models/index.d.ts +2 -0
  75. package/dist/models/index.d.ts.map +1 -0
  76. package/dist/models/index.js +2 -0
  77. package/dist/models/index.js.map +1 -0
  78. package/dist/stores/__tests__/chatbot-ui-backend.store.test.d.ts +2 -0
  79. package/dist/stores/__tests__/chatbot-ui-backend.store.test.d.ts.map +1 -0
  80. package/dist/stores/__tests__/chatbot-ui-backend.store.test.js +341 -0
  81. package/dist/stores/__tests__/chatbot-ui-backend.store.test.js.map +1 -0
  82. package/dist/stores/__tests__/chatbot-ui.store.test.d.ts +2 -0
  83. package/dist/stores/__tests__/chatbot-ui.store.test.d.ts.map +1 -0
  84. package/dist/stores/__tests__/chatbot-ui.store.test.js +166 -0
  85. package/dist/stores/__tests__/chatbot-ui.store.test.js.map +1 -0
  86. package/dist/stores/__tests__/filter.store.test.d.ts +2 -0
  87. package/dist/stores/__tests__/filter.store.test.d.ts.map +1 -0
  88. package/dist/stores/__tests__/filter.store.test.js +316 -0
  89. package/dist/stores/__tests__/filter.store.test.js.map +1 -0
  90. package/dist/stores/__tests__/initialize.store.test.d.ts +2 -0
  91. package/dist/stores/__tests__/initialize.store.test.d.ts.map +1 -0
  92. package/dist/stores/__tests__/initialize.store.test.js +54 -0
  93. package/dist/stores/__tests__/initialize.store.test.js.map +1 -0
  94. package/dist/stores/chatbot-ui-backend.store.d.ts +61 -0
  95. package/dist/stores/chatbot-ui-backend.store.d.ts.map +1 -0
  96. package/dist/stores/chatbot-ui-backend.store.js +396 -0
  97. package/dist/stores/chatbot-ui-backend.store.js.map +1 -0
  98. package/dist/stores/chatbot-ui.store.d.ts +25 -0
  99. package/dist/stores/chatbot-ui.store.d.ts.map +1 -0
  100. package/dist/stores/chatbot-ui.store.js +87 -0
  101. package/dist/stores/chatbot-ui.store.js.map +1 -0
  102. package/dist/stores/filter.store.d.ts +30 -0
  103. package/dist/stores/filter.store.d.ts.map +1 -0
  104. package/dist/stores/filter.store.js +334 -0
  105. package/dist/stores/filter.store.js.map +1 -0
  106. package/dist/stores/index.d.ts +4 -0
  107. package/dist/stores/index.d.ts.map +1 -0
  108. package/dist/stores/index.js +4 -0
  109. package/dist/stores/index.js.map +1 -0
  110. package/dist/stores/initialize.store.d.ts +17 -0
  111. package/dist/stores/initialize.store.d.ts.map +1 -0
  112. package/dist/stores/initialize.store.js +98 -0
  113. package/dist/stores/initialize.store.js.map +1 -0
  114. package/dist/utils/__tests__/axios-utils.test.d.ts +2 -0
  115. package/dist/utils/__tests__/axios-utils.test.d.ts.map +1 -0
  116. package/dist/utils/__tests__/axios-utils.test.js +33 -0
  117. package/dist/utils/__tests__/axios-utils.test.js.map +1 -0
  118. package/dist/utils/axios-utils.d.ts +5 -0
  119. package/dist/utils/axios-utils.d.ts.map +1 -0
  120. package/dist/utils/axios-utils.js +23 -0
  121. package/dist/utils/axios-utils.js.map +1 -0
  122. package/dist/utils/test-utils.d.ts +5 -0
  123. package/dist/utils/test-utils.d.ts.map +1 -0
  124. package/dist/utils/test-utils.js +17 -0
  125. package/dist/utils/test-utils.js.map +1 -0
  126. package/package.json +45 -0
  127. package/src/api-client/__mocks__/chatbot-api-client.mock.ts +11 -0
  128. package/src/api-client/base/chatbot-api-client.ts +33 -0
  129. package/src/api-client/help-center/__tests__/converter-from-models.test.ts +41 -0
  130. package/src/api-client/help-center/__tests__/converter-to-models.test.ts +89 -0
  131. package/src/api-client/help-center/chatbot-api-client.ts +107 -0
  132. package/src/api-client/help-center/converter-from-models.ts +132 -0
  133. package/src/api-client/help-center/converter-to-models.ts +124 -0
  134. package/src/api-client/help-center/index.ts +1 -0
  135. package/src/api-client/help-center/native-client.ts +5662 -0
  136. package/src/api-client/index.ts +12 -0
  137. package/src/api-client/models/__mocks__/models.mock.ts +141 -0
  138. package/src/api-client/models/index.ts +48 -0
  139. package/src/api-client/titan-chat/chatbot-api-client.ts +77 -0
  140. package/src/api-client/titan-chat/index.ts +1 -0
  141. package/src/api-client/titan-chat/native-client.ts +826 -0
  142. package/src/api-client/utils/model-utils.ts +68 -0
  143. package/src/cypress.d.ts +10 -0
  144. package/src/index.ts +6 -0
  145. package/src/models/chatbot-customizations.ts +16 -0
  146. package/src/models/index.ts +1 -0
  147. package/src/stores/__tests__/chatbot-ui-backend.store.test.ts +426 -0
  148. package/src/stores/__tests__/chatbot-ui.store.test.ts +196 -0
  149. package/src/stores/__tests__/filter.store.test.ts +363 -0
  150. package/src/stores/__tests__/initialize.store.test.ts +73 -0
  151. package/src/stores/chatbot-ui-backend.store.ts +401 -0
  152. package/src/stores/chatbot-ui.store.ts +82 -0
  153. package/src/stores/filter.store.ts +250 -0
  154. package/src/stores/index.ts +12 -0
  155. package/src/stores/initialize.store.ts +62 -0
  156. package/src/utils/__tests__/axios-utils.test.ts +40 -0
  157. package/src/utils/axios-utils.ts +25 -0
  158. package/src/utils/test-utils.ts +22 -0
  159. package/tsconfig.json +19 -0
  160. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,250 @@
1
+ import cloneDeep from 'lodash/cloneDeep';
2
+ import { action, makeObservable, observable } from 'mobx';
3
+ import { nanoid } from 'nanoid';
4
+ import { Models, ModelsUtils } from '../api-client';
5
+
6
+ interface IOption extends Models.IOption {
7
+ uid?: string;
8
+ parentKeys?: string[];
9
+ }
10
+
11
+ export class FilterStore {
12
+ @observable selectedOptions: Record<string, string[]> = {};
13
+ @observable filters: IOption[] = [];
14
+
15
+ private model?: Models.IFrontendModel;
16
+ private originalFilters: IOption[] = [];
17
+
18
+ constructor() {
19
+ makeObservable(this);
20
+ }
21
+
22
+ @action
23
+ initFilters(model: Models.IFrontendModel) {
24
+ const setUids = (o?: IOption[]) => {
25
+ o?.forEach(x => {
26
+ x.uid = nanoid();
27
+ setUids(x.subOptions as IOption[]);
28
+ });
29
+ };
30
+
31
+ this.model = model;
32
+ this.originalFilters = cloneDeep(
33
+ model!.options.subOptions?.filter(x => x.type === Models.OptionType.Group) ?? []
34
+ );
35
+ setUids(this.originalFilters);
36
+ this.filters = this.originalFilters;
37
+ }
38
+
39
+ @action
40
+ selectOption = (filterKey: string, optionKey: string) => {
41
+ const allFilterOptions = this.getExistingFilterOptions(filterKey);
42
+ const option = allFilterOptions.find(o => o.key === optionKey);
43
+ if (!option) {
44
+ throw new Error(`Option "${optionKey}" does not exist in the filter "${filterKey}".`);
45
+ }
46
+ if (!this.selectedOptions[filterKey]) {
47
+ this.selectedOptions[filterKey] = [];
48
+ }
49
+ this.selectedOptions[filterKey] = [...this.selectedOptions[filterKey], option.key];
50
+ this.addSubSequentFilters(filterKey);
51
+ };
52
+
53
+ @action
54
+ selectAll = (filterKey: string) => {
55
+ this.selectedOptions[filterKey] = this.getExistingFilterOptions(filterKey).map(o => o.key);
56
+ this.addSubSequentFilters(filterKey);
57
+ };
58
+
59
+ @action
60
+ deselectOption = (filterKey: string, optionKey: string) => {
61
+ this.selectedOptions[filterKey] = this.selectedOptions[filterKey].filter(
62
+ o => o !== optionKey
63
+ );
64
+ this.cleanUpFilterOptions(filterKey, optionKey);
65
+ this.removeSubSequentFilters(filterKey);
66
+ this.addSubSequentFilters(filterKey);
67
+ };
68
+
69
+ @action
70
+ deselectAll = (filterKey: string) => {
71
+ for (const selectedKey of this.selectedOptions[filterKey]) {
72
+ this.deselectOption(filterKey, selectedKey);
73
+ }
74
+ };
75
+
76
+ getFilterLabel = (key: string) => {
77
+ // transform camel case key to human-readable label
78
+ return key.replace(/([A-Z])/g, ' $1').replace(/^./, function (str) {
79
+ return str.toUpperCase();
80
+ });
81
+ };
82
+
83
+ export(): Models.Selections | undefined {
84
+ return ModelsUtils.createSelectionsModel(this.originalFilters, this.selectedOptions);
85
+ }
86
+
87
+ @action
88
+ private removeSubSequentFilters = (filterKey: string) => {
89
+ const filter = this.getExistingFilterByKey(filterKey);
90
+ const subFilters = this.getSubFilters(filter);
91
+ this.filters = this.filters.filter(x => !subFilters.some(f => f.key === x.key));
92
+ };
93
+
94
+ @action
95
+ private addSubSequentFilters = (filterKey: string) => {
96
+ const filter = this.getExistingFilterByKey(filterKey);
97
+ if (!filter.subOptions) {
98
+ return;
99
+ }
100
+
101
+ // Get selected items for the filter
102
+ const filterSelectedKeys = this.selectedOptions[filterKey] ?? [];
103
+ const filterSelectedOptions: IOption[] = filter.subOptions.filter(
104
+ x => x.type === Models.OptionType.Selectable && filterSelectedKeys.includes(x.key!)
105
+ );
106
+
107
+ // Find all sub-filters for the selected options
108
+ const allSubFilters: IOption[] =
109
+ filterSelectedOptions.flatMap<IOption>(selectedOption => {
110
+ // For each selected option, collect its sub-filters
111
+ const subFilters: IOption[] = [];
112
+ for (const subFilter of selectedOption.subOptions ?? []) {
113
+ const originalFilter = cloneDeep(
114
+ this.getOriginalFilter((subFilter as IOption).uid)
115
+ );
116
+ originalFilter.parentKeys = [selectedOption.uid!];
117
+ subFilters.push(originalFilter);
118
+ }
119
+ return subFilters;
120
+ }) ?? [];
121
+
122
+ // Merge sub-filters with the same key and their options (with parent keys merging)
123
+ const mergedSubFilters = allSubFilters.reduce<IOption[]>((acc, subFilter) => {
124
+ if (!subFilter.subOptions) {
125
+ return acc;
126
+ }
127
+ const subfilterOptions = (subFilter.subOptions ?? []) as IOption[];
128
+ const subfilterParentKeys = subFilter.parentKeys;
129
+
130
+ // Merge filters with the same key and their options if they already exist
131
+ subfilterOptions.forEach(subfilterOption => {
132
+ subfilterOption.parentKeys = this.mergeParentKeys(
133
+ subfilterOption.parentKeys,
134
+ subfilterParentKeys
135
+ );
136
+ });
137
+
138
+ // Check if the filter already exists in the accumulator
139
+ const existingFilter = acc.find(x => x.key === subFilter.key);
140
+ if (!existingFilter) {
141
+ return [...acc, subFilter];
142
+ }
143
+
144
+ // Merge options for subfilters by key
145
+ existingFilter.subOptions = subfilterOptions.reduce(
146
+ (acc2, subfilterOption) => {
147
+ const existing = acc2.find(o => o.key === subfilterOption.key) as
148
+ | IOption
149
+ | undefined;
150
+ if (!existing) {
151
+ return [...acc2, subfilterOption];
152
+ }
153
+ existing.parentKeys = this.mergeParentKeys(
154
+ existing.parentKeys,
155
+ subfilterOption.parentKeys
156
+ );
157
+
158
+ return acc2;
159
+ },
160
+ [...(existingFilter.subOptions ?? [])]
161
+ ) as Models.Option[];
162
+ return acc;
163
+ }, []);
164
+
165
+ // Update the filters with the merged sub-filters
166
+ for (const newFilter of mergedSubFilters) {
167
+ const existingFilterIdx = this.filters.findIndex(f => f.key === newFilter.key);
168
+ if (existingFilterIdx < 0) {
169
+ this.filters.push(newFilter);
170
+ } else {
171
+ this.filters[existingFilterIdx] = newFilter;
172
+ }
173
+ this.addSubSequentFilters(newFilter.key);
174
+ }
175
+ };
176
+
177
+ private mergeParentKeys = (keys1?: string[], keys2?: string[]): string[] => {
178
+ if (!keys1 && !keys2) {
179
+ return [];
180
+ }
181
+ if (!keys1) {
182
+ return keys2 ?? [];
183
+ }
184
+ if (!keys2) {
185
+ return keys1;
186
+ }
187
+ const mergedKeys = new Set<string>([...keys1, ...keys2]);
188
+ return Array.from(mergedKeys);
189
+ };
190
+
191
+ private cleanUpFilterOptions = (filterKey: string, parentKeyToRemove: string) => {
192
+ const filter = this.getExistingFilterByKey(filterKey);
193
+ const filterSubOptionToRemove = filter.subOptions?.find(x => x.key === parentKeyToRemove);
194
+ const filterSubOptionToRemoveUid = (filterSubOptionToRemove as IOption)?.uid;
195
+ const filterIdx = this.filters.findIndex(f => f.key === filterKey);
196
+ for (let i = filterIdx + 1; i < this.filters.length; i++) {
197
+ const subFilter = this.filters[i];
198
+ subFilter.subOptions?.forEach((o: IOption) => {
199
+ o.parentKeys = (o.parentKeys ?? []).filter(p => p !== filterSubOptionToRemoveUid);
200
+ // Uncheck sub-filter options if they no longer have parent keys
201
+ if (!o.parentKeys?.length) {
202
+ this.selectedOptions[subFilter.key] = this.selectedOptions[
203
+ subFilter.key
204
+ ]?.filter(x => x !== o.key);
205
+ }
206
+ });
207
+ subFilter.subOptions = subFilter.subOptions?.filter((o: IOption) =>
208
+ Boolean(o.parentKeys?.length)
209
+ );
210
+ }
211
+ };
212
+
213
+ private getSubFilters = (filter: IOption): IOption[] => {
214
+ const result: IOption[] = [];
215
+ filter.subOptions?.forEach(filterSelectable => {
216
+ filterSelectable.subOptions?.forEach(subFilter => {
217
+ result.push(subFilter, ...this.getSubFilters(subFilter));
218
+ });
219
+ });
220
+ return result;
221
+ };
222
+
223
+ private getOriginalFilters = (): IOption[] => {
224
+ return this.originalFilters.reduce<IOption[]>((acc, next) => {
225
+ return [...acc, next, ...this.getSubFilters(next)];
226
+ }, []);
227
+ };
228
+
229
+ private getOriginalFilter = (uid?: string): IOption => {
230
+ const originalFilters = this.getOriginalFilters();
231
+ const filter = originalFilters.find(f => f.uid === uid);
232
+ if (!filter) {
233
+ throw new Error(`Filter with key "${uid}" does not exist.`);
234
+ }
235
+ return filter;
236
+ };
237
+
238
+ private getExistingFilterOptions = (filterKey: string): IOption[] => {
239
+ const filter = this.getExistingFilterByKey(filterKey);
240
+ return filter.subOptions?.filter(x => x.type === Models.OptionType.Selectable) ?? [];
241
+ };
242
+
243
+ private getExistingFilterByKey = (filterKey: string): IOption => {
244
+ const filter = this.filters.find(f => f.key === filterKey);
245
+ if (!filter) {
246
+ throw new Error(`Filter with key "${filterKey}" does not exist.`);
247
+ }
248
+ return filter;
249
+ };
250
+ }
@@ -0,0 +1,12 @@
1
+ export {
2
+ CHATBOT_UI_BACKEND_STORE_TOKEN,
3
+ IChatbotUiBackendStore,
4
+ ChatbotUiBackendStore,
5
+ } from './chatbot-ui-backend.store';
6
+ export {
7
+ CHATBOT_UI_STORE_TOKEN,
8
+ IChatbotUiStore,
9
+ ChatbotUiStore,
10
+ ChatbotUiEvent,
11
+ } from './chatbot-ui.store';
12
+ export { InitializeStore, InitializeStoreStatus } from './initialize.store';
@@ -0,0 +1,62 @@
1
+ import { injectable } from '@servicetitan/react-ioc';
2
+ import { action, computed, makeObservable, observable } from 'mobx';
3
+
4
+ export enum InitializeStoreStatus {
5
+ None = 'None',
6
+ Pending = 'Pending',
7
+ Success = 'Success',
8
+ Error = 'Error',
9
+ }
10
+
11
+ @injectable()
12
+ export class InitializeStore {
13
+ @observable
14
+ initializeStatus = InitializeStoreStatus.None;
15
+
16
+ @observable.ref
17
+ private initializePromiseInner?: Promise<void>;
18
+
19
+ @computed
20
+ get isInitialized() {
21
+ return this.initializeStatus === InitializeStoreStatus.Success;
22
+ }
23
+
24
+ constructor() {
25
+ makeObservable(this);
26
+ }
27
+
28
+ async initialize(force = false): Promise<void> {
29
+ if (force) {
30
+ this.resetInitialization();
31
+ }
32
+ if (this.initializePromiseInner) {
33
+ return this.initializePromiseInner;
34
+ }
35
+
36
+ try {
37
+ this.setInitializeStatus(InitializeStoreStatus.Pending);
38
+ this.initializePromiseInner = this.initializeInternal();
39
+ await this.initializePromiseInner;
40
+ this.setInitializeStatus(InitializeStoreStatus.Success);
41
+ } catch (e) {
42
+ this.setInitializeStatus(InitializeStoreStatus.Error);
43
+ throw e;
44
+ }
45
+ return this.initializePromiseInner;
46
+ }
47
+
48
+ protected initializeInternal(): Promise<void> {
49
+ return Promise.resolve();
50
+ }
51
+
52
+ @action
53
+ private setInitializeStatus(initializeStatus: InitializeStoreStatus) {
54
+ this.initializeStatus = initializeStatus;
55
+ }
56
+
57
+ @action
58
+ private resetInitialization() {
59
+ this.initializeStatus = InitializeStoreStatus.None;
60
+ this.initializePromiseInner = undefined;
61
+ }
62
+ }
@@ -0,0 +1,40 @@
1
+ import { expect } from '@jest/globals';
2
+ import { withTimeout } from '../axios-utils';
3
+
4
+ describe('axios-utils', () => {
5
+ beforeEach(() => {
6
+ jest.useFakeTimers();
7
+ jest.setSystemTime(new Date('2000-01-01T00:00:00.000Z'));
8
+ });
9
+
10
+ afterEach(() => {
11
+ jest.clearAllMocks();
12
+ jest.useRealTimers();
13
+ });
14
+
15
+ test('should withTimeout (timeout first)', async () => {
16
+ const abortController = new AbortController();
17
+ const spyAbort = jest.spyOn(abortController, 'abort');
18
+ const action = new Promise(resolve => setTimeout(() => resolve('done'), 100));
19
+ const timeoutMs = 50;
20
+
21
+ const p = expect(withTimeout(action, timeoutMs, abortController)).rejects.toThrow(
22
+ 'The request is timed out'
23
+ );
24
+ await jest.advanceTimersByTimeAsync(50);
25
+ await p;
26
+ expect(spyAbort).toHaveBeenCalledWith('The request is timed out');
27
+ });
28
+
29
+ test('should withTimeout (resolves first)', async () => {
30
+ const abortController = new AbortController();
31
+ const spyAbort = jest.spyOn(abortController, 'abort');
32
+ const action = new Promise(resolve => setTimeout(() => resolve('done'), 100));
33
+ const timeoutMs = 150;
34
+
35
+ const p = expect(withTimeout(action, timeoutMs, abortController)).resolves.toEqual('done');
36
+ await jest.advanceTimersByTimeAsync(100);
37
+ await p;
38
+ expect(spyAbort).not.toHaveBeenCalled();
39
+ });
40
+ });
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Aborts the action if it takes longer than the specified timeout.
3
+ */
4
+ export async function withTimeout<T>(
5
+ action: Promise<T>,
6
+ timeoutMs: number,
7
+ abortController: AbortController
8
+ ): Promise<T> {
9
+ let timeoutId: ReturnType<typeof setTimeout> | undefined = undefined;
10
+ const timeoutPromise = new Promise<T>((_, reject) => {
11
+ timeoutId = setTimeout(() => {
12
+ abortController.abort('The request is timed out');
13
+ reject(new Error('The request is timed out'));
14
+ }, timeoutMs);
15
+ });
16
+ const promise = Promise.race([action, timeoutPromise]);
17
+ try {
18
+ return await promise;
19
+ } finally {
20
+ if (timeoutId) {
21
+ clearTimeout(timeoutId);
22
+ timeoutId = undefined;
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,22 @@
1
+ import { Container } from '@servicetitan/react-ioc';
2
+
3
+ type Newable<T> = new (...args: never[]) => T;
4
+
5
+ export const initTestContainer = (
6
+ serviceIdentifier: Newable<unknown> | Newable<unknown>[],
7
+ initDependenciesFn: (container: Container) => void
8
+ ) => {
9
+ const rootContainer = new Container();
10
+ if (Array.isArray(serviceIdentifier)) {
11
+ serviceIdentifier.forEach(identifier => rootContainer.bind(identifier).toSelf());
12
+ } else {
13
+ rootContainer.bind(serviceIdentifier).toSelf();
14
+ }
15
+
16
+ return () => {
17
+ const container = new Container();
18
+ container.parent = rootContainer;
19
+ initDependenciesFn(container);
20
+ return container;
21
+ };
22
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "extends": "@servicetitan/startup/tsconfig/base",
3
+ "compilerOptions": {
4
+ "composite": true,
5
+ "outDir": "dist",
6
+ "rootDir": "src",
7
+ "jsx": "react-jsx",
8
+ "moduleResolution": "bundler"
9
+ },
10
+ "include": ["src/**/*"],
11
+ "exclude": [
12
+ "node_modules"
13
+ ],
14
+ "references": [
15
+ {
16
+ "path": "../titan-chat-ui-common"
17
+ }
18
+ ]
19
+ }