@miso.ai/client-sdk-ui 1.11.0-beta.9 → 1.11.1-beta.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 (111) hide show
  1. package/dist/cjs/actor/feedback.js +6 -39
  2. package/dist/cjs/actor/fields.js +14 -6
  3. package/dist/cjs/actor/index.js +0 -7
  4. package/dist/cjs/actor/interactions.js +4 -15
  5. package/dist/cjs/actor/sessions.js +2 -32
  6. package/dist/cjs/actor/tracker.js +25 -26
  7. package/dist/cjs/actor/utils.js +0 -49
  8. package/dist/cjs/actor/view.js +40 -40
  9. package/dist/cjs/actor/views.js +29 -8
  10. package/dist/cjs/constants.js +18 -19
  11. package/dist/cjs/defaults/hybrid-search/phrases.js +3 -3
  12. package/dist/cjs/defaults/hybrid-search/templates.js +22 -7
  13. package/dist/cjs/element/role/index.js +12 -5
  14. package/dist/cjs/element/role/{miso-hits.js → miso-more.js} +4 -4
  15. package/dist/cjs/element/role/miso-total.js +20 -0
  16. package/dist/cjs/layout/affiliation/affiliation.js +0 -3
  17. package/dist/cjs/layout/banner/banner.js +0 -4
  18. package/dist/cjs/layout/button/more-button.js +73 -0
  19. package/dist/cjs/layout/container/container.js +0 -3
  20. package/dist/cjs/layout/error/error.js +0 -4
  21. package/dist/cjs/layout/index.js +7 -0
  22. package/dist/cjs/layout/input/facets.js +0 -4
  23. package/dist/cjs/layout/input/option-list.js +0 -4
  24. package/dist/cjs/layout/input/search-box.js +13 -12
  25. package/dist/cjs/layout/list/cards.js +0 -3
  26. package/dist/cjs/layout/list/carousel.js +0 -3
  27. package/dist/cjs/layout/list/collection.js +22 -7
  28. package/dist/cjs/layout/list/list.js +0 -3
  29. package/dist/cjs/layout/radio/feedback.js +0 -3
  30. package/dist/cjs/layout/radio/radio.js +0 -4
  31. package/dist/cjs/layout/raf.js +4 -2
  32. package/dist/cjs/layout/text/text.js +0 -4
  33. package/dist/cjs/layout/text/typewriter/index.js +0 -3
  34. package/dist/cjs/layout/triggerable.js +79 -0
  35. package/dist/cjs/util/states.js +0 -15
  36. package/dist/cjs/version.js +1 -1
  37. package/dist/cjs/workflow/answer-based.js +70 -105
  38. package/dist/cjs/workflow/ask.js +29 -27
  39. package/dist/cjs/workflow/asks.js +1 -1
  40. package/dist/cjs/workflow/autocompletable.js +33 -0
  41. package/dist/cjs/workflow/autocomplete.js +122 -0
  42. package/dist/cjs/workflow/base.js +156 -98
  43. package/dist/cjs/workflow/explore.js +48 -46
  44. package/dist/cjs/workflow/hybrid-search-answer.js +17 -7
  45. package/dist/cjs/workflow/hybrid-search-results.js +17 -87
  46. package/dist/cjs/workflow/hybrid-search-views.js +122 -0
  47. package/dist/cjs/workflow/hybrid-search.js +47 -168
  48. package/dist/cjs/workflow/options.js +32 -17
  49. package/dist/cjs/workflow/processors.js +166 -2
  50. package/dist/cjs/workflow/recommendation.js +36 -49
  51. package/dist/cjs/workflow/search-based.js +228 -0
  52. package/dist/cjs/workflow/search.js +21 -68
  53. package/dist/cjs/workflow/use-link.js +44 -0
  54. package/package.json +2 -2
  55. package/src/actor/feedback.js +6 -23
  56. package/src/actor/fields.js +14 -6
  57. package/src/actor/index.js +0 -1
  58. package/src/actor/interactions.js +4 -10
  59. package/src/actor/sessions.js +3 -31
  60. package/src/actor/tracker.js +25 -19
  61. package/src/actor/utils.js +0 -47
  62. package/src/actor/view.js +27 -15
  63. package/src/actor/views.js +32 -8
  64. package/src/constants.js +25 -16
  65. package/src/defaults/hybrid-search/phrases.js +2 -2
  66. package/src/defaults/hybrid-search/templates.js +17 -5
  67. package/src/element/role/index.js +2 -1
  68. package/src/element/role/{miso-hits.js → miso-more.js} +3 -3
  69. package/src/element/role/miso-total.js +16 -0
  70. package/src/layout/affiliation/affiliation.js +1 -5
  71. package/src/layout/banner/banner.js +0 -5
  72. package/src/layout/button/more-button.js +69 -0
  73. package/src/layout/container/container.js +1 -5
  74. package/src/layout/error/error.js +0 -5
  75. package/src/layout/index.js +2 -1
  76. package/src/layout/input/facets.js +0 -5
  77. package/src/layout/input/option-list.js +0 -5
  78. package/src/layout/input/search-box.js +11 -14
  79. package/src/layout/list/cards.js +0 -4
  80. package/src/layout/list/carousel.js +0 -4
  81. package/src/layout/list/collection.js +21 -9
  82. package/src/layout/list/list.js +0 -4
  83. package/src/layout/radio/feedback.js +0 -4
  84. package/src/layout/radio/radio.js +0 -5
  85. package/src/layout/raf.js +2 -2
  86. package/src/layout/text/text.js +0 -5
  87. package/src/layout/text/typewriter/index.js +1 -5
  88. package/src/layout/triggerable.js +76 -0
  89. package/src/util/states.js +0 -14
  90. package/src/version.js +1 -1
  91. package/src/workflow/answer-based.js +61 -72
  92. package/src/workflow/ask.js +30 -24
  93. package/src/workflow/asks.js +1 -1
  94. package/src/workflow/autocompletable.js +29 -0
  95. package/src/workflow/autocomplete.js +113 -0
  96. package/src/workflow/base.js +133 -87
  97. package/src/workflow/explore.js +38 -30
  98. package/src/workflow/hybrid-search-answer.js +17 -6
  99. package/src/workflow/hybrid-search-results.js +17 -77
  100. package/src/workflow/hybrid-search-views.js +124 -0
  101. package/src/workflow/hybrid-search.js +47 -157
  102. package/src/workflow/options.js +26 -11
  103. package/src/workflow/processors.js +151 -3
  104. package/src/workflow/recommendation.js +32 -33
  105. package/src/workflow/search-based.js +222 -0
  106. package/src/workflow/search.js +21 -65
  107. package/src/workflow/use-link.js +40 -0
  108. package/dist/cjs/actor/autocomplete.js +0 -160
  109. package/dist/cjs/workflow/utils.js +0 -42
  110. package/src/actor/autocomplete.js +0 -133
  111. package/src/workflow/utils.js +0 -29
@@ -0,0 +1,124 @@
1
+ import { ROLE } from '../constants.js';
2
+
3
+ function getSubworkflowByRole(role) {
4
+ // TODO: ROLE.ERROR
5
+ switch (role) {
6
+ case ROLE.PRODUCTS:
7
+ case ROLE.TOTAL:
8
+ case ROLE.FACETS:
9
+ case ROLE.KEYWORDS:
10
+ case ROLE.MORE:
11
+ return 'results';
12
+ default:
13
+ return 'answer';
14
+ }
15
+ }
16
+
17
+ export default class HybridSearchViewsActor {
18
+
19
+ constructor(workflow) {
20
+ this._workflow = workflow;
21
+ this._containers = new Set();
22
+ }
23
+
24
+ get(role) {
25
+ return this._getSubviews(role).get(role);
26
+ }
27
+
28
+ addContainer(element) {
29
+ if (this._containers.has(element)) {
30
+ return;
31
+ }
32
+ this._containers.add(element);
33
+
34
+ const { components } = element;
35
+ for (const component of components) {
36
+ this.addComponent(component);
37
+ }
38
+ }
39
+
40
+ removeContainer(element) {
41
+ for (const subworkflow of this._workflow._subworkflows) {
42
+ subworkflow._views.removeContainer(element);
43
+ }
44
+ this._containers.delete(element);
45
+ }
46
+
47
+ addComponent(element) {
48
+ this._getViewByElement(element).element = element;
49
+ this._addContainerToSubviews(element._container, element);
50
+ }
51
+
52
+ removeComponent(element) {
53
+ this._getViewByElement(element).element = undefined;
54
+ this._removeContainerFromSubviewsIfNecessary(element._container);
55
+ }
56
+
57
+ updateComponentRole(element, oldRole, newRole) {
58
+ // TODO
59
+ }
60
+
61
+ refreshElement(element) {
62
+ const view = element.isContainer ? this._containers.get(element) : this._getViewByElement(element);
63
+ view && view.refresh({ force: true });
64
+ }
65
+
66
+ // helpers //
67
+ _getSubworkflow(role) {
68
+ return this._workflow._getSubworkflow(getSubworkflowByRole(role));
69
+ }
70
+
71
+ _getAllSubviews() {
72
+ return this._workflow._subworkflows.flatMap(subworkflow => subworkflow._views);
73
+ }
74
+
75
+ _getSubviews(role) {
76
+ return this._getSubworkflow(role)._views;
77
+ }
78
+
79
+ _getViewByElement(element) {
80
+ let { role } = element;
81
+ if (!role) {
82
+ throw new Error('Component must have a role');
83
+ }
84
+ return this.get(role);
85
+ }
86
+
87
+ _addContainerToSubviews(container, child) {
88
+ if (!container) {
89
+ return;
90
+ }
91
+ const { role } = child;
92
+ const subviews = this._getSubviews(role);
93
+ if (subviews._containers.has(container)) {
94
+ return;
95
+ }
96
+ // check if the container is in other subviews
97
+ for (const otherSubviews of this._getAllSubviews()) {
98
+ if (otherSubviews === subviews) {
99
+ continue;
100
+ }
101
+ if (otherSubviews._containers.has(container)) {
102
+ // find the conflicting element
103
+ for (const component of container.components) {
104
+ if (otherSubviews.containsElement(component)) {
105
+ throw new Error(`<${container.tagName}> cannot contain both <${component.tagName}> and <${child.tagName}>, for they don't share the same data lifecycle.`);
106
+ }
107
+ }
108
+ throw new Error(`Failed to add container <${container.tagName}> with child component <${child.tagName}>.`);
109
+ }
110
+ }
111
+ // add the container to the subviews
112
+ subviews.addContainer(container);
113
+ }
114
+
115
+ _removeContainerFromSubviewsIfNecessary(container) {
116
+ if (!container || container.components.length > 0) {
117
+ return;
118
+ }
119
+ for (const subviews of this._getAllSubviews()) {
120
+ subviews.removeContainer(container);
121
+ }
122
+ }
123
+
124
+ }
@@ -1,70 +1,70 @@
1
1
  import { API } from '@miso.ai/commons';
2
2
  import { InteractionsActor } from '../actor/index.js';
3
3
  import Workflow from './base.js';
4
+ import SearchBasedWorkflow from './search-based.js';
4
5
  import AnswerBasedWorkflow from './answer-based.js';
5
6
  import { ROLE } from '../constants.js';
6
- import { SearchBoxLayout, ListLayout, TextLayout, FacetsLayout } from '../layout/index.js';
7
+ import { SearchBoxLayout, TextLayout } from '../layout/index.js';
7
8
  import HybridSearchAnswer from './hybrid-search-answer.js';
8
9
  import HybridSearchResults from './hybrid-search-results.js';
10
+ import HybridSearchViewsActor from './hybrid-search-views.js';
9
11
 
10
12
  const DEFAULT_API_OPTIONS = Object.freeze({
11
- ...AnswerBasedWorkflow.DEFAULT_API_OPTIONS,
13
+ group: API.GROUP.ASK,
12
14
  name: API.NAME.SEARCH,
13
15
  payload: {
14
16
  ...AnswerBasedWorkflow.DEFAULT_API_OPTIONS.payload,
17
+ ...SearchBasedWorkflow.DEFAULT_API_OPTIONS.payload,
15
18
  source_fl: ['cover_image', 'url', 'created_at', 'updated_at', 'published_at', 'title'],
16
- fl: ['cover_image', 'url', 'created_at', 'updated_at', 'published_at', 'title'],
17
19
  },
18
20
  });
19
21
 
20
22
  const DEFAULT_LAYOUTS = Object.freeze({
21
23
  ...AnswerBasedWorkflow.DEFAULT_LAYOUTS,
24
+ ...SearchBasedWorkflow.DEFAULT_LAYOUTS,
22
25
  [ROLE.QUERY]: [SearchBoxLayout.type, { placeholder: '' }],
23
26
  [ROLE.QUESTION]: [TextLayout.type, { raw: true }],
24
- [ROLE.PRODUCTS]: [ListLayout.type, { itemType: 'article' }],
25
- [ROLE.KEYWORDS]: [TextLayout.type, { raw: true }],
26
- [ROLE.HITS]: [TextLayout.type, { raw: true, format: 'number' }],
27
- [ROLE.FACETS]: [FacetsLayout.type],
28
27
  });
29
28
 
30
29
  const DEFAULT_TRACKERS = Object.freeze({
31
30
  ...AnswerBasedWorkflow.DEFAULT_TRACKERS,
31
+ ...SearchBasedWorkflow.DEFAULT_TRACKERS,
32
+ });
33
+
34
+ const DEFAULT_PAGINATION = Object.freeze({
35
+ ...SearchBasedWorkflow.DEFAULT_PAGINATION,
36
+ active: true,
37
+ });
38
+
39
+ const DEFAULT_AUTOCOMPLETE_OPTIONS = Object.freeze({
40
+ api: {
41
+ group: API.GROUP.ASK,
42
+ name: API.NAME.SEARCH_AUTOCOMPLETE,
43
+ payload: {
44
+ completion_fields: ['suggested_queries'],
45
+ },
46
+ },
32
47
  });
33
48
 
34
49
  const DEFAULT_OPTIONS = Object.freeze({
35
50
  ...AnswerBasedWorkflow.DEFAULT_OPTIONS,
51
+ ...SearchBasedWorkflow.DEFAULT_OPTIONS,
36
52
  api: DEFAULT_API_OPTIONS,
37
53
  layouts: DEFAULT_LAYOUTS,
38
54
  trackers: DEFAULT_TRACKERS,
55
+ autocomplete: DEFAULT_AUTOCOMPLETE_OPTIONS,
56
+ pagination: DEFAULT_PAGINATION,
39
57
  });
40
58
 
41
- const ROLES_CONFIG = Object.freeze({
42
- [ROLE.QUESTION]: {
43
- mapping: ROLE.KEYWORDS,
44
- },
45
- [ROLE.FACETS]: {
46
- mapping: 'facet_counts',
47
- },
48
- });
49
-
50
- const SUBWORKFLOW = Object.freeze({
51
- ANSWER: 'answer',
52
- RESULTS: 'results',
59
+ const ROLES_OPTIONS = Object.freeze({
60
+ main: ROLE.PRODUCTS,
61
+ members: Object.keys(DEFAULT_LAYOUTS),
62
+ mappings: Object.freeze({
63
+ ...AnswerBasedWorkflow.ROLES_OPTIONS.mappings,
64
+ ...SearchBasedWorkflow.ROLES_OPTIONS.mappings,
65
+ }),
53
66
  });
54
67
 
55
- function getSubworkflowByRole(role) {
56
- // TODO: ROLE.ERROR
57
- switch (role) {
58
- case ROLE.PRODUCTS:
59
- case ROLE.HITS:
60
- case ROLE.FACETS:
61
- case ROLE.KEYWORDS:
62
- return SUBWORKFLOW.RESULTS;
63
- default:
64
- return SUBWORKFLOW.ANSWER;
65
- }
66
- }
67
-
68
68
  export default class HybridSearch extends Workflow {
69
69
 
70
70
  constructor(plugin, client) {
@@ -72,8 +72,7 @@ export default class HybridSearch extends Workflow {
72
72
  name: 'hybrid-search',
73
73
  plugin,
74
74
  client,
75
- roles: Object.keys(DEFAULT_LAYOUTS),
76
- rolesConfig: ROLES_CONFIG,
75
+ roles: ROLES_OPTIONS,
77
76
  defaults: DEFAULT_OPTIONS,
78
77
  });
79
78
  }
@@ -88,9 +87,9 @@ export default class HybridSearch extends Workflow {
88
87
 
89
88
  _getSubworkflow(name) {
90
89
  switch (name) {
91
- case SUBWORKFLOW.ANSWER:
90
+ case 'answer':
92
91
  return this._answer;
93
- case SUBWORKFLOW.RESULTS:
92
+ case 'results':
94
93
  return this._results;
95
94
  default:
96
95
  throw new Error(`Invalid subworkflow: ${name}`);
@@ -106,18 +105,14 @@ export default class HybridSearch extends Workflow {
106
105
  this._interactions = new InteractionsActor(hub, { client, options });
107
106
  }
108
107
 
109
- _initReset() {
110
- this._results.reset();
111
- this._answer.reset();
108
+ _initSession() {
109
+ this._results._sessions.restart();
110
+ this._answer._sessions.restart();
112
111
  }
113
112
 
114
- restart({ answer = true } = {}) {
115
- const previousSession = this.session;
116
- this._sessions.restart();
117
- const { session } = this;
118
- if (!answer) {
119
- session._answer = previousSession._answer || previousSession;
120
- }
113
+ // configuration //
114
+ useLink(fn, options) {
115
+ this._answer.useLink(fn, options);
121
116
  return this;
122
117
  }
123
118
 
@@ -130,6 +125,10 @@ export default class HybridSearch extends Workflow {
130
125
  return this._results._views.filters;
131
126
  }
132
127
 
128
+ get autocomplete() {
129
+ return this._answer.autocomplete;
130
+ }
131
+
133
132
  // query //
134
133
  autoQuery(options) {
135
134
  this._answer.autoQuery(options);
@@ -140,8 +139,8 @@ export default class HybridSearch extends Workflow {
140
139
  }
141
140
 
142
141
  // interactions //
143
- _preprocessInteraction(payload) {
144
- return this._answer._preprocessInteraction(payload);
142
+ _defaultProcessInteraction(payload, args) {
143
+ return this._answer._defaultProcessInteraction(payload, args);
145
144
  }
146
145
 
147
146
  // destroy //
@@ -153,112 +152,3 @@ export default class HybridSearch extends Workflow {
153
152
  }
154
153
 
155
154
  }
156
-
157
- class HybridSearchViewsActor {
158
-
159
- constructor(workflow) {
160
- this._workflow = workflow;
161
- this._containers = new Set();
162
- }
163
-
164
- get(role) {
165
- return this._getSubviews(role).get(role);
166
- }
167
-
168
- addContainer(element) {
169
- if (this._containers.has(element)) {
170
- return;
171
- }
172
- this._containers.add(element);
173
-
174
- const { components } = element;
175
- for (const component of components) {
176
- this.addComponent(component);
177
- }
178
- }
179
-
180
- removeContainer(element) {
181
- for (const subworkflow of this._workflow._subworkflows) {
182
- subworkflow._views.removeContainer(element);
183
- }
184
- this._containers.delete(element);
185
- }
186
-
187
- addComponent(element) {
188
- this._getViewByElement(element).element = element;
189
- this._addContainerToSubviews(element._container, element);
190
- }
191
-
192
- removeComponent(element) {
193
- this._getViewByElement(element).element = undefined;
194
- this._removeContainerFromSubviewsIfNecessary(element._container);
195
- }
196
-
197
- updateComponentRole(element, oldRole, newRole) {
198
- // TODO
199
- }
200
-
201
- refreshElement(element) {
202
- const view = element.isContainer ? this._containers.get(element) : this._getViewByElement(element);
203
- view && view.refresh({ force: true });
204
- }
205
-
206
- // helpers //
207
- _getSubworkflow(role) {
208
- return this._workflow._getSubworkflow(getSubworkflowByRole(role));
209
- }
210
-
211
- _getAllSubviews() {
212
- return this._workflow._subworkflows.flatMap(subworkflow => subworkflow._views);
213
- }
214
-
215
- _getSubviews(role) {
216
- return this._getSubworkflow(role)._views;
217
- }
218
-
219
- _getViewByElement(element) {
220
- let { role } = element;
221
- if (!role) {
222
- throw new Error('Component must have a role');
223
- }
224
- return this.get(role);
225
- }
226
-
227
- _addContainerToSubviews(container, child) {
228
- if (!container) {
229
- return;
230
- }
231
- const { role } = child;
232
- const subviews = this._getSubviews(role);
233
- if (subviews._containers.has(container)) {
234
- return;
235
- }
236
- // check if the container is in other subviews
237
- for (const otherSubviews of this._getAllSubviews()) {
238
- if (otherSubviews === subviews) {
239
- continue;
240
- }
241
- if (otherSubviews._containers.has(container)) {
242
- // find the conflicting element
243
- for (const component of container.components) {
244
- if (otherSubviews.containsElement(component)) {
245
- throw new Error(`<${container.tagName}> cannot contain both <${component.tagName}> and <${child.tagName}>, for they don't share the same data lifecycle.`);
246
- }
247
- }
248
- throw new Error(`Failed to add container <${container.tagName}> with child component <${child.tagName}>.`);
249
- }
250
- }
251
- // add the container to the subviews
252
- subviews.addContainer(container);
253
- }
254
-
255
- _removeContainerFromSubviewsIfNecessary(container) {
256
- if (!container || container.components.length > 0) {
257
- return;
258
- }
259
- for (const subviews of this._getAllSubviews()) {
260
- subviews.removeContainer(container);
261
- }
262
- }
263
-
264
- }
@@ -110,15 +110,14 @@ export function normalizeInteractionsOptions(options) {
110
110
  return { ...options, preprocess };
111
111
  }
112
112
 
113
- export function normalizeAutocompleteOptions(options) {
113
+ export function normalizePaginationOptions(options) {
114
114
  if (options === undefined) {
115
- throw new Error(`Expect autocomplete options to be an object or a boolean value: ${options}`);
115
+ throw new Error(`Expect pagination options to be an object or a boolean value: ${options}`);
116
116
  }
117
117
  if (typeof options === 'boolean') {
118
- options = { actor: options };
118
+ options = { active: options };
119
119
  }
120
- const processData = normalizeDataProcessorOptions(options.processData);
121
- return { ...options, processData };
120
+ return options;
122
121
  }
123
122
 
124
123
  // merge //
@@ -206,17 +205,33 @@ export function mergeInteractionsOptions(...optionsList) {
206
205
  }));
207
206
  }
208
207
 
209
- export function mergeAutocompleteOptions(...optionsList) {
208
+ export function mergePaginationOptions(...optionsList) {
210
209
  if (optionsList[optionsList.length - 1] === false) {
211
210
  return false;
212
211
  }
212
+ return mergeOptions(optionsList, (merged, options) => Object.assign(merged, options));
213
+ }
214
+
215
+ export function mergeAutocompleteOptions(...optionsList) {
213
216
  return mergeOptions(optionsList, (merged, options) => Object.assign(merged, {
214
217
  ...options,
215
218
  api: mergeApiOptions(merged.api, options.api),
216
- processData: mergeDataProcessorOptions(merged.processData, options.processData),
217
219
  }));
218
220
  }
219
221
 
222
+ export function mergeRolesOptions(base, overrides) {
223
+ return Object.freeze({
224
+ ...base,
225
+ ...overrides,
226
+ members: mergeRolesMembers(base.members, overrides.members),
227
+ mappings: Object.freeze({ ...base.mappings, ...overrides.mappings }),
228
+ });
229
+ }
230
+
231
+ function mergeRolesMembers(base = [], overrides = []) {
232
+ return [...new Set([...base, ...overrides])];
233
+ }
234
+
220
235
 
221
236
 
222
237
  // manifest //
@@ -247,9 +262,9 @@ const FEATURES = [
247
262
  merge: mergeInteractionsOptions,
248
263
  },
249
264
  {
250
- key: 'autocomplete',
251
- normalize: normalizeAutocompleteOptions,
252
- merge: mergeAutocompleteOptions,
265
+ key: 'pagination',
266
+ normalize: normalizePaginationOptions,
267
+ merge: mergePaginationOptions,
253
268
  },
254
269
  ];
255
270
 
@@ -272,7 +287,7 @@ export class WorkflowOptions {
272
287
 
273
288
  constructor(context = {}, defaults = {}) {
274
289
  this._events = new EventEmitter({ target: this });
275
- this._defaults = defaults;
290
+ this._defaults = defaults; // TODO: we should call normalize() on defaults
276
291
  this._context = context || {};
277
292
  this._locals = {};
278
293
 
@@ -1,9 +1,14 @@
1
1
  import { trimObj } from '@miso.ai/commons';
2
- import { STATUS } from '../constants.js';
2
+ import { STATUS, ROLE, EVENT_TYPE, isPerformanceEventType, isProductRole } from '../constants.js';
3
+
4
+ // data //
5
+ export function getRevision(data) {
6
+ return data && data.value && data.value.revision;
7
+ }
3
8
 
4
9
  export function writeDataStatus(data) {
5
10
  const status = getStatus(data);
6
- const ongoing = status === STATUS.READY ? !!data.ongoing : undefined;
11
+ const ongoing = status === STATUS.READY ? data.ongoing : undefined;
7
12
  return trimObj({ status, ongoing, ...data }); // let orinigal data override status and ongoing
8
13
  }
9
14
 
@@ -74,7 +79,14 @@ export function writeAnswerStageToMeta(data) {
74
79
  };
75
80
  }
76
81
 
77
- export function composeFiltersPayload(filters) {
82
+ export function writeFiltersToPayload(payload = {}, filters) {
83
+ return trimObj({
84
+ ...payload,
85
+ ...composeFiltersPayload(filters),
86
+ });
87
+ }
88
+
89
+ function composeFiltersPayload(filters) {
78
90
  if (!filters) {
79
91
  return undefined;
80
92
  }
@@ -100,3 +112,139 @@ function composeFacetFilters(facets) {
100
112
  }
101
113
  return Object.keys(filters).length ? filters : undefined;
102
114
  }
115
+
116
+ export function retainFacetCountsInData(data, currentFacetCounts) {
117
+ if (!currentFacetCounts) {
118
+ return data;
119
+ }
120
+ const { status, value } = data;
121
+ switch (status) {
122
+ case STATUS.INITIAL:
123
+ case STATUS.LOADING:
124
+ break;
125
+ default:
126
+ return data;
127
+ }
128
+ if (value && value.facet_counts) {
129
+ return data;
130
+ }
131
+ return {
132
+ ...data,
133
+ value: {
134
+ ...value,
135
+ facet_counts: currentFacetCounts,
136
+ },
137
+ };
138
+ }
139
+
140
+ export function concatItemsFromMoreResponse(oldData, newData, { role = ROLE.PRODUCTS } = {}) {
141
+ if (!newData.value) {
142
+ return oldData;
143
+ }
144
+ const { [role]: oldProducts = [], facet_counts, total } = oldData.value;
145
+ const { [role]: newProducts = [] } = newData.value;
146
+ // keep old exhaustion flag, just in case
147
+ // TODO
148
+
149
+ // use old values of facet_counts and total, for they are affected by product_id exclusion
150
+ return {
151
+ ...newData,
152
+ facet_counts,
153
+ total,
154
+ value: {
155
+ ...newData.value,
156
+ [role]: [...oldProducts, ...newProducts],
157
+ },
158
+ }
159
+ }
160
+
161
+ export function writeExhaustionToData(data, { role = ROLE.PRODUCTS } = {}) {
162
+ const { status, request, value } = data;
163
+ // keep track of the exhaustion state
164
+ if (status !== STATUS.READY || !request || !value) {
165
+ return data;
166
+ }
167
+ const { [role]: items } = value;
168
+ if (!items) {
169
+ return data;
170
+ }
171
+ const { rows } = request.payload;
172
+ const { length } = items;
173
+ const exhausted = length < rows;
174
+ if (!exhausted) {
175
+ return data;
176
+ }
177
+ return {
178
+ ...data,
179
+ meta: {
180
+ ...data.meta,
181
+ exhausted: true,
182
+ },
183
+ };
184
+ }
185
+
186
+ // interactions //
187
+ export function buildBaseInteraction(args) {
188
+ const { role, type, items } = args;
189
+ const payload = {
190
+ type: type === 'viewable' ? 'viewable_impression' : type,
191
+ context: {
192
+ custom_context: trimObj({
193
+ property: role,
194
+ }),
195
+ },
196
+ };
197
+
198
+ if (isPerformanceEventType(type)) {
199
+ if (isProductRole(role)) {
200
+ payload.product_ids = (items || []).map(v => typeof v === 'string' ? v : v.product_id).filter(v => v);
201
+ } else {
202
+ payload.product_ids = [];
203
+ payload.context.custom_context.items = (items || []).map(v => v.text || v.id || v);
204
+ }
205
+ }
206
+
207
+ switch (type) {
208
+ case EVENT_TYPE.SUBMIT:
209
+ payload.context.custom_context.value = args.value;
210
+ break;
211
+ case EVENT_TYPE.FEEDBACK:
212
+ payload.context.custom_context.value = payload.value = args.value;
213
+ payload.context.custom_context.result_type = payload.result_type = args.result_type;
214
+ break;
215
+ }
216
+
217
+ return trimObj(payload);
218
+ }
219
+
220
+ export function writeAffiliationInfoToInteraction(payload, args) {
221
+ if (args.role !== ROLE.AFFILIATION) {
222
+ return payload;
223
+ }
224
+ const { items } = args;
225
+ const channel = (items[0] && items[0].channel) || undefined;
226
+ const positions = items.map(v => v.position);
227
+ return mergeInteraction(payload, {
228
+ context: {
229
+ custom_context: {
230
+ channel,
231
+ positions,
232
+ },
233
+ },
234
+ });
235
+ }
236
+
237
+ export function mergeInteraction(base = {}, patch = {}) {
238
+ return trimObj({
239
+ ...base,
240
+ ...patch,
241
+ context:trimObj({
242
+ ...base.context,
243
+ ...patch.context,
244
+ custom_context: trimObj({
245
+ ...(base.context && base.context.custom_context),
246
+ ...(patch.context && patch.context.custom_context),
247
+ }),
248
+ }),
249
+ });
250
+ }