@telia-ace/knowledge-data-client-flamingo 1.1.2 → 1.1.3

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.
@@ -1,585 +0,0 @@
1
- import { ServiceClient } from '@telia-ace/knowledge-serviceclient';
2
- import { WidgetRenderState } from '@telia-ace/widget-core';
3
- import {
4
- deepClone,
5
- lock,
6
- removeNullAndUndefinedValues,
7
- shallowCompare,
8
- } from '@telia-ace/widget-utilities';
9
- import { Container, EventManager, Widget } from '@webprovisions/platform';
10
- import { createServiceClient } from './create-serviceclient';
11
- import { formatLegacyData } from './legacy-conversion';
12
-
13
- export type Subscriber = (...args: any[]) => void;
14
-
15
- export enum DataType {
16
- Guide = 'guide',
17
- Guides = 'guides',
18
- GuidesByCategory = 'guides-by-category',
19
- RowNotifications = 'row-notices',
20
- NotificationLists = 'notification-lists',
21
- GuideCategories = 'guide-categories',
22
- ContactMethodCategories = 'contact-method-categories',
23
- ContactMethod = 'contact-method',
24
- ContactMethods = 'contact-methods',
25
- Tags = 'tags',
26
- TagsOnGuides = 'tagsonguides',
27
- }
28
-
29
- export type DataError = {
30
- status: number;
31
- message: string;
32
- };
33
-
34
- export enum ServiceClientQueryType {
35
- Match = 'match',
36
- Categories = 'categories',
37
- Guide = 'guide',
38
- Contacts = 'contacts',
39
- ContactMethod = 'contact-method',
40
- MatchByCategory = 'match-by-category',
41
- Tags = 'tags',
42
- TagsOnGuides = 'tagsonguides',
43
- Notifications = 'notifications',
44
- }
45
-
46
- export enum FeedbackType {
47
- Positive = 'Positive',
48
- Negative = 'Negative',
49
- }
50
-
51
- export type QueryParameters = {
52
- guideId?: string | number;
53
- categories?: string | number | string[] | number[];
54
- tagId?: string | number;
55
- connection?: string;
56
-
57
- contactMethodCategoryId?: string | number;
58
- contactMethodId?: string | number;
59
-
60
- searchPhrase?: string | number;
61
- take?: string | number;
62
- [key: string]: any;
63
-
64
- expandCategories?: 'none' | 'children' | 'descendants';
65
- sorting?: SortingType;
66
- guideIds?: string[];
67
- };
68
-
69
- export enum SortingType {
70
- POPULARITY_DESCENDING = 'popularity-descending',
71
- ALPHABETIC_ASCENDING = 'alphabetic-ascending',
72
- ALPHABETIC_DESCENDING = 'alphabetic-descending',
73
- MODIFIED_ASCENDING = 'modified-ascending',
74
- MODIFIED_DESCENDING = 'modified-descending',
75
- PUBLISHED_ASCENDING = 'published-ascending',
76
- PUBLISHED_DESCENDING = 'published-descending',
77
- }
78
-
79
- export type Query = {
80
- params: QueryParameters;
81
- resolved: boolean;
82
- loading: boolean;
83
- error?: DataError;
84
- data: any;
85
- resolvers: {
86
- resolve: (data: any) => void;
87
- reject: (data: any) => void;
88
- }[];
89
- };
90
-
91
- export const determineServiceClientQueryType = (
92
- type: DataType
93
- ): ServiceClientQueryType => {
94
- switch (type) {
95
- case DataType.Guides:
96
- return ServiceClientQueryType.Match;
97
- case DataType.GuidesByCategory:
98
- return ServiceClientQueryType.MatchByCategory;
99
- case DataType.Guide:
100
- return ServiceClientQueryType.Guide;
101
- case DataType.GuideCategories:
102
- return ServiceClientQueryType.Categories;
103
- case DataType.ContactMethodCategories:
104
- case DataType.ContactMethods:
105
- return ServiceClientQueryType.Contacts;
106
- case DataType.ContactMethod:
107
- return ServiceClientQueryType.ContactMethod;
108
- case DataType.Tags:
109
- return ServiceClientQueryType.Tags;
110
- case DataType.TagsOnGuides:
111
- return ServiceClientQueryType.TagsOnGuides;
112
- case DataType.NotificationLists:
113
- case DataType.RowNotifications:
114
- return ServiceClientQueryType.Notifications;
115
- default:
116
- throw 'error';
117
- }
118
- };
119
-
120
- const widgetIsHidden = async (container: Container) => {
121
- const { invoke } = container.get('$widget');
122
- const renderState = await invoke('renderState');
123
- return renderState === WidgetRenderState.hidden;
124
- };
125
-
126
- export default class DataClient {
127
- private queries: Map<ServiceClientQueryType, Query[]> = new Map();
128
- // private lastTake: number = 0;
129
- public events: EventManager;
130
- constructor(
131
- private container: Container,
132
- private matchingClient: ServiceClient
133
- ) {
134
- this.events = (<Widget>container.get('$widget')).events;
135
-
136
- this.events.subscribe(
137
- 'widget:settings-updated',
138
- () => (this.queries = new Map())
139
- );
140
-
141
- this.events.subscribe(
142
- 'widget:render-state-changed',
143
- (_event, { next, previous }) => {
144
- if (
145
- previous === WidgetRenderState.hidden &&
146
- next === WidgetRenderState.open
147
- ) {
148
- this.fetchAllUnresolvedQueries();
149
- }
150
- }
151
- );
152
- }
153
-
154
- static getInstance(
155
- container: Container,
156
- key = 'dataClient'
157
- ): Promise<DataClient> {
158
- return lock(this)(() => {
159
- return container.getAsync(key).then(async (value: DataClient) => {
160
- let serviceClient = await container.getAsync('matchingClient');
161
-
162
- if (!serviceClient) {
163
- serviceClient = await createServiceClient(container);
164
- container.registerAsync('matchingClient', () => serviceClient);
165
- }
166
-
167
- let platform = value;
168
- if (!platform) {
169
- platform = new DataClient(container, serviceClient);
170
- container.registerAsync(key, () => platform);
171
- }
172
- return platform;
173
- });
174
- });
175
- }
176
-
177
- private getUnresolvedQueries = () => {
178
- let unresolvedCount = 0;
179
- this.queries.forEach((queries, _type) => {
180
- const hasUnresolved = queries.some((query: Query) => !query.resolved);
181
- if (hasUnresolved) {
182
- unresolvedCount = +1;
183
- }
184
- });
185
- return unresolvedCount;
186
- };
187
-
188
- public fetch(
189
- type: DataType,
190
- params: QueryParameters,
191
- options?: {
192
- // this is useful when something else in the environment may have changed that
193
- // could have side effects, will ensure that a fresh fetch is always initiated
194
- noCache: boolean;
195
- }
196
- ): Promise<any> {
197
- return new Promise((resolve, reject) => {
198
- if (params.take) {
199
- // this.lastTake = +(params.take || 0);
200
- }
201
-
202
- removeNullAndUndefinedValues(params);
203
- const serviceClientQueryType = determineServiceClientQueryType(type);
204
- let queries = this.queries.get(serviceClientQueryType) || [];
205
-
206
- let query: Query | null =
207
- queries.find((q) => shallowCompare(q.params, params)) || null;
208
-
209
- if (query && options?.noCache) {
210
- const index = queries.findIndex((q) =>
211
- shallowCompare(q.params, params)
212
- );
213
- queries = [...queries.slice(0, index), ...queries.slice(index + 1)];
214
- query = null;
215
- }
216
-
217
- if (query) {
218
- const index = queries.indexOf(query);
219
- if (query.resolved) {
220
- this.events.dispatch('data-client:fetched', {
221
- params,
222
- type: serviceClientQueryType,
223
- response: query.data,
224
- unresolvedQueries: this.getUnresolvedQueries(),
225
- });
226
- this.track(query.data, serviceClientQueryType, query.error);
227
- if (query.data && query.error) {
228
- reject({ error: query.error });
229
- } else {
230
- resolve(query.data);
231
- }
232
- } else {
233
- query.resolvers.push({ resolve, reject });
234
- }
235
- queries.splice(index, 1);
236
- queries.push(query);
237
- this.queries.set(serviceClientQueryType, queries);
238
- if (!query.loading && !query.resolved) {
239
- return this.runQuery(serviceClientQueryType, params);
240
- }
241
- } else {
242
- queries.push({
243
- params,
244
- resolved: false,
245
- loading: true,
246
- data: {},
247
- resolvers: [{ resolve, reject }],
248
- });
249
- this.queries.set(serviceClientQueryType, queries);
250
-
251
- return this.runQuery(serviceClientQueryType, params);
252
- }
253
- }).then((res) => deepClone(res));
254
- }
255
-
256
- // query the cached data
257
- public read(
258
- type: DataType,
259
- options: {
260
- select?: (data: any) => any; // either return all data by the DataType, or use an selector
261
- }
262
- ) {
263
- const { select } = options;
264
- const serviceClientQueryType = determineServiceClientQueryType(type);
265
- const queries = this.queries.get(serviceClientQueryType) || [];
266
-
267
- const result = queries.reduce<any[]>((acc, query) => {
268
- if (typeof select === 'function') {
269
- const match = select(query.data);
270
- if (match) {
271
- acc.push(match);
272
- }
273
- } else {
274
- acc.push(query.data);
275
- }
276
- return acc;
277
- }, []);
278
- return result;
279
- }
280
-
281
- public feedback(
282
- id: string,
283
- connection: string,
284
- feedback: FeedbackType
285
- ): Promise<any> {
286
- return this.matchingClient.giveFeedback(id, connection, feedback);
287
- }
288
-
289
- private setLoadingStatus(
290
- type: ServiceClientQueryType,
291
- params: QueryParameters,
292
- loading: boolean
293
- ) {
294
- const queries = this.queries.get(type) || [];
295
- const query = queries.find((q) => shallowCompare(q.params, params));
296
-
297
- if (query) {
298
- query.loading = loading;
299
- }
300
- this.queries.set(type, queries);
301
- }
302
-
303
- private async runQuery(
304
- type: ServiceClientQueryType,
305
- params: QueryParameters
306
- ) {
307
- const {
308
- searchPhrase = '',
309
- categories = '0',
310
- contactMethodCategoryId = '0',
311
- contactMethodId = '0',
312
- tagId = undefined,
313
- take = 5,
314
- connection = '',
315
- guideId,
316
- expandCategories = 'descendants',
317
- currentCategory,
318
- sorting = SortingType.POPULARITY_DESCENDING,
319
- guideIds = [],
320
- } = params;
321
-
322
- const trimmedPhrase = searchPhrase.toString().trim();
323
- if (await widgetIsHidden(this.container)) {
324
- return Promise.resolve();
325
- }
326
-
327
- switch (type) {
328
- case ServiceClientQueryType.Match: {
329
- const formatSortingValues = (sorting: string) => {
330
- const [type, direction] = sorting.split('-');
331
- const sortKeysMap = new Map([
332
- ['popularity', 'popularity'],
333
- ['alphabetic', 'title'],
334
- ['modified', 'lastModified'],
335
- ['published', 'firstPublished'],
336
- ]);
337
-
338
- return {
339
- type: sortKeysMap.get(type),
340
- direction: direction,
341
- };
342
- };
343
-
344
- if (guideIds.length) {
345
- return this.matchingClient
346
- .customRequest('guides', 'POST', {
347
- configuration: { ids: guideIds, take: +take },
348
- })
349
- .then(
350
- (matches) => matches && this.handleResponse(matches, type, params)
351
- );
352
- }
353
-
354
- return this.matchingClient
355
- .match(trimmedPhrase, {
356
- categories:
357
- categories === '0'
358
- ? []
359
- : Array.isArray(categories)
360
- ? categories
361
- : [+categories],
362
- take: +take,
363
- tags: tagId,
364
- sorting: formatSortingValues(sorting),
365
- ids: guideIds,
366
- })
367
- .then(
368
- (matches) => matches && this.handleResponse(matches, type, params)
369
- );
370
- }
371
- case ServiceClientQueryType.MatchByCategory: {
372
- return this.matchingClient
373
- .match(trimmedPhrase, {
374
- groupByCategory: true,
375
- categories:
376
- categories === '0'
377
- ? []
378
- : Array.isArray(categories)
379
- ? categories
380
- : [+categories],
381
- take: +take,
382
- tags: tagId,
383
- })
384
- .then(
385
- (matches) => matches && this.handleResponse(matches, type, params)
386
- );
387
- }
388
- case ServiceClientQueryType.Guide: {
389
- return this.matchingClient
390
- .getGuide(guideId, { connectionId: connection })
391
- .then((guide) => {
392
- if (guide) {
393
- this.handleResponse(guide, type, params);
394
- }
395
- })
396
- .catch((e) => {
397
- const error = {
398
- message: 'Something went wrong.',
399
- status: e.status || 400,
400
- };
401
- if (error.status === 404) {
402
- error.message = 'The guide could not be found.';
403
- }
404
- this.handleResponse({}, type, params, error);
405
- });
406
- }
407
- case ServiceClientQueryType.Categories: {
408
- return this.matchingClient
409
- .getCategories({
410
- phrase: trimmedPhrase,
411
- expand: expandCategories,
412
- tags: tagId,
413
- })
414
- .then(
415
- (categories) =>
416
- categories && this.handleResponse(categories, type, params)
417
- );
418
- }
419
- case ServiceClientQueryType.Contacts: {
420
- if (guideId) {
421
- return this.matchingClient
422
- .contactMethods(+guideId, { phrase: trimmedPhrase })
423
- .then(
424
- (result) => result && this.handleResponse(result, type, params)
425
- );
426
- }
427
- return this.matchingClient
428
- .contacts(+contactMethodCategoryId, { phrase: trimmedPhrase })
429
- .then(
430
- (result) => result && this.handleResponse(result, type, params)
431
- );
432
- }
433
- case ServiceClientQueryType.ContactMethod: {
434
- return this.matchingClient
435
- .getContactMethod(+contactMethodId, { guideId, currentCategory })
436
- .then(
437
- (result) => result && this.handleResponse(result, type, params)
438
- );
439
- }
440
- case ServiceClientQueryType.Tags: {
441
- return this.matchingClient
442
- .customRequest('tags', 'GET', {
443
- configuration: {
444
- take: 999999,
445
- phrase: trimmedPhrase,
446
- categories:
447
- categories === '0'
448
- ? []
449
- : Array.isArray(categories)
450
- ? categories
451
- : [+categories],
452
- },
453
- })
454
- .then(
455
- (result) => result && this.handleResponse(result, type, params)
456
- );
457
- }
458
- case ServiceClientQueryType.TagsOnGuides: {
459
- return this.matchingClient
460
- .customRequest('tagsonguides', 'GET', {
461
- configuration: {
462
- take: 999999,
463
- phrase: trimmedPhrase,
464
- categories:
465
- categories === '0'
466
- ? []
467
- : Array.isArray(categories)
468
- ? categories
469
- : [+categories],
470
- tags: tagId,
471
- },
472
- })
473
- .then(
474
- (result) => result && this.handleResponse(result, type, params)
475
- );
476
- }
477
- case ServiceClientQueryType.Notifications: {
478
- return this.matchingClient
479
- .customRequest('notices', 'GET', {
480
- configuration: {
481
- phrase: trimmedPhrase,
482
- categories:
483
- categories === '0'
484
- ? []
485
- : Array.isArray(categories)
486
- ? categories
487
- : [+categories],
488
- },
489
- })
490
- .then(
491
- (result) => result && this.handleResponse(result, type, params)
492
- );
493
- }
494
- }
495
- }
496
-
497
- handleResponse(
498
- data: any = {},
499
- type: ServiceClientQueryType,
500
- params: QueryParameters,
501
- error?: DataError
502
- ) {
503
- const queries = this.queries.get(type) || [];
504
- const existing = queries.find((q) => shallowCompare(q.params, params));
505
- if (existing) {
506
- const formatted = error ? error : formatLegacyData(type, data);
507
- this.track(formatted, type, error);
508
- existing.resolvers.forEach((r) => {
509
- if (error) {
510
- r.reject({ error: formatted });
511
- } else {
512
- r.resolve(formatted);
513
- }
514
- });
515
-
516
- const index = queries.indexOf(existing);
517
- if (index > -1) {
518
- queries.splice(index, 1);
519
- }
520
- queries.push({
521
- ...existing,
522
- error,
523
- resolvers: [],
524
- data: formatted,
525
- resolved: true,
526
- });
527
- this.setLoadingStatus(type, params, false);
528
- this.queries.set(type, queries);
529
- if (!error) {
530
- this.events.dispatch('data-client:fetched', {
531
- type,
532
- params,
533
- response: formatted,
534
- unresolvedQueries: this.getUnresolvedQueries(),
535
- });
536
- }
537
- }
538
- }
539
-
540
- private fetchAllUnresolvedQueries() {
541
- for (const [queryType, queries] of this.queries) {
542
- queries
543
- .filter((query: Query) => !query.resolved)
544
- .forEach((query) => {
545
- this.runQuery(queryType, query.params);
546
- });
547
- }
548
- }
549
-
550
- private track(
551
- data: any = {},
552
- type: ServiceClientQueryType,
553
- error?: DataError
554
- ) {
555
- const widget = this.container.get('$widget');
556
- if (widget && !error) {
557
- widget.events.dispatch('tracking:service-client-response', {
558
- type,
559
- data,
560
- });
561
- } else if (widget) {
562
- widget.events.dispatch('tracking:service-client-error', {
563
- type,
564
- error,
565
- });
566
- }
567
- }
568
-
569
- static create(container: Container): Promise<DataClient> {
570
- return container.getAsync('matchingClient').then(async (matchingClient) => {
571
- if (matchingClient) {
572
- return new DataClient(container, matchingClient);
573
- }
574
-
575
- const client = await createServiceClient(container);
576
- container.registerAsync('matchingClient', () => client);
577
-
578
- return new DataClient(container, client);
579
- });
580
- }
581
-
582
- getClient() {
583
- return this.matchingClient;
584
- }
585
- }