@servicetitan/titan-chatbot-api 4.3.3 → 4.4.1
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.
- package/CHANGELOG.md +24 -0
- package/dist/api-client/models/__mocks__/models.mock.d.ts +1 -0
- package/dist/api-client/models/__mocks__/models.mock.d.ts.map +1 -1
- package/dist/api-client/models/__mocks__/models.mock.js +31 -7
- package/dist/api-client/models/__mocks__/models.mock.js.map +1 -1
- package/dist/api-client/utils/__tests__/model-utils.test.d.ts +2 -0
- package/dist/api-client/utils/__tests__/model-utils.test.d.ts.map +1 -0
- package/dist/api-client/utils/__tests__/model-utils.test.js +327 -0
- package/dist/api-client/utils/__tests__/model-utils.test.js.map +1 -0
- package/dist/api-client/utils/model-utils.d.ts +1 -1
- package/dist/api-client/utils/model-utils.d.ts.map +1 -1
- package/dist/api-client/utils/model-utils.js +33 -29
- package/dist/api-client/utils/model-utils.js.map +1 -1
- package/dist/stores/__tests__/chatbot-ui.store.test.js +1 -1
- package/dist/stores/__tests__/filter.store.test.js +49 -40
- package/dist/stores/__tests__/filter.store.test.js.map +1 -1
- package/dist/stores/chatbot-ui.store.d.ts +3 -3
- package/dist/stores/chatbot-ui.store.d.ts.map +1 -1
- package/dist/stores/chatbot-ui.store.js.map +1 -1
- package/dist/stores/filter.store.d.ts +50 -17
- package/dist/stores/filter.store.d.ts.map +1 -1
- package/dist/stores/filter.store.js +255 -179
- package/dist/stores/filter.store.js.map +1 -1
- package/dist/stores/index.d.ts +1 -0
- package/dist/stores/index.d.ts.map +1 -1
- package/dist/stores/index.js.map +1 -1
- package/package.json +3 -3
- package/src/api-client/models/__mocks__/models.mock.ts +33 -6
- package/src/api-client/utils/__tests__/model-utils.test.ts +388 -0
- package/src/api-client/utils/model-utils.ts +36 -32
- package/src/stores/__tests__/chatbot-ui.store.test.ts +1 -1
- package/src/stores/__tests__/filter.store.test.ts +63 -45
- package/src/stores/chatbot-ui.store.ts +3 -3
- package/src/stores/filter.store.ts +250 -187
- package/src/stores/index.ts +1 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -73,6 +73,29 @@ export const mockFrontendModelFlat = (): Models.FrontendModel => {
|
|
|
73
73
|
});
|
|
74
74
|
};
|
|
75
75
|
|
|
76
|
+
export const mockFrontendModelMixed = (): Models.FrontendModel => {
|
|
77
|
+
const sources = mockOptionSimple('Sources', 'Sources', Models.OptionType.Group, [
|
|
78
|
+
mockOptionSimple('KnowledgeBase', 'Knowledge Base', Models.OptionType.Selectable, [
|
|
79
|
+
mockOptionSimple('ContentTypes', 'Content Types', Models.OptionType.Group, [
|
|
80
|
+
mockOptionSimple('kbReleaseNotes', 'Release Notes', Models.OptionType.Selectable),
|
|
81
|
+
mockOptionSimple('kbFaq', 'FAQ', Models.OptionType.Selectable),
|
|
82
|
+
mockOptionSimple('kbHowTo', 'How To', Models.OptionType.Selectable),
|
|
83
|
+
]),
|
|
84
|
+
mockOptionSimple('ProductAreas', 'Product Areas', Models.OptionType.Group, [
|
|
85
|
+
mockOptionSimple('Call Booking', 'Call Booking', Models.OptionType.Selectable),
|
|
86
|
+
mockOptionSimple('Marketing', 'Marketing', Models.OptionType.Selectable),
|
|
87
|
+
mockOptionSimple('Marketing Pro', 'Marketing Pro', Models.OptionType.Selectable),
|
|
88
|
+
]),
|
|
89
|
+
]),
|
|
90
|
+
mockOptionSimple('Jarvis', 'Jarvis', Models.OptionType.Selectable),
|
|
91
|
+
]);
|
|
92
|
+
return new Models.FrontendModel({
|
|
93
|
+
options: mockOptionSimple('customer-knowledge', undefined, Models.OptionType.Group, [
|
|
94
|
+
sources,
|
|
95
|
+
]),
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
|
|
76
99
|
export const mockFrontendModel = (): Models.FrontendModel => {
|
|
77
100
|
const sources = mockOptionSimple('Sources', 'Sources', Models.OptionType.Group, [
|
|
78
101
|
mockOptionSimple('KnowledgeBase', 'Knowledge Base', Models.OptionType.Selectable, [
|
|
@@ -127,14 +150,18 @@ export const mockFeedback = (overrides?: Partial<Models.IFeedback>): Models.Feed
|
|
|
127
150
|
...overrides,
|
|
128
151
|
});
|
|
129
152
|
|
|
130
|
-
export const mockSelections = (overrides?: Models.Selections): Models.Selections | undefined =>
|
|
131
|
-
new Models.Selections({
|
|
132
|
-
...ModelsUtils.createSelectionsModel(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
153
|
+
export const mockSelections = (overrides?: Models.Selections): Models.Selections | undefined => {
|
|
154
|
+
return new Models.Selections({
|
|
155
|
+
...ModelsUtils.createSelectionsModel(
|
|
156
|
+
[mockFrontendModel().options],
|
|
157
|
+
new Map<string, string[]>([
|
|
158
|
+
['Sources', ['KnowledgeBase', 'Jarvis']],
|
|
159
|
+
['ContentTypes', ['kbReleaseNotes', 'kbFaq', 'xxx']],
|
|
160
|
+
])
|
|
161
|
+
),
|
|
136
162
|
...overrides,
|
|
137
163
|
});
|
|
164
|
+
};
|
|
138
165
|
|
|
139
166
|
export const mockUserMessage = (overrides?: Partial<Models.IUserMessage>): Models.UserMessage =>
|
|
140
167
|
new Models.UserMessage({
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { expect } from '@jest/globals';
|
|
2
|
+
import { Models, ModelsMocks } from '../..';
|
|
3
|
+
import { createSelectionsModel } from '../model-utils';
|
|
4
|
+
|
|
5
|
+
describe('[model-utils] createSelectionsModel', () => {
|
|
6
|
+
afterEach(() => {
|
|
7
|
+
jest.clearAllMocks();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
describe('with no selections', () => {
|
|
11
|
+
test('should return undefined when no selections are made', () => {
|
|
12
|
+
const frontendModel = ModelsMocks.mockFrontendModel();
|
|
13
|
+
const filters = [frontendModel.options];
|
|
14
|
+
const selected = new Map<string, string[]>();
|
|
15
|
+
|
|
16
|
+
const result = createSelectionsModel(filters, selected);
|
|
17
|
+
|
|
18
|
+
expect(result).toBeUndefined();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('should return undefined when selected map is empty', () => {
|
|
22
|
+
const frontendModel = ModelsMocks.mockFrontendModel();
|
|
23
|
+
const filters = [frontendModel.options];
|
|
24
|
+
const selected = new Map<string, string[]>();
|
|
25
|
+
|
|
26
|
+
const result = createSelectionsModel(filters, selected);
|
|
27
|
+
|
|
28
|
+
expect(result).toBeUndefined();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('should handle empty selection arrays', () => {
|
|
32
|
+
const frontendModel = ModelsMocks.mockFrontendModel();
|
|
33
|
+
const filters = [frontendModel.options];
|
|
34
|
+
const selected = new Map<string, string[]>([
|
|
35
|
+
['Sources', []],
|
|
36
|
+
['ContentTypes', []],
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
const result = createSelectionsModel(filters, selected);
|
|
40
|
+
|
|
41
|
+
expect(result).toBeUndefined();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('with flat filter structure (leaf filters only)', () => {
|
|
46
|
+
test('should handle single leaf filter selection', () => {
|
|
47
|
+
const flatModel = ModelsMocks.mockFrontendModelFlat();
|
|
48
|
+
const flatFilters = flatModel.options.subOptions ?? [];
|
|
49
|
+
const selected = new Map<string, string[]>([['ContentTypes', ['kbReleaseNotes']]]);
|
|
50
|
+
|
|
51
|
+
const result = createSelectionsModel(flatFilters, selected);
|
|
52
|
+
|
|
53
|
+
expect(result).toBeDefined();
|
|
54
|
+
expect(result?.subOptions).toBeDefined();
|
|
55
|
+
expect(result?.subOptions!.ContentTypes).toBeDefined();
|
|
56
|
+
expect(result?.subOptions!.ContentTypes.values).toEqual(['kbReleaseNotes']);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('should handle multiple values in single leaf filter', () => {
|
|
60
|
+
const flatModel = ModelsMocks.mockFrontendModelFlat();
|
|
61
|
+
const flatFilters = flatModel.options.subOptions ?? [];
|
|
62
|
+
const selected = new Map<string, string[]>([
|
|
63
|
+
['ContentTypes', ['kbReleaseNotes', 'kbFaq', 'kbHowTo']],
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
const result = createSelectionsModel(flatFilters, selected);
|
|
67
|
+
|
|
68
|
+
expect(result).toBeDefined();
|
|
69
|
+
expect(result?.subOptions!.ContentTypes.values).toEqual([
|
|
70
|
+
'kbReleaseNotes',
|
|
71
|
+
'kbFaq',
|
|
72
|
+
'kbHowTo',
|
|
73
|
+
]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('should handle multiple different leaf filters', () => {
|
|
77
|
+
const flatModel = ModelsMocks.mockFrontendModelFlat();
|
|
78
|
+
const flatFilters = flatModel.options.subOptions ?? [];
|
|
79
|
+
const selected = new Map<string, string[]>([
|
|
80
|
+
['ContentTypes', ['kbReleaseNotes', 'kbFaq']],
|
|
81
|
+
['ProductAreas', ['Marketing']],
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
const result = createSelectionsModel(flatFilters, selected);
|
|
85
|
+
|
|
86
|
+
expect(result).toBeDefined();
|
|
87
|
+
expect(result?.subOptions!.ContentTypes.values).toEqual(['kbReleaseNotes', 'kbFaq']);
|
|
88
|
+
expect(result?.subOptions!.ProductAreas.values).toEqual(['Marketing']);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('should filter out non-matching selections', () => {
|
|
92
|
+
const flatModel = ModelsMocks.mockFrontendModelFlat();
|
|
93
|
+
const flatFilters = flatModel.options.subOptions ?? [];
|
|
94
|
+
const selected = new Map<string, string[]>([
|
|
95
|
+
['ContentTypes', ['kbReleaseNotes', 'nonExistentOption']],
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
const result = createSelectionsModel(flatFilters, selected);
|
|
99
|
+
|
|
100
|
+
expect(result).toBeDefined();
|
|
101
|
+
// Only kbReleaseNotes should be included, nonExistentOption should be filtered out
|
|
102
|
+
expect(result?.subOptions!.ContentTypes.values).toEqual(['kbReleaseNotes']);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('should return undefined when all selected options are invalid', () => {
|
|
106
|
+
const flatModel = ModelsMocks.mockFrontendModelFlat();
|
|
107
|
+
const flatFilters = flatModel.options.subOptions ?? [];
|
|
108
|
+
const selected = new Map<string, string[]>([
|
|
109
|
+
['ContentTypes', ['nonExistent1', 'nonExistent2']],
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
const result = createSelectionsModel(flatFilters, selected);
|
|
113
|
+
|
|
114
|
+
expect(result).toBeUndefined();
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('with mixed filter structure', () => {
|
|
119
|
+
test('should handle selections in mixed structure (1st level option without nested filters)', () => {
|
|
120
|
+
const frontendModel = ModelsMocks.mockFrontendModelMixed();
|
|
121
|
+
const filters = frontendModel.options.subOptions ?? [];
|
|
122
|
+
const selected = new Map<string, string[]>([['Sources', ['Jarvis']]]);
|
|
123
|
+
|
|
124
|
+
const result = createSelectionsModel(filters, selected);
|
|
125
|
+
|
|
126
|
+
expect(result).toBeDefined();
|
|
127
|
+
expect(result?.subOptions).toBeDefined();
|
|
128
|
+
expect(result?.subOptions!.Sources).toBeDefined();
|
|
129
|
+
expect(result?.subOptions!.Sources.values).toEqual(['Jarvis']);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('should handle selections in mixed structure (1st level option with nested filters)', () => {
|
|
133
|
+
const frontendModel = ModelsMocks.mockFrontendModelMixed();
|
|
134
|
+
const filters = frontendModel.options.subOptions ?? [];
|
|
135
|
+
const selected = new Map<string, string[]>([['Sources', ['KnowledgeBase', 'Jarvis']]]);
|
|
136
|
+
|
|
137
|
+
const result = createSelectionsModel(filters, selected);
|
|
138
|
+
|
|
139
|
+
expect(result).toBeDefined();
|
|
140
|
+
expect(result?.subOptions).toBeDefined();
|
|
141
|
+
expect(result?.subOptions!.Sources).toBeDefined();
|
|
142
|
+
expect(result?.subOptions!.Sources.values).toEqual(['Jarvis']);
|
|
143
|
+
expect(result?.subOptions!.Sources.subOptions).toBeDefined();
|
|
144
|
+
expect(result?.subOptions!.Sources.subOptions!.KnowledgeBase).toBeDefined();
|
|
145
|
+
expect(result?.subOptions!.Sources.subOptions!.KnowledgeBase.subOptions).toEqual({});
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('with nested filter structure', () => {
|
|
150
|
+
test('should handle single source selection without sub-filters', () => {
|
|
151
|
+
const frontendModel = ModelsMocks.mockFrontendModel();
|
|
152
|
+
const filters = frontendModel.options.subOptions ?? [];
|
|
153
|
+
const selected = new Map<string, string[]>([['Sources', ['KnowledgeBase']]]);
|
|
154
|
+
|
|
155
|
+
const result = createSelectionsModel(filters, selected);
|
|
156
|
+
|
|
157
|
+
expect(result).toBeDefined();
|
|
158
|
+
expect(result?.subOptions).toBeDefined();
|
|
159
|
+
expect(result?.subOptions!.Sources).toBeDefined();
|
|
160
|
+
expect(result?.subOptions!.Sources.subOptions).toBeDefined();
|
|
161
|
+
expect(result?.subOptions!.Sources.subOptions!.KnowledgeBase).toBeDefined();
|
|
162
|
+
expect(result?.subOptions!.Sources.subOptions!.KnowledgeBase.subOptions).toEqual({});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('should handle source with content type selection', () => {
|
|
166
|
+
const frontendModel = ModelsMocks.mockFrontendModel();
|
|
167
|
+
const filters = frontendModel.options.subOptions ?? [];
|
|
168
|
+
const selected = new Map<string, string[]>([
|
|
169
|
+
['Sources', ['KnowledgeBase']],
|
|
170
|
+
['ContentTypes', ['kbReleaseNotes']],
|
|
171
|
+
]);
|
|
172
|
+
|
|
173
|
+
const result = createSelectionsModel(filters, selected);
|
|
174
|
+
|
|
175
|
+
expect(result).toBeDefined();
|
|
176
|
+
const kbSubOptions = result?.subOptions!.Sources.subOptions!.KnowledgeBase.subOptions;
|
|
177
|
+
expect(kbSubOptions).toBeDefined();
|
|
178
|
+
expect(kbSubOptions!.ContentTypes).toBeDefined();
|
|
179
|
+
expect(kbSubOptions!.ContentTypes.values).toEqual(['kbReleaseNotes']);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test('should handle multiple sources with same content type selection', () => {
|
|
183
|
+
const frontendModel = ModelsMocks.mockFrontendModel();
|
|
184
|
+
const filters = frontendModel.options.subOptions ?? [];
|
|
185
|
+
const selected = new Map<string, string[]>([
|
|
186
|
+
['Sources', ['KnowledgeBase', 'Jarvis']],
|
|
187
|
+
['ContentTypes', ['kbReleaseNotes']],
|
|
188
|
+
]);
|
|
189
|
+
|
|
190
|
+
const result = createSelectionsModel(filters, selected);
|
|
191
|
+
|
|
192
|
+
expect(result).toBeDefined();
|
|
193
|
+
expect(result?.subOptions!.Sources.subOptions).toBeDefined();
|
|
194
|
+
expect(result?.subOptions!.Sources.subOptions!.KnowledgeBase).toBeDefined();
|
|
195
|
+
expect(result?.subOptions!.Sources.subOptions!.Jarvis).toBeDefined();
|
|
196
|
+
|
|
197
|
+
// Both sources should have the ContentTypes selection (filtered by available options)
|
|
198
|
+
expect(
|
|
199
|
+
result?.subOptions!.Sources.subOptions!.KnowledgeBase.subOptions!.ContentTypes
|
|
200
|
+
.values
|
|
201
|
+
).toEqual(['kbReleaseNotes']);
|
|
202
|
+
expect(
|
|
203
|
+
result?.subOptions!.Sources.subOptions!.Jarvis.subOptions!.ContentTypes.values
|
|
204
|
+
).toEqual(['kbReleaseNotes']);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test('should filter content types based on each sources available options', () => {
|
|
208
|
+
const frontendModel = ModelsMocks.mockFrontendModel();
|
|
209
|
+
const filters = frontendModel.options.subOptions ?? [];
|
|
210
|
+
const selected = new Map<string, string[]>([
|
|
211
|
+
['Sources', ['KnowledgeBase', 'Jarvis']],
|
|
212
|
+
['ContentTypes', ['kbReleaseNotes', 'kbFaq', 'xxx']],
|
|
213
|
+
]);
|
|
214
|
+
|
|
215
|
+
const result = createSelectionsModel(filters, selected);
|
|
216
|
+
|
|
217
|
+
expect(result).toBeDefined();
|
|
218
|
+
|
|
219
|
+
// KnowledgeBase has [kbReleaseNotes, kbFaq, kbHowTo], so it should have [kbReleaseNotes, kbFaq]
|
|
220
|
+
const kbContentTypes =
|
|
221
|
+
result?.subOptions!.Sources.subOptions!.KnowledgeBase.subOptions!.ContentTypes
|
|
222
|
+
.values;
|
|
223
|
+
expect(kbContentTypes).toEqual(['kbReleaseNotes', 'kbFaq']);
|
|
224
|
+
|
|
225
|
+
// Jarvis has [kbReleaseNotes, xxx, yyy], so it should have [kbReleaseNotes, xxx]
|
|
226
|
+
const jarvisContentTypes =
|
|
227
|
+
result?.subOptions!.Sources.subOptions!.Jarvis.subOptions!.ContentTypes.values;
|
|
228
|
+
expect(jarvisContentTypes).toEqual(['kbReleaseNotes', 'xxx']);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('should handle product areas with nested structure', () => {
|
|
232
|
+
const frontendModel = ModelsMocks.mockFrontendModel();
|
|
233
|
+
const filters = frontendModel.options.subOptions ?? [];
|
|
234
|
+
const selected = new Map<string, string[]>([
|
|
235
|
+
['Sources', ['KnowledgeBase']],
|
|
236
|
+
['ProductAreas', ['Marketing', 'Marketing Pro']],
|
|
237
|
+
]);
|
|
238
|
+
|
|
239
|
+
const result = createSelectionsModel(filters, selected);
|
|
240
|
+
|
|
241
|
+
expect(result).toBeDefined();
|
|
242
|
+
const kbSubOptions = result?.subOptions!.Sources.subOptions!.KnowledgeBase.subOptions;
|
|
243
|
+
expect(kbSubOptions!.ProductAreas).toBeDefined();
|
|
244
|
+
expect(kbSubOptions!.ProductAreas.values).toEqual(['Marketing', 'Marketing Pro']);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test('should handle both content types and product areas', () => {
|
|
248
|
+
const frontendModel = ModelsMocks.mockFrontendModel();
|
|
249
|
+
const filters = frontendModel.options.subOptions ?? [];
|
|
250
|
+
const selected = new Map<string, string[]>([
|
|
251
|
+
['Sources', ['KnowledgeBase']],
|
|
252
|
+
['ContentTypes', ['kbReleaseNotes', 'kbHowTo']],
|
|
253
|
+
['ProductAreas', ['Marketing']],
|
|
254
|
+
]);
|
|
255
|
+
|
|
256
|
+
const result = createSelectionsModel(filters, selected);
|
|
257
|
+
|
|
258
|
+
expect(result).toBeDefined();
|
|
259
|
+
const kbSubOptions = result?.subOptions!.Sources.subOptions!.KnowledgeBase.subOptions;
|
|
260
|
+
|
|
261
|
+
expect(kbSubOptions!.ContentTypes.values).toEqual(['kbReleaseNotes', 'kbHowTo']);
|
|
262
|
+
expect(kbSubOptions!.ProductAreas.values).toEqual(['Marketing']);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test('should handle complex scenario with multiple sources and filters', () => {
|
|
266
|
+
const frontendModel = ModelsMocks.mockFrontendModel();
|
|
267
|
+
const filters = frontendModel.options.subOptions ?? [];
|
|
268
|
+
const selected = new Map<string, string[]>([
|
|
269
|
+
['Sources', ['KnowledgeBase', 'Jarvis']],
|
|
270
|
+
['ContentTypes', ['kbReleaseNotes', 'yyy', 'kbHowTo']],
|
|
271
|
+
['ProductAreas', ['Marketing', 'Marketing Pro']],
|
|
272
|
+
]);
|
|
273
|
+
|
|
274
|
+
const result = createSelectionsModel(filters, selected);
|
|
275
|
+
|
|
276
|
+
expect(result).toBeDefined();
|
|
277
|
+
expect(result?.subOptions!.Sources.subOptions).toBeDefined();
|
|
278
|
+
|
|
279
|
+
// Verify KnowledgeBase selections
|
|
280
|
+
const kbSubOptions = result?.subOptions!.Sources.subOptions!.KnowledgeBase.subOptions;
|
|
281
|
+
// KB has kbReleaseNotes, kbFaq, kbHowTo - matches kbReleaseNotes and kbHowTo
|
|
282
|
+
expect(kbSubOptions!.ContentTypes.values).toEqual(['kbReleaseNotes', 'kbHowTo']);
|
|
283
|
+
expect(kbSubOptions!.ProductAreas.values).toEqual(['Marketing', 'Marketing Pro']);
|
|
284
|
+
|
|
285
|
+
// Verify Jarvis selections
|
|
286
|
+
const jarvisSubOptions = result?.subOptions!.Sources.subOptions!.Jarvis.subOptions;
|
|
287
|
+
// Jarvis has kbReleaseNotes, xxx, yyy - matches kbReleaseNotes and yyy
|
|
288
|
+
expect(jarvisSubOptions!.ContentTypes.values).toEqual(['kbReleaseNotes', 'yyy']);
|
|
289
|
+
// Jarvis doesn't have ProductAreas, so it shouldn't be present
|
|
290
|
+
expect(jarvisSubOptions!.ProductAreas).toBeUndefined();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test('should match the FilterStore export test case', () => {
|
|
294
|
+
// This test matches the scenario from filter.store.test.ts line 266
|
|
295
|
+
const frontendModel = ModelsMocks.mockFrontendModel();
|
|
296
|
+
const filters = frontendModel.options.subOptions ?? [];
|
|
297
|
+
const selected = new Map<string, string[]>([
|
|
298
|
+
['Sources', ['KnowledgeBase', 'Jarvis']],
|
|
299
|
+
['ContentTypes', ['kbReleaseNotes', 'yyy', 'kbHowTo']],
|
|
300
|
+
['ProductAreas', ['Marketing', 'Marketing Pro']],
|
|
301
|
+
]);
|
|
302
|
+
|
|
303
|
+
const result = createSelectionsModel(filters, selected);
|
|
304
|
+
|
|
305
|
+
// Should match the expected output from filter.store.test.ts
|
|
306
|
+
expect(result).toEqual({
|
|
307
|
+
subOptions: {
|
|
308
|
+
Sources: {
|
|
309
|
+
subOptions: {
|
|
310
|
+
KnowledgeBase: {
|
|
311
|
+
subOptions: {
|
|
312
|
+
ContentTypes: {
|
|
313
|
+
values: ['kbReleaseNotes', 'kbHowTo'],
|
|
314
|
+
},
|
|
315
|
+
ProductAreas: {
|
|
316
|
+
values: ['Marketing', 'Marketing Pro'],
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
Jarvis: {
|
|
321
|
+
subOptions: {
|
|
322
|
+
ContentTypes: {
|
|
323
|
+
values: ['kbReleaseNotes', 'yyy'],
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
describe('edge cases', () => {
|
|
335
|
+
test('should ignore non-Group type filters', () => {
|
|
336
|
+
const customFilters: Models.IOption[] = [
|
|
337
|
+
new Models.Option({
|
|
338
|
+
key: 'NotAGroup',
|
|
339
|
+
displayName: 'Not A Group',
|
|
340
|
+
type: Models.OptionType.Selectable,
|
|
341
|
+
subOptions: [
|
|
342
|
+
new Models.Option({
|
|
343
|
+
key: 'option1',
|
|
344
|
+
displayName: 'Option 1',
|
|
345
|
+
type: Models.OptionType.Selectable,
|
|
346
|
+
}),
|
|
347
|
+
],
|
|
348
|
+
}),
|
|
349
|
+
];
|
|
350
|
+
const selected = new Map<string, string[]>([['NotAGroup', ['option1']]]);
|
|
351
|
+
|
|
352
|
+
const result = createSelectionsModel(customFilters, selected);
|
|
353
|
+
|
|
354
|
+
expect(result).toBeUndefined();
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test('should handle filters with no subOptions', () => {
|
|
358
|
+
const customFilters: Models.IOption[] = [
|
|
359
|
+
new Models.Option({
|
|
360
|
+
key: 'EmptyFilter',
|
|
361
|
+
displayName: 'Empty Filter',
|
|
362
|
+
type: Models.OptionType.Group,
|
|
363
|
+
subOptions: [],
|
|
364
|
+
}),
|
|
365
|
+
];
|
|
366
|
+
const selected = new Map<string, string[]>([['EmptyFilter', ['anything']]]);
|
|
367
|
+
|
|
368
|
+
const result = createSelectionsModel(customFilters, selected);
|
|
369
|
+
|
|
370
|
+
expect(result).toBeUndefined();
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
test('should create proper Selections model structure', () => {
|
|
374
|
+
const frontendModel = ModelsMocks.mockFrontendModel();
|
|
375
|
+
const filters = frontendModel.options.subOptions ?? [];
|
|
376
|
+
const selected = new Map<string, string[]>([
|
|
377
|
+
['Sources', ['KnowledgeBase']],
|
|
378
|
+
['ContentTypes', ['kbReleaseNotes']],
|
|
379
|
+
]);
|
|
380
|
+
|
|
381
|
+
const result = createSelectionsModel(filters, selected);
|
|
382
|
+
|
|
383
|
+
expect(result).toBeInstanceOf(Models.Selections);
|
|
384
|
+
expect(result?.subOptions).toBeDefined();
|
|
385
|
+
expect(typeof result?.subOptions).toBe('object');
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
});
|
|
@@ -6,7 +6,7 @@ export function createNewSessionModel(data?: Models.ISession): Models.Session {
|
|
|
6
6
|
|
|
7
7
|
export function createSelectionsModel(
|
|
8
8
|
filters: Models.IOption[],
|
|
9
|
-
selected:
|
|
9
|
+
selected: Map<string, string[]>
|
|
10
10
|
): Models.Selections | undefined {
|
|
11
11
|
const process = (filters: Models.IOption[]): Models.Selections | undefined => {
|
|
12
12
|
let result: Models.Selections | undefined;
|
|
@@ -22,44 +22,48 @@ export function createSelectionsModel(
|
|
|
22
22
|
x => x.type === Models.OptionType.Group && Boolean(x.subOptions?.length)
|
|
23
23
|
);
|
|
24
24
|
for (const filter of filterList) {
|
|
25
|
-
const
|
|
26
|
-
|
|
25
|
+
const selectedLeafOptions = filter.subOptions!.filter(
|
|
26
|
+
x =>
|
|
27
|
+
x.type === Models.OptionType.Selectable &&
|
|
28
|
+
selected.get(filter.key)?.includes(x.key!) &&
|
|
29
|
+
!x.subOptions?.length
|
|
27
30
|
);
|
|
28
|
-
|
|
31
|
+
let values: string[] | undefined;
|
|
32
|
+
if (selectedLeafOptions.length) {
|
|
29
33
|
// Leaf filter: just collect selected values
|
|
30
|
-
|
|
34
|
+
values = selectedLeafOptions
|
|
31
35
|
?.map(o => o.key)
|
|
32
|
-
.filter(o => selected
|
|
36
|
+
.filter(o => selected.get(filter.key)?.includes(o));
|
|
33
37
|
if (values?.length) {
|
|
34
38
|
ensureResult();
|
|
35
39
|
result!.subOptions![filter.key] = new Models.Selections({ values });
|
|
36
40
|
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
41
|
+
}
|
|
42
|
+
// Non-leaf filter: add selected options as selectables and process sub-filters
|
|
43
|
+
const selectedNonLeafOptions = filter.subOptions!.filter(
|
|
44
|
+
x =>
|
|
45
|
+
x.type === Models.OptionType.Selectable &&
|
|
46
|
+
selected.get(filter.key)?.includes(x.key!) &&
|
|
47
|
+
Boolean(x.subOptions?.length)
|
|
48
|
+
);
|
|
49
|
+
if (!selectedNonLeafOptions.length) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
ensureResult();
|
|
53
|
+
const filterResult = new Models.Selections({
|
|
54
|
+
values,
|
|
55
|
+
subOptions: {},
|
|
56
|
+
});
|
|
57
|
+
result!.subOptions![filter.key] = filterResult;
|
|
58
|
+
for (const noneLeafOption of selectedNonLeafOptions) {
|
|
59
|
+
// Process sub-filters: if any sub-filters selected, add them to the result
|
|
60
|
+
const subFilters = noneLeafOption.subOptions!;
|
|
61
|
+
const resultSubFilters = process(subFilters);
|
|
62
|
+
filterResult.subOptions![noneLeafOption.key] =
|
|
63
|
+
resultSubFilters ??
|
|
64
|
+
new Models.Selections({
|
|
65
|
+
subOptions: {},
|
|
66
|
+
});
|
|
63
67
|
}
|
|
64
68
|
}
|
|
65
69
|
return result;
|
|
@@ -52,7 +52,7 @@ describe('[ChatbotUiStore]', () => {
|
|
|
52
52
|
const frontendModel = ModelsMocks.mockFrontendModel();
|
|
53
53
|
store.setFilters(frontendModel);
|
|
54
54
|
|
|
55
|
-
expect(store.filterStore.filters.length).toEqual(
|
|
55
|
+
expect(store.filterStore.filters.length).toEqual(3);
|
|
56
56
|
expect(store.filterStore.filters[0].key).toEqual('Sources');
|
|
57
57
|
});
|
|
58
58
|
|